def test_orthonormality_probabilists_hermite_polynomial(self): rho = 0. degree = 2 probability_measure = True ab = hermite_recurrence(degree + 1, rho, probability=probability_measure) x, w = np.polynomial.hermite.hermgauss(degree + 1) # transform rule to probablity weight # function w=1/sqrt(2*PI)exp(-x^2/2) x *= np.sqrt(2.0) w /= np.sqrt(np.pi) p = evaluate_orthonormal_polynomial_1d(x, degree, ab) # Note if using pecos the following is done (i.e. ptFactpr=sqrt(2)), # but if I switch to using orthonormal recursion, used here, in Pecos # then I will need to set ptFactor=1.0 as done implicitly above p_exact = np.asarray([1 + 0. * x, x, x**2 - 1]).T / np.sqrt( sp.factorial(np.arange(degree + 1))) assert np.allclose(p, p_exact) # 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 print(np.allclose(np.dot(p.T * w, p), np.eye(degree + 1))) assert np.allclose(np.dot(p.T * w, p), np.eye(degree + 1))
def test_orthonormality_physicists_hermite_polynomial(self): rho = 0. degree = 2 probability_measure = False ab = hermite_recurrence(degree + 1, rho, probability=probability_measure) x, w = np.polynomial.hermite.hermgauss(degree + 1) p = evaluate_orthonormal_polynomial_1d(x, degree, ab) p_exact = np.asarray([1 + 0. * x, 2 * x, 4. * x**2 - 2]).T / np.sqrt( sp.factorial(np.arange(degree + 1)) * np.sqrt(np.pi) * 2**np.arange(degree + 1)) assert np.allclose(p, p_exact) # test orthogonality exact_moments = np.zeros((degree + 1)) # basis is orthonormal so integration of constant basis will be # non-zero but will not integrate to 1.0 exact_moments[0] = np.pi**0.25 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))
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_continuous_rv_sample(self): N, degree = int(1e6), 5 xk, pk = np.random.normal(0, 1, N), np.ones(N) / N ab = modified_chebyshev_orthonormal(degree + 1, [xk, pk]) hermite_ab = hermite_recurrence(degree + 1, 0, True) x, w = gauss_quadrature(hermite_ab, degree + 1) p = evaluate_orthonormal_polynomial_1d(x, degree, ab) gaussian_moments = np.zeros(degree + 1) gaussian_moments[0] = 1 assert np.allclose(p.T.dot(w), gaussian_moments, atol=1e-2) assert np.allclose(np.dot(p.T * w, p), np.eye(degree + 1), atol=7e-2)
def test_convert_orthonormal_recurence_to_three_term_recurence(self): rho = 0. degree = 2 probability_measure = True ab = hermite_recurrence(degree + 1, rho, probability=probability_measure) abc = convert_orthonormal_recurence_to_three_term_recurence(ab) x = np.linspace(-3, 3, 101) p_2term = evaluate_orthonormal_polynomial_1d(x, degree, ab) p_3term = evaluate_three_term_recurrence_polynomial_1d(abc, degree, x) assert np.allclose(p_2term, p_3term)
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_convert_orthonormal_expansion_to_monomial_expansion_1d(self): """ Approximate function f1 = lambda x: ((x-mu)/sigma)**3 using hermite polynomials tailored for normal random variable with mean mu and variance sigma**2 The function defined on canonical domain of the hermite polynomials, i.e. normal with mean zero and unit variance, is f2 = lambda x: x.T**3 """ degree = 4 mu, sigma = 1, 2 ortho_coef = np.array([0, 3, 0, np.sqrt(6)]) ab = hermite_recurrence(degree + 1, 0, True) mono_coefs = convert_orthonormal_expansion_to_monomial_expansion_1d( ortho_coef, ab, mu, sigma) true_mono_coefs = np.array([-mu**3, 3 * mu**2, -3 * mu, 1]) / sigma**3 assert np.allclose(mono_coefs, true_mono_coefs)
def test_convert_monomials_to_orthonormal_polynomials_1d(self): rho = 0. degree = 10 probability_measure = True ab = hermite_recurrence(degree + 1, rho, probability=probability_measure) basis_mono_coefs = convert_orthonormal_polynomials_to_monomials_1d( ab, degree) x = np.random.normal(0, 1, (100)) print('Cond number', np.linalg.cond(basis_mono_coefs)) basis_ortho_coefs = np.linalg.inv(basis_mono_coefs) ortho_basis_matrix = evaluate_orthonormal_polynomial_1d(x, degree, ab) mono_basis_matrix = x[:, None]**np.arange(degree + 1)[None, :] assert np.allclose(mono_basis_matrix, ortho_basis_matrix.dot(basis_ortho_coefs.T))
def test_convert_orthonormal_polynomials_to_monomials_1d(self): """ Example: orthonormal Hermite polynomials deg monomial coeffs 0 [1,0,0] 1 [0,1,0] 1/1*((x-0)*1-1*0)=x 2 [1/c,0,1/c] 1/c*((x-0)*x-1*1)=(x**2-1)/c, c=sqrt(2) 3 [0,-3/d,0,1/d] 1/d*((x-0)*(x**2-1)/c-c*x)= 1/(c*d)*(x**3-x-c**2*x)=(x**3-3*x)/(c*d),d=sqrt(3) """ rho = 0. degree = 10 probability_measure = True ab = hermite_recurrence(degree + 1, rho, probability=probability_measure) basis_mono_coefs = convert_orthonormal_polynomials_to_monomials_1d( ab, 4) true_basis_mono_coefs = np.zeros((5, 5)) true_basis_mono_coefs[0, 0] = 1 true_basis_mono_coefs[1, 1] = 1 true_basis_mono_coefs[2, [0, 2]] = -1 / np.sqrt(2), 1 / np.sqrt(2) true_basis_mono_coefs[3, [1, 3]] = -3 / np.sqrt(6), 1 / np.sqrt(6) true_basis_mono_coefs[4, [0, 2, 4]] = np.array([3, -6, 1]) / np.sqrt(24) assert np.allclose(basis_mono_coefs, true_basis_mono_coefs) coefs = np.ones(degree + 1) basis_mono_coefs = convert_orthonormal_polynomials_to_monomials_1d( ab, degree) mono_coefs = np.sum(basis_mono_coefs * coefs, axis=0) x = np.linspace(-3, 3, 5) p_ortho = evaluate_orthonormal_polynomial_1d(x, degree, ab) ortho_vals = p_ortho.dot(coefs) from pyapprox.monomial import evaluate_monomial mono_vals = evaluate_monomial( np.arange(degree + 1)[np.newaxis, :], mono_coefs, x[np.newaxis, :])[:, 0] assert np.allclose(ortho_vals, mono_vals)
def gauss_hermite_pts_wts_1D(num_samples): """ Return Gauss Hermite quadrature rule that exactly integrates polynomials of degree 2*num_samples-1 with respect to the Gaussian probability measure 1/sqrt(2*pi)exp(-x**2/2) Parameters ---------- num_samples : integer The number of samples in the quadrature rule Returns ------- x : np.ndarray(num_samples) Quadrature samples w : np.ndarray(num_samples) Quadrature weights """ rho = 0.0 ab = hermite_recurrence(num_samples, rho, probability=True) x, w = gauss_quadrature(ab, num_samples) return x, w
def test_hermite_pdf_weighted_leja_sequence_1d(self): max_nsamples = 3 initial_points = np.array([[0]]) ab = hermite_recurrence(max_nsamples + 1, True) basis_fun = partial(evaluate_orthonormal_polynomial_deriv_1d, ab=ab) pdf = partial(gaussian_pdf, 0, 1) pdf_jac = partial(gaussian_pdf_derivative, 0, 1) # def callback(leja_sequence, coef, new_samples, obj_vals, # initial_guesses): # degree = coef.shape[0]-1 # def plot_fun(x): # return -pdf_weighted_leja_objective_fun_1d( # pdf, partial(basis_fun, nmax=degree + # 1, deriv_order=0), coef, # x[None, :]) # xx = np.linspace(-10, 10, 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_pdf_weighted_leja_sequence_1d(max_nsamples, initial_points, [-np.inf, np.inf], basis_fun, pdf, pdf_jac, { 'gtol': 1e-8, 'verbose': False }, callback=None) # compare to lu based leja samples # given the same set of initial samples the next sample chosen # should be close with the one from the gradient based method having # slightly better objective value num_candidate_samples = 1001 def generate_candidate_samples(n): return np.linspace(-5, 5, n)[None, :] discrete_leja_sequence = \ get_candidate_based_pdf_weighted_leja_sequence_1d( max_nsamples, ab, generate_candidate_samples, num_candidate_samples, pdf, initial_points=leja_sequence[:, :max_nsamples-1]) degree = max_nsamples - 1 tmp = basis_fun(discrete_leja_sequence[0, :-1], nmax=degree + 1, deriv_order=0) basis_mat = tmp[:, :-1] new_basis = tmp[:, -1:] coef = compute_coefficients_of_pdf_weighted_leja_interpolant_1d( pdf(discrete_leja_sequence[0, :-1]), basis_mat, new_basis) discrete_obj_val = pdf_weighted_leja_objective_fun_1d( pdf, partial(basis_fun, nmax=degree + 1, deriv_order=0), coef, discrete_leja_sequence[:, -1:]) tmp = basis_fun(leja_sequence[0, :-1], nmax=degree + 1, deriv_order=0) basis_mat = tmp[:, :-1] new_basis = tmp[:, -1:] coef = compute_coefficients_of_pdf_weighted_leja_interpolant_1d( pdf(leja_sequence[0, :-1]), basis_mat, new_basis) obj_val = pdf_weighted_leja_objective_fun_1d( pdf, partial(basis_fun, nmax=degree + 1, deriv_order=0), coef, leja_sequence[:, -1:]) x = generate_candidate_samples(num_candidate_samples) assert obj_val > discrete_obj_val assert (abs(leja_sequence[0, -1] - discrete_leja_sequence[0, -1]) < x[0, 1] - x[0, 0])
def test_hermite_christoffel_leja_sequence_1d(self): import warnings warnings.filterwarnings('error') # for unbounded variables can get overflow warnings because when # optimizing interval with one side unbounded and no local minima # exists then optimization will move towards inifinity max_nsamples = 20 initial_points = np.array([[0, stats.norm(0, 1).ppf(0.75)]]) # initial_points = np.array([[stats.norm(0, 1).ppf(0.75)]]) ab = hermite_recurrence(max_nsamples + 1, 0, False) basis_fun = partial(evaluate_orthonormal_polynomial_deriv_1d, ab=ab) # plot_degree = np.inf # max_nsamples-1 # assert plot_degree < max_nsamples # def callback(leja_sequence, coef, new_samples, obj_vals, # initial_guesses): # degree = coef.shape[0]-1 # new_basis_degree = degree+1 # if new_basis_degree != plot_degree: # return # plt.clf() # def plot_fun(x): # return -christoffel_leja_objective_fun_1d( # partial(basis_fun, nmax=new_basis_degree, deriv_order=0), # coef, x[None, :]) # xx = np.linspace(-20, 20, 1001) # plt.plot(xx, plot_fun(xx)) # plt.plot(leja_sequence[0, :], plot_fun(leja_sequence[0, :]), 'o') # I = np.argmin(obj_vals) # plt.plot(new_samples[0, I], obj_vals[I], 's') # plt.plot( # initial_guesses[0, :], plot_fun(initial_guesses[0, :]), '*') # #plt.xlim(-10, 10) # print('s', new_samples[0], obj_vals) leja_sequence = get_christoffel_leja_sequence_1d(max_nsamples, initial_points, [-np.inf, np.inf], basis_fun, { 'gtol': 1e-8, 'verbose': False, 'iprint': 2 }, callback=None) # compare to lu based leja samples # given the same set of initial samples the next sample chosen # should be close with the one from the gradient based method having # slightly better objective value num_candidate_samples = 10001 def generate_candidate_samples(n): return np.linspace(-20, 20, n)[None, :] for ii in range(initial_points.shape[1], max_nsamples): degree = ii - 1 new_basis_degree = degree + 1 candidate_samples = generate_candidate_samples( num_candidate_samples) candidate_samples = np.hstack( [leja_sequence[:, :ii], candidate_samples]) bfun = partial(basis_fun, nmax=new_basis_degree, deriv_order=0) basis_mat = bfun(candidate_samples[0, :]) basis_mat = sqrt_christoffel_function_inv_1d( bfun, candidate_samples)[:, None] * basis_mat LU, pivots = truncated_pivoted_lu_factorization( basis_mat, ii + 1, ii, False) # cannot use get_candidate_based_leja_sequence_1d # because it uses christoffel function that is for maximum # degree discrete_leja_sequence = candidate_samples[:, pivots[:ii + 1]] # if new_basis_degree == plot_degree: # # mulitply by LU[ii, ii]**2 to account for LU factorization # # dividing the column by this number # discrete_obj_vals = -LU[:, ii]**2*LU[ii, ii]**2 # # account for pivoting of ith column of LU factor # # value of best objective can be found in the iith pivot # discrete_obj_vals[ii] = discrete_obj_vals[pivots[ii]] # discrete_obj_vals[pivots[ii]] = -LU[ii, ii]**2 # I = np.argsort(candidate_samples[0, ii:])+ii # plt.plot(candidate_samples[0, I], discrete_obj_vals[I], '--') # plt.plot(candidate_samples[0, pivots[ii]], # -LU[ii, ii]**2, 'k^') # plt.show() def objective_value(sequence): tmp = bfun(sequence[0, :]) basis_mat = tmp[:, :-1] new_basis = tmp[:, -1:] coef = compute_coefficients_of_christoffel_leja_interpolant_1d( basis_mat, new_basis) return christoffel_leja_objective_fun_1d( bfun, coef, sequence[:, -1:]) # discrete_obj_val = objective_value( # discrete_leja_sequence[:, :ii+1]) # obj_val = objective_value(leja_sequence[:, :ii+1]) diff = candidate_samples[0, -1] - candidate_samples[0, -2] # print(ii, obj_val - discrete_obj_val) print(leja_sequence[:, :ii + 1], discrete_leja_sequence) # assert obj_val >= discrete_obj_val # obj_val will not always be greater than because of optimization # tolerance and discretization of candidate samples # assert abs(obj_val - discrete_obj_val) < 1e-4 assert (abs(leja_sequence[0, ii] - discrete_leja_sequence[0, -1]) < diff)
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