diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ff2a3ac3d4..1c30d8313a 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.32.1-dev +current_version = 3.33.1-dev commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 370c3e4863..85d3fb507e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -23,7 +23,7 @@ body: attributes: label: OpenMDAO Version description: What version of OpenMDAO is being used. - placeholder: "3.32.1-dev" + placeholder: "3.33.1-dev" validations: required: true - type: textarea diff --git a/.github/scripts/get_poem_id.py b/.github/scripts/get_poem_id.py index 3b7899301b..29741669ec 100644 --- a/.github/scripts/get_poem_id.py +++ b/.github/scripts/get_poem_id.py @@ -30,13 +30,18 @@ def get_poem_id(repository, pull_id): print(f"Checking Pull Request #{pull_id} for associated issue...") print("-------------------------------------------------------------------------------") try: - pull_json = subprocess.check_output(["gh", "--repo", repository, - "issue", "view", "--json", "body", pull_id]) + p = subprocess.run(["gh", "--repo", repository, "issue", "view", "--json", "body", pull_id], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) except subprocess.CalledProcessError as err: print(f"Unable to access pull request #{pull_id}:\nrc={err.returncode}") + print(f"stdout:\n------\n{p.stdout}") + print(f"stderr:\n------\n{p.stderr}") return ERROR - pull_body = json.loads(pull_json)["body"] + print(f"stdout:\n------\n{p.stdout}") + print(f"stderr:\n------\n{p.stderr}") + + pull_body = json.loads(p.stdout)["body"] issue_id = "" @@ -58,14 +63,20 @@ def get_poem_id(repository, pull_id): print(f"Checking Issue #{issue_id} for associated POEM...") print("-------------------------------------------------------------------------------") + repository = repository.replace('swryan', 'OpenMDAO') try: - issue_json = subprocess.check_output(["gh", "--repo", repository, - "issue", "view", "--json", "body", issue_id]) + p = subprocess.run(["gh", "--repo", repository, "issue", "view", "--json", "body", issue_id], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) except subprocess.CalledProcessError as err: print(f"Unable to access issue #{issue_id}:\nrc={err.returncode}") + print(f"stdout:\n------\n{p.stdout}") + print(f"stderr:\n------\n{p.stderr}") return ERROR - issue_body = json.loads(issue_json)["body"] + print(f"stdout:\n------\n{p.stdout}") + print(f"stderr:\n------\n{p.stderr}") + + issue_body = json.loads(p.stdout)["body"] poem_id = "" diff --git a/.github/workflows/openmdao_latest_workflow.yml b/.github/workflows/openmdao_latest_workflow.yml index 99c1aaf368..bdec9a4d55 100644 --- a/.github/workflows/openmdao_latest_workflow.yml +++ b/.github/workflows/openmdao_latest_workflow.yml @@ -56,6 +56,7 @@ jobs: PETSc: True PYOPTSPARSE: true SNOPT: true + BANDIT: true BUILD_DOCS: true # test latest versions on ubuntu @@ -352,6 +353,28 @@ jobs: grep '^0 unique deprecation warnings' $RPT_FILE + - name: Scan for security issues + if: matrix.BANDIT + id: bandit + continue-on-error: true + run: | + python -m pip install bandit + echo "=============================================================" + echo "Run bandit scan for high/medium severity issues" + echo "=============================================================" + cd ${{ github.workspace }} + python -m bandit -c bandit.yml -ll -r openmdao + + - name: Slack security issue + if: steps.bandit.outcome == 'failure' + uses: act10ns/slack@v2.0.0 + with: + webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} + status: 'warning' + message: + Security issue found on `${{ matrix.NAME }}` build. + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + - name: Slack env change if: steps.env_info.outputs.errors != '' uses: act10ns/slack@v2.0.0 diff --git a/.github/workflows/openmdao_test_workflow.yml b/.github/workflows/openmdao_test_workflow.yml index 862d080c8d..715019240d 100644 --- a/.github/workflows/openmdao_test_workflow.yml +++ b/.github/workflows/openmdao_test_workflow.yml @@ -726,7 +726,6 @@ jobs: - name: Scan for security issues if: matrix.BANDIT id: bandit - continue-on-error: true run: | python -m pip install bandit echo "=============================================================" @@ -735,16 +734,6 @@ jobs: cd ${{ github.workspace }} python -m bandit -c bandit.yml -ll -r openmdao - - name: Slack security issue - if: steps.bandit.outcome == 'failure' - uses: act10ns/slack@v2.0.0 - with: - webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} - status: ${{ steps.bandit.outcome }} - message: - Security issue found on `${{ matrix.NAME }}` build. - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - coveralls: name: Finish coverage diff --git a/.github/workflows/openmdao_update_poem.yml b/.github/workflows/openmdao_update_poem.yml index b757c19323..854223850f 100644 --- a/.github/workflows/openmdao_update_poem.yml +++ b/.github/workflows/openmdao_update_poem.yml @@ -5,15 +5,15 @@ name: Update Associated POEM on: pull_request: - types: [ opened, reopened, closed ] + types: [ opened, reopened, closed, synchronize ] branches: [ master ] -permissions: {} +# permissions: {} jobs: check_for_poem: - if: github.event.action == 'opened' || github.event.action == 'reopened' || github.event.pull_request.merged + if: github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize' || github.event.pull_request.merged runs-on: ubuntu-latest @@ -51,7 +51,7 @@ jobs: fi - name: Comment on PR - if: (github.event.action == 'opened' || github.event.action == 'reopened') && steps.check_for_poem.outputs.POEM_ID != '' + if: (github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize') && steps.check_for_poem.outputs.POEM_ID != '' env: POEM_ID: ${{ steps.check_for_poem.outputs.POEM_ID }} POEM_URL: ${{ github.server_url }}/${{ github.repository_owner }}/POEMs/blob/master/POEM_${{ steps.check_for_poem.outputs.POEM_ID }}.md diff --git a/README.md b/README.md index 827fdf494e..8c970d15d6 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![PyPI version][10]][11] [![PyPI Monthly Downloads][12]][11] + # [OpenMDAO][0] OpenMDAO is an open-source high-performance computing platform for diff --git a/openmdao/__init__.py b/openmdao/__init__.py index 91f35f2b7d..6c93545033 100644 --- a/openmdao/__init__.py +++ b/openmdao/__init__.py @@ -1,3 +1,3 @@ -__version__ = '3.32.1-dev' +__version__ = '3.33.1-dev' INF_BOUND = 1.0E30 diff --git a/openmdao/components/ks_comp.py b/openmdao/components/ks_comp.py index 74ce3ecff0..e27f3e9b2b 100644 --- a/openmdao/components/ks_comp.py +++ b/openmdao/components/ks_comp.py @@ -159,8 +159,13 @@ def initialize(self): self.options.declare('width', types=int, default=1, desc='Width of constraint vector.') self.options.declare('vec_size', types=int, default=1, desc='The number of rows to independently aggregate.') + self.options.declare('minimum', types=bool, default=False, + desc='Return the minimum instead of the maximum by multiplying both ' + 'the inputs and output by -1. It is not recommended to use both ' + 'this option and the lower_flag option (it will return the ' + 'negative of the aggregated max.)') self.options.declare('lower_flag', types=bool, default=False, - desc="Set to True to reverse sign of input constraints.") + desc='Set to True to reverse sign of input constraints.') self.options.declare('rho', 50.0, desc="Constraint Aggregation Factor.") self.options.declare('upper', 0.0, desc="Upper bound for constraint, default is zero.") self.options.declare('add_constraint', types=bool, default=False, @@ -229,8 +234,15 @@ def compute(self, inputs, outputs): con_val = inputs['g'] - opt['upper'] if opt['lower_flag']: con_val = -con_val + if opt['minimum']: + con_val = -con_val + + ks_val = KSfunction.compute(con_val, opt['rho']) - outputs['KS'] = KSfunction.compute(con_val, opt['rho']) + if opt['minimum']: + ks_val = -ks_val + + outputs['KS'] = ks_val def compute_partials(self, inputs, partials): """ @@ -244,11 +256,12 @@ def compute_partials(self, inputs, partials): Sub-jac components written to partials[output_name, input_name]. """ opt = self.options - width = opt['width'] con_val = inputs['g'] - opt['upper'] if opt['lower_flag']: con_val = -con_val + if opt['minimum']: + con_val = -con_val derivs = KSfunction.derivatives(con_val, opt['rho'])[0] diff --git a/openmdao/components/tests/test_ks_comp.py b/openmdao/components/tests/test_ks_comp.py index 006dca0927..b699aae6ad 100644 --- a/openmdao/components/tests/test_ks_comp.py +++ b/openmdao/components/tests/test_ks_comp.py @@ -6,7 +6,7 @@ import openmdao.api as om from openmdao.test_suite.components.simple_comps import DoubleArrayComp from openmdao.test_suite.test_examples.beam_optimization.multipoint_beam_stress import MultipointBeamGroup -from openmdao.utils.assert_utils import assert_near_equal +from openmdao.utils.assert_utils import assert_near_equal, assert_check_partials from openmdao.utils.testing_utils import force_check_partials @@ -320,6 +320,67 @@ def test_units(self): assert_near_equal(prob.get_val('ks.KS', indices=0), np.amax(prob.get_val('x')), tolerance=1e-8) + def test_minimum(self): + + n = 10 + + model = om.Group() + + model.add_subsystem('ks', om.KSComp(width=n, minimum=True), promotes_inputs=[('g', 'x')]) + model.set_input_defaults('x', range(n)) + + prob = om.Problem(model=model) + prob.setup() + prob.run_model() + + assert_near_equal(prob.get_val('ks.KS', indices=0), np.amin(prob.get_val('x')), tolerance=1e-8) + + def test_minimum_partials(self): + + n = 10 + + model = om.Group() + + model.add_subsystem('ks', om.KSComp(width=n, minimum=True), promotes_inputs=[('g', 'x')]) + model.set_input_defaults('x', range(n)) + + prob = om.Problem(model=model) + prob.setup(force_alloc_complex=True) + prob.run_model() + + partials = force_check_partials(prob, includes=['ks'], out_stream=None, method="cs", step=1e-200) + assert_check_partials(partials) + + def test_minimum_and_lower_flag(self): + + n = 10 + + model = om.Group() + + model.add_subsystem('ks', om.KSComp(width=n, minimum=True, lower_flag=True), promotes_inputs=[('g', 'x')]) + model.set_input_defaults('x', range(n)) + + prob = om.Problem(model=model) + prob.setup() + prob.run_model() + + assert_near_equal(prob.get_val('ks.KS', indices=0), -np.amax(prob.get_val('x')), tolerance=1e-8) + + def test_minimum_and_lower_flag_partials(self): + + n = 10 + + model = om.Group() + + model.add_subsystem('ks', om.KSComp(width=n, minimum=True, lower_flag=True), promotes_inputs=[('g', 'x')]) + model.set_input_defaults('x', range(n)) + + prob = om.Problem(model=model) + prob.setup(force_alloc_complex=True) + prob.run_model() + + partials = force_check_partials(prob, includes=['ks'], out_stream=None, method="cs", step=1e-200) + assert_check_partials(partials) if __name__ == "__main__": unittest.main() diff --git a/openmdao/docs/openmdao_book/features/core_features/adding_desvars_cons_objs/modifying_desvars_cons_obj.ipynb b/openmdao/docs/openmdao_book/features/core_features/adding_desvars_cons_objs/modifying_desvars_cons_obj.ipynb index d3588501be..2b3a37f1cd 100644 --- a/openmdao/docs/openmdao_book/features/core_features/adding_desvars_cons_objs/modifying_desvars_cons_obj.ipynb +++ b/openmdao/docs/openmdao_book/features/core_features/adding_desvars_cons_objs/modifying_desvars_cons_obj.ipynb @@ -143,7 +143,6 @@ "# modify the constraint and run again\n", "\n", "model.set_constraint_options(name='con1', upper=-1.0)\n", - "prob.setup()\n", "prob.run_driver()\n", "print(f\"con1 = {prob.get_val('con1')}\")" ] diff --git a/openmdao/utils/code_utils.py b/openmdao/utils/code_utils.py index 7f3e3c848c..f8cf5006dd 100644 --- a/openmdao/utils/code_utils.py +++ b/openmdao/utils/code_utils.py @@ -440,7 +440,7 @@ def __setstate__(self, state): The state of this object. """ self.__dict__.update(state) - self._func = eval(state['_func']) + self._func = eval(state['_func']) # nosec def _getsrc(self): if self._src is None: diff --git a/openmdao/utils/relevance.py b/openmdao/utils/relevance.py index 072719611c..7544b543f4 100644 --- a/openmdao/utils/relevance.py +++ b/openmdao/utils/relevance.py @@ -798,6 +798,8 @@ def is_relevant_system(self, name): """ Return True if the given named system is relevant. + Returns False if system has no subsystems with outputs. + Parameters ---------- name : str @@ -811,7 +813,10 @@ def is_relevant_system(self, name): if not self._active: return True - return self._current_rel_sarray[self._sys2idx[name]] + try: + return self._current_rel_sarray[self._sys2idx[name]] + except KeyError: + return False def filter(self, systems, relevant=True): """ diff --git a/openmdao/utils/tests/test_relevance.py b/openmdao/utils/tests/test_relevance.py index 5cad0ffcde..af05c18f90 100644 --- a/openmdao/utils/tests/test_relevance.py +++ b/openmdao/utils/tests/test_relevance.py @@ -43,3 +43,23 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): prob.run_model() chk = prob.check_totals(of='b', wrt='a', show_only_incorrect=True) assert_check_totals(chk) + +class TestRelevanceEmptyGroups(unittest.TestCase): + def test_emptygroup(self): + '''Tests that relevance checks do not error if empty groups are present''' + prob = om.Problem() + model = prob.model + + model.add_subsystem('empy_group', om.Group(), promotes=['*']) + grp2: om.Group = model.add_subsystem('non_empty_group', om.Group(), promotes=['*']) + grp2.add_subsystem('idv', om.IndepVarComp('x', val=1), promotes=['*']) + grp2.add_subsystem('comp', om.ExecComp('y=2*x**2'), promotes=['*']) + model.add_design_var('x') + model.add_objective('y') + + prob.driver = om.ScipyOptimizeDriver() + + prob.setup(force_alloc_complex=True) + prob.run_driver() + + assert_check_totals(prob.check_totals(method='cs', out_stream=None)) \ No newline at end of file diff --git a/release_notes.md b/release_notes.md index 948b2d6463..d723275eb3 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,10 +1,58 @@ +*********************************** +# Release Notes for OpenMDAO 3.33.0 + +June 07, 2024 + +OpenMDAO 3.33.0 adds a new DriverResult object which is returned by the drivers. This replaces the previous "failed" flag that users often found confusing because a value of False indicated successful optimization. This object contains a `success` attribute for tracking whether the optimization was successful. + +Pyoptsparse print_results can be set to `minimal` to show only violated constraints. This requires a recent version of pyoptsparse to function, but will not cause errors in its absense. + +Users on FIPS-enabled sytems reported that OpenMDAO could not be used due to the use of `hashlib.md5` for non-security related purposes. We now pass the `usedforsecurity=False` argument that allows this behavior. + +Finally, a `list_vars` method has been added that lists all variables in component execution order, regardless of whether they are inputs or outputs. + +## New Features + +- Changed return of `prob.run_driver()` from a bool to an object containing information about the Driver execution. [#3214](https://github.com/OpenMDAO/OpenMDAO/pull/3214) +- Added ability to set pyoptsparse driver print_results to "minimal" [#3216](https://github.com/OpenMDAO/OpenMDAO/pull/3216) +- Moved get_free_port function to utils directory file [#3224](https://github.com/OpenMDAO/OpenMDAO/pull/3224) +- Added `minimum` option to KSComp. [#3229](https://github.com/OpenMDAO/OpenMDAO/pull/3229) +- Added `usedforsecurity=False` flag to hashlib.md5 uses for FIPS-enabled systems. [#3237](https://github.com/OpenMDAO/OpenMDAO/pull/3237) +- Add a new `list_vars` method to list all variables by component in execution order. [#3233](https://github.com/OpenMDAO/OpenMDAO/pull/3233) + +## Bug Fixes + +- Updated `pyoptsparse_driver` to address change in `pyoptsparse v2.11.0` [#3218](https://github.com/OpenMDAO/OpenMDAO/pull/3218) +- Added ability to handle reading case recorder files with class instances when the associated class cannot be imported [#3228](https://github.com/OpenMDAO/OpenMDAO/pull/3228) +- A couple of fixes for 'om total_coloring' and 'om partial_coloring' [#3234](https://github.com/OpenMDAO/OpenMDAO/pull/3234) +- Fix Cut&Paste error in docstring for `list_vars`. [#3238](https://github.com/OpenMDAO/OpenMDAO/pull/3238) +- Fix for SubmodelComp indexing bug. [#3239](https://github.com/OpenMDAO/OpenMDAO/pull/3239) +- Removed the 'distributed' option from ExecComp [#3245](https://github.com/OpenMDAO/OpenMDAO/pull/3245) +- Fix for group set_val when using set_input_defaults [#3248](https://github.com/OpenMDAO/OpenMDAO/pull/3248) +- Fixed error message when a component has an inconsistent set of variables across ranks. [#3254](https://github.com/OpenMDAO/OpenMDAO/pull/3254) +- Fix for incorrect warning about response size vs. dv size [#3255](https://github.com/OpenMDAO/OpenMDAO/pull/3255) +- Fixed a bug in Case when a VOI is not recorded [#3256](https://github.com/OpenMDAO/OpenMDAO/pull/3256) +- Fix the sparkline plots in the optimization report - height was too small [#3258](https://github.com/OpenMDAO/OpenMDAO/pull/3258) +- Added a wrapper for lambda functions to allow pickling. [#3259](https://github.com/OpenMDAO/OpenMDAO/pull/3259) +- Fixed relevance check for empty groups. [#3265](https://github.com/OpenMDAO/OpenMDAO/pull/3265) + +## Miscellaneous + +- Added minimum version requirements to address vulnerable dependencies [#3227](https://github.com/OpenMDAO/OpenMDAO/pull/3227) +- Updated tests for constrained differential evolution [#3232](https://github.com/OpenMDAO/OpenMDAO/pull/3232) +- Updates for NumPy 2.0 compatibility and testing [#3241](https://github.com/OpenMDAO/OpenMDAO/pull/3241) +- Fixed a NumPy 2.0 compatibility issue in the test suite [#3251](https://github.com/OpenMDAO/OpenMDAO/pull/3251) +- Adjusted the CS step size in an approx_totals test for compatibility with SciPy 1.13 [#3257](https://github.com/OpenMDAO/OpenMDAO/pull/3257) +- Updated the test workflow to fail if a security issue is found [#3261](https://github.com/OpenMDAO/OpenMDAO/pull/3261) +- Removed unnecessary setup() from doc page [#3263](https://github.com/OpenMDAO/OpenMDAO/pull/3263) + + *********************************** # Release Notes for OpenMDAO 3.32.0 May 03, 2024 -OpenMDAO 3.32.0 is a regular update with a few new features and several bug fixes. We're continuing to refine our submodel implementation, and now we give the user explicit access to the subproblem so that they can interact with it -using the `Problem` API. +OpenMDAO 3.32.0 is a regular update with a few new features and several bug fixes. We're continuing to refine our submodel implementation, and now we give the user explicit access to the subproblem so that they can interact with it using the `Problem` API. POEM 093 is implemented, which allows caching of the linear solution which can improve performance in some situations.