def get_askey_recursion_coefficients(poly_name, opts, num_coefs): if poly_name not in askey_poly_names: raise ValueError(f"poly_name {poly_name} not in {askey_poly_names}") if poly_name == "legendre": return jacobi_recurrence(num_coefs, alpha=0, beta=0, probability=True) if poly_name == "jacobi": return jacobi_recurrence(num_coefs, alpha=opts["alpha_poly"], beta=opts["beta_poly"], probability=True) if poly_name == "hermite": return hermite_recurrence(num_coefs, rho=0., probability=True) if poly_name == "krawtchouk": msg = "Although bounded the Krawtchouk polynomials are not defined " msg += "on the canonical domain [-1,1]. Must use numeric recursion " msg += "to generate polynomials on [-1,1] for consistency" warn(msg, UserWarning) num_coefs = min(num_coefs, opts["n"]) return krawtchouk_recurrence(num_coefs, opts["n"], opts["p"]) if poly_name == "hahn": msg = "Although bounded the Hahn polynomials are not defined " msg += "on the canonical domain [-1,1]. Must use numeric recursion " msg += "to generate polynomials on [-1,1] for consistency" warn(msg, UserWarning) num_coefs = min(num_coefs, opts["N"]) return hahn_recurrence(num_coefs, opts["N"], opts["alpha_poly"], opts["beta_poly"]) if poly_name == "charlier": return charlier_recurrence(num_coefs, opts["mu"])
def test_modified_chebyshev(self): nterms = 10 alpha_stat, beta_stat = 2, 2 probability_measure = True # using scipy to compute moments is extermely slow # moments = [stats.beta.moment(n,alpha_stat,beta_stat,loc=-1,scale=2) # for n in range(2*nterms)] quad_x, quad_w = gauss_jacobi_pts_wts_1D(4 * nterms, beta_stat - 1, alpha_stat - 1) true_ab = jacobi_recurrence(nterms, alpha=beta_stat - 1, beta=alpha_stat - 1, probability=probability_measure) ab = modified_chebyshev_orthonormal(nterms, [quad_x, quad_w], get_input_coefs=None, probability=True) assert np.allclose(true_ab, ab) get_input_coefs = partial(jacobi_recurrence, alpha=beta_stat - 2, beta=alpha_stat - 2) ab = modified_chebyshev_orthonormal(nterms, [quad_x, quad_w], get_input_coefs=get_input_coefs, probability=True) assert np.allclose(true_ab, ab)
def test_christoffel_inv_gradients(self): degree = 2 ab = jacobi_recurrence(degree + 1, 0, 0, True) basis_fun = partial(evaluate_orthonormal_polynomial_1d, nmax=degree, ab=ab) basis_fun_and_jac = partial(evaluate_orthonormal_polynomial_deriv_1d, nmax=degree, ab=ab, deriv_order=1) sample = np.random.uniform(-1, 1, (1, 1)) # sample = np.atleast_2d(-0.99) fun = partial(christoffel_function_inv_1d, basis_fun) jac = partial(christoffel_function_inv_jac_1d, basis_fun_and_jac) # xx = np.linspace(-1, 1, 101); plt.plot(xx, fun(xx[None, :])); # plt.plot(sample[0], fun(sample), 'o'); plt.show() err = check_gradients(fun, jac, sample) assert err.max() > .5 and err.min() < 1e-7 basis_fun_jac_hess = partial(evaluate_orthonormal_polynomial_deriv_1d, nmax=degree, ab=ab, deriv_order=2) hess = partial(christoffel_function_inv_hess_1d, basis_fun_jac_hess, normalize=False) err = check_gradients(jac, hess, sample) assert err.max() > .5 and err.min() < 1e-7
def test_derivatives_of_legendre_polynomial(self): alpha = 0. beta = 0. degree = 3 probability_measure = True deriv_order = 2 ab = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=probability_measure) x, w = np.polynomial.legendre.leggauss(degree + 1) pd = evaluate_orthonormal_polynomial_deriv_1d(x, degree, ab, deriv_order) pd_exact = [ np.asarray([ 1 + 0. * x, x, 0.5 * (3. * x**2 - 1), 0.5 * (5. * x**3 - 3. * x) ]).T ] pd_exact.append( np.asarray([0. * x, 1.0 + 0. * x, 3. * x, 7.5 * x**2 - 1.5]).T) pd_exact.append(np.asarray([0. * x, 0. * x, 3. + 0. * x, 15 * x]).T) pd_exact = np.asarray(pd_exact) / np.sqrt( 1. / (2 * np.arange(degree + 1) + 1)) for ii in range(deriv_order + 1): assert np.allclose( pd[:, ii * (degree + 1):(ii + 1) * (degree + 1)], pd_exact[ii])
def sparsity_example(decay_rate, rank, num_vars, num_params_1d): num_trials = 20 sparsity_fractions = np.arange(1, 10, dtype=float)/10. assert sparsity_fractions[-1] != 1 # error will always be zero ranks = ranks_vector(num_vars, rank) num_1d_functions = num_univariate_functions(ranks) num_ft_parameters = num_params_1d*num_1d_functions alpha = 0 beta = 0 recursion_coeffs = jacobi_recurrence( num_params_1d, alpha=alpha, beta=beta, probability=True) assert int(os.environ['OMP_NUM_THREADS']) == 1 max_eval_concurrency = 4 # max(multiprocessing.cpu_count()-2,1) pool = Pool(max_eval_concurrency) partial_func = partial( sparsity_example_engine, ranks=ranks, num_ft_parameters=num_ft_parameters, decay_rate=decay_rate, recursion_coeffs=recursion_coeffs, sparsity_fractions=sparsity_fractions) filename = 'function-train-sparsity-effect-%d-%d-%d-%1.1f-%d.npz' % ( num_vars, rank, num_params_1d, decay_rate, num_trials) if not os.path.exists(filename): result = pool.map(partial_func, [(num_params_1d)]*num_trials) l2_errors = np.asarray(result).T # (num_sparsity_fractions,num_trials) np.savez( filename, l2_errors=l2_errors, sparsity_fractions=sparsity_fractions, num_ft_parameters=num_ft_parameters) return filename
def test_orthonormality_asymetric_jacobi_polynomial(self): from scipy.stats import beta as beta_rv alpha = 4. beta = 1. degree = 3 probability_measure = True ab = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=probability_measure) x, w = np.polynomial.legendre.leggauss(10 * degree) p = evaluate_orthonormal_polynomial_1d(x, degree, ab) w *= beta_rv.pdf((x + 1.) / 2., a=beta + 1, b=alpha + 1) / 2. # test orthogonality exact_moments = np.zeros((degree + 1)) exact_moments[0] = 1.0 assert np.allclose(np.dot(p.T, w), exact_moments) # test orthonormality assert np.allclose(np.dot(p.T * w, p), np.eye(degree + 1)) assert np.allclose( evaluate_orthonormal_polynomial_deriv_1d(x, degree, ab, 0), p)
def test_evaluate_function_train_additive_function(self): """ Test the evaluation of a function train representation of an additive function. Assume same parameterization for each core and for each univariate function within a core. Use polynomial basis for each univariate function. """ alpha = 0 beta = 0 degree = 2 num_vars = 3 num_samples = 1 recursion_coeffs = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=True) univariate_function_params = [np.random.normal(0., 1., (degree + 1)) ] * num_vars ft_data = generate_additive_function_in_function_train_format( univariate_function_params, True) samples = np.random.uniform(-1., 1., (num_vars, num_samples)) values = evaluate_function_train(samples, ft_data, recursion_coeffs) true_values = additive_polynomial(samples, univariate_function_params, recursion_coeffs) assert np.allclose(values, true_values)
def test_gauss_quadrature(self): degree = 4 alpha = 0. beta = 0. ab = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=True) x, w = gauss_quadrature(ab, degree + 1) for ii in range(degree + 1): if ii % 2 == 0: assert np.allclose(np.dot(x**ii, w), 1. / (ii + 1.)) else: assert np.allclose(np.dot(x**ii, w), 0.) x_np, w_np = np.polynomial.legendre.leggauss(degree + 1) assert np.allclose(x_np, x) assert np.allclose(w_np / 2, w) degree = 4 alpha = 4. beta = 1. ab = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=True) x, w = gauss_quadrature(ab, degree + 1) true_moments = [1., -3. / 7., 2. / 7., -4. / 21., 1. / 7.] for ii in range(degree + 1): assert np.allclose(np.dot(x**ii, w), true_moments[ii]) degree = 4 rho = 0. ab = hermite_recurrence(degree + 1, rho, probability=True) x, w = gauss_quadrature(ab, degree + 1) from scipy.special import factorial2 assert np.allclose(np.dot(x**degree, w), factorial2(degree - 1)) x_sp, w_sp = sp.roots_hermitenorm(degree + 1) w_sp /= np.sqrt(2 * np.pi) assert np.allclose(x_sp, x) assert np.allclose(w_sp, w)
def test_arbitraty_polynomial_chaos(self): nterms = 5 alpha_stat, beta_stat = 1, 1 true_ab = jacobi_recurrence(nterms, alpha=beta_stat - 1, beta=alpha_stat - 1, probability=True) rv = stats.uniform(-1, 2) moments = [rv.moment(n) for n in range(2 * nterms + 1)] ab = arbitrary_polynomial_chaos_recursion_coefficients(moments, nterms) assert np.allclose(true_ab, ab)
def test_continous_induced_measure_ppf(self): degree = 2 alpha_stat, beta_stat = 3, 3 ab = jacobi_recurrence( degree+1, alpha=beta_stat-1, beta=alpha_stat-1, probability=True) tol = 1e-15 var = stats.beta(alpha_stat, beta_stat, -5, 10) can_lb, can_ub = -1, 1 lb, ub = var.support() print(lb, ub) cx = np.linspace(can_lb, can_ub, 51) def can_pdf(xx): loc, scale = lb+(ub-lb)/2, (ub-lb)/2 return var.pdf(xx*scale+loc)*scale cdf_vals = continuous_induced_measure_cdf( can_pdf, ab, degree, can_lb, can_ub, tol, cx) assert np.all(cdf_vals <= 1.0) ppf_vals = continuous_induced_measure_ppf( var, ab, degree, cdf_vals, 1e-10, 1e-8) assert np.allclose(cx, ppf_vals) try: var = stats.loguniform(1.e-5, 1.e-3) except: var = stats.reciprocal(1.e-5, 1.e-3) ab = get_recursion_coefficients_from_variable(var, degree+5, {}) can_lb, can_ub = -1, 1 cx = np.linspace(can_lb, can_ub, 51) lb, ub = var.support() def can_pdf(xx): loc, scale = lb+(ub-lb)/2, (ub-lb)/2 return var.pdf(xx*scale+loc)*scale cdf_vals = continuous_induced_measure_cdf( can_pdf, ab, degree, can_lb, can_ub, tol, cx) # differences caused by root finding optimization tolerance assert np.all(cdf_vals <= 1.0) ppf_vals = continuous_induced_measure_ppf( var, ab, degree, cdf_vals, 1e-10, 1e-8) # import matplotlib.pyplot as plt # plt.plot(cx, cdf_vals) # plt.plot(ppf_vals, cdf_vals, 'r*', ms=2) # plt.show() assert np.allclose(cx, ppf_vals)
def gauss_jacobi_pts_wts_1D(num_samples, alpha_poly, beta_poly): """ Return Gauss Jacobi quadrature rule that exactly integrates polynomials of num_samples 2*num_samples-1 with respect to the probabilty density function of Beta random variables on [-1,1] C*(1+x)^(beta_poly)*(1-x)^alpha_poly where C = 1/(2**(alpha_poly+beta_poly)*beta_fn(beta_poly+1,alpha_poly+1)) or equivalently C*(1+x)**(alpha_stat-1)*(1-x)**(beta_stat-1) where C = 1/(2**(alpha_stat+beta_stat-2)*beta_fn(alpha_stat,beta_stat)) Parameters ---------- num_samples : integer The number of samples in the quadrature rule alpha_poly : float The Jaocbi parameter alpha = beta_stat-1 beta_poly : float The Jacobi parameter beta = alpha_stat-1 Returns ------- x : np.ndarray(num_samples) Quadrature samples w : np.ndarray(num_samples) Quadrature weights """ ab = jacobi_recurrence(num_samples, alpha=alpha_poly, beta=beta_poly, probability=True) return gauss_quadrature(ab, num_samples)
def test_christoffel_leja_objective_gradients(self): # leja_sequence = np.array([[-1, 1]]) leja_sequence = np.array([[-1, 0, 1]]) degree = leja_sequence.shape[1] - 1 ab = jacobi_recurrence(degree + 2, 0, 0, True) basis_fun = partial(evaluate_orthonormal_polynomial_1d, nmax=degree + 1, ab=ab) tmp = basis_fun(leja_sequence[0, :]) nterms = degree + 1 basis_mat = tmp[:, :nterms] new_basis = tmp[:, nterms:] coef = compute_coefficients_of_christoffel_leja_interpolant_1d( basis_mat, new_basis) fun = partial(christoffel_leja_objective_fun_1d, basis_fun, coef) # xx = np.linspace(-1, 1, 101); plt.plot(xx, fun(xx[None, :])); # plt.plot(leja_sequence[0, :], fun(leja_sequence), 'o'); plt.show() basis_fun_and_jac = partial(evaluate_orthonormal_polynomial_deriv_1d, nmax=degree + 1, ab=ab, deriv_order=1) jac = partial(christoffel_leja_objective_jac_1d, basis_fun_and_jac, coef) sample = sample = np.random.uniform(-1, 1, (1, 1)) err = check_gradients(fun, jac, sample) assert err.max() > 0.5 and err.min() < 1e-7 basis_fun_jac_hess = partial(evaluate_orthonormal_polynomial_deriv_1d, nmax=degree + 1, ab=ab, deriv_order=2) hess = partial(christoffel_leja_objective_hess_1d, basis_fun_jac_hess, coef) err = check_gradients(jac, hess, sample) assert err.max() > .5 and err.min() < 1e-7
def test_pdf_weighted_leja_objective_gradients(self): # leja_sequence = np.array([[-1, 1]]) leja_sequence = np.array([[-1, 0, 1]]) degree = leja_sequence.shape[1] - 1 ab = jacobi_recurrence(degree + 2, 0, 0, True) basis_fun = partial(evaluate_orthonormal_polynomial_1d, nmax=degree + 1, ab=ab) def pdf(x): return beta_pdf(1, 1, (x + 1) / 2) / 2 def pdf_jac(x): return beta_pdf_derivative(1, 1, (x + 1) / 2) / 4 tmp = basis_fun(leja_sequence[0, :]) nterms = degree + 1 basis_mat = tmp[:, :nterms] new_basis = tmp[:, nterms:] coef = compute_coefficients_of_pdf_weighted_leja_interpolant_1d( pdf(leja_sequence[0, :]), basis_mat, new_basis) fun = partial(pdf_weighted_leja_objective_fun_1d, pdf, basis_fun, coef) # xx = np.linspace(-1, 1, 101); plt.plot(xx, fun(xx[None, :])); # plt.plot(leja_sequence[0, :], fun(leja_sequence), 'o'); plt.show() basis_fun_and_jac = partial(evaluate_orthonormal_polynomial_deriv_1d, nmax=degree + 1, ab=ab, deriv_order=1) jac = partial(pdf_weighted_leja_objective_jac_1d, pdf, pdf_jac, basis_fun_and_jac, coef) sample = sample = np.random.uniform(-1, 1, (1, 1)) err = check_gradients(fun, jac, sample) assert err.max() > 0.4 and err.min() < 1e-7
def test_uniform_christoffel_leja_sequence_1d(self): max_nsamples = 3 initial_points = np.array([[0]]) ab = jacobi_recurrence(max_nsamples + 1, 0, 0, True) basis_fun = partial(evaluate_orthonormal_polynomial_deriv_1d, ab=ab) # def callback(leja_sequence, coef, new_samples, obj_vals, # initial_guesses): # degree = coef.shape[0]-1 # def plot_fun(x): # return -christoffel_leja_objective_fun_1d( # partial(basis_fun, nmax=degree+1, deriv_order=0), coef, # x[None, :]) # xx = np.linspace(-1, 1, 101) # plt.plot(xx, plot_fun(xx)) # plt.plot(leja_sequence[0, :], plot_fun(leja_sequence[0, :]), 'o') # plt.plot(new_samples[0, :], obj_vals, 's') # plt.plot( # initial_guesses[0, :], plot_fun(initial_guesses[0, :]), '*') # plt.show() leja_sequence = get_christoffel_leja_sequence_1d(max_nsamples, initial_points, [-1, 1], basis_fun, { 'gtol': 1e-8, 'verbose': False }, callback=None) from pyapprox.univariate_quadrature import leja_growth_rule level = 3 __basis_fun = partial(basis_fun, nmax=max_nsamples - 1, deriv_order=0) weights = get_christoffel_leja_quadrature_weights_1d( leja_sequence, leja_growth_rule, __basis_fun, level, True) assert np.allclose((leja_sequence**2).dot(weights[-1]), 1 / 3)
def test_orthonormality_legendre_polynomial(self): alpha = 0. beta = 0. degree = 3 probability_measure = True ab = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=probability_measure) x, w = np.polynomial.legendre.leggauss(degree + 1) # make weights have probablity weight function w=1/2 w /= 2.0 p = evaluate_orthonormal_polynomial_1d(x, degree, ab) # test orthogonality exact_moments = np.zeros((degree + 1)) exact_moments[0] = 1.0 assert np.allclose(np.dot(p.T, w), exact_moments) # test orthonormality assert np.allclose(np.dot(p.T * w, p), np.eye(degree + 1)) assert np.allclose( evaluate_orthonormal_polynomial_deriv_1d(x, degree, ab, 0), p)
def test_gradient_random_function_train(self): """ Test the gradient of a random function train. Gradient is with respect to coefficients of the univariate functions Assume different parameterization for some univariate functions. Zero and ones are stored as a constant basis. Where as other entries are stored as a polynomial of a fixed degree d. """ alpha = 0 beta = 0 degree = 2 num_vars = 3 rank = 2 recursion_coeffs = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=True) ranks = np.ones((num_vars + 1), dtype=int) ranks[1:-1] = rank num_params_1d = degree + 1 num_1d_functions = num_univariate_functions(ranks) ft_params = np.random.normal(0., 1., (num_params_1d * num_1d_functions)) ft_data = generate_homogeneous_function_train(ranks, num_params_1d, ft_params) sample = np.random.uniform(-1., 1., (num_vars, 1)) value, ft_gradient = evaluate_function_train_grad( sample, ft_data, recursion_coeffs) fd_gradient = ft_parameter_finite_difference_gradient( sample, ft_data, recursion_coeffs) assert np.allclose(fd_gradient, ft_gradient)
def test_gradient_function_train_additive_function(self): """ Test the gradient of a function train representation of an additive function. Gradient is with respect to coefficients of the univariate functions Assume different parameterization for some univariate functions. Zero and ones are stored as a constant basis. Where as other entries are stored as a polynomial of a fixed degree d. """ alpha = 0 beta = 0 degree = 2 num_vars = 3 recursion_coeffs = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=True) univariate_function_params = [np.random.normal(0., 1., (degree + 1)) ] * num_vars ft_data = generate_additive_function_in_function_train_format( univariate_function_params, True) sample = np.random.uniform(-1., 1., (num_vars, 1)) value, ft_gradient = evaluate_function_train_grad( sample, ft_data, recursion_coeffs) true_values, univariate_values = additive_polynomial( sample, univariate_function_params, recursion_coeffs, return_univariate_vals=True) true_value = true_values[0, 0] assert np.allclose(value, true_value) true_gradient = np.empty((0), dtype=float) # var 0 univariate function 1,1 basis_matrix_var_0 = evaluate_orthonormal_polynomial_1d( sample[0, :], degree, recursion_coeffs) true_gradient = np.append(true_gradient, basis_matrix_var_0) # var 0 univariate function 1,2 true_gradient = np.append(true_gradient, np.sum(univariate_values[0, 1:])) basis_matrix_var_1 = evaluate_orthonormal_polynomial_1d( sample[1, :], degree, recursion_coeffs) # var 1 univariate function 1,1 true_gradient = np.append(true_gradient, univariate_values[0, 0]) # var 1 univariate function 2,1 true_gradient = np.append(true_gradient, basis_matrix_var_1) # var 1 univariate function 1,2 true_gradient = np.append( true_gradient, univariate_values[0, 0] * univariate_values[0, 2]) # var 1 univariate function 2,2 true_gradient = np.append(true_gradient, univariate_values[0, 2]) basis_matrix_var_2 = evaluate_orthonormal_polynomial_1d( sample[2, :], degree, recursion_coeffs) # var 2 univariate function 1,1 true_gradient = np.append(true_gradient, univariate_values[0, :2].sum()) # var 2 univariate function 2,1 true_gradient = np.append(true_gradient, basis_matrix_var_2) fd_gradient = ft_parameter_finite_difference_gradient( sample, ft_data, recursion_coeffs) # print 'true',true_gradient # print 'ft ',ft_gradient # print fd_gradient assert np.allclose(fd_gradient, true_gradient) assert np.allclose(ft_gradient, true_gradient)
def test_restricted_least_squares_regression_sparse(self): """ Use non-linear least squares to estimate the coefficients of the function train approximation of a rank-2 bivariate function, optimizing only over a subset of the FT parameters. """ alpha = 0 beta = 0 degree = 5 num_vars = 3 rank = 2 num_samples = 20 sparsity_ratio = 0.2 recursion_coeffs = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=True) ranks = np.ones((num_vars + 1), dtype=int) ranks[1:-1] = rank ft_data = generate_random_sparse_function_train( num_vars, rank, degree + 1, sparsity_ratio) def function(samples): return evaluate_function_train(samples, ft_data, recursion_coeffs) samples = np.random.uniform(-1, 1, (num_vars, num_samples)) values = function(samples) assert values.shape[0] == num_samples active_indices = np.where((ft_data[1] != 0) & (ft_data[1] != 1))[0] linear_ft_data = ft_linear_least_squares_regression(samples, values, degree, perturb=None) initial_guess = linear_ft_data[1].copy() initial_guess = initial_guess[active_indices] # initial_guess = true_sol[active_indices] + np.random.normal( # 0.,1.,(active_indices.shape[0])) lstsq_ft_params = ft_non_linear_least_squares_regression( samples, values, ft_data, recursion_coeffs, initial_guess, active_indices=active_indices) lstsq_ft_data = copy.deepcopy(ft_data) lstsq_ft_data[1] = lstsq_ft_params num_valid_samples = 100 validation_samples = np.random.uniform(-1., 1., (num_vars, num_valid_samples)) validation_values = function(validation_samples) ft_validation_values = evaluate_function_train(validation_samples, lstsq_ft_data, recursion_coeffs) ft_error = np.linalg.norm(validation_values - ft_validation_values ) / np.sqrt(num_valid_samples) # print ft_error assert ft_error < 1e-3, ft_error
def test_restricted_least_squares_regression_additive_function(self): """ Use non-linear least squares to estimate the coefficients of the function train approximation of a rank-2 bivariate function, optimizing only over a subset of the FT parameters. """ alpha = 0 beta = 0 degree = 5 num_vars = 3 rank = 2 num_samples = 20 recursion_coeffs = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=True) ranks = np.ones((num_vars + 1), dtype=int) ranks[1:-1] = rank num_params_1d = degree + 1 univariate_function_params = [np.random.normal(0., 1., (degree + 1)) ] * num_vars ft_data = generate_additive_function_in_function_train_format( univariate_function_params, False) def function(samples): return evaluate_function_train(samples, ft_data, recursion_coeffs) samples = np.random.uniform(-1, 1, (num_vars, num_samples)) values = function(samples) assert values.shape[0] == num_samples linear_ft_data = ft_linear_least_squares_regression(samples, values, degree, perturb=None) initial_guess = linear_ft_data[1].copy() active_indices = [] active_indices += list(range(num_params_1d)) active_indices += list(range(3 * num_params_1d, 4 * num_params_1d)) active_indices += list(range(7 * num_params_1d, 8 * num_params_1d)) active_indices = np.asarray(active_indices) # active_indices = np.where((ft_data[1]!=0)&(ft_data[1]!=1))[0] initial_guess = initial_guess[active_indices] lstsq_ft_params = ft_non_linear_least_squares_regression( samples, values, linear_ft_data, recursion_coeffs, initial_guess, active_indices=active_indices) lstsq_ft_data = copy.deepcopy(linear_ft_data) lstsq_ft_data[1] = lstsq_ft_params num_valid_samples = 100 validation_samples = np.random.uniform(-1., 1., (num_vars, num_valid_samples)) validation_values = function(validation_samples) ft_validation_values = evaluate_function_train(validation_samples, lstsq_ft_data, recursion_coeffs) ft_error = np.linalg.norm(validation_values - ft_validation_values ) / np.sqrt(num_valid_samples) assert ft_error < 1e-3, ft_error
def test_least_squares_regression(self): """ Use non-linear least squares to estimate the coefficients of the function train approximation of a rank-2 bivariate function. """ alpha = 0 beta = 0 degree = 5 num_vars = 3 rank = 2 num_samples = 100 recursion_coeffs = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=True) ranks = np.ones((num_vars + 1), dtype=int) ranks[1:-1] = rank def function(samples): return np.cos(samples.sum(axis=0))[:, np.newaxis] samples = np.random.uniform(-1, 1, (num_vars, num_samples)) values = function(samples) assert values.shape[0] == num_samples linear_ft_data = ft_linear_least_squares_regression(samples, values, degree, perturb=None) initial_guess = linear_ft_data[1].copy() # test jacobian # residual_func = partial( # least_squares_residual,samples,values,linear_ft_data, # recursion_coeffs) # # jacobian = least_squares_jacobian( # samples,values,linear_ft_data,recursion_coeffs,initial_guess) # finite difference is expensive check on subset of points # for ii in range(2): # func = lambda x: residual_func(x)[ii] # assert np.allclose( # scipy.optimize.approx_fprime(initial_guess, func, 1e-7), # jacobian[ii,:]) lstsq_ft_params = ft_non_linear_least_squares_regression( samples, values, linear_ft_data, recursion_coeffs, initial_guess) lstsq_ft_data = copy.deepcopy(linear_ft_data) lstsq_ft_data[1] = lstsq_ft_params num_valid_samples = 100 validation_samples = np.random.uniform(-1., 1., (num_vars, num_valid_samples)) validation_values = function(validation_samples) ft_validation_values = evaluate_function_train(validation_samples, lstsq_ft_data, recursion_coeffs) ft_error = np.linalg.norm(validation_values - ft_validation_values ) / np.sqrt(num_valid_samples) assert ft_error < 1e-3, ft_error # compare against tensor-product linear least squares from pyapprox.monomial import monomial_basis_matrix, evaluate_monomial from pyapprox.indexing import tensor_product_indices indices = tensor_product_indices([degree] * num_vars) basis_matrix = monomial_basis_matrix(indices, samples) coef = np.linalg.lstsq(basis_matrix, values, rcond=None)[0] monomial_validation_values = evaluate_monomial(indices, coef, validation_samples) monomial_error = np.linalg.norm(validation_values - monomial_validation_values) / np.sqrt( num_valid_samples) assert ft_error < monomial_error
def test_evaluate_multivariate_orthonormal_polynomial(self): num_vars = 2 alpha = 0. beta = 0. degree = 2 deriv_order = 1 probability_measure = True ab = jacobi_recurrence(degree + 1, alpha=alpha, beta=beta, probability=probability_measure) x, w = np.polynomial.legendre.leggauss(degree) samples = cartesian_product([x] * num_vars, 1) indices = compute_hyperbolic_indices(num_vars, degree, 1.0) # sort lexographically to make testing easier II = np.lexsort((indices[0, :], indices[1, :], indices.sum(axis=0))) indices = indices[:, II] basis_matrix = evaluate_multivariate_orthonormal_polynomial( samples, indices, ab, deriv_order) exact_basis_vals_1d = [] exact_basis_derivs_1d = [] for dd in range(num_vars): x = samples[dd, :] exact_basis_vals_1d.append( np.asarray([1 + 0. * x, x, 0.5 * (3. * x**2 - 1)]).T) exact_basis_derivs_1d.append( np.asarray([0. * x, 1.0 + 0. * x, 3. * x]).T) exact_basis_vals_1d[-1] /= np.sqrt(1. / (2 * np.arange(degree + 1) + 1)) exact_basis_derivs_1d[-1] /= np.sqrt( 1. / (2 * np.arange(degree + 1) + 1)) exact_basis_matrix = np.asarray([ exact_basis_vals_1d[0][:, 0], exact_basis_vals_1d[0][:, 1], exact_basis_vals_1d[1][:, 1], exact_basis_vals_1d[0][:, 2], exact_basis_vals_1d[0][:, 1] * exact_basis_vals_1d[1][:, 1], exact_basis_vals_1d[1][:, 2] ]).T # x1 derivative exact_basis_matrix = np.vstack( (exact_basis_matrix, np.asarray([ 0. * x, exact_basis_derivs_1d[0][:, 1], 0. * x, exact_basis_derivs_1d[0][:, 2], exact_basis_derivs_1d[0][:, 1] * exact_basis_vals_1d[1][:, 1], 0. * x ]).T)) # x2 derivative exact_basis_matrix = np.vstack( (exact_basis_matrix, np.asarray([ 0. * x, 0. * x, exact_basis_derivs_1d[1][:, 1], 0. * x, exact_basis_vals_1d[0][:, 1] * exact_basis_derivs_1d[1][:, 1], exact_basis_derivs_1d[1][:, 2] ]).T)) def func(x): return evaluate_multivariate_orthonormal_polynomial( x, indices, ab, 0) basis_matrix_derivs = basis_matrix[samples.shape[1]:] basis_matrix_derivs_fd = np.empty_like(basis_matrix_derivs) for ii in range(samples.shape[1]): basis_matrix_derivs_fd[ii::samples.shape[1], :] = approx_fprime( samples[:, ii:ii + 1], func, 1e-7) assert np.allclose(exact_basis_matrix[samples.shape[1]:], basis_matrix_derivs_fd) assert np.allclose(exact_basis_matrix, basis_matrix)
def test_sparse_function_train(self): np.random.seed(5) num_vars = 2 degree = 5 rank = 2 tol = 1e-5 sparsity_ratio = 0.2 sample_ratio = 0.6 ranks = rank*np.ones(num_vars+1, dtype=np.uint64) ranks[0] = 1 ranks[-1] = 1 alpha = 0 beta = 0 recursion_coeffs = jacobi_recurrence( degree+1, alpha=alpha, beta=beta, probability=True) ft_data = generate_random_sparse_function_train( num_vars, rank, degree+1, sparsity_ratio) true_sol = ft_data[1] num_ft_params = true_sol.shape[0] num_samples = int(sample_ratio*num_ft_params) samples = np.random.uniform(-1., 1., (num_vars, num_samples)) def function(samples): return evaluate_function_train( samples, ft_data, recursion_coeffs) #function = lambda samples: np.cos(samples.sum(axis=0))[:,np.newaxis] values = function(samples) print(values.shape) assert np.linalg.norm(values) > 0, (np.linalg.norm(values)) num_validation_samples = 100 validation_samples = np.random.uniform( -1., 1., (num_vars, num_validation_samples)) validation_values = function(validation_samples) zero_ft_data = copy.deepcopy(ft_data) zero_ft_data[1] = np.zeros_like(zero_ft_data[1]) # DO NOT use ft_data in following two functions. # These function only overwrites parameters associated with the # active indices the rest of the parameters are taken from ft_data. # If ft_data is used some of the true data will be kept and give # an unrealisticaly accurate answer approx_eval = partial(modify_and_evaluate_function_train, samples, zero_ft_data, recursion_coeffs, None) apply_approx_adjoint_jacobian = partial( apply_function_train_adjoint_jacobian, samples, zero_ft_data, recursion_coeffs, 1e-3) def least_squares_regression(indices, initial_guess): # if initial_guess is None: st0 = np.random.get_state() np.random.seed(1) initial_guess = np.random.normal(0., .01, indices.shape[0]) np.random.set_state(st0) result = ft_non_linear_least_squares_regression( samples, values, ft_data, recursion_coeffs, initial_guess, indices, {'gtol': tol, 'ftol': tol, 'xtol': tol, 'verbosity': 0}) return result[indices] sparsity = np.where(true_sol != 0)[0].shape[0] print(('sparsity', sparsity, 'num_samples', num_samples, 'num_ft_params', num_ft_params)) print(true_sol) active_indices = None use_omp = True #use_omp = False if not use_omp: sol = least_squares_regression(np.arange(num_ft_params), None) else: result = orthogonal_matching_pursuit( approx_eval, apply_approx_adjoint_jacobian, least_squares_regression, values[:, 0], active_indices, num_ft_params, tol, min(num_samples, num_ft_params), verbosity=1) sol = result[0] residnorm = result[1] recovered_ft_data = copy.deepcopy(ft_data) recovered_ft_data[1] = sol ft_validation_values = evaluate_function_train( validation_samples, recovered_ft_data, recursion_coeffs) validation_error = np.linalg.norm( validation_values-ft_validation_values) rel_validation_error = validation_error / \ np.linalg.norm(validation_values) # compare relative error because exit condition is based upon # relative residual print(rel_validation_error) assert rel_validation_error < 100*tol, rel_validation_error
def test_random_function_train(self): np.random.seed(5) num_vars = 2 degree = 5 rank = 2 sparsity_ratio = 0.2 sample_ratio = .9 ranks = rank*np.ones(num_vars+1, dtype=np.uint64) ranks[0] = 1 ranks[-1] = 1 alpha = 0 beta = 0 recursion_coeffs = jacobi_recurrence( degree+1, alpha=alpha, beta=beta, probability=True) ft_data = generate_random_sparse_function_train( num_vars, rank, degree+1, sparsity_ratio) true_sol = ft_data[1] num_ft_params = true_sol.shape[0] num_samples = int(sample_ratio*num_ft_params) samples = np.random.uniform(-1., 1., (num_vars, num_samples)) def function(samples): return evaluate_function_train( samples, ft_data, recursion_coeffs) values = function(samples) assert np.linalg.norm(values) > 0, (np.linalg.norm(values)) num_validation_samples = 100 validation_samples = np.random.uniform( -1., 1., (num_vars, num_validation_samples)) validation_values = function(validation_samples) zero_ft_data = copy.deepcopy(ft_data) zero_ft_data[1] = np.zeros_like(zero_ft_data[1]) # DO NOT use ft_data in following two functions. # These function only overwrites parameters associated with the # active indices the rest of the parameters are taken from ft_data. # If ft_data is used some of the true data will be kept and give # an unrealisticaly accurate answer approx_eval = partial(modify_and_evaluate_function_train, samples, zero_ft_data, recursion_coeffs, None) apply_approx_adjoint_jacobian = partial( apply_function_train_adjoint_jacobian, samples, zero_ft_data, recursion_coeffs, 1e-3) sparsity = np.where(true_sol != 0)[0].shape[0] print(('sparsity', sparsity, 'num_samples', num_samples)) # sparse project = partial(s_sparse_projection, sparsity=sparsity) # non-linear least squres #project = partial(s_sparse_projection,sparsity=num_ft_params) # use uninormative initial guess #initial_guess = np.zeros_like(true_sol) # use linear approximation as initial guess linear_ft_data = ft_linear_least_squares_regression( samples, values, degree, perturb=None) initial_guess = linear_ft_data[1] # use initial guess that is close to true solution # num_samples required to obtain accruate answer decreases signficantly # over linear or uniformative guesses. As size of perturbation from # truth increases num_samples must increase initial_guess = true_sol.copy()+np.random.normal(0., .1, (num_ft_params)) tol = 5e-3 max_iter = 1000 result = iterative_hard_thresholding( approx_eval, apply_approx_adjoint_jacobian, project, values[:, 0], initial_guess, tol, max_iter, verbosity=1) sol = result[0] residnorm = result[1] recovered_ft_data = copy.deepcopy(ft_data) recovered_ft_data[1] = sol ft_validation_values = evaluate_function_train( validation_samples, recovered_ft_data, recursion_coeffs) validation_error = np.linalg.norm( validation_values-ft_validation_values) rel_validation_error = validation_error / \ np.linalg.norm(validation_values) # compare relative error because exit condition is based upon # relative residual assert rel_validation_error < 10*tol, rel_validation_error
def test_predictor_corrector_known_pdf(self): nterms = 12 tol = 1e-12 quad_options = { 'epsrel': tol, 'epsabs': tol, "limlst": 10, "limit": 1000 } rv = stats.beta(1, 1, -1, 2) ab = predictor_corrector_known_pdf(nterms, -1, 1, rv.pdf, quad_options) true_ab = jacobi_recurrence(nterms, 0, 0) assert np.allclose(ab, true_ab) rv = stats.beta(3, 3, -1, 2) ab = predictor_corrector_known_pdf(nterms, -1, 1, rv.pdf, quad_options) true_ab = jacobi_recurrence(nterms, 2, 2) rv = stats.norm(0, 2) loc, scale = transform_scale_parameters(rv) ab = predictor_corrector_known_pdf( nterms, -np.inf, np.inf, lambda x: rv.pdf(x * scale + loc) * scale, quad_options) true_ab = hermite_recurrence(nterms) assert np.allclose(ab, true_ab) # lognormal is a very hard test # rv = stats.lognorm(1) # custom_integrate_fun = native_recursion_integrate_fun # interval_size = abs(np.diff(rv.interval(0.99))) # integrate_fun = partial(custom_integrate_fun, interval_size) # quad_opts = {"integrate_fun": integrate_fun} # # quad_opts = {} # opts = {"numeric": True, "quad_options": quad_opts} # loc, scale = transform_scale_parameters(rv) # ab = predictor_corrector_known_pdf( # nterms, 0, np.inf, lambda x: rv.pdf(x*scale+loc)*scale, opts) # for ii in range(1, nterms): # assert np.all(gauss_quadrature(ab, ii)[0] > 0) # gram_mat = ortho_polynomial_grammian_bounded_continuous_variable( # rv, ab, nterms-1, tol=tol, integrate_fun=integrate_fun) # # print(gram_mat-np.eye(gram_mat.shape[0])) # # print(np.absolute(gram_mat-np.eye(gram_mat.shape[0])).max()) # assert np.absolute(gram_mat-np.eye(gram_mat.shape[0])).max() < 5e-10 nterms = 2 mean, std = 1e4, 7.5e3 beta = std * np.sqrt(6) / np.pi mu = mean - beta * np.euler_gamma # mu, beta = 1, 1 rv = stats.gumbel_r(loc=mu, scale=beta) custom_integrate_fun = native_recursion_integrate_fun tabulated_quad_rules = {} from numpy.polynomial.legendre import leggauss for nquad_samples in [100, 200, 400]: tabulated_quad_rules[nquad_samples] = leggauss(nquad_samples) # interval_size must be in canonical domain interval_size = abs(np.diff(rv.interval(0.99))) / beta integrate_fun = partial(custom_integrate_fun, interval_size, tabulated_quad_rules=tabulated_quad_rules, verbose=3) quad_opts = {"integrate_fun": integrate_fun} # quad_opts = {} opts = {"numeric": True, "quad_options": quad_opts} loc, scale = transform_scale_parameters(rv) ab = predictor_corrector_known_pdf( nterms, -np.inf, np.inf, lambda x: rv.pdf(x * scale + loc) * scale, opts) gram_mat = ortho_polynomial_grammian_bounded_continuous_variable( rv, ab, nterms - 1, tol=tol, integrate_fun=integrate_fun) # print(gram_mat-np.eye(gram_mat.shape[0])) print(np.absolute(gram_mat - np.eye(gram_mat.shape[0])).max()) assert np.absolute(gram_mat - np.eye(gram_mat.shape[0])).max() < 5e-10