def get_candidate_based_leja_sequence_1d(num_leja_samples, recursion_coeffs, generate_candidate_samples, num_candidate_samples, weight_function, initial_points=None, samples_filename=None): from pyapprox.orthonormal_polynomials_1d import \ evaluate_orthonormal_polynomial_1d from pyapprox.polynomial_sampling import get_lu_leja_samples generate_basis_matrix = lambda x: evaluate_orthonormal_polynomial_1d( x[0, :], num_leja_samples, recursion_coeffs) if samples_filename is None or not os.path.exists(samples_filename): leja_sequence, __ = get_lu_leja_samples( generate_basis_matrix, generate_candidate_samples, num_candidate_samples, num_leja_samples, preconditioning_function=weight_function, initial_samples=initial_points) if samples_filename is not None: np.savez(samples_filename, samples=leja_sequence) else: leja_sequence = np.load(samples_filename)['samples'] assert leja_sequence.shape[1] >= num_leja_samples leja_sequence = leja_sequence[:, :num_leja_samples] return leja_sequence
def demo_idist_jacobi(): alph = -0.8 bet = np.sqrt(101) #n = 8; M = 1001 n = 4 M = 5 x = np.cos(np.linspace(0, np.pi, M + 2))[:, np.newaxis] x = x[1:-1] F = idist_jacobi(x, n, alph, bet) recursion_coeffs = jacobi_recurrence(n + 1, alph, bet, True) #recursion_coeffs[:,1]=np.sqrt(recursion_coeffs[:,1]) polys = evaluate_orthonormal_polynomial_1d(x[:, 0], n, recursion_coeffs) wt_function = (1 - x)**alph * (1 + x)**bet / (2**(alph + bet + 1) * betafn(bet + 1, alph + 1)) f = polys[:, -1:]**2 * wt_function fig, ax = plt.subplots(1, 1) plt.plot(x, f) ax.set_xlabel('$x$') ax.set_ylabel('$p_n^2(x) \mathrm{d}\mu(x)$') ax.set_xlim(-1, 1) ax.set_ylim(0, 4) fig, ax = plt.subplots(1, 1) plt.plot(x, F) ax.set_xlabel('$x$') ax.set_ylabel('$F_n(x)$') ax.set_xlim(-1, 1) plt.show()
def compute_univariate_orthonormal_basis_products(get_recursion_coefficients, max_degree1, max_degree2): """ Compute all the products of univariate orthonormal bases and re-express them as expansions using the orthnormal basis. """ assert max_degree1 >= max_degree2 max_degree = max_degree1 + max_degree2 num_quad_points = max_degree + 1 recursion_coefs = get_recursion_coefficients(num_quad_points) x_quad, w_quad = gauss_quadrature(recursion_coefs, num_quad_points) w_quad = w_quad[:, np.newaxis] # evaluate the orthonormal basis at the quadrature points. This can # be computed once for all degrees up to the maximum degree ortho_basis_matrix = evaluate_orthonormal_polynomial_1d( x_quad, max_degree, recursion_coefs) # compute coefficients of orthonormal basis using pseudo # spectral projection product_coefs = [] for d1 in range(max_degree1 + 1): for d2 in range(min(d1 + 1, max_degree2 + 1)): product_vals = ortho_basis_matrix[:, d1] * ortho_basis_matrix[:, d2] coefs = w_quad.T.dot(product_vals[:, np.newaxis] * ortho_basis_matrix[:, :d1 + d2 + 1]).T product_coefs.append(coefs) return product_coefs
def integrand(x): pvals = evaluate_orthonormal_polynomial_1d(x, ii, ab) #from matplotlib import pyplot as plt #print(ab[0, 1]) #plt.plot(x, pvals[:, 1]) #plt.plot(x, x/ab[0, 1]**2) #plt.show() return measure(x) * pvals[:, ii] * pvals[:, ii - 1]
def float_rv_discrete_inverse_transform_sampling_1d(xk, pk, ab, ii, u_samples): poly_vals = evaluate_orthonormal_polynomial_1d(np.asarray(xk, dtype=float), ii, ab)[:, -1] probability_masses = pk * poly_vals**2 cdf_vals = np.cumsum(probability_masses) assert np.allclose(cdf_vals[-1], 1), cdf_vals[-1] #cdf_vals/=cdf_vals[-1] sample_indices = np.searchsorted(cdf_vals, u_samples) samples = xk[sample_indices] return samples
def convert_univariate_lagrange_basis_to_orthonormal_polynomials( samples_1d, get_recursion_coefficients): """ Returns ------- coeffs_1d : list [np.ndarray(num_terms_i,num_terms_i)] The coefficients of the orthonormal polynomial representation of each Lagrange basis. The columns are the coefficients of each lagrange basis. The rows are the coefficient of the degree i orthonormalbasis """ # Get the maximum number of terms in the orthonormal polynomial that # are need to interpolate all the interpolation nodes in samples_1d max_num_terms = samples_1d[-1].shape[0] num_quad_points = max_num_terms + 1 # Get the recursion coefficients of the orthonormal basis recursion_coeffs = get_recursion_coefficients(num_quad_points) # compute the points and weights of the correct quadrature rule x_quad, w_quad = gauss_quadrature(recursion_coeffs, num_quad_points) # evaluate the orthonormal basis at the quadrature points. This can # be computed once for all degrees up to the maximum degree ortho_basis_matrix = evaluate_orthonormal_polynomial_1d( x_quad, max_num_terms, recursion_coeffs) # compute coefficients of orthonormal basis using pseudo spectral projection coeffs_1d = [] w_quad = w_quad[:, np.newaxis] for ll in range(len(samples_1d)): num_terms = samples_1d[ll].shape[0] # evaluate the lagrange basis at the quadrature points barycentric_weights_1d = [ compute_barycentric_weights_1d(samples_1d[ll]) ] values = np.eye((num_terms), dtype=float) # Sometimes the following function will cause the erro # interpolation abscissa are not unique. This can be due to x_quad # not abscissa. E.g. x_quad may have points far enough outside # range of abscissa, e.g. abscissa are clenshaw curtis points and # x_quad points are Gauss-Hermite quadrature points lagrange_basis_vals = multivariate_barycentric_lagrange_interpolation( x_quad[np.newaxis, :], samples_1d[ll][np.newaxis, :], barycentric_weights_1d, values, np.zeros(1, dtype=int)) # compute fourier like coefficients basis_coeffs = [] for ii in range(num_terms): basis_coeffs.append( np.dot(w_quad.T, lagrange_basis_vals * ortho_basis_matrix[:, ii:ii + 1])[0, :]) coeffs_1d.append(np.asarray(basis_coeffs)) return coeffs_1d
def continuous_induced_measure_cdf(pdf, ab, ii, lb, ub, tol, x): x = np.atleast_1d(x) assert x.ndim == 1 assert x.min() >= lb and x.max() <= ub integrand = lambda xx: evaluate_orthonormal_polynomial_1d( np.atleast_1d(xx), ii, ab)[:, -1]**2 * pdf(xx) #from pyapprox.cython.orthonormal_polynomials_1d import\ # induced_measure_pyx #integrand = lambda x: induced_measure_pyx(x,ii,ab,pdf) vals = np.empty_like(x, dtype=float) for jj in range(x.shape[0]): integral, err = integrate.quad(integrand, lb, x[jj], epsrel=tol, epsabs=tol, limit=100) vals[jj] = integral # avoid numerical issues at boundary of domain if vals[jj] > 1 and vals[jj] - 1 < tol: vals[jj] = 1. return vals
def candidate_based_leja_rule(recursion_coeffs, generate_candidate_samples, num_candidate_samples, level, initial_points=None, growth_rule=leja_growth_rule, samples_filename=None, return_weights_for_all_levels=True): from pyapprox.orthonormal_polynomials_1d import \ evaluate_orthonormal_polynomial_1d from pyapprox.polynomial_sampling import get_lu_leja_samples,\ christoffel_preconditioner, christoffel_weights num_leja_samples = growth_rule(level) generate_basis_matrix = lambda x: evaluate_orthonormal_polynomial_1d( x[0, :], num_leja_samples, recursion_coeffs) if samples_filename is None or not os.path.exists(samples_filename): leja_sequence, __ = get_lu_leja_samples( generate_basis_matrix, generate_candidate_samples, num_candidate_samples, num_leja_samples, preconditioning_function=christoffel_preconditioner, initial_samples=initial_points) if samples_filename is not None: np.savez(samples_filename, samples=leja_sequence) else: leja_sequence = np.load(samples_filename)['samples'] assert leja_sequence.shape[1] >= growth_rule(level) leja_sequence = leja_sequence[:, :growth_rule(level)] weight_function = lambda x: christoffel_weights(generate_basis_matrix(x)) ordered_weights_1d = get_leja_sequence_quadrature_weights( leja_sequence, growth_rule, generate_basis_matrix, weight_function, level, return_weights_for_all_levels) return leja_sequence[0, :], ordered_weights_1d
def basis_matrix_generator_1d(pce, nmax, dd, samples): vals = evaluate_orthonormal_polynomial_1d( samples, nmax, pce.recursion_coeffs[pce.basis_type_index_map[dd]]) return vals
def conditional_moments_of_polynomial_chaos_expansion(poly, samples, inactive_idx, return_variance=False): """ Return mean and variance of polynomial chaos expansion with some variables fixed at specified values. Parameters ---------- poly: PolynomialChaosExpansion The polynomial used to compute moments inactive_idx : np.ndarray (ninactive_vars) The indices of the fixed variables samples : np.ndarray (ninactive_vars) The samples of the inacive dimensions fixed when computing moments Returns ------- mean : np.ndarray The conditional mean (num_qoi) variance : np.ndarray The conditional variance (num_qoi). Only returned if return_variance=True. Computing variance is significantly slower than computing mean. TODO check it is indeed slower """ assert samples.shape[0] == len(inactive_idx) assert samples.ndim == 2 and samples.shape[1] == 1 assert poly.coefficients is not None coef = poly.get_coefficients() indices = poly.get_indices() # precompute 1D basis functions for faster evaluation of # multivariate terms basis_vals_1d = [] for dd in range(len(inactive_idx)): basis_vals_1d_dd = evaluate_orthonormal_polynomial_1d( samples[dd, :], indices[inactive_idx[dd], :].max(), poly.recursion_coeffs[poly.basis_type_index_map[inactive_idx[dd]]]) basis_vals_1d.append(basis_vals_1d_dd) active_idx = np.setdiff1d(np.arange(poly.num_vars()), inactive_idx) mean = coef[0].copy() for ii in range(1, indices.shape[1]): index = indices[:, ii] coef_ii = coef[ii] # this intentionally updates the coef matrix for dd in range(len(inactive_idx)): coef_ii *= basis_vals_1d[dd][0, index[inactive_idx[dd]]] if index[active_idx].sum() == 0: mean += coef_ii if not return_variance: return mean unique_indices, repeated_idx = np.unique(indices[active_idx, :], axis=1, return_inverse=True) new_coef = np.zeros((unique_indices.shape[1], coef.shape[1])) for ii in range(repeated_idx.shape[0]): new_coef[repeated_idx[ii]] += coef[ii] variance = np.sum(new_coef**2, axis=0) - mean**2 return mean, variance
def get_recursion_coefficients(self, opts, num_coefs): poly_type = opts.get('poly_type', None) var_type = None if poly_type is None: var_type = opts['rv_type'] if poly_type == 'legendre' or var_type == 'uniform': recursion_coeffs = jacobi_recurrence(num_coefs, alpha=0, beta=0, probability=True) elif poly_type == 'jacobi' or var_type == 'beta': if poly_type is not None: alpha_poly, beta_poly = opts['alpha_poly'], opts['beta_poly'] else: alpha_poly, beta_poly = opts['shapes']['b'] - 1, opts[ 'shapes']['a'] - 1 recursion_coeffs = jacobi_recurrence(num_coefs, alpha=alpha_poly, beta=beta_poly, probability=True) elif poly_type == 'hermite' or var_type == 'norm': recursion_coeffs = hermite_recurrence(num_coefs, rho=0., probability=True) elif poly_type == 'krawtchouk' or var_type == 'binom': if poly_type is None: opts = opts['shapes'] n, p = opts['n'], opts['p'] num_coefs = min(num_coefs, n) recursion_coeffs = krawtchouk_recurrence(num_coefs, n, p) elif poly_type == 'hahn' or var_type == 'hypergeom': if poly_type is not None: apoly, bpoly = opts['alpha_poly'], opts['beta_poly'] N = opts['N'] else: M, n, N = [opts['shapes'][key] for key in ['M', 'n', 'N']] apoly, bpoly = -(n + 1), -M - 1 + n num_coefs = min(num_coefs, N) recursion_coeffs = hahn_recurrence(num_coefs, N, apoly, bpoly) elif poly_type == 'discrete_chebyshev' or var_type == 'discrete_chebyshev': if poly_type is not None: N = opts['N'] else: N = opts['shapes']['xk'].shape[0] assert np.allclose(opts['shapes']['xk'], np.arange(N)) assert np.allclose(opts['shapes']['pk'], np.ones(N) / N) num_coefs = min(num_coefs, N) recursion_coeffs = discrete_chebyshev_recurrence(num_coefs, N) elif poly_type == 'discrete_numeric' or var_type == 'float_rv_discrete': if poly_type is None: opts = opts['shapes'] xk, pk = opts['xk'], opts['pk'] #shapes['xk'] will be in [0,1] but canonical domain is [-1,1] xk = xk * 2 - 1 assert xk.min() >= -1 and xk.max() <= 1 if num_coefs > xk.shape[0]: msg = 'Number of coefs requested is larger than number of ' msg += 'probability masses' raise Exception(msg) recursion_coeffs = modified_chebyshev_orthonormal( num_coefs, [xk, pk]) p = evaluate_orthonormal_polynomial_1d(np.asarray(xk, dtype=float), num_coefs - 1, recursion_coeffs) error = np.absolute((p.T * pk).dot(p) - np.eye(num_coefs)).max() if error > self.numerically_generated_poly_accuracy_tolerance: msg = f'basis created is ill conditioned. ' msg += f'Max error: {error}. Max terms: {xk.shape[0]}, ' msg += f'Terms requested: {num_coefs}' raise Exception(msg) elif poly_type == 'monomial': recursion_coeffs = None else: if poly_type is not None: raise Exception('poly_type (%s) not supported' % poly_type) else: raise Exception('var_type (%s) not supported' % var_type) return recursion_coeffs
def get_recursion_coefficients( opts, num_coefs, numerically_generated_poly_accuracy_tolerance=1e-12): """ Parameters ---------- num_coefs : interger The number of recursion coefficients desired numerically_generated_poly_accuracy_tolerance : float Tolerance used to construct any numerically generated polynomial basis functions. opts : dictionary Dictionary with the following attributes rv_type : string The type of variable associated with the polynomial. If poly_type is not provided then the recursion coefficients chosen is selected using the Askey scheme. E.g. uniform -> legendre, norm -> hermite. rv_type is assumed to be the name of the distribution of scipy.stats variables, e.g. for gaussian rv_type = norm(0, 1).dist poly_type : string The type of polynomial which overides rv_type. Supported types ['legendre', 'hermite', 'jacobi', 'krawtchouk', 'hahn', 'discrete_chebyshev', 'discrete_numeric', 'continuous_numeric', 'function_indpnt_vars', 'product_indpnt_vars', 'monomial'] Note 'monomial' does not produce an orthogonal basis The remaining options are specific to rv_type and poly_type. See - :func:`pyapprox.univariate_quadrature.get_jacobi_recursion_coefficients` - :func:`pyapprox.univariate_quadrature.get_function_independent_vars_recursion_coefficients` - :func:`pyapprox.univariate_quadrature.get_product_independent_vars_recursion_coefficients` Note Legendre is just a special instance of a Jacobi polynomial with alpha_poly, beta_poly = 0, 0 and alpha_stat, beta_stat = 1, 1 Returns ------- recursion_coeffs : np.ndarray (num_coefs, 2) """ # variables that require numerically generated polynomials with # predictor corrector method from scipy import stats from scipy.stats import _continuous_distns poly_type = opts.get('poly_type', None) var_type = None if poly_type is None: var_type = opts['rv_type'] if poly_type == 'legendre' or var_type == 'uniform': recursion_coeffs = jacobi_recurrence( num_coefs, alpha=0, beta=0, probability=True) elif poly_type == 'jacobi' or var_type == 'beta': recursion_coeffs = get_jacobi_recursion_coefficients( poly_type, opts, num_coefs) elif poly_type == 'hermite' or var_type == 'norm': recursion_coeffs = hermite_recurrence( num_coefs, rho=0., probability=True) elif poly_type == 'krawtchouk' or var_type == 'binom': # although bounded the krwatchouk polynomials are not defined # on the canonical domain [-1,1] but rather the user and # canconical domain are the same if poly_type is None: opts = opts['shapes'] n, p = opts['n'], opts['p'] num_coefs = min(num_coefs, n) recursion_coeffs = krawtchouk_recurrence( num_coefs, n, p) elif poly_type == 'hahn' or var_type == 'hypergeom': # although bounded the hahn polynomials are not defined # on the canonical domain [-1,1] but rather the user and # canconical domain are the same if poly_type is not None: apoly, bpoly = opts['alpha_poly'], opts['beta_poly'] N = opts['N'] else: M, n, N = [opts['shapes'][key] for key in ['M', 'n', 'N']] apoly, bpoly = -(n+1), -M-1+n num_coefs = min(num_coefs, N) recursion_coeffs = hahn_recurrence(num_coefs, N, apoly, bpoly) xk = np.arange(max(0, N-M+n), min(n, N)+1, dtype=float) elif poly_type == 'discrete_chebyshev' or var_type == 'discrete_chebyshev': # although bounded the discrete_chebyshev polynomials are not defined # on the canonical domain [-1,1] but rather the user and # canconical domain are the same if poly_type is not None: N = opts['N'] else: N = opts['shapes']['xk'].shape[0] assert np.allclose(opts['shapes']['xk'], np.arange(N)) assert np.allclose(opts['shapes']['pk'], np.ones(N)/N) num_coefs = min(num_coefs, N) recursion_coeffs = discrete_chebyshev_recurrence(num_coefs, N) elif poly_type == 'discrete_numeric' or var_type == 'float_rv_discrete': if poly_type is None: opts = opts['shapes'] xk, pk = opts['xk'], opts['pk'] # shapes['xk'] will be in [0, 1] but canonical domain is [-1, 1] xk = xk*2-1 assert xk.min() >= -1 and xk.max() <= 1 if num_coefs > xk.shape[0]: msg = 'Number of coefs requested is larger than number of ' msg += 'probability masses' raise Exception(msg) #recursion_coeffs = modified_chebyshev_orthonormal(num_coefs, [xk, pk]) recursion_coeffs = lanczos(xk, pk, num_coefs) p = evaluate_orthonormal_polynomial_1d( np.asarray(xk, dtype=float), num_coefs-1, recursion_coeffs) error = np.absolute((p.T*pk).dot(p)-np.eye(num_coefs)).max() if error > numerically_generated_poly_accuracy_tolerance: msg = f'basis created is ill conditioned. ' msg += f'Max error: {error}. Max terms: {xk.shape[0]}, ' msg += f'Terms requested: {num_coefs}' raise Exception(msg) elif (poly_type == 'continuous_numeric' or var_type == 'continuous_rv_sample'): if poly_type is None: opts = opts['shapes'] xk, pk = opts['xk'], opts['pk'] if num_coefs > xk.shape[0]: msg = 'Number of coefs requested is larger than number of ' msg += 'samples' raise Exception(msg) #print(num_coefs) #recursion_coeffs = modified_chebyshev_orthonormal(num_coefs, [xk, pk]) #recursion_coeffs = lanczos(xk, pk, num_coefs) recursion_coeffs = predictor_corrector( num_coefs, (xk, pk), xk.min(), xk.max(), interval_size=xk.max()-xk.min()) p = evaluate_orthonormal_polynomial_1d( np.asarray(xk, dtype=float), num_coefs-1, recursion_coeffs) error = np.absolute((p.T*pk).dot(p)-np.eye(num_coefs)).max() if error > numerically_generated_poly_accuracy_tolerance: msg = f'basis created is ill conditioned. ' msg += f'Max error: {error}. Max terms: {xk.shape[0]}, ' msg += f'Terms requested: {num_coefs}' raise Exception(msg) elif poly_type == 'monomial': recursion_coeffs = None elif var_type in _continuous_distns._distn_names: quad_options = { 'nquad_samples': 10, 'atol': numerically_generated_poly_accuracy_tolerance, 'rtol': numerically_generated_poly_accuracy_tolerance, 'max_steps': 10000, 'verbose': 0} rv = getattr(stats, var_type)(**opts['shapes']) recursion_coeffs = predictor_corrector_known_scipy_pdf( num_coefs, rv, quad_options) elif poly_type == 'function_indpnt_vars': recursion_coeffs = get_function_independent_vars_recursion_coefficients( opts, num_coefs) elif poly_type == 'product_indpnt_vars': recursion_coeffs = get_product_independent_vars_recursion_coefficients( opts, num_coefs) else: if poly_type is not None: raise Exception('poly_type (%s) not supported' % poly_type) else: raise Exception('var_type (%s) not supported' % var_type) return recursion_coeffs
def integrand(x): y = fun(x).squeeze() pvals = evaluate_orthonormal_polynomial_1d(y, ii, ab) # measure not included in integral because it is assumed to # be in the quadrature rules return pvals[:, ii]**2
def integrand(measure, x): # Note eval orthogonal poly uses the new value for ab[ii, 0] # This is the desired behavior pvals = evaluate_orthonormal_polynomial_1d(x, ii, ab) return measure(x) * pvals[:, ii]**2
def integrand(measure, x): pvals = evaluate_orthonormal_polynomial_1d(x, ii, ab) return measure(x) * pvals[:, ii] * pvals[:, ii - 1]
def integrand(xx): return evaluate_orthonormal_polynomial_1d( np.atleast_1d(xx), ii, ab)[:, -1]**2*pdf(xx) # from pyapprox.cython.orthonormal_polynomials_1d import\ # induced_measure_pyx #integrand = lambda x: induced_measure_pyx(x,ii,ab,pdf) vals = np.empty_like(x, dtype=float)