From 77e9067fe501bf8d24e1aa7973b533e9fcafe684 Mon Sep 17 00:00:00 2001 From: Joanna Schmidt Date: Wed, 3 Dec 2025 11:49:48 -0500 Subject: [PATCH 1/4] adding test workflow --- .github/workflows/tests_workflow.yml | 191 +++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 .github/workflows/tests_workflow.yml diff --git a/.github/workflows/tests_workflow.yml b/.github/workflows/tests_workflow.yml new file mode 100644 index 0000000..cc37ace --- /dev/null +++ b/.github/workflows/tests_workflow.yml @@ -0,0 +1,191 @@ +# Run Tests + +name: UQPCE Tests + +on: + # Trigger on push, pull request + push: + branches: [ main ] + pull_request: + branches: [ main, develop ] + + # Trigger via workflow_dispatch event + workflow_dispatch: + + inputs: + + run_name: + type: string + description: 'Name of workflow run as it will appear under Actions tab:' + required: false + default: "" + +run-name: ${{ inputs.run_name }} + +jobs: + + test_ubuntu: + runs-on: ubuntu-22.04 + + timeout-minutes: 90 + + strategy: + fail-fast: false + matrix: + include: + # baseline versions except with pyoptsparse but no SNOPT + # build docs to verify those that use pyoptsparse do not use SNOPT + - NAME: baseline_no_snopt + PY: '3.11' + NUMPY: '1.26' + SCIPY: '1.13' + PYYAML: '6.0.2' + JAX: '0.6.1' + OPENMDAO: 'latest' + DYMOS: 'latest' + MPI4PY: '3.1.4' + OPTIONAL: '[docs]' + PUBLISH_DOCS: 1 + + # # make sure the latest versions of things don't break the docs + # # sticking with Python 3.12 for now, 3.13 requires NumPy 2.1 which does not work yet with PETSc/pyoptsparse + # # Pin PETSc back to 3.22.2 + # - NAME: latest + # PY: '3.12' + # NUMPY: 1 + # SCIPY: 1 + # PETSc: 3.21.0 + # PYOPTSPARSE: 'latest' + # SNOPT: 7.7 + # OPENMDAO: 'dev' + # OPTIONAL: '[docs]' + # JAX: 'latest' + # PUBLISH_DOCS: 0 + + steps: + - name: Display run details + run: | + echo "=============================================================" + echo "Run #${GITHUB_RUN_NUMBER}" + echo "Run ID: ${GITHUB_RUN_ID}" + echo "Testing: ${GITHUB_REPOSITORY}" + echo "Triggered by: ${GITHUB_EVENT_NAME}" + echo "Initiated by: ${GITHUB_ACTOR}" + echo "=============================================================" + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup conda + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ matrix.PY }} + channels: conda-forge + conda-remove-defaults: true + + - name: Install Numpy/Scipy + shell: bash -l {0} + run: | + echo "=============================================================" + echo "Install Numpy/Scipy" + echo "=============================================================" + conda install numpy=${{ matrix.NUMPY }} scipy=${{ matrix.SCIPY }} -q -y + + - name: Install OpenMDAO + if: matrix.OPENMDAO + shell: bash -l {0} + run: | + echo "=============================================================" + echo "Install OpenMDAO" + echo "=============================================================" + if [[ "${{ matrix.OPENMDAO }}" == "dev" ]]; then + pip install git+https://github.com/OpenMDAO/OpenMDAO + elif [[ "${{ matrix.OPENMDAO }}" == "latest" ]]; then + pip install openmdao + else + pip install openmdao==${{ matrix.OPENMDAO }} + fi + + - name: Install Dymos + if: matrix.DYMOS + shell: bash -l {0} + run: | + echo "=============================================================" + echo "Install Dymos" + echo "=============================================================" + if [[ "${{ matrix.DYMOS }}" == "dev" ]]; then + pip install git+https://github.com/OpenMDAO/dymos + elif [[ "${{ matrix.DYMOS }}" == "latest" ]]; then + pip install dymos + else + pip install dymos==${{ matrix.DYMOS }} + fi + + - name: Install PyYAML + if: matrix.PYYAML + shell: bash -l {0} + run: | + echo "=============================================================" + echo "Install PyYAML" + echo "=============================================================" + pip install PyYAML==${{ matrix.PYYAML }} + + - name: Install JAX + if: matrix.JAX + shell: bash -l {0} + run: | + echo "=============================================================" + echo "Install JAX" + echo "=============================================================" + pip install jax==${{ matrix.JAX }} + + - name: Install mpi4py + if: matrix.MPI4PY + shell: bash -l {0} + run: | + echo "=============================================================" + echo "Install mpi4py" + echo "=============================================================" + conda install mpi4py==${{ matrix.MPI4PY }} -q -y + + - name: Install UQPCE + shell: bash -l {0} + run: | + echo "=============================================================" + echo "Install UQPCE" + echo "=============================================================" + pip install .${{ matrix.OPTIONAL }} + + - name: Display environment info + id: env_info + shell: bash -l {0} + run: | + conda info + conda list + + echo "=============================================================" + echo "Check installed versions of Python, Numpy and Scipy" + echo "=============================================================" + python -c "import sys; assert str(sys.version).startswith(str(${{ matrix.PY }})), \ + f'Python version {sys.version} is not the requested version (${{ matrix.PY }})'" + + python -c "import numpy; assert str(numpy.__version__).startswith(str(${{ matrix.NUMPY }})), \ + f'Numpy version {numpy.__version__} is not the requested version (${{ matrix.NUMPY }})'" + + python -c "import scipy; assert str(scipy.__version__).startswith(str(${{ matrix.SCIPY }})), \ + f'Scipy version {scipy.__version__} is not the requested version (${{ matrix.SCIPY }})'" + + - name: Display dependency tree + if: failure() && steps.env_info.outcome == 'failure' + run: | + pip install pipdeptree + pipdeptree + + - name: Run tests + shell: bash -l {0} + run: | + echo "=============================================================" + echo "Run Tests" + echo "=============================================================" + cd $HOME/work/UQPCE/UQPCE + python -m unittest discover uqpce "test*.py" \ No newline at end of file From beac57fd4ab5c66770a2a30ced6daef9171ff392 Mon Sep 17 00:00:00 2001 From: Joanna Schmidt Date: Thu, 18 Dec 2025 12:16:39 -0500 Subject: [PATCH 2/4] update to test workflow --- .github/workflows/tests_workflow.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/tests_workflow.yml b/.github/workflows/tests_workflow.yml index cc37ace..5d38d6e 100644 --- a/.github/workflows/tests_workflow.yml +++ b/.github/workflows/tests_workflow.yml @@ -12,16 +12,6 @@ on: # Trigger via workflow_dispatch event workflow_dispatch: - inputs: - - run_name: - type: string - description: 'Name of workflow run as it will appear under Actions tab:' - required: false - default: "" - -run-name: ${{ inputs.run_name }} - jobs: test_ubuntu: From 4266f85dee57e3e54cae109901fd76fe76f9142c Mon Sep 17 00:00:00 2001 From: Joanna Schmidt Date: Thu, 18 Dec 2025 12:41:15 -0500 Subject: [PATCH 3/4] updated lognorm test to reflect more reasonable tolerance --- .../test_uqpce/test_continuous_variables.py | 198 +++++++++--------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/uqpce/test_suite/test_uqpce/test_continuous_variables.py b/uqpce/test_suite/test_uqpce/test_continuous_variables.py index 1dcfef3..5987744 100644 --- a/uqpce/test_suite/test_uqpce/test_continuous_variables.py +++ b/uqpce/test_suite/test_uqpce/test_continuous_variables.py @@ -12,7 +12,7 @@ from uqpce.pce.variables.continuous import ( ContinuousVariable, UniformVariable, NormalVariable, BetaVariable, ExponentialVariable, GammaVariable, EpistemicVariable, LognormalVariable, - GaussianMixtureVariable + GaussianMixtureVariable ) from uqpce.pce.enums import UncertaintyType from uqpce.pce.pce import PCE @@ -237,7 +237,7 @@ def test_recursive_var_basis(self): def test__norm_sq(self): """ - Testing the general Variable _norm_sq to ensure that the three + Testing the general Variable _norm_sq to ensure that the three different calculation attempts all give the same correct answer. """ i = 2 @@ -276,7 +276,7 @@ def test__norm_sq(self): def test__invert(self): """ - Testing the general Variable _invert to ensure that the correct + Testing the general Variable _invert to ensure that the correct equation is found when _invert is called. """ manager = Manager() @@ -296,7 +296,7 @@ def test__invert(self): def test__calc_cdf(self): """ - Testing the general Variable _calc_cdf to ensure that the correct + Testing the general Variable _calc_cdf to ensure that the correct cumulative density function is found when _calc_cdf is called. """ manager = Manager() @@ -366,7 +366,7 @@ def test_standardize(self): def test_standardize_points(self): """ - Testing the UniformVariable standardize_points and insuring that the + Testing the UniformVariable standardize_points and insuring that the values follow a standardized distribution. """ p_val_min = 0.05 @@ -411,7 +411,7 @@ def test_standardize_points(self): def test_unstandardize_points(self): """ - Testing the UniformVariable unstandardize_points and insuring that the + Testing the UniformVariable unstandardize_points and insuring that the values follow an unstandardized distribution. """ p_val_min = 0.05 @@ -544,7 +544,7 @@ def test_generate_orthopoly(self): def test_get_mean(self): """ - Tests that the mean for the distributions is consistent with the true + Tests that the mean for the distributions is consistent with the true mean value. """ cnt = 100000 @@ -604,7 +604,7 @@ def test_standardize(self): def test_standardize_points(self): """ - Testing the NormalVariable standardize_points and insuring that the + Testing the NormalVariable standardize_points and insuring that the values follow a standardized distribution. """ p_val_min = 0.05 @@ -643,7 +643,7 @@ def test_standardize_points(self): def test_unstandardize_points(self): """ - Testing the NormalVariable unstandardize_points and insuring that the + Testing the NormalVariable unstandardize_points and insuring that the values follow an unstandardized distribution. """ p_val_min = 0.05 @@ -814,7 +814,7 @@ def test_generate_orthopoly(self): def test_get_mean(self): """ - Tests that the mean for the distributions is consistent with the true + Tests that the mean for the distributions is consistent with the true mean value. """ cnt = 100000 @@ -926,7 +926,7 @@ def test_standardize_points(self): def test_unstandardize_points(self): """ - Testing the NormalVariable unstandardize_points and insuring that the + Testing the NormalVariable unstandardize_points and insuring that the values follow an unstandardized distribution. """ p_val_min = 0.05 @@ -1114,7 +1114,7 @@ def test_generate_orthopoly(self): def test_get_mean(self): """ - Tests that the mean for the distributions is consistent with the true + Tests that the mean for the distributions is consistent with the true mean value. """ cnt = 100000 @@ -1223,7 +1223,7 @@ def test_standardize_points(self): def test_unstandardize_points(self): """ - Testing the ExponentialVariable unstandardize_points and insuring that + Testing the ExponentialVariable unstandardize_points and insuring that the values follow an unstandardized distribution. """ p_val_min = 0.05 @@ -1365,7 +1365,7 @@ def test_generate_orthopoly(self): def test_get_norm_sq_val(self): """ - Testing the ExponentialVariable get_norm_sq_val and ensuring that the + Testing the ExponentialVariable get_norm_sq_val and ensuring that the norm squared values are correct. """ norm_sq_count = 6 @@ -1386,7 +1386,7 @@ def test_get_norm_sq_val(self): def test_get_mean(self): """ - Tests that the mean for the distributions is consistent with the true + Tests that the mean for the distributions is consistent with the true mean value. """ cnt = 100000 @@ -1451,7 +1451,7 @@ def test_standardize(self): def test_standardize_points(self): """ - Testing the GammaVariable standardize_points and insuring that the + Testing the GammaVariable standardize_points and insuring that the values follow a standardized distribution. """ p_val_min = 0.05 @@ -1496,7 +1496,7 @@ def test_standardize_points(self): def test_unstandardize_points(self): """ - Testing the GammaVariable unstandardize_points and insuring that + Testing the GammaVariable unstandardize_points and insuring that the values follow an unstandardized distribution. """ p_val_min = 0.05 @@ -1653,7 +1653,7 @@ def test_get_norm_sq_val(self): def test_get_mean(self): """ - Tests that the mean for the distributions is consistent with the true + Tests that the mean for the distributions is consistent with the true mean value. """ cnt = 100000 @@ -1732,7 +1732,7 @@ def test_standardize(self): def test_standardize_points(self): """ - Testing the LognormalVariable standardize_points and insuring that the + Testing the LognormalVariable standardize_points and insuring that the values follow a standardized distribution. """ p_val_min = 0.05 @@ -1764,7 +1764,7 @@ def test_standardize_points(self): def test_unstandardize_points(self): """ - Testing the LognormalVariable unstandardize_points and insuring that + Testing the LognormalVariable unstandardize_points and insuring that the values follow an unstandardized distribution. """ stand_samps = np.array([ @@ -1775,7 +1775,7 @@ def test_unstandardize_points(self): 4.69280396, 1.99387692, 0.12926983, 1.39719524, 0.52652384, 0.80051761, 0.29218316, 1.22856675, 2.27361575, 0.6103148 ]) - + unstand_points_calc = self.lognorm_var.unstandardize_points(stand_samps) self.assertTrue( np.isclose(unstand_points_calc, self.lognorm_var.vals).all(), @@ -1847,13 +1847,13 @@ def test_recursive_var_basis(self): x0 = symbols('x0') true_var_orthopoly_vect = Matrix(np.array([ - [1], [x0 - 1.64872127070013], + [1], [x0 - 1.64872127070013], [x0**2 - 16.6641830310416*x0 + 20.0855369231878], - [x0**3 - 135.31507722011*x0**2 + 1648.47511102698*x0 + [x0**3 - 135.31507722011*x0**2 + 1648.47511102698*x0 - 1808.04241445994], - [x0**4 - 1032.96614856745*x0**3 + 102184.366510724*x0**2 + [x0**4 - 1032.96614856745*x0**3 + 102184.366510724*x0**2 - 1132784.93005544*x0 + 1202604.28416708], - [x0**5 - 7722.66195136508*x0**4 + 5831835.8566439*x0**3 + [x0**5 - 7722.66195136508*x0**4 + 5831835.8566439*x0**3 - 524965134.031249*x0**2 + 5633036046.13447*x0 - 5910522063.03488], ])) @@ -1889,22 +1889,22 @@ def test_get_norm_sq_val(self): norm_sq_count = 6 true_norm_sq = np.array([ - 1, 4.6707743, 1629.3091942, 34101168.748002, 40259083404382.54, + 1, 4.6707743, 1629.3091942, 34101168.748002, 40259083404382.54, 2625599016180850360320. ]) norm_sq = np.zeros(norm_sq_count) for i in range(norm_sq_count): norm_sq[i] = self.lognorm_var.get_norm_sq_val(i) - + self.assertTrue( - np.isclose(true_norm_sq, norm_sq, rtol=0, atol=1e-6).all(), + np.isclose(true_norm_sq, norm_sq, rtol=1e-12, atol=1e-12).all(), msg='LognormalVariable get_norm_sq_val is not correct' ) def test_get_mean(self): """ - Tests that the mean for the distributions is consistent with the true + Tests that the mean for the distributions is consistent with the true mean value. """ cnt = 10_000_000 @@ -1927,7 +1927,7 @@ def test_get_mean(self): class TestGaussianMixtureVariable(unittest.TestCase): """Comprehensive test suite for GaussianMixtureVariable.""" - + def test_single_component_standard_normal(self): """Test that single-component GMM with mean=0, std=1 uses Hermite polynomials.""" gmm = GaussianMixtureVariable( @@ -1936,18 +1936,18 @@ def test_single_component_standard_normal(self): stdevs=[1.0], order=3 ) - + # Should be detected as standard normal self.assertTrue(gmm.is_standard_normal) - + # Check statistics self.assertAlmostEqual(gmm.mean, 0.0, places=10) self.assertAlmostEqual(gmm.stdev, 1.0, places=10) - + # Should use Hermite polynomials with factorial norm_sq expected_norm_sq = [1, 1, 2, 6] # 0!, 1!, 2!, 3! np.testing.assert_array_equal(gmm.norm_sq_vals[:4], expected_norm_sq) - + def test_single_component_shifted_scaled(self): """Test single-component GMM with non-zero mean and non-unit stdev.""" gmm = GaussianMixtureVariable( @@ -1956,24 +1956,24 @@ def test_single_component_shifted_scaled(self): stdevs=[2.0], order=2 ) - + # Should NOT be detected as standard normal self.assertFalse(gmm.is_standard_normal) - + # Check statistics self.assertAlmostEqual(gmm.mean, 3.0, places=10) self.assertAlmostEqual(gmm.stdev, 2.0, places=10) - + # Test standardization test_vals = np.array([1.0, 3.0, 5.0, 7.0]) expected_std = np.array([-1.0, 0.0, 1.0, 2.0]) std_vals = gmm.standardize_points(test_vals) np.testing.assert_array_almost_equal(std_vals, expected_std) - + # Test unstandardization unstd_vals = gmm.unstandardize_points(std_vals) np.testing.assert_array_almost_equal(unstd_vals, test_vals) - + def test_bimodal_symmetric(self): """Test symmetric bimodal GMM.""" gmm = GaussianMixtureVariable( @@ -1982,39 +1982,39 @@ def test_bimodal_symmetric(self): stdevs=[0.5, 0.5], order=2 ) - + # Mean should be 0 due to symmetry self.assertAlmostEqual(gmm.mean, 0.0, places=10) - + # Variance calculation: E[X^2] - (E[X])^2 # E[X^2] = 0.5 * (0.25 + 4) + 0.5 * (0.25 + 4) = 4.25 # E[X] = 0 # Var = 4.25 expected_std = np.sqrt(4.25) self.assertAlmostEqual(gmm.stdev, expected_std, places=10) - + def test_multimodal_asymmetric(self): """Test asymmetric multimodal GMM.""" weights = [0.3, 0.5, 0.2] means = [-1.0, 0.0, 2.0] stdevs = [0.3, 0.5, 0.4] - + gmm = GaussianMixtureVariable( weights=weights, means=means, stdevs=stdevs, order=2 ) - + # Calculate expected statistics expected_mean = np.sum(np.array(weights) * np.array(means)) e_x_squared = np.sum(np.array(weights) * (np.array(stdevs)**2 + np.array(means)**2)) expected_var = e_x_squared - expected_mean**2 expected_std = np.sqrt(expected_var) - + self.assertAlmostEqual(gmm.mean, expected_mean, places=10) self.assertAlmostEqual(gmm.stdev, expected_std, places=10) - + def test_weight_normalization(self): """Test that weights are normalized to sum to 1.""" # Provide unnormalized weights @@ -2024,11 +2024,11 @@ def test_weight_normalization(self): stdevs=[1.0, 1.0, 1.0], order=1 ) - + # Weights should be normalized np.testing.assert_almost_equal(np.sum(gmm.weights), 1.0) np.testing.assert_array_almost_equal(gmm.weights, [0.2, 0.3, 0.5]) - + def test_input_validation(self): """Test input validation for GMM parameters.""" # Test mismatched lengths @@ -2039,7 +2039,7 @@ def test_input_validation(self): stdevs=[1.0, 1.0], order=1 ) - + # Test negative weights with self.assertRaises(VariableInputError): GaussianMixtureVariable( @@ -2048,7 +2048,7 @@ def test_input_validation(self): stdevs=[1.0, 1.0], order=1 ) - + # Test negative standard deviations with self.assertRaises(VariableInputError): GaussianMixtureVariable( @@ -2057,7 +2057,7 @@ def test_input_validation(self): stdevs=[1.0, -1.0], order=1 ) - + # Test empty components with self.assertRaises(VariableInputError): GaussianMixtureVariable( @@ -2066,7 +2066,7 @@ def test_input_validation(self): stdevs=[], order=1 ) - + def test_orthogonal_polynomials_standard_normal(self): """Test that standard normal GMM produces Hermite polynomials.""" gmm = GaussianMixtureVariable( @@ -2075,9 +2075,9 @@ def test_orthogonal_polynomials_standard_normal(self): stdevs=[1.0], order=3 ) - + x = symbols('x0') - + # Expected Hermite polynomials (physicists' convention) expected = { 0: 1, @@ -2085,13 +2085,13 @@ def test_orthogonal_polynomials_standard_normal(self): 2: x**2 - 1, 3: x**3 - 3*x } - + for i in range(4): poly = gmm.var_orthopoly_vect[i] expected_poly = expected[i] diff = expand(poly - expected_poly) self.assertEqual(diff, 0, f"Hermite polynomial H_{i} incorrect") - + def test_orthogonal_polynomials_multimodal(self): """Test that multimodal GMM produces orthogonal polynomials via Gram-Schmidt.""" gmm = GaussianMixtureVariable( @@ -2100,36 +2100,36 @@ def test_orthogonal_polynomials_multimodal(self): stdevs=[0.3, 0.5, 0.4], order=2 ) - + # Should use Gram-Schmidt, not Hermite self.assertFalse(gmm.is_standard_normal) - + # Verify orthogonality empirically n_samples = 100000 X = gmm.generate_samples(n_samples, standardize=True) - + x = symbols('x0') for i in range(3): for j in range(i+1, 3): poly_i = lambdify(x, gmm.var_orthopoly_vect[i], 'numpy') poly_j = lambdify(x, gmm.var_orthopoly_vect[j], 'numpy') - + # Inner product should be near zero inner_prod = np.mean(poly_i(X) * poly_j(X)) self.assertAlmostEqual(inner_prod, 0.0, places=1, msg=f"Polynomials P_{i} and P_{j} not orthogonal") - + # Verify norm squared values for i in range(3): poly_i = lambdify(x, gmm.var_orthopoly_vect[i], 'numpy') empirical_norm_sq = np.mean(poly_i(X)**2) expected_norm_sq = gmm.norm_sq_vals[i] - + # Should match within 10% (accounting for sampling variance) rel_error = abs(empirical_norm_sq - expected_norm_sq) / expected_norm_sq self.assertLess(rel_error, 0.1, msg=f"Norm squared for P_{i} doesn't match") - + def test_generate_samples(self): """Test that generate_samples produces correct distribution.""" gmm = GaussianMixtureVariable( @@ -2138,26 +2138,26 @@ def test_generate_samples(self): stdevs=[0.5, 1.0], order=1 ) - + # Generate many samples n_samples = 100000 samples = gmm.generate_samples(n_samples, standardize=False) - + # Check mean and std are approximately correct sample_mean = np.mean(samples) sample_std = np.std(samples) - + self.assertAlmostEqual(sample_mean, gmm.mean, places=1) self.assertAlmostEqual(sample_std, gmm.stdev, places=1) - + # Check standardized samples std_samples = gmm.generate_samples(n_samples, standardize=True) std_mean = np.mean(std_samples) std_std = np.std(std_samples) - + self.assertAlmostEqual(std_mean, 0.0, places=1) self.assertAlmostEqual(std_std, 1.0, places=1) - + def test_resample(self): """Test resample method for PCE internal use.""" gmm = GaussianMixtureVariable( @@ -2166,17 +2166,17 @@ def test_resample(self): stdevs=[0.5, 0.5], order=2 ) - + samples = gmm.resample(1000) - + # Check that samples are standardized self.assertAlmostEqual(np.mean(samples), 0, places=1) self.assertAlmostEqual(np.std(samples), 1, places=1) - + # They should be within reasonable bounds but not forced to extremes self.assertLess(np.max(np.abs(samples)), 5) # Should be within ~5 sigma self.assertGreater(np.max(np.abs(samples)), 2) # Should have some spread - + def test_get_norm_sq_val(self): """Test norm squared value retrieval.""" gmm = GaussianMixtureVariable( @@ -2185,13 +2185,13 @@ def test_get_norm_sq_val(self): stdevs=[1.0], order=3 ) - + # For standard normal, should be factorials self.assertEqual(gmm.get_norm_sq_val(0), 1) # 0! self.assertEqual(gmm.get_norm_sq_val(1), 1) # 1! self.assertEqual(gmm.get_norm_sq_val(2), 2) # 2! self.assertEqual(gmm.get_norm_sq_val(3), 6) # 3! - + # Test out of bounds with self.assertRaises(ValueError): gmm.get_norm_sq_val(10) @@ -2199,7 +2199,7 @@ def test_get_norm_sq_val(self): class TestGMMWithPCE(unittest.TestCase): """Test GMM integration with PCE.""" - + def test_pce_with_standard_normal_gmm(self): """Test PCE with standard normal GMM.""" pce = PCE(order=2, outputs=False, verbose=False, plot=False) @@ -2209,19 +2209,19 @@ def test_pce_with_standard_normal_gmm(self): means=[0.0], stdevs=[1.0] ) - + # Function: f(x) = x^2 n_train = 100 X_train = pce.sample(n_train) y_train = X_train[:, 0]**2 - + pce.fit(X_train, y_train) - + # For f(x) = x^2 with standard normal: # E[X^2] = 1, Var[X^2] = E[X^4] - (E[X^2])^2 = 3 - 1 = 2 self.assertAlmostEqual(pce.mean[0], 1.0, places=1) self.assertAlmostEqual(pce.variance[0], 2.0, places=0) - + def test_pce_with_bimodal_gmm(self): """Test PCE with bimodal GMM.""" @@ -2232,40 +2232,40 @@ def test_pce_with_bimodal_gmm(self): means=[-1.0, 1.0], stdevs=[0.3, 0.3] ) - + # Linear function: f(x) = 2*x + 1 n_train = 100 X_train = pce.sample(n_train) y_train = 2 * X_train[:, 0] + 1 - + pce.fit(X_train, y_train) - + # E[2X + 1] = 2*E[X] + 1 = 2*0 + 1 = 1 self.assertAlmostEqual(pce.mean[0], 1.0, places=1) - + # Test prediction - FIX: Need to get the right shape X_test = np.array([[-1], [0], [1]]) y_pred_raw = pce.predict(X_test) - + # Handle the output shape correctly if isinstance(y_pred_raw, tuple): y_pred = y_pred_raw[0].flatten() else: y_pred = y_pred_raw.flatten() - + y_true = 2 * X_test[:, 0] + 1 - + # Make sure shapes match - self.assertEqual(len(y_pred), len(y_true), + self.assertEqual(len(y_pred), len(y_true), f"Shape mismatch: pred {y_pred.shape} vs true {y_true.shape}") - + # Now test values np.testing.assert_array_almost_equal(y_pred, y_true, decimal=0) # Reduced precision - + def test_pce_multivariate_with_gmm(self): """Test multivariate PCE with GMM variables.""" pce = PCE(order=2, outputs=False, verbose=False, plot=False) # Increased order - + # Variable 1: Standard normal GMM pce.add_variable( distribution='GAUSSIAN_MIXTURE', @@ -2273,7 +2273,7 @@ def test_pce_multivariate_with_gmm(self): means=[0.0], stdevs=[1.0] ) - + # Variable 2: Bimodal GMM pce.add_variable( distribution='GAUSSIAN_MIXTURE', @@ -2281,34 +2281,34 @@ def test_pce_multivariate_with_gmm(self): means=[-1.0, 1.0], stdevs=[0.5, 0.5] ) - + # Function: f(x1, x2) = x1 + 0.5*x2 n_train = 200 # Increased sample size X_train = pce.sample(n_train) y_train = X_train[:, 0] + 0.5 * X_train[:, 1] - + pce.fit(X_train, y_train) - + # E[X1 + 0.5*X2] = E[X1] + 0.5*E[X2] = 0 + 0.5*0 = 0 self.assertAlmostEqual(pce.mean[0], 0.0, places=1) - + # Check that the model fits well y_pred_raw = pce.predict(X_train) if isinstance(y_pred_raw, tuple): y_pred = y_pred_raw[0].flatten() else: y_pred = y_pred_raw.flatten() - + # Check R² score residuals = y_train - y_pred ss_res = np.sum(residuals**2) ss_tot = np.sum((y_train - np.mean(y_train))**2) - + if ss_tot > 0: r2 = 1 - (ss_res / ss_tot) else: r2 = 1.0 if ss_res < 1e-10 else 0.0 - + self.assertGreater(r2, 0.9, f"Model should fit training data well (R²={r2:.3f})") if __name__ == '__main__': From 808b7633ce05f168707bd26bb2776c90588b5554 Mon Sep 17 00:00:00 2001 From: Joanna Schmidt Date: Thu, 18 Dec 2025 14:05:28 -0500 Subject: [PATCH 4/4] replaced activation function call with the jax version --- .../test_uqpce/mdao/cdf/test_cdfgroup.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/uqpce/test_suite/test_uqpce/mdao/cdf/test_cdfgroup.py b/uqpce/test_suite/test_uqpce/mdao/cdf/test_cdfgroup.py index 12ae255..10b8706 100644 --- a/uqpce/test_suite/test_uqpce/mdao/cdf/test_cdfgroup.py +++ b/uqpce/test_suite/test_uqpce/mdao/cdf/test_cdfgroup.py @@ -7,10 +7,10 @@ from scipy.stats import beta, nbinom, expon from uqpce.mdao.cdf.cdfgroup import CDFGroup -from uqpce.mdao.cdf.cdfresidcomp import tanh_activation +from openmdao.jax import act_tanh tanh_omega = 1e-6 -aleat_cnt = 75_000 +aleat_cnt = 500_000 class TestCDFGroup(unittest.TestCase): def setUp(self): @@ -76,14 +76,14 @@ def test_beta(self): x = np.sort(self.beta_samples) plt.figure() - act = tanh_activation(x, omega=1e-12, z=ci_lower, a=1, b=0) + act = act_tanh(x, mu=1e-12, z=ci_lower, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_lower, ci_lower], [act.min(), act.max()], 'k--') plt.title('Beta Distribution') # plt.savefig('beta_lower_ci') - + plt.figure() - act = tanh_activation(x, omega=1e-12, z=ci_upper, a=1, b=0) + act = act_tanh(x, mu=1e-12, z=ci_upper, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_upper, ci_upper], [act.min(), act.max()], 'k--') plt.title('Beta Distribution') @@ -141,14 +141,14 @@ def test_expon(self): x = np.sort(self.expon_samples) plt.figure() - act = tanh_activation(x, omega=tanh_omega, z=ci_lower, a=1, b=0) + act = act_tanh(x, mu=tanh_omega, z=ci_lower, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_lower, ci_lower], [act.min(), act.max()], 'k--') plt.title('Exponential Distribution') # plt.savefig('expon_lower_ci') - + plt.figure() - act = tanh_activation(x, omega=tanh_omega, z=ci_upper, a=1, b=0) + act = act_tanh(x, mu=tanh_omega, z=ci_upper, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_upper, ci_upper], [act.min(), act.max()], 'k--') plt.title('Exponential Distribution') @@ -172,7 +172,7 @@ def test_nbinom(self): 'upper', CDFGroup( alpha=alpha, tail='upper', vec_size=aleat_cnt, tanh_omega=tanh_omega, - epistemic_cnt=1, aleatory_cnt=aleat_cnt, + epistemic_cnt=1, aleatory_cnt=aleat_cnt, sample_ref0=float(self.nbinom_samples.min()), sample_ref=float(self.nbinom_samples.max()) ), promotes_inputs=['*'] ) @@ -205,14 +205,14 @@ def test_nbinom(self): x = np.sort(self.nbinom_samples) plt.figure() - act = tanh_activation(x, omega=tanh_omega, z=ci_lower, a=1, b=0) + act = act_tanh(x, mu=tanh_omega, z=ci_lower, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_lower, ci_lower], [act.min()-0.1, act.max()+0.1], 'k--') plt.title('Binomial Distribution') # plt.savefig('nbinom_lower_ci') - + plt.figure() - act = tanh_activation(x, omega=tanh_omega, z=ci_upper, a=1, b=0) + act = act_tanh(x, mu=tanh_omega, z=ci_upper, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_upper, ci_upper], [act.min(), act.max()], 'k--') plt.title('Binomial Distribution') @@ -271,14 +271,14 @@ def test_combined(self): x = np.sort(samps) plt.figure() - act = tanh_activation(x, omega=tanh_omega, z=ci_lower, a=1, b=0) + act = act_tanh(x, mu=tanh_omega, z=ci_lower, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_lower, ci_lower], [act.min(), act.max()], 'k--') plt.title('Combined Distributions') # plt.savefig('combined_lower_ci') - + plt.figure() - act = tanh_activation(x, omega=tanh_omega, z=ci_upper, a=1, b=0) + act = act_tanh(x, mu=tanh_omega, z=ci_upper, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_upper, ci_upper], [act.min(), act.max()], 'k--') plt.title('Combined Distributions') @@ -338,14 +338,14 @@ def test_combined_high_order(self): x = np.sort(samps) plt.figure() - act = tanh_activation(x, omega=1e-12, z=ci_lower, a=1, b=0) + act = act_tanh(x, mu=1e-12, z=ci_lower, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_lower, ci_lower], [act.min(), act.max()], 'k--') plt.title('High-Order Combined Distributions') # plt.savefig('high_order_lower_ci') - + plt.figure() - act = tanh_activation(x, omega=1e-12, z=ci_upper, a=1, b=0) + act = act_tanh(x, mu=1e-12, z=ci_upper, a=1, b=0) plt.plot(x, act, '-o') plt.plot([ci_upper, ci_upper], [act.min(), act.max()], 'k--') plt.title('High-Order Combined Distributions')