def get_askey_recursion_coefficients_from_variable(var, num_coefs): var_name, scales, shapes = get_distribution_info(var) if var_name not in askey_variable_names: msg = f"Variable name {var_name} not in {askey_variable_names}" raise ValueError(msg) # Askey polynomials associated with continuous variables if var_name == "uniform": poly_name, opts = "legendre", {} elif var_name == "beta": poly_name = "jacobi" opts = {"alpha_poly": shapes["b"] - 1, "beta_poly": shapes["a"] - 1} elif var_name == "norm": poly_name, opts = "hermite", {} opts # Askey polynomials associated with discrete variables elif var_name == "binom": poly_name, opts = "krawtchouk", shapes elif var_name == "hypergeom": # note xk = np.arange(max(0, N-M+n), min(n, N)+1, dtype=float) poly_name = "hahn" M, n, N = [shapes[key] for key in ["M", "n", "N"]] opts = {"alpha_poly": -(n + 1), "beta_poly": -M - 1 + n, "N": N} elif var_name == "poisson": poly_name, opts = "charlier", shapes return get_askey_recursion_coefficients(poly_name, opts, num_coefs)
def __init__(self, variable, enforce_bounds=False): """ Variable uniquness dependes on both the type of random variable e.g. beta, gaussian, etc. and the parameters of that distribution e.g. loc and scale parameters as well as any additional parameters """ if (type(variable) != IndependentMultivariateRandomVariable): variable = IndependentMultivariateRandomVariable(variable) self.variable = variable self.enforce_bounds = enforce_bounds self.identity_map_indices = None self.scale_parameters = np.empty((self.variable.nunique_vars, 2)) for ii in range(self.variable.nunique_vars): var = self.variable.unique_variables[ii] name, scale_dict, __ = get_distribution_info(var) # copy is essential here because code below modifies scale loc, scale = scale_dict['loc'].copy(), scale_dict['scale'].copy() if (is_bounded_continuous_variable(var) or (type(var.dist) == float_rv_discrete and var.dist.name != 'discrete_chebyshev')): lb, ub = -1, 1 scale /= (ub - lb) loc = loc - scale * lb self.scale_parameters[ii, :] = loc, scale
def test_get_univariate_leja_rule_float_rv_discrete(self): nmasses = 20 xk = np.array(range(1, nmasses + 1), dtype='float') pk = np.ones(nmasses) / nmasses variable = float_rv_discrete(name='float_rv_discrete', values=(xk, pk))() growth_rule = partial(constant_increment_growth_rule, 2) quad_rule = get_univariate_leja_quadrature_rule(variable, growth_rule) level = 3 scales, shapes = get_distribution_info(variable)[1:] print(scales) x, w = quad_rule(level) # x in [-1,1], scales for x in [0,1] loc, scale = scales['loc'], scales['scale'] scale /= 2 loc = loc + scale x = x * scale + loc true_moment = (xk**(x.shape[0] - 1)).dot(pk) moment = (x**(x.shape[0] - 1)).dot(w[-1]) #print(moment) #print(true_moment) assert np.allclose(moment, true_moment)
def continuous_induced_measure_ppf(var, ab, ii, u_samples, quad_tol=1e-8, opt_tol=1e-6): if is_bounded_continuous_variable(var): # canonical domain of bounded variables does not coincide with scipy # variable canonical pdf domain with loc,scale=0,1 lb, ub = -1, 1 loc, scale = -1, 2 else: lb, ub = var.support() loc, scale = 0, 1 shapes = get_distribution_info(var)[2] # need to map x from canonical polynomial domain to canonical domain of pdf pdf = lambda x: var.dist._pdf((x - loc) / scale, **shapes) / scale def pdf(x): vals = var.dist._pdf((x - loc) / scale, **shapes) / scale #print('x',x,(x-loc)/scale,vals) return vals #pdf = var.pdf #func = partial(continuous_induced_measure_cdf,pdf,ab,ii,lb,ub,quad_tol) from pyapprox.cython.orthonormal_polynomials_1d import\ continuous_induced_measure_cdf_pyx func = partial(continuous_induced_measure_cdf_pyx, pdf, ab, ii, lb, quad_tol) method = 'bisect' samples = invert_monotone_function(func, [lb, ub], u_samples, method, opt_tol) assert np.all(np.isfinite(samples)) return samples
def get_discrete_univariate_leja_quadrature_rule(variable, growth_rule): var_type, __, shapes = get_distribution_info(variable) if var_type == 'binom': num_trials = variable_parameters['num_trials'] prob_success = variable_parameters['prob_success'] def generate_candidate_samples(num_samples): assert num_samples == num_trials + 1 return np.arange(0, num_trials + 1)[np.newaxis, :] recursion_coeffs = krawtchouk_recurrence(num_trials, num_trials, probability=True) quad_rule = partial(candidate_based_christoffel_leja_rule_1d, recursion_coeffs, generate_candidate_samples, num_trials + 1, growth_rule=growth_rule, initial_points=np.atleast_2d([ binomial_rv.ppf(0.5, num_trials, prob_success) ])) elif var_type == 'float_rv_discrete' or var_type == 'discrete_chebyshev': from pyapprox.numerically_generate_orthonormal_polynomials_1d import \ modified_chebyshev_orthonormal from pyapprox.orthonormal_polynomials_1d import \ discrete_chebyshev_recurrence nmasses = shapes['xk'].shape[0] if var_type == 'discrete_chebyshev': xk = shapes['xk'] #do not map discrete_chebyshev assert np.allclose(shapes['xk'], np.arange(nmasses)) assert np.allclose(shapes['pk'], np.ones(nmasses) / nmasses) num_coefs = nmasses recursion_coeffs = discrete_chebyshev_recurrence( num_coefs, nmasses) else: #shapes['xk'] will be in [0,1] but canonical domain is [-1,1] xk = shapes['xk'] * 2 - 1 num_coefs = nmasses recursion_coeffs = modified_chebyshev_orthonormal( num_coefs, [xk, shapes['pk']]) def generate_candidate_samples(num_samples): assert num_samples == nmasses return xk[np.newaxis, :] # do not specify init_samples in partial or a sparse grid cannot # update the samples_1d so that next level has same samples_1d # TODO: add test that samples_1d[ii] and samples_1d[ii+1] subset # are equal #init_samples = np.atleast_2d(np.sort(xk)[nmasses//2]) quad_rule = partial(candidate_based_christoffel_leja_rule_1d, recursion_coeffs, generate_candidate_samples, nmasses, growth_rule=growth_rule) else: raise Exception('var_type %s not implemented' % var_type) return quad_rule
def get_pymc_variable(rv,pymc_var_name): name, scales, shapes = get_distribution_info(rv) if rv.dist.name=='norm': return pm.Normal( pymc_var_name,mu=scales['loc'],sigma=scales['scale']) if rv.dist.name=='uniform': return pm.Uniform(pymc_var_name,lower=scales['loc'], upper=scales['loc']+scales['scale']) msg = f'Variable type: {name} not supported' raise Exception(msg)
def define_poly_options_from_variable_transformation(var_trans): pce_opts = {'var_trans':var_trans} basis_opts = dict() for ii in range(len(var_trans.variable.unique_variables)): var = var_trans.variable.unique_variables[ii] name, scales, shapes = get_distribution_info(var) opts = {'rv_type':name,'shapes':shapes, 'var_nums':var_trans.variable.unique_variable_indices[ii]} basis_opts['basis%d'%ii]=opts pce_opts['poly_types']=basis_opts return pce_opts
def cvar_beta_variable(rv, beta): """ cvar of Beta variable on [0,1] """ qq = rv.ppf(beta) scales, shapes = get_distribution_info(rv)[1:] qq = (qq - scales["loc"]) / scales["scale"] g1, g2 = shapes["a"], shapes["b"] cvar01 = ((1 - betainc(1 + g1, g2, qq)) * gamma_fn(1 + g1) * gamma_fn(g2) / gamma_fn(1 + g1 + g2)) / (beta_fn(g1, g2) * (1 - beta)) return cvar01 * scales["scale"] + scales["loc"]
def define_poly_options_from_variable(variable): basis_opts = dict() for ii in range(len(variable.unique_variables)): var = variable.unique_variables[ii] name, scales, shapes = get_distribution_info(var) opts = { 'var': var, 'shapes': shapes, 'var_nums': variable.unique_variable_indices[ii] } basis_opts['basis%d' % ii] = opts return basis_opts
def get_univariate_leja_quadrature_rule( variable, growth_rule, method='pdf', numerically_generated_poly_accuracy_tolerance=1e-12, initial_points=None): if not is_continuous_variable(variable): return get_discrete_univariate_leja_quadrature_rule( variable, growth_rule, numerically_generated_poly_accuracy_tolerance=numerically_generated_poly_accuracy_tolerance, initial_points=initial_points) if method == 'christoffel': return partial( univariate_christoffel_leja_quadrature_rule, variable, growth_rule, numerically_generated_poly_accuracy_tolerance=numerically_generated_poly_accuracy_tolerance, initial_points=initial_points) if method == 'pdf': return partial( univariate_pdf_weighted_leja_quadrature_rule, variable, growth_rule, numerically_generated_poly_accuracy_tolerance=numerically_generated_poly_accuracy_tolerance, initial_points=initial_points) assert method == 'deprecated' var_name, __, shapes = get_distribution_info(variable) if var_name == 'uniform': quad_rule = partial( beta_leja_quadrature_rule, 1, 1, growth_rule=growth_rule, samples_filename=None) elif var_name == 'beta': quad_rule = partial( beta_leja_quadrature_rule, shapes['a'], shapes['b'], growth_rule=growth_rule) elif var_name == 'norm': quad_rule = partial( gaussian_leja_quadrature_rule, growth_rule=growth_rule) else: raise Exception('var_name %s not implemented' % var_name) return quad_rule
def get_pdf_weight_functions(variable): name, scales, shapes = get_distribution_info(variable) if name == 'uniform' or name == 'beta': if name == 'uniform': alpha_stat, beta_stat = 1, 1 else: alpha_stat, beta_stat = shapes['a'], shapes['b'] def pdf(x): return beta_pdf(alpha_stat, beta_stat, (x+1)/2)/2 def pdf_jac(x): return beta_pdf_derivative(alpha_stat, beta_stat, (x+1)/2)/4 return pdf, pdf_jac if name == 'norm': return partial(gaussian_pdf, 0, 1), \ partial(gaussian_pdf_derivative, 0, 1) raise Exception(f'var_type (name) not supported')
def test_get_univariate_leja_rule_discrete_chebyshev(self): nmasses = 20 xk = np.array(range(0, nmasses), dtype='float') pk = np.ones(nmasses) / nmasses variable = float_rv_discrete(name='discrete_chebyshev', values=(xk, pk))() growth_rule = partial(constant_increment_growth_rule, 2) quad_rule = get_univariate_leja_quadrature_rule(variable, growth_rule) level = 3 scales, shapes = get_distribution_info(variable)[1:] x, w = quad_rule(level) true_moment = (xk**(x.shape[0] - 1)).dot(pk) moment = (x**(x.shape[0] - 1)).dot(w[-1]) #print(moment) #print(true_moment) assert np.allclose(moment, true_moment)
def get_discrete_univariate_leja_quadrature_rule( variable, growth_rule, initial_points=None, orthonormality_tol=1e-12, return_weights_for_all_levels=True, recursion_opts=None): from pyapprox.variables import get_probability_masses, \ is_bounded_discrete_variable var_name = get_distribution_info(variable)[0] if is_bounded_discrete_variable(variable): xk, pk = get_probability_masses(variable) loc, scale = transform_scale_parameters(variable) xk = (xk - loc) / scale if initial_points is None: initial_points = (np.atleast_2d([variable.ppf(0.5)]) - loc) / scale # initial samples must be in canonical space assert np.all((initial_points >= -1) & (initial_points <= 1)) assert np.all((xk >= -1) & (xk <= 1)) def generate_candidate_samples(num_samples): return xk[None, :] if recursion_opts is None: recursion_opts = {"orthonormality_tol": orthonormality_tol} ab = get_recursion_coefficients_from_variable(variable, xk.shape[0], recursion_opts) quad_rule = partial( candidate_based_christoffel_leja_rule_1d, ab, generate_candidate_samples, xk.shape[0], growth_rule=growth_rule, initial_points=initial_points, return_weights_for_all_levels=return_weights_for_all_levels) return quad_rule raise ValueError('var_name %s not implemented' % var_name)
def get_discrete_univariate_leja_quadrature_rule(variable, growth_rule, initial_points=None, numerically_generated_poly_accuracy_tolerance=1e-12): from pyapprox.variables import get_probability_masses, \ is_bounded_discrete_variable var_name, scales, shapes = get_distribution_info(variable) if is_bounded_discrete_variable(variable): if initial_points is None: initial_points = np.atleast_2d([variable.ppf(0.5)]) xk, pk = get_probability_masses(variable) def generate_candidate_samples(num_samples): return xk[None, :] opts = {'rv_type': var_name, 'shapes': shapes} recursion_coeffs = get_recursion_coefficients( opts, xk.shape[0], numerically_generated_poly_accuracy_tolerance=numerically_generated_poly_accuracy_tolerance) quad_rule = partial( candidate_based_christoffel_leja_rule_1d, recursion_coeffs, generate_candidate_samples, xk.shape[0], growth_rule=growth_rule, initial_points=initial_points) else: raise Exception('var_name %s not implemented' % var_name) return quad_rule
def get_recursion_coefficients_from_variable(var, num_coefs, opts): """ Generate polynomial recursion coefficients by inspecting a random variable. """ var_name, _, shapes = get_distribution_info(var) if var_name == "continuous_monomial": return None loc, scale = transform_scale_parameters(var) if var_name == "rv_function_indpndt_vars": shapes["loc"] = loc shapes["scale"] = scale return get_function_independent_vars_recursion_coefficients( shapes, num_coefs) if var_name == "rv_product_indpndt_vars": shapes["loc"] = loc shapes["scale"] = scale return get_product_independent_vars_recursion_coefficients( shapes, num_coefs) if (var_name in askey_variable_names and opts.get("numeric", False) is False): return get_askey_recursion_coefficients_from_variable(var, num_coefs) orthonormality_tol = opts.get("orthonormality_tol", 1e-8) truncated_probability_tol = opts.get("truncated_probability_tol", 0) if (not is_continuous_variable(var)): if hasattr(shapes, "xk"): xk, pk = shapes["xk"], shapes["pk"] else: xk, pk = get_probability_masses(var, truncated_probability_tol) xk = (xk - loc) / scale return get_numerically_generated_recursion_coefficients_from_samples( xk, pk, num_coefs, orthonormality_tol, truncated_probability_tol) # integration performed in canonical domain so need to map back to # domain of pdf lb, ub = var.interval(1) # Get version var.pdf without error checking which runs much faster pdf = get_pdf(var) def canonical_pdf(x): # print(x, lb, ub, x*scale+loc) # print(var.pdf(x*scale+loc)*scale) # assert np.all(x*scale+loc >= lb) and np.all(x*scale+loc <= ub) return pdf(x * scale + loc) * scale # return var.pdf(x*scale+loc)*scale if (is_bounded_continuous_variable(var) or is_bounded_discrete_variable(var)): can_lb, can_ub = -1, 1 elif is_continuous_variable(var): can_lb = (lb - loc) / scale can_ub = (ub - loc) / scale return predictor_corrector_known_pdf(num_coefs, can_lb, can_ub, canonical_pdf, opts)
def univariate_christoffel_leja_quadrature_rule( variable, growth_rule, level, return_weights_for_all_levels=True, initial_points=None, orthonormality_tol=1e-12, recursion_opts=None, minimizer_opts=None): """ Return the samples and weights of the Leja quadrature rule for any continuous variable using the inverse Christoffel weight function By construction these rules have polynomial ordering. Parameters ---------- variable : scipy.stats.dist The variable used to construct an orthogonormal polynomial growth_rule : callable Function which returns the number of samples in the quadrature rule With signature `growth_rule(level) -> integer` where level is an integer level : integer The level of the univariate rule. return_weights_for_all_levels : boolean True - return weights [w(0),w(1),...,w(level)] False - return w(level) initial_points : np.ndarray (1, ninit_samples) Any points that must be included in the Leja sequence. This argument is typically used to pass in previously computed sequence which is updated efficiently here. MUST be in the canonical domain Return ------ ordered_samples_1d : np.ndarray (num_samples_1d) The reordered samples. ordered_weights_1d : np.ndarray (num_samples_1d) The reordered weights. """ if not is_continuous_variable(variable): raise Exception('Only supports continuous variables') name, scales, shapes = get_distribution_info(variable) max_nsamples = growth_rule(level) if recursion_opts is None: recursion_opts = {"orthonormality_tol": orthonormality_tol} ab = get_recursion_coefficients_from_variable(variable, max_nsamples + 1, recursion_opts) basis_fun = partial(evaluate_orthonormal_polynomial_deriv_1d, ab=ab) initial_points, bounds = transform_initial_samples(variable, initial_points) if minimizer_opts is None: minimizer_opts = {'gtol': 1e-8, 'verbose': False} if ("artificial_bounds" not in minimizer_opts and not is_bounded_continuous_variable(variable)): # make bounds twice that of gauss quadrature points xg, wg = gauss_quadrature(ab, max_nsamples) artificial_bounds = bounds.copy() if not np.isfinite(bounds[0]): artificial_bounds[0] = xg.min() artificial_bounds[0] = artificial_bounds[0] - abs( artificial_bounds[0]) if not np.isfinite(bounds[1]): artificial_bounds[1] = xg.max() artificial_bounds[1] = artificial_bounds[1] + abs( artificial_bounds[1]) minimizer_opts["artificial_bounds"] = artificial_bounds leja_sequence = get_christoffel_leja_sequence_1d(max_nsamples, initial_points, bounds, basis_fun, minimizer_opts, callback=None) __basis_fun = partial(basis_fun, nmax=max_nsamples - 1, deriv_order=0) ordered_weights_1d = get_christoffel_leja_quadrature_weights_1d( leja_sequence, growth_rule, __basis_fun, level, True) if return_weights_for_all_levels: return leja_sequence[0, :], ordered_weights_1d return leja_sequence[0, :], ordered_weights_1d[-1]
# the following use the product of uniforms to define basis from pyapprox.variables import get_distribution_info from pyapprox.univariate_quadrature import gauss_jacobi_pts_wts_1D from pyapprox.utilities import total_degree_space_dimension def identity_fun(x): return x degree = 3 poly = PolynomialChaosExpansion() basis_opts = dict() identity_map_indices = [] cnt = 0 for ii in range(re_variable.nunique_vars): rv = re_variable.unique_variables[ii] name, scales, shapes = get_distribution_info(rv) if ii not in [0, 2]: opts = {'rv_type': name, 'shapes': shapes, 'var_nums': re_variable.unique_variable_indices[ii]} basis_opts['basis%d' % ii] = opts continue #identity_map_indices += re_variable.unique_variable_indices[ii] # wrong identity_map_indices += list(re_variable.unique_variable_indices[ii]) # right quad_rules = [] inds = index_product[cnt] nquad_samples_1d = 50 for jj in inds: a, b = variable.all_variables()[jj].interval(1)
def univariate_pdf_weighted_leja_quadrature_rule( variable, growth_rule, level, return_weights_for_all_levels=True, initial_points=None, numerically_generated_poly_accuracy_tolerance=1e-12): """ Return the samples and weights of the Leja quadrature rule for any continuous variable using the PDF of the random variable as the weight function By construction these rules have polynomial ordering. Parameters ---------- variable : scipy.stats.dist The variable used to construct an orthogonormal polynomial growth_rule : callable Function which returns the number of samples in the quadrature rule With signature `growth_rule(level) -> integer` where level is an integer level : integer The level of the univariate rule. return_weights_for_all_levels : boolean True - return weights [w(0),w(1),...,w(level)] False - return w(level) initial_points : np.ndarray (1, ninit_samples) Any points that must be included in the Leja sequence. This argument is typically used to pass in previously computed sequence which is updated efficiently here. Return ------ ordered_samples_1d : np.ndarray (num_samples_1d) The reordered samples. ordered_weights_1d : np.ndarray (num_samples_1d) The reordered weights. """ if not is_continuous_variable(variable): raise Exception('Only supports continuous variables') name, scales, shapes = get_distribution_info(variable) opts = {'rv_type': name, 'shapes': shapes, 'var_nums': variable} max_nsamples = growth_rule(level) ab = get_recursion_coefficients( opts, max_nsamples+1, numerically_generated_poly_accuracy_tolerance) basis_fun = partial(evaluate_orthonormal_polynomial_deriv_1d, ab=ab) pdf, pdf_jac = get_pdf_weight_functions(variable) if is_bounded_continuous_variable(variable): bounds = variable.interval(1.) canonical_bounds = [-1, 1] if initial_points is None: initial_points = np.asarray( [[variable.ppf(0.5)]]).T loc, scale = scales['loc'], scales['scale'] lb, ub = -1, 1 scale /= (ub-lb) loc = loc-scale*lb initial_points = (initial_points-loc)/scale assert np.all((initial_points >= canonical_bounds[0]) & (initial_points <= canonical_bounds[1])) # always produce sequence in canonical space bounds = canonical_bounds else: bounds = list(variable.interval(1)) if initial_points is None: initial_points = np.asarray( [[variable.ppf(0.5)]]).T loc, scale = scales['loc'], scales['scale'] initial_points = (initial_points-loc)/scale leja_sequence = get_pdf_weighted_leja_sequence_1d( max_nsamples, initial_points, bounds, basis_fun, pdf, pdf_jac, {'gtol': 1e-8, 'verbose': False}, callback=None) __basis_fun = partial(basis_fun, nmax=max_nsamples-1, deriv_order=0) ordered_weights_1d = get_pdf_weighted_leja_quadrature_weights_1d( leja_sequence, growth_rule, pdf, __basis_fun, level, True) return leja_sequence[0, :], ordered_weights_1d
def test_get_distribution_params(self): name, scales, shapes = get_distribution_info( stats.beta(a=1, b=2, loc=0, scale=1)) assert name == 'beta' assert shapes == {'a': 1, 'b': 2} assert scales == {'loc': 0, 'scale': 1} rv = stats.beta(a=1, b=2, loc=3, scale=4) pdf = get_pdf(rv) xx = rv.rvs(100) assert np.allclose(pdf(xx), rv.pdf(xx)) name, scales, shapes = get_distribution_info( stats.beta(1, 2, loc=0, scale=1)) assert name == 'beta' assert shapes == {'a': 1, 'b': 2} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info( stats.beta(1, 2, 0, scale=1)) assert name == 'beta' assert shapes == {'a': 1, 'b': 2} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.beta(1, 2, 0, 1)) assert name == 'beta' assert shapes == {'a': 1, 'b': 2} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.norm(0, 1)) assert name == 'norm' assert shapes == dict() assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.norm(0, scale=1)) assert name == 'norm' assert shapes == dict() assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.norm( loc=0, scale=1)) assert name == 'norm' assert shapes == dict() assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info( stats.gamma(a=1, loc=0, scale=1)) assert name == 'gamma' assert shapes == {'a': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info( stats.gamma(1, loc=0, scale=1)) assert name == 'gamma' assert shapes == {'a': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info( stats.gamma(1, 0, scale=1)) assert name == 'gamma' assert shapes == {'a': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.gamma(1, 0, 1)) assert name == 'gamma' assert shapes == {'a': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.gamma(1)) assert name == 'gamma' assert shapes == {'a': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.gamma(1, loc=0)) assert name == 'gamma' assert shapes == {'a': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.gamma(1, scale=1)) assert name == 'gamma' assert shapes == {'a': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info( stats.binom(n=1, p=1, loc=0)) assert name == 'binom' assert shapes == {'n': 1, 'p': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info( stats.binom(1, p=1, loc=0)) assert name == 'binom' assert shapes == {'n': 1, 'p': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.binom(1, 1, loc=0)) assert name == 'binom' assert shapes == {'n': 1, 'p': 1} assert scales == {'loc': 0, 'scale': 1} name, scales, shapes = get_distribution_info(stats.binom(1, 1, 0)) assert name == 'binom' assert shapes == {'n': 1, 'p': 1} assert scales == {'loc': 0, 'scale': 1}