From a23fab907cfc7aec7a4ee5e43760191436a32e3c Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Mon, 13 Oct 2025 17:53:09 -0700 Subject: [PATCH 01/14] Update build for current macOS, unpin specific old versions --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 33531a10..91d7f470 100644 --- a/setup.py +++ b/setup.py @@ -6,11 +6,11 @@ author="Michael Tupek and Brandon Talamini", author_email='talamini1@llnl.gov', # todo: make an email list install_requires=['equinox', - 'jax[cpu]==0.4.28', + 'jax[cpu]', 'jaxtyping', 'matplotlib', # this is not strictly necessary 'netcdf4', - 'scipy<1.15.0'], + 'scipy'], #tests_require=[], # could put chex and pytest here extras_require={'sparse': ['scikit-sparse'], 'test': ['pytest', 'pytest-cov', 'pytest-xdist'], From d31fe3af6731feed7255ea6f798040172d9cbc01 Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Mon, 13 Oct 2025 17:54:29 -0700 Subject: [PATCH 02/14] Fix tensor tests --- optimism/test/test_TensorMath.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/optimism/test/test_TensorMath.py b/optimism/test/test_TensorMath.py index f39719fd..b425528a 100644 --- a/optimism/test/test_TensorMath.py +++ b/optimism/test/test_TensorMath.py @@ -28,7 +28,7 @@ def lam(A): class TensorMathFixture(TestFixture): def setUp(self): - key = jax.random.PRNGKey(1) + key = jax.random.PRNGKey(0) self.R = jax.random.orthogonal(key, 3) self.assertGreater(np.linalg.det(self.R), 0) # make sure this is a rotation and not a reflection self.log_squared = lambda A: np.tensordot(TensorMath.log_sqrt(A), TensorMath.log_sqrt(A)) @@ -219,7 +219,6 @@ def test_pow_symm_gradient_distinct_eigenvalues(self): m = 0.25 check_grads(lambda A: TensorMath.pow_symm(C, m), (C,), order=1) - @unittest.expectedFailure def test_pow_symm_gradient_almost_double_degenerate(self): C = self.R@np.diag(np.array([2.1, 2.1 + 1e-8, 3.0]))@self.R.T m = 0.25 From 51ad1ad78735eee44a17cc29a46d7b7b8f5e80a6 Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Tue, 14 Oct 2025 16:24:19 -0700 Subject: [PATCH 03/14] Work around current jax bug with auxiliary solver output --- optimism/ScalarRootFind.py | 8 +++++-- optimism/test/test_ScalarRootFinder.py | 30 ++++++++++++++++---------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/optimism/ScalarRootFind.py b/optimism/ScalarRootFind.py index c42d14c4..89727eea 100644 --- a/optimism/ScalarRootFind.py +++ b/optimism/ScalarRootFind.py @@ -144,8 +144,12 @@ def loop_body(carry): x = np.where(converged, x, np.nan) - return x, SolutionInfo(converged=converged, function_calls=functionCalls, - iterations=iters, residual_norm=np.abs(F), correction_norm=np.abs(dx)) + # BT 10/14/2025 As of Jax 0.4.34, the has_aux argument of custom_root is broken + # and cannot handle non-differentiable outputs. + # See https://github.com/jax-ml/jax/issues/24295 + # return x, SolutionInfo(converged=converged, function_calls=functionCalls, + # iterations=iters, residual_norm=np.abs(F), correction_norm=np.abs(dx)) + return x, None def bisection_step(x, xl, xh, df, f): diff --git a/optimism/test/test_ScalarRootFinder.py b/optimism/test/test_ScalarRootFinder.py index 1fff5737..79c641b9 100644 --- a/optimism/test/test_ScalarRootFinder.py +++ b/optimism/test/test_ScalarRootFinder.py @@ -15,7 +15,7 @@ def f(x): return x**3 - 4.0 class ScalarRootFindTestFixture(TestFixture.TestFixture): def setUp(self): - self.settings = ScalarRootFind.get_settings() + self.settings = ScalarRootFind.get_settings(r_tol=1e-12, x_tol=0) self.rootGuess = 1e-5 self.rootExpected = np.cbrt(4.0) @@ -33,16 +33,19 @@ def setUp(self): def test_find_root(self): rootBracket = np.array([float_info.epsilon, 100.0]) - root, status = ScalarRootFind.find_root(f, self.rootGuess, rootBracket, self.settings) - self.assertTrue(status.converged) + root, _ = ScalarRootFind.find_root(f, self.rootGuess, rootBracket, self.settings) + #self.assertTrue(status.converged) + converged = np.abs(f(root)) <= self.settings.r_tol + self.assertTrue(converged) self.assertNear(root, self.rootExpected, 13) def test_find_root_with_jit(self): rtsafe_jit = jax.jit(ScalarRootFind.find_root, static_argnums=(0,3)) rootBracket = np.array([float_info.epsilon, 100.0]) - root, status = rtsafe_jit(f, self.rootGuess, rootBracket, self.settings) - self.assertTrue(status.converged) + root, _ = rtsafe_jit(f, self.rootGuess, rootBracket, self.settings) + converged = np.abs(f(root)) <= self.settings.r_tol + self.assertTrue(converged) self.assertNear(root, self.rootExpected, 13) @@ -56,8 +59,9 @@ def test_find_root_converges_on_hard_function(self): g = lambda x: np.sin(x) + x rootBracket = np.array([-3.0, 20.0]) x0 = 19.0 - root, status = ScalarRootFind.find_root(f, x0, rootBracket, self.settings) - self.assertTrue(status.converged) + root, _ = ScalarRootFind.find_root(f, x0, rootBracket, self.settings) + converged = np.abs(f(root)) <= self.settings.r_tol + self.assertTrue(converged) self.assertNear(root, self.rootExpected, 13) @@ -106,16 +110,20 @@ def my_sqrt(a): def test_solves_when_left_bracket_is_solution(self): rootBracket = np.array([0.0, 1.0]) guess = 3.0 - root, status = ScalarRootFind.find_root(lambda x: x*(x**2 - 10.0), guess, rootBracket, self.settings) - self.assertTrue(status.converged) + f = lambda x: x*(x**2 - 10.0) + root, _ = ScalarRootFind.find_root(f, guess, rootBracket, self.settings) + converged = np.abs(f(root)) <= self.settings.r_tol + self.assertTrue(converged) self.assertNear(root, 0.0, 12) def test_solves_when_right_bracket_is_solution(self): rootBracket = np.array([-1.0, 0.0]) guess = 3.0 - root, status = ScalarRootFind.find_root(lambda x: x*(x**2 - 10.0), guess, rootBracket, self.settings) - self.assertTrue(status.converged) + f = lambda x: x*(x**2 - 10.0) + root, _ = ScalarRootFind.find_root(f, guess, rootBracket, self.settings) + converged = np.abs(f(root)) <= self.settings.r_tol + self.assertTrue(converged) self.assertNear(root, 0.0, 12) if __name__ == '__main__': From beac186aa45d29699b9193b70187c2cfcb5ed225 Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Tue, 14 Oct 2025 19:10:02 -0700 Subject: [PATCH 04/14] Fix test failure due to apparent rounding error Test results math to 14 digits or so, but now fall just outside the prior tolerance --- optimism/test/test_LinAlg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimism/test/test_LinAlg.py b/optimism/test/test_LinAlg.py index 68d27475..142a39b8 100644 --- a/optimism/test/test_LinAlg.py +++ b/optimism/test/test_LinAlg.py @@ -59,7 +59,7 @@ def test_sqrtm_on_10x10(self): C = F.T@F sqrtC = LinAlg.sqrtm(C) shouldBeC = np.dot(sqrtC,sqrtC) - self.assertArrayNear(shouldBeC, C, 11) + self.assertArrayNear(shouldBeC, C, 10) def test_sqrtm_derivatives_on_10x10(self): From 7a233676a624bbae89930c9362dc505c9750c008 Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Wed, 15 Oct 2025 14:19:15 -0700 Subject: [PATCH 05/14] Fix new test failures due to jax handling int arrays differently Scipy was sending integer-valued arrays into jax jvp functions. I don't know why - maybe the initial guess is a zero vector of ints? Anyway, this guards against that by explicitly converting back and forth between numpy and jax.numpy arrays as necessary, with jax arrays having their dtype explicitly set to a float type. --- optimism/NewtonSolver.py | 26 ++++++++++++------- ...t_multi_block_J2Plastic_gradient_checks.py | 18 +++++++++++-- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/optimism/NewtonSolver.py b/optimism/NewtonSolver.py index f7b66949..e6d38928 100644 --- a/optimism/NewtonSolver.py +++ b/optimism/NewtonSolver.py @@ -27,13 +27,23 @@ def compute_min_p(ps, bounds): return min(max(quadMin, bounds[0]), bounds[1]) -def newton_step(residual, linear_op, x, settings=Settings(1e-2,100), precond=None): +def make_scipy_linear_function(linear_function): + def linear_op(v): + # The v is going into a jax function (probably a jvp). + # Sometimes scipy passes in an array of dtype int, which breaks + # jax tracing and differentiation, so explicitly set type to + # something jax can handle. + jax_v = np.array(v, dtype=np.float64) + jax_Av = linear_function(jax_v) + # The result is going back into a scipy solver, so convert back + # to a standard numpy array. + return onp.array(jax_Av) + return linear_op + + +def newton_step(residual, residual_jvp, x, settings=Settings(1e-2,100), precond=None): sz = x.size - # The call to onp.array copies the jax array output into a plain numpy - # array. The copy is necessary for safety, since as far as scipy knows, - # it is allowed to modify the output in place. - A = LinearOperator((sz,sz), - lambda v: onp.array(linear_op(v))) + A = LinearOperator((sz, sz), make_scipy_linear_function(residual_jvp)) r = onp.array(residual(x)) numIters = 0 @@ -45,9 +55,7 @@ def callback(xk): maxIters = settings.max_gmres_iters if precond is not None: - # Another copy to a plain numpy array, see comment for A above. - M = LinearOperator((sz,sz), - lambda v: onp.array(precond(v))) + M = LinearOperator((sz, sz), make_scipy_linear_function(precond)) else: M = None diff --git a/optimism/inverse/test/test_multi_block_J2Plastic_gradient_checks.py b/optimism/inverse/test/test_multi_block_J2Plastic_gradient_checks.py index 332ca681..76811126 100644 --- a/optimism/inverse/test/test_multi_block_J2Plastic_gradient_checks.py +++ b/optimism/inverse/test/test_multi_block_J2Plastic_gradient_checks.py @@ -24,6 +24,20 @@ ['energy_function_coords', 'compute_dissipation']) + +def make_scipy_linear_function(linear_function): + def linear_op(v): + # The v is going into a jax function (like a jvp). + # Sometimes scipy passes in an array of dtype int, which breaks + # jax tracing and differentiation, so explicitly set type to + # something jax can handle. + jax_v = np.array(v, dtype=np.float64) + jax_Av = linear_function(jax_v) + # The result is going back into a scipy solver, so convert back + # to a standard numpy array. + return onp.array(jax_Av) + return linear_op + class J2GlobalMeshAdjointSolveFixture(FiniteDifferenceFixture): def setUp(self): dispGrad0 = np.array([[0.4, -0.2], @@ -172,8 +186,8 @@ def dissipated_energy_gradient(self, storedState, parameters): p_objective = Objective.Params(bc_data=p.bc_data, state_data=ivs_prev, prop_data=self.props) # remember R is a function of ivs_prev self.objective.p = p_objective self.objective.update_precond(Uu) # update preconditioner for use in cg (will converge in 1 iteration as long as the preconditioner is not approximate) - dRdu = linalg.LinearOperator((n, n), lambda V: onp.asarray(self.objective.hessian_vec(Uu, V))) - dRdu_decomp = linalg.LinearOperator((n, n), lambda V: onp.asarray(self.objective.apply_precond(V))) + dRdu = linalg.LinearOperator((n, n), make_scipy_linear_function(lambda V: self.objective.hessian_vec(Uu, V))) + dRdu_decomp = linalg.LinearOperator((n, n), make_scipy_linear_function(self.objective.apply_precond)) adjointVector = linalg.cg(dRdu, onp.array(adjointLoad, copy=False), rtol=1e-10, atol=0.0, M=dRdu_decomp)[0] gradient += residualInverseFuncs.residual_jac_coords_vjp(Uu, p, ivs_prev, parameters, adjointVector) From 67930bb3d70a43fe467b25ca9a0b6e2395432d38 Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Wed, 15 Oct 2025 15:25:02 -0700 Subject: [PATCH 06/14] Refactor to avoid repetition in fixing jax to scipy translation --- optimism/NewtonSolver.py | 16 +--------------- optimism/ScipyInterface.py | 16 ++++++++++++++++ .../test/test_Hyperelastic_gradient_checks.py | 5 +++-- .../test/test_J2Plastic_gradient_checks.py | 9 +++++---- ...test_multi_block_J2Plastic_gradient_checks.py | 14 +------------- 5 files changed, 26 insertions(+), 34 deletions(-) create mode 100644 optimism/ScipyInterface.py diff --git a/optimism/NewtonSolver.py b/optimism/NewtonSolver.py index e6d38928..7308a395 100644 --- a/optimism/NewtonSolver.py +++ b/optimism/NewtonSolver.py @@ -2,7 +2,7 @@ from scipy.sparse.linalg import LinearOperator, gmres from optimism.JaxConfig import * - +from optimism.ScipyInterface import make_scipy_linear_function Settings = namedtuple('Settings', ['relative_gmres_tol', 'max_gmres_iters']) @@ -27,20 +27,6 @@ def compute_min_p(ps, bounds): return min(max(quadMin, bounds[0]), bounds[1]) -def make_scipy_linear_function(linear_function): - def linear_op(v): - # The v is going into a jax function (probably a jvp). - # Sometimes scipy passes in an array of dtype int, which breaks - # jax tracing and differentiation, so explicitly set type to - # something jax can handle. - jax_v = np.array(v, dtype=np.float64) - jax_Av = linear_function(jax_v) - # The result is going back into a scipy solver, so convert back - # to a standard numpy array. - return onp.array(jax_Av) - return linear_op - - def newton_step(residual, residual_jvp, x, settings=Settings(1e-2,100), precond=None): sz = x.size A = LinearOperator((sz, sz), make_scipy_linear_function(residual_jvp)) diff --git a/optimism/ScipyInterface.py b/optimism/ScipyInterface.py new file mode 100644 index 00000000..b9423dd5 --- /dev/null +++ b/optimism/ScipyInterface.py @@ -0,0 +1,16 @@ +import jax.numpy as np +import numpy as onp + +def make_scipy_linear_function(linear_function): + """Transform a linear function of a jax array to one that can be used with scipy.linalg.LinearOperator.""" + def linear_op(v): + # The v is going into a jax function (probably a jvp). + # Sometimes scipy passes in an array of dtype int, which breaks + # jax tracing and differentiation, so explicitly set type to + # something jax can handle. + jax_v = np.array(v, dtype=np.float64) + jax_Av = linear_function(jax_v) + # The result is going back into a scipy solver, so convert back + # to a standard numpy array. + return onp.array(jax_Av) + return linear_op \ No newline at end of file diff --git a/optimism/inverse/test/test_Hyperelastic_gradient_checks.py b/optimism/inverse/test/test_Hyperelastic_gradient_checks.py index 7facd73e..58c4c31c 100644 --- a/optimism/inverse/test/test_Hyperelastic_gradient_checks.py +++ b/optimism/inverse/test/test_Hyperelastic_gradient_checks.py @@ -13,6 +13,7 @@ from optimism import Objective from optimism import Mesh from optimism.material import Neohookean +from optimism.ScipyInterface import make_scipy_linear_function from .FiniteDifferenceFixture import FiniteDifferenceFixture @@ -166,8 +167,8 @@ def total_work_gradient_with_adjoint(self, storedState, parameters): n = self.dofManager.get_unknown_size() self.objective.p = p # have to update parameters to get precond to work self.objective.update_precond(Uu) # update preconditioner for use in cg (will converge in 1 iteration as long as the preconditioner is not approximate) - dRdu = linalg.LinearOperator((n, n), lambda V: onp.asarray(self.objective.hessian_vec(Uu, V))) - dRdu_decomp = linalg.LinearOperator((n, n), lambda V: onp.asarray(self.objective.apply_precond(V))) + dRdu = linalg.LinearOperator((n, n), make_scipy_linear_function(lambda V: self.objective.hessian_vec(Uu, V))) + dRdu_decomp = linalg.LinearOperator((n, n), make_scipy_linear_function(self.objective.apply_precond)) adjointVector = linalg.cg(dRdu, onp.array(adjointLoad, copy=False), rtol=1e-10, atol=0.0, M=dRdu_decomp)[0] gradient += df_dx diff --git a/optimism/inverse/test/test_J2Plastic_gradient_checks.py b/optimism/inverse/test/test_J2Plastic_gradient_checks.py index cdb126c3..5ada26d5 100644 --- a/optimism/inverse/test/test_J2Plastic_gradient_checks.py +++ b/optimism/inverse/test/test_J2Plastic_gradient_checks.py @@ -13,6 +13,7 @@ from optimism import Objective from optimism import Mesh from optimism.material import J2Plastic as J2 +from optimism.ScipyInterface import make_scipy_linear_function from .FiniteDifferenceFixture import FiniteDifferenceFixture @@ -174,8 +175,8 @@ def total_work_gradient(self, storedState, parameters): self.objective.p = p_objective self.objective.update_precond(Uu) # update preconditioner for use in cg (will converge in 1 iteration as long as the preconditioner is not approximate) - dRdu = linalg.LinearOperator((n, n), lambda V: onp.asarray(self.objective.hessian_vec(Uu, V))) - dRdu_decomp = linalg.LinearOperator((n, n), lambda V: onp.asarray(self.objective.apply_precond(V))) + dRdu = linalg.LinearOperator((n, n), make_scipy_linear_function(lambda V: self.objective.hessian_vec(Uu, V))) + dRdu_decomp = linalg.LinearOperator((n, n), make_scipy_linear_function(self.objective.apply_precond)) adjointVector = linalg.cg(dRdu, onp.array(adjointLoad, copy=False), rtol=1e-10, atol=0.0, M=dRdu_decomp)[0] gradient += residualInverseFuncs.residual_jac_coords_vjp(Uu, p, ivs_prev, parameters, adjointVector) @@ -255,8 +256,8 @@ def target_curve_gradient(self, storedState, parameters): p_objective = Objective.Params(bc_data=p.bc_data, state_data=p_prev.state_data, prop_data=self.props) # remember R is a function of ivs_prev self.objective.p = p_objective self.objective.update_precond(Uu) # update preconditioner for use in cg (will converge in 1 iteration as long as the preconditioner is not approximate) - dRdu = linalg.LinearOperator((n, n), lambda V: onp.asarray(self.objective.hessian_vec(Uu, V))) - dRdu_decomp = linalg.LinearOperator((n, n), lambda V: onp.asarray(self.objective.apply_precond(V))) + dRdu = linalg.LinearOperator((n, n), make_scipy_linear_function(lambda V: self.objective.hessian_vec(Uu, V))) + dRdu_decomp = linalg.LinearOperator((n, n), make_scipy_linear_function(self.objective.apply_precond)) adjointVector = linalg.cg(dRdu, onp.array(adjointLoad, copy=False), rtol=1e-10, atol=0.0, M=dRdu_decomp)[0] gradient += residualInverseFuncs.residual_jac_coords_vjp(Uu, p, ivs_prev, parameters, adjointVector) diff --git a/optimism/inverse/test/test_multi_block_J2Plastic_gradient_checks.py b/optimism/inverse/test/test_multi_block_J2Plastic_gradient_checks.py index 76811126..1be4535d 100644 --- a/optimism/inverse/test/test_multi_block_J2Plastic_gradient_checks.py +++ b/optimism/inverse/test/test_multi_block_J2Plastic_gradient_checks.py @@ -13,6 +13,7 @@ from optimism import Objective from optimism import Mesh from optimism.material import J2Plastic as J2 +from optimism.ScipyInterface import make_scipy_linear_function from .FiniteDifferenceFixture import FiniteDifferenceFixture @@ -25,19 +26,6 @@ 'compute_dissipation']) -def make_scipy_linear_function(linear_function): - def linear_op(v): - # The v is going into a jax function (like a jvp). - # Sometimes scipy passes in an array of dtype int, which breaks - # jax tracing and differentiation, so explicitly set type to - # something jax can handle. - jax_v = np.array(v, dtype=np.float64) - jax_Av = linear_function(jax_v) - # The result is going back into a scipy solver, so convert back - # to a standard numpy array. - return onp.array(jax_Av) - return linear_op - class J2GlobalMeshAdjointSolveFixture(FiniteDifferenceFixture): def setUp(self): dispGrad0 = np.array([[0.4, -0.2], From 6f2ac9fc77d360fc9ba5bf5c6bca2550508a65fe Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Wed, 15 Oct 2025 15:34:16 -0700 Subject: [PATCH 07/14] Fix deprecation warnings for 2d cross product --- optimism/test/test_Mesh.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/optimism/test/test_Mesh.py b/optimism/test/test_Mesh.py index 725d2448..22ebde78 100644 --- a/optimism/test/test_Mesh.py +++ b/optimism/test/test_Mesh.py @@ -153,8 +153,12 @@ def test_conversion_to_quadratic_mesh_is_valid(self): # plt.show() +def cross_prod_2d(v, w): + return v[0]*w[1] - w[0]*v[1] + + def triangle_inradius(tcoords): - area = 0.5*onp.cross(tcoords[1]-tcoords[0], tcoords[2]-tcoords[0]) + area = 0.5*cross_prod_2d(tcoords[1]-tcoords[0], tcoords[2]-tcoords[0]) peri = (onp.linalg.norm(tcoords[1]-tcoords[0]) + onp.linalg.norm(tcoords[2]-tcoords[1]) + onp.linalg.norm(tcoords[0]-tcoords[2])) From 6f445aef1fb0a3796855658ce602b01d4babf498 Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Wed, 15 Oct 2025 16:23:49 -0700 Subject: [PATCH 08/14] Bump tested python version to 3.13 --- .github/workflows/ci-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 38fce0da..2d372e1a 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10"] + python-version: ["3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} From adb00f563053b2eb3debd54a7302c835decb7483 Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Wed, 15 Oct 2025 16:32:38 -0700 Subject: [PATCH 09/14] Bump minimum python version in setup to 3.13 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 91d7f470..cb89c5a8 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ extras_require={'sparse': ['scikit-sparse'], 'test': ['pytest', 'pytest-cov', 'pytest-xdist'], 'docs': ['sphinx', 'sphinx-copybutton', 'sphinx-rtd-theme', 'sphinxcontrib-bibtex', 'sphinxcontrib-napoleon']}, - python_requires='>=3.7', + python_requires='>=3.13', version='0.0.1', license='MIT', url='https://github.com/sandialabs/optimism' From 7bc62d6d6bf99e18f07345089a0d9f8fe05f0a5c Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Thu, 16 Oct 2025 06:55:46 -0700 Subject: [PATCH 10/14] Increased step size on a finite difference test to reduce roundoff error causing failure on linux --- optimism/test/test_TensorMath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimism/test/test_TensorMath.py b/optimism/test/test_TensorMath.py index b425528a..2141b6b0 100644 --- a/optimism/test/test_TensorMath.py +++ b/optimism/test/test_TensorMath.py @@ -177,7 +177,7 @@ def test_exp_symm_gradient_distinct_eigenvalues(self): def test_sqrt_symm_gradient_almost_double_degenerate(self): C = self.R@np.diag(np.array([2.1, 2.1 + 1e-8, 3.0]))@self.R.T - check_grads(TensorMath.exp_symm, (C,), order=1, eps=1e-10) + check_grads(TensorMath.exp_symm, (C,), order=1, eps=1e-8) # pow_symm tests From fd0c8601c12d3895bf2ad8f00b99e5dafb3c0aac Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Thu, 16 Oct 2025 07:12:20 -0700 Subject: [PATCH 11/14] Loosen tolerance slightly on finite difference test A test is barely failing on github actions. I can't reproduce it because my machine (which is a mac, not linux ubuntu like GH actions) apparently uses a different random seed and has values for the test. --- optimism/test/test_TensorMath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimism/test/test_TensorMath.py b/optimism/test/test_TensorMath.py index 2141b6b0..add6486a 100644 --- a/optimism/test/test_TensorMath.py +++ b/optimism/test/test_TensorMath.py @@ -177,7 +177,7 @@ def test_exp_symm_gradient_distinct_eigenvalues(self): def test_sqrt_symm_gradient_almost_double_degenerate(self): C = self.R@np.diag(np.array([2.1, 2.1 + 1e-8, 3.0]))@self.R.T - check_grads(TensorMath.exp_symm, (C,), order=1, eps=1e-8) + check_grads(TensorMath.exp_symm, (C,), order=1, eps=1e-8, rtol=5e-5) # pow_symm tests From e2d019d658fdd24700bec72609f7292bd930e6e3 Mon Sep 17 00:00:00 2001 From: Brandon Talamini Date: Fri, 17 Oct 2025 13:32:59 -0700 Subject: [PATCH 12/14] Increase step size in finite difference grad check because cancellation error was causing test failure --- optimism/test/test_TensorMath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optimism/test/test_TensorMath.py b/optimism/test/test_TensorMath.py index add6486a..eb695277 100644 --- a/optimism/test/test_TensorMath.py +++ b/optimism/test/test_TensorMath.py @@ -222,7 +222,7 @@ def test_pow_symm_gradient_distinct_eigenvalues(self): def test_pow_symm_gradient_almost_double_degenerate(self): C = self.R@np.diag(np.array([2.1, 2.1 + 1e-8, 3.0]))@self.R.T m = 0.25 - check_grads(lambda A: TensorMath.pow_symm(A, 0.25), (C,), order=1, atol=1e-16, eps=1e-10) + check_grads(lambda A: TensorMath.pow_symm(A, 0.25), (C,), order=1, atol=1e-16, eps=1e-6) def test_determinant(self): From b59b01b2facedab3c8d0dd1b08a8416ad3472fc1 Mon Sep 17 00:00:00 2001 From: Craig Hamel <31457225+cmhamel@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:42:21 -0400 Subject: [PATCH 13/14] Update Python version matrix in CI workflow --- .github/workflows/ci-build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 2d372e1a..6ae1ccc5 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -15,7 +15,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.13"] + python-version: + - 3.11 + - 3.12 + - 3.13 steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} From 968180d46f9d3006ca510963d2da7c4f6b690178 Mon Sep 17 00:00:00 2001 From: Craig Hamel <31457225+cmhamel@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:43:42 -0400 Subject: [PATCH 14/14] Update Python version requirement and package version --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index cb89c5a8..28c434f1 100644 --- a/setup.py +++ b/setup.py @@ -15,8 +15,8 @@ extras_require={'sparse': ['scikit-sparse'], 'test': ['pytest', 'pytest-cov', 'pytest-xdist'], 'docs': ['sphinx', 'sphinx-copybutton', 'sphinx-rtd-theme', 'sphinxcontrib-bibtex', 'sphinxcontrib-napoleon']}, - python_requires='>=3.13', - version='0.0.1', + python_requires='>=3.11', + version='0.0.2', license='MIT', url='https://github.com/sandialabs/optimism' )