def collect_res(cls, key_path = [], nameFile = None, allPrefix = 'res_', folderName = None, replace_func = None): """ collect results and group them according to some key values Arguments --------- key_path: <list> if not empty defines how to group results: provides a path in the dict structure of the results to get a value used for the grouping nameFile, allPrefix, folderName: cf. read_res() Output: ------- a dictionary where key is the concatenation of the unique set of keys found and value is the res is a list of all res matching this key """ listRes, listNames = cls.read_res(nameFile, allPrefix, folderName, returnName=True, replace_func=replace_func) if(len(key_path) == 0): res = {k:v for k,v in zip(listNames, listRes)} else: res_keys = [tuple([ut.extract_from_nested(res, k) for k in key_path]) for res in listRes] res_keys_unique = list(set(res_keys)) res = {ut.concat2String(*k_u):[listRes[n] for n, r in enumerate(res_keys) if r == k_u] for k_u in res_keys_unique} return res
def collect_res_bug(cls, key_path=[], nameFile=None, allPrefix='res_', folderName=None): """Extract results stored in (a) txt file(s) and group them according to some key values (where key_path provides the path in the potentially nested structure of the results to find the key(s)) Output: a dictionary where key is the concatenation of the unique set of keys found and value is the res is a list of all res matching this key """ listRes = cls.read_res_bug(nameFile, allPrefix, folderName) res_keys = [ tuple([ut.extract_from_nested(res, k) for k in key_path]) for res in listRes ] res_keys_unique = list(set(res_keys)) res = { ut.concat2String(*k_u): [listRes[n] for n, r in enumerate(res_keys) if r == k_u] for k_u in res_keys_unique } return res
def plot_one_best_func(list_res, path = ['fun'], criterion = np.argmin): run_fun = [ut.extract_from_nested(r, path) for r in list_res] best_arg = criterion(run_fun) best_run = list_res[best_arg] best_func = pFunc_base.pFunc_base.build_pfunc(ut.extract_from_nested(best_run, ['func'])) best_T = ut.extract_from_nested(best_run, ['config', 'testing_dico', 'T']) best_tt = np.linspace(-0.1, best_T + 0.1, 10000) best_func.plot_function(best_tt)
def get_keys_from_onefile(cls, name_file, path_keys, replace_func = None): """ Get concatenated keys from one res (passed as the name of the file)""" res = ut.eval_from_file(name_file, evfunc = pFunc_base.eval_with_pFunc, replace_func = replace_func) res_keys = "_".join([ut.extract_from_nested(res, k) for k in path_keys]) return res_keys
def get_keys_from_onefile_safe(cls, name_file, path_keys, replace_func = None): """ Get concatenated keys from one res (passed as the name of the file)""" try: res = ut.eval_from_file(name_file, evfunc = pFunc_base.eval_with_pFunc, replace_func = replace_func) res_keys = "_".join([ut.extract_from_nested(res, k) for k in path_keys]) except: print("Error reading file {}".format(name_file)) res_keys = None return res_keys
def parse_compound(cls, expr, db_atom): """ Parse compound expressions (based on some atomic expressions defined in db_atom) Compound expressions follow the syntax either '#expr' if expr is an atomic expression '$(expr_0,..,expr_n)' where $ is an operator (#, *, **, +) acting on expr_0 to _n e.g. expr = '**(#a,*(#c,+(#d,#e)))' with db = {'a':xx,'c':yy,'d':qq,'e':zz} """ if (not (ut.is_str(expr))): raise SystemError('prse_atom: atom arg should be a string') op, list_sub_expr = cls._split_op_args(expr) if (op == '#'): res = db_atom[list_sub_expr] elif (op in cls._PARSING_DICO_OP): parsed_sub = [ cls.parse_compound(sub, db_atom) for sub in list_sub_expr ] res = cls._apply_operator(op, parsed_sub) else: raise SystemError('operator {0} not recognized'.format(op)) return res
def _build_custom_FourierFunc(self, dico_source, **extra_args): """ custom rules to build a FourierFunc """ info_func = self._LIST_CUSTOM_FUNC['FourierFunc'] constructor = info_func[1] Om = dico_source.get('Om') use_bounds = False if (Om is None): T = dico_source['T'] nb_H = dico_source['nb_H'] freq_type = dico_source.get('freq_type') Om = self._gen_frequencies(T, nb_H, freq_type) if (isinstance(Om, tuple)): use_bounds = True Om_bounds = Om[1] Om = Om[0] elif (ut.is_iter(Om)): nb_H = len(Om) else: Om = [Om] nb_H = 1 phi = dico_source.get('phi', np.zeros(nb_H)) A = dico_source.get('A', np.zeros(nb_H)) B = dico_source.get('B', np.zeros(nb_H)) c0 = dico_source.get('c0', 0) dico_constructor = {'A': A, 'B': B, 'c0': c0, 'phi': phi, 'Om': Om} if (use_bounds): dico_constructor['Om_bounds'] = Om_bounds self._add_bounds(dico_constructor, dico_source) return constructor(**dico_constructor)
def _read_configs(cls, config_object, list_output = True): """ Allow for different type of configs to be passed (<dic>, <list<dic>>, <str>, <list<str>>) """ if(ut.is_dico(config_object)): configs = [config_object] if(list_output) else config_object elif(ut.is_list(config_object)): configs = [cls._read_configs(conf, list_output = False) for conf in config_object] elif(ut.is_str(config_object)): configs = ut.file_to_dico(config_object) if(list_output): configs = [configs] else: raise NotImplementedError() return configs
def group_res(cls, key_path , nameFile = None, allPrefix = 'res_', folderName = None, replace_func = None): """ return a dict <key:flags:list<str:>> Arguments --------- key_path: <list> Defines how to group results: given as a path in the dict structure of the results to get a value used for the grouping nameFile, allPrefix, folderName: cf. read_res() Output: ------- a dictionary where key is the concatenation of the unique set of keys found and value is a list of all the files corresponding to this key """ listFileName = ut.findFile(nameFile, allPrefix, folderName) results = [(f, cls.get_keys_from_onefile_safe(f, key_path, replace_func = replace_func)) for f in listFileName] nb_errors = np.sum([r[1] is None for r in results]) if (nb_errors > 0): print("{} files couldn't be read".format(nb_errors)) results = [r for r in results if r[1] is not None] groupped_res = collections.defaultdict(list) for k, v in results: groupped_res[v].append(k) return groupped_res
def context(self, context): if (context is None): self._context = {} elif (ut.is_dico(context)): self._context = context else: SystemError('context should be a dictionnary')
def read_res(cls, nameFile = None, allPrefix = 'res_', folderName = None, returnName = False, replace_func = None): """ Extract result(s) stored in a (several) txt file (s) and return them as a (list) of evaluated objects Arguments --------- nameFile: None or <str> If not None only read the file with this name If None will rely on folderName and allPrefix folderName: None or <str> In which folder should we look for the files allPrefix: None or <str> If not None enforce collecting only the results starting with this prefix returnName: bool if True returns also the name of the file(s) replace_func: <func> take the string extracted from the file and transform it Output ------ results: <list> each element is the evaluated string contained i the relevant file """ listFileName = ut.findFile(nameFile, allPrefix, folderName) results = [cls.eval_from_onefile(f, replace_func = replace_func) for f in listFileName] filter_none = [r is None for r in results] if np.any(filter_none): logger.warning('{} empty files'.format(np.sum(filter_none))) results = [r for r, f in zip(results, filter_none) if not(f) ] if returnName: return results, listFileName else: return results
def save_plot_best_func(list_name, save_fig): if(ut.is_str(save_fig)): plt.close() for n in list_name: plot_one_best_func(rawres[n]) plt.legend(list_name) plt.savefig(save_fig) plt.close()
def plot_pop_adiab(model, **args_pop_adiab): """ Plot pop adiab where each population_t is dispatched on one of the three subplot #TODO: better plots """ if (hasattr(model, 'pop_adiab')): limit_legend = args_pop_adiab.get('lim_legend', 10) limit_enlevels = args_pop_adiab.get('lim_enlevels', np.inf) pop_adiab = model.adiab_pop #txn t = model.adiab_t en = model.adiab_en #txn cf = model.adiab_cf # txcf nb_levels = min(pop_adiab.shape[1], limit_enlevels) en_0 = en[:, 0] #[0,0] control function f, axarr = plt.subplots(2, 2, sharex=True) axarr[0, 0].plot(t, cf, label='f(t)') for i in range(nb_levels): pop_tmp = pop_adiab[:, i] max_tmp = np.max(pop_tmp) if (i <= limit_legend): lbl_tmp = str(i) else: lbl_tmp = None if (max_tmp > 0.1): axarr[0, 1].plot(t, pop_tmp, label=lbl_tmp) elif (max_tmp > 0.01): axarr[1, 1].plot(t, pop_tmp, label=lbl_tmp) axarr[1, 0].plot(t, en[:, i] - en_0, label=lbl_tmp) ax_tmp = axarr[0, 1] ax_tmp.legend(fontsize='x-small') ax_tmp.set_title('main pop') ax_tmp.set(xlabel='t', ylabel='%') ax_tmp = axarr[1, 1] ax_tmp.legend(fontsize='x-small') ax_tmp.set_title('sec pop') ax_tmp.set(xlabel='t', ylabel='%') ax_tmp = axarr[0, 0] ax_tmp.legend() ax_tmp.set_title('control') ax_tmp.set(xlabel='t', ylabel='cf') ax_tmp = axarr[1, 0] ax_tmp.set_title('instantaneous ein') ax_tmp.set(xlabel='t', ylabel='E') save_fig = args_pop_adiab.get('save_fig') if (ut.is_str(save_fig)): f.savefig(save_fig) else: logger.warning( "pcModel_qspin.plot_pop_adiab: no pop_adiab found.. Generate it first" )
def _apply_metaparams(cls, list_configs, dico_meta): """ Deal with genearting random runs, names of the simulation METAPARAMETERS: RANDOM RUNS --------------------------- '_RDM_RUNS': default = 1 type = 'int': Number of random runs for each config '_RDM_FIXSEED': default = False type = 'bool' If True all the configurations for the same random run will have the same random seed (new key, value added in config '_RDM_SEED' != None) To keep track of which configurations map to the same initial configuration (i.e. without taking care of rdm runs) a flag 'ID_DET' is added METAPARAMETERS: OUTPUT ---------------------- '_OUT_PREFIX': ('res_','str'), '_OUT_FOLDER': (None, 'str'), '_OUT_COUNTER': (None, 'int'), '_OUT_NAME': (None, 'list'), '_OUT_STORE_CONFIG': (True, 'bool'), METAPARAMETERS: MISC ------------------------ '_MP_FLAG':(False, 'bool')} """ dico_meta_filled = cls._parse_metaparams(dico_meta) #Management of the random runs nb_rdm_run = dico_meta_filled['_RDM_RUNS'] fix_seed = dico_meta_filled['_RDM_FIXSEED'] runs = range(nb_rdm_run) if(fix_seed): seeds = RandomGenerator.RandomGenerator.gen_seed(nb = nb_rdm_run) if(nb_rdm_run == 1): seeds = [seeds] else: seeds = [None for _ in runs] random_bits = [{'_RDM_RUN':n, '_RDM_SEED': seeds[n]} for n in runs] for conf in list_configs: conf['_ID_DET'] = RandomGenerator.RandomGenerator.gen_seed() list_configs = ut.cartesianProduct(list_configs, random_bits, ut.add_dico) #Management of the output name_res_list = [] for conf in list_configs: name_res_tmp, dico_meta_filled = cls._gen_name_res(conf, dico_meta_filled) conf['_RES_NAME'] = name_res_tmp assert (name_res_tmp not in name_res_list), "Duplicated name: {0}".format(name_res_tmp) name_res_list.append(name_res_tmp) conf['_OUT_FOLDER'] = dico_meta_filled['_OUT_FOLDER'] conf['_OUT_STORE_CONFIG'] = dico_meta_filled['_OUT_STORE_CONFIG'] conf['_MP_FLAG'] = dico_meta_filled['_MP_FLAG'] return list_configs
def _gen_name_res(cls, config, metadico = {}, type_output = '.txt'): """ Generate a name associated to a config Rules ----- if _OUT_NAME is not None and has a <dic> type: {'k':v} --> 'k_XXX' where XXX has been found in the config structure following path given by v elif _OUT_COUNTER is not None increment this counter for each new config add _OUT_PREFIX """ res_name = '' if(metadico.get('_OUT_NAME') is not None): name_rules = metadico['_OUT_NAME'] if(ut.is_dico(name_rules)): for k, v in name_rules.items(): res_name += (k + "_" + str(ut.extract_from_nested(config, v))) else: raise NotImplementedError() if((config.get("_RDM_RUN") is not None)): res_name += "_" res_name += str(config["_RDM_RUN"]) elif(metadico.get('_OUT_COUNTER') is not None): res_name += str(metadico['_OUT_COUNTER']) metadico['_OUT_COUNTER'] +=1 if(res_name == ''): res_name = str(RandomGenerator.RandomGenerator.gen_seed()) prefix = metadico.get('_OUT_PREFIX', '') res_name = prefix + res_name + type_output return res_name, metadico
def _process_kappa(self, options_GP): """ static or dynamic kappa""" kappa = options_GP['kappa'] niter = options_GP['maxiter'] if(isinstance(kappa, float)): kappa = np.repeat(kappa, niter) else: bits = ut.splitString(kappa) if(bits[0] == 'linear'): kappa = float(bits[1]) * (1 - np.arange(niter) / (niter - 1)) else: raise NotImplementedError() return kappa
def read_res_bug(cls, nameFile=None, allPrefix='res_', folderName=None): """ Extract result(s) stored in a (several) txt file (s) and return them in a (list) of evaluated objects Rules: +if nameFile is provided it will try to match it either in folderName if provided or in the current directory +if no nameFile is provided it will try to match the allPrefix or fetch everything if None (directory considered follow the same rules based on folderName as before) """ listFileName = ut.findFile(nameFile, allPrefix, folderName) results = [learner1DBH.eval_from_onefile_bug(f) for f in listFileName] #results = [ut.file_to_dico(f, evfunc = (lambda x: eval(x)) ) for f in listFileName] return results
def parse_and_save_meta_config(cls, input_file='inputfile.txt', output_folder='Config', update_rules=False, debug=False): """ Parse an input file containing a meta-configuration, generate the differnt configs, and save them as files. Parameters ---------- input_file: str where the file containing the meta configuration is output_folder: str, none In which folder to store the configs update_rules: bool rules to apply when generating the configs debug: bool debug mode """ list_configs = cls.parse_meta_config(input_file, update_rules, debug=debug) for conf in list_configs: name_conf = 'config_' + conf['_RES_NAME'] if(not(output_folder is None)): pathlib.Path(output_folder).mkdir(parents=True, exist_ok=True) name_conf = os.path.join(output_folder, name_conf) ut.dico_to_text_rep(conf, fileName = name_conf, typeWrite = 'w')
def _init_params_BO(self, **args_optim): """ Provides different ways to initialize bo depending of the input type: <None> / <int>: returns an integer (nb of points to be eval) e.g. None >> 2 * nb_params <str>: random_init based on a string. returns a <P x N np.array> P is the population size N number of parameters e.g. '40_lhs' >> 40 points drawn by latin hypercube sampling '50_uniform' >> 50 points drawn uniformly range of each params is infered from bounds <P * N array>: passs it though """ init_obj = args_optim['init_obj'] nb_params = args_optim['nb_params'] bounds = args_optim['bounds_params'] if (init_obj is None): init_args = nb_params *2 elif ut.is_int(init_obj): init_args = init_obj elif ut.is_string(init_obj): bits = init_obj.split("_",1) nb_points_init = int(bits[0]) if(bits[1] == 'lhs'): size_pop = [nb_points_init, nb_params] limits = np.array(bounds).T init_args = self.rdm_gen.init_population_lhs(size_pop, limits) else: distrib_one_param = [bits[1]+'_'+ bounds[i][0] + '_' + bounds[i][1] for i in range(nb_params)] init_matrix = [self.rdm_gen.gen_rdmfunc_from_string(d, dim = nb_points_init) for d in distrib_one_param] init_args = np.array(init_matrix).T else: init_args = init_obj return init_args
def init_random_generator(cls, rdm_object=None): """ Return a RandomGenerator. Can deal with multiple input: Seed/ RandomGenerator/None """ if (rdm_object is None): return RandomGenerator() elif (ut.is_int(rdm_object)): return RandomGenerator(seed=rdm_object) elif (isinstance(rdm_object, RandomGenerator)): return rdm_object else: raise NotImplementedError()
def write_one_res(cls, res, name_res = None, folder = None): """ Write a res as a text file. Arguments: + res - a dictionnary + folder - in which folder to write the results + forceName - Force the name given to the file (if None look for a key name in the results, if none use default name) """ #Create name of the file if(name_res is None): if("_RES_NAME" in res): name = res['_RES_NAME'] else: name = cls._DEF_RES_NAME else: name = name_res if(not(folder is None)): pathlib.Path(folder).mkdir(parents=True, exist_ok=True) name = os.path.join(folder, name) #Write ut.dico_to_text_rep(res, fileName = name, typeWrite = 'w')
def _setup_learner_options(self, model, **params_learner): """ save main characteristics, fetch default parameters depending on the algo generate init and bounds of the parameters (most tricky part) """ self._backup_initparams = params_learner # store initial params just in case default = self._ALGO_INFOS[params_learner['algo']][0] # default hyper parameters opt_l = ut.merge_dico(default, params_learner, update_type = 4) opt_l.update({'model': model, 'algo': params_learner['algo'], 'nb_params':model.n_params, 'bounds_obj':params_learner.get('bounds_obj'), 'init_obj':params_learner.get('init_obj'), 'rdm_gen':self.rdm_gen, 'mp_obj':self.mp}) opt_l.update({'bounds_params':self._gen_boundaries_params(**opt_l)}) opt_l.update({'init_params':self._gen_init_params(**opt_l)}) self.options_learner = opt_l
def gen_rdmnb_from_string(self, method_rdm, dim=1): """ #TODO: change name gen_rdm_XXX // use genrdmdunction // #Old name = GenRdmNumbersFromStr Purpose: Generate Random Sequences based on a string or list of string {X0, XN-1} is represented as a N row NxD matrix if each RandomVar has dim D More generally dim = [dim_pop, dim_RV] """ functmp = self.gen_rdmfunc_from_string(method_rdm, dim) if (ut.is_list(functmp)): res = [f() for f in functmp] else: res = functmp() return res
def _gen_frequencies(self, T, nb_freq=1, freq_type=None): """Generate (potentially randomized) frequencies based on 'freq_type' 'principal' or None om[l] = 2 * Pi * l /T 'CRAB' om[l] = 2 * Pi * l * (1 + eps[l]) /T with eps iid U[-0.5, 0.5] 'DCRAB' om[l] ~ U[0, w_max] others """ Om_ref = 2 * np.pi / T #dico_args = {'freq_type':freq_type} args_rdm = freq_type.split("_") rgen = self._rdm_gen #Use this rdamgen (provided or created at init of the factory) if (args_rdm[0] in [None, 'principal']): Om = (1 + np.arange(nb_freq)) * Om_ref elif (args_rdm[0] == 'CRAB'): rdv_method = 'uniform_-0.5_0.5' rdvgen = rgen.gen_rdmnb_from_string(rdv_method, nb_freq) Om = (1 + np.arange(nb_freq) + rdvgen) * Om_ref elif (args_rdm[0] == 'DCRAB'): if (len(args_rdm) > 1): Nmax = int(args_rdm[1]) else: Nmax = nb_freq wmax = Nmax * Om_ref rdv_method = ut.concat2String('uniform', 0, wmax) Om = rgen.gen_rdmnb_from_string(rdv_method, nb_freq) Om = np.sort(Om) #dico_args['flag'] = 'DCRAB' # Omega is also a param with some specific boundaries elif (args_rdm[0] == 'CRAB_FREEOM'): om = (1 + np.arange(nb_freq)) * Om_ref om_bounds = [(0.5 + nb, 1.5 + nb) * Om_ref for nb in np.arange(nb_freq)] Om = (om, om_bounds) else: Om = rgen.gen_rdmnb_from_string(freq_type, nb_freq) Om = np.sort(Om) #dico_args['omegas'] = om return Om
def _init_params_NM(self, **args_optim): """Methods: + 'zero' for each params 0 <(nb_params) np.array> + 'uniform', 'normal', 'lhs' <(n_bparams+1, n_params ) np.array> + 'nmguess_guess_step0_stepNon0': <(n_params+1, n_params ) np.array> Init produce N+1 vertices arround a guess (adapted fm Matlab routine) guess[0] = guess, guess[i] = guess + e * n_i, with e= step0/stepNon0 (dep on value of guess for a particular element) and n_i is one basis unit vector) e.g. nmguess_[3,0,1]_0.5_1 >> [[3,0,1], [4, 0, 1], [3,0.5,1], [3,0,2]] """ init_obj = args_optim['init_obj'] nb_params = args_optim['nb_params'] if(ut.is_str(init_obj)): args = init_obj.split("_") if(args[0] == 'zero'): init_args = np.zeros_like(nb_params) elif(args[0] in ['uniform','normal', 'lhs']): dim = [nb_params + 1, nb_params] init_args = self.rdm_gen.gen_rdmnb_from_string(init_obj, dim) elif(args[0] == 'nmguess'): assert (len(args)==4), 'nmguess, not the right format' guess, step0, stepNon0 = args[1], args[2], args[3] init_args = np.zeros_like([nb_params + 1, nb_params]) init_args[0, :] = guess for i in range(nb_params): perturb = np.zeros(nb_params) if(guess[i] == 0): perturb[i] = step0 else: perturb[i] = stepNon0 init_args[(i+1), :] = init_args[0,:] + perturb return init_args print(init_args) elif init_obj is None: print('params set up to zero-vect') init_args = np.zeros(nb_params) else: init_args = np.array(init_obj) return init_args
def plot_pop_adiab(model, **args_pop_adiab): """ Plot pop adiab where each population_t is dispatched on one of the three subplot #TODO: better plots """ col_list = [ 'b', 'g', 'r', 'c', 'm', 'k', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9' ] * 10 if (hasattr(model, 'pop_adiab')): limit_legend = args_pop_adiab.get('lim_legend', 15) limit_enlevels = args_pop_adiab.get('lim_enlevels', np.inf) pop_adiab = model.adiab_pop #txn t = model.adiab_t en = model.adiab_en #txn cf = model.adiab_cf # txcf nb_levels = min(pop_adiab.shape[1], limit_enlevels) #[0,0] control function f, axarr = plt.subplots(2, 2, sharex=True) axarr[0, 0].plot(t, cf, label='f') #r"$\Gamma(t)$") for i in range(nb_levels): col = col_list[i] pop_tmp = pop_adiab[:, i] max_tmp = np.max(pop_tmp) if (i <= limit_legend): lbl_tmp = str(i) else: lbl_tmp = None if (max_tmp > 0.1): axarr[0, 1].plot(t, pop_tmp, label=lbl_tmp, color=col) elif (max_tmp > 0.01): axarr[1, 1].plot(t, pop_tmp, label=lbl_tmp, color=col) if (i < 10): axarr[1, 0].plot(t, en[:, i] - en[:, 0], label=lbl_tmp, color=col) ax_tmp = axarr[0, 1] ax_tmp.legend(fontsize='x-small') ax_tmp.set_title('Population', fontsize=8) ax_tmp.set(xlabel='t') ax_tmp = axarr[1, 1] ax_tmp.legend(fontsize='x-small') ax_tmp.set(xlabel='t') ax_tmp = axarr[0, 0] ax_tmp.legend() ax_tmp.set(xlabel='t', ylabel='cf') ax_tmp = axarr[1, 0] ax_tmp.set(xlabel='t', ylabel='E') #r"$E_i - E_0$" save_fig = args_pop_adiab.get('save_fig') if (ut.is_str(save_fig)): f.savefig(save_fig) else: print( "pcModel_qspin.plot_pop_adiab: no pop_adiab found.. Generate it first" )
def parse_atom(cls, atom): """ parse atomic expression simply append """ if (not (ut.is_str(atom))): raise SystemError('prse_atom: atom arg should be a string') res = 'self.build_atom_func(' + atom + ')' return res
def update_context(self, more_context): if (ut.is_dico(more_context)): self._context.update(more_context) else: SystemError('context should be a dictionnary')
def eval_from_onefile(cls, name_file, replace_func = None): """ Get results from a file. Open a file and evaluate (with eval) its first element""" res = ut.eval_from_file(name_file, evfunc = pFunc_base.eval_with_pFunc,replace_func = replace_func) return res
def eval_from_onefile_bug(cls, name): """ eval the first element of the first line of a file """ res = ut.eval_from_file_supercustom(name, evfunc=pFunc_base.eval_with_pFunc) return res