From 8963d133ea8a6700eb6f2a3584d14e67a9eb9aca Mon Sep 17 00:00:00 2001 From: Rob Falck Date: Wed, 8 Nov 2023 12:37:15 -0500 Subject: [PATCH 1/8] Create POEM_094.md: Driver Autoscaling and Refactor --- POEM_094.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 POEM_094.md diff --git a/POEM_094.md b/POEM_094.md new file mode 100644 index 00000000..acbd924d --- /dev/null +++ b/POEM_094.md @@ -0,0 +1,78 @@ +POEM ID: 094. +Title: Driver Autoscaling and Refactor. +authors: robfalck (Rob Falck) +Competing POEMs: +Related POEMs: N/A +Associated implementation PR: N/A. + +Status: + +- [x] Active +- [ ] Requesting decision +- [ ] Accepted +- [ ] Rejected +- [ ] Integrated + +## Motivation + +OpenMDAO currently requires users provide manual scaling for their optimization problems, or use the autoscaling +options in IPOPT. + +Since IPOPT has limited options for this and not all users use IPOPT, it makes sense to have OpenMDAO provide _some_ autoscaling capability in an extensible manner. + +No autoscaling algorithm works for all use-cases, but if we can ease the burden for some users by providing this capability it seems like it may be worthwhile. + +## Proposed Solution + +This POEM consists of a few thrusts. + +1. We will refactor the Driver class to implement `OptimizationDriver` and `AnalysisDriver`. + +`OptimizationDriver` will be associated with optmization and will support some methods that just don't make sense in an Analysis standpoint. This will be the parent class for `ScipyOptimizeDriver`, `pyOptSparseDriver`, `SimpleGADriver`, and `DifferentialEvolutionDriver`. + +`AnalysisDriver` will support design exploration, but the notion of scaling doesn't apply here, as this driver will not support objectives or constraints, but just "responses". This will be the parent class for `DOEDriver`. + +2. OptimizationDrivers will support the notion of an `Autoscaler` that is called early in their `run`. The autoscaler will be set using `driver.set_autoscaler(AutoscalerClass())`. + +3. OpenMDAO will provide some default set of Autoscalers (discussed below), and allow users to implement their own. + +## Changes to OptimizationDriver + +### Autoscaling Changes + + - `Driver.add_autoscaler(Autoscaler())` will be used to add autoscalers to the driver. + - Autoscalers will be run in sequence, so autoscaling algorithms that only apply to certain systems in the model can be responsible for setting their scaling factors. + - The autoscaler will default to None, which results in the current behavior of manual scaling only. + - `Driver._setup_driver` will set `has_scaling` to True if the current condition is True *OR* it has one or more autoscalers. + - `Driver.run` will call the autoscaling algorithms before run_case. It will set `total_scaler` and `total_adder` for the dvs, cons, and objectives. + +### New Methods + +- `get_feasibility_tol` + +Return the current feasibility tolerance for the optimizer. This provides a consistent optimizer-independent way of getting the feasibility tolerance for optimizers which support this concept. This is useful for scaling and potentially working out the active set of constraints from the OpenMDAO side of things. This can be obtained as option `Major Feasilibity Tol` from SNOPT or `constraint_viol_tol` from IPOPT, for instance. + +- `get_lagrange_multipliers` + +Get the lagrange multipliers for optimizers which provide them, in an optimizer-independent way. This will be useful for evaluating post-optimality sensititivity. + +## Proposed Initial Autoscalers + +### `SimpleAutoscaler` + +SimpleAutoscaler will enforce that the design variable vector is scaled to have a norm of approximately 1.0. +If users impose scaler/adder or ref0/ref scaling on their design variables, those will be assumed to be the correct scalers. +Otherwise, if scaler/adder/ref/ref0 are `None`, DefaultAutoscaler use the reciprocal of the initial value as the scaler if it's absolute value is greater than one, otherwise it will use ref0=-1, ref=1. + +Constraints will have a option, autoscale_tolerance, which will default to 1.0E-3. This specifies the number of decimal places to which the constraint should be satisfied. The scale factor can then be computed from this as `scaler = autoscale_tolerance / feasibility_tolerance`. + +If a specific driver used does not support the notion of feasibility tolerance, raise an error so that this Autoscaler may not be used. + +### `PJRNAutoscaler` + + Projected Jacobian Row Norm scaling algorithm + [Reference](https://elib.dlr.de/93327/1/Performance_analysis_of_linear_and_nonlinear_techniques.pdf) + + This scaler will require that bounds be set on all design variables. + This scaler will use the values of `lower` and `upper` as `ref0` and `ref`, and compute the corresponding `scaler` and `adder` values. + The `scaler` is `1 / K_x` as referenced by the paper, and adder is `b_x`. From bb3dd4e24890ca43db11c79b6eea7249b34a442a Mon Sep 17 00:00:00 2001 From: Rob Falck Date: Wed, 8 Nov 2023 12:41:02 -0500 Subject: [PATCH 2/8] Update POEM_094.md --- POEM_094.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/POEM_094.md b/POEM_094.md index acbd924d..bb601db6 100644 --- a/POEM_094.md +++ b/POEM_094.md @@ -1,4 +1,4 @@ -POEM ID: 094. +POEM ID: 094 Title: Driver Autoscaling and Refactor. authors: robfalck (Rob Falck) Competing POEMs: From d463bffcd1d884713e85d2d2c1dd1270674254f3 Mon Sep 17 00:00:00 2001 From: Rob Falck Date: Wed, 8 Nov 2023 12:56:47 -0500 Subject: [PATCH 3/8] Update POEM_094.md --- POEM_094.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/POEM_094.md b/POEM_094.md index bb601db6..3fc742ac 100644 --- a/POEM_094.md +++ b/POEM_094.md @@ -56,6 +56,24 @@ Return the current feasibility tolerance for the optimizer. This provides a cons Get the lagrange multipliers for optimizers which provide them, in an optimizer-independent way. This will be useful for evaluating post-optimality sensititivity. +## Autoscale API + +Autoscaler will provide a `scale` method with a signaiture + +``` +def scale(problem, desvar_scaling, constraint_scaling, objective_scaling) +``` + +Problem provides access to both the model and the driver, so we can interrogate things like optimizer settings. +The other arguments are output dictionaries each keyed by the desvar, constraint, or objective name. +For each key in these dictionaries, the user can provide the scaler, adder, ref, or ref0. + +``` +# scale x by dividing by it's initial value +x_val = problem.get_val('x') +desvar_scaling['x']['scaler'] = 1 / x_val +``` + ## Proposed Initial Autoscalers ### `SimpleAutoscaler` From 12d4e19e4f701610b9d1001863e2a9c95b6daccb Mon Sep 17 00:00:00 2001 From: Rob Falck Date: Thu, 25 Apr 2024 14:30:35 -0400 Subject: [PATCH 4/8] Update POEM_094.md --- POEM_094.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/POEM_094.md b/POEM_094.md index 3fc742ac..01c9cbaf 100644 --- a/POEM_094.md +++ b/POEM_094.md @@ -32,9 +32,32 @@ This POEM consists of a few thrusts. `AnalysisDriver` will support design exploration, but the notion of scaling doesn't apply here, as this driver will not support objectives or constraints, but just "responses". This will be the parent class for `DOEDriver`. -2. OptimizationDrivers will support the notion of an `Autoscaler` that is called early in their `run`. The autoscaler will be set using `driver.set_autoscaler(AutoscalerClass())`. +2. Driver runs will return a DriverResults object. -3. OpenMDAO will provide some default set of Autoscalers (discussed below), and allow users to implement their own. +The current return value of `failed` doesn't provide any information on the optimization and forces the user to go digging through the optimizers to find things like iteration counts, informs/exit status, Lagrange multipliers, etc. + +In addition, the users find the notion that a successful optimization returns a value of `False` to be confusing. + +This proposal will change the return value of a driver run to a new type called `DriverResults`. + +Any aspect that we expect to be common across several drivers should be an attribute/property of `DriverResults`. + +This will include: +- `success`: Flag that is `True` if the optimization was successful. +- `f_eval`: The number of objective evaluations. +- `g_eval`: The number of gradient evaluations. +- `objectives`: A dictionary containing the objective name, units, and optimal value. +- `design_vars`: A dictionary of design variable names, units, and their optimal values. +- `constraints`: A dictionary of the constraint names, units, and their values at the optimal point. + +`DriverResults` will contain an attribute/property `success` that is a boolean indicating whether the driver successfully ended. The meaning of this flag will vary from driver to driver (and optimizer to optmizer). For instance, SLSQP has a rather straight-forward success criteria, while SNOPT has multiple inform results that might indicate success. + +**Note: These changes are backwards incompatible and will impact anyone who is checking the return value of `run_driver`, +since this object (as most Python objects), will evaluate to `True`. + +4. OptimizationDrivers will support the notion of an `Autoscaler` that is called early in their `run`. The autoscaler will be set using `driver.set_autoscaler(AutoscalerClass())`. + +5. OpenMDAO will provide some default set of Autoscalers (discussed below), and allow users to implement their own. ## Changes to OptimizationDriver From b159ff3b2bd0abb3bde1762925b20a5e4e68e727 Mon Sep 17 00:00:00 2001 From: Rob Falck Date: Mon, 29 Apr 2024 11:47:35 -0400 Subject: [PATCH 5/8] Update POEM_094.md --- POEM_094.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/POEM_094.md b/POEM_094.md index 01c9cbaf..021266cf 100644 --- a/POEM_094.md +++ b/POEM_094.md @@ -26,13 +26,20 @@ No autoscaling algorithm works for all use-cases, but if we can ease the burden This POEM consists of a few thrusts. -1. We will refactor the Driver class to implement `OptimizationDriver` and `AnalysisDriver`. +### We will refactor the Driver class to implement `OptimizationDriver` and `AnalysisDriver`. `OptimizationDriver` will be associated with optmization and will support some methods that just don't make sense in an Analysis standpoint. This will be the parent class for `ScipyOptimizeDriver`, `pyOptSparseDriver`, `SimpleGADriver`, and `DifferentialEvolutionDriver`. -`AnalysisDriver` will support design exploration, but the notion of scaling doesn't apply here, as this driver will not support objectives or constraints, but just "responses". This will be the parent class for `DOEDriver`. +`AnalysisDriver` will support design exploration, but the notion of scaling doesn't apply here. -2. Driver runs will return a DriverResults object. +- AnalysisDriver will replace a deprecated DOEDriver. +- Different ways of providing run points to AnalysisDriver will dictate if it acts like a run-once driver, a DOE driver, a monte-carlo driver, etc. +- AnalysisDriver will have a recorder attached by default. +- Drivers will support `add_constraint`, `add_objective`, and `add_design_var` (just passed through to apply to the underlying model.) +- AnalysisDriver will support `add_response`, an output to be recorded but the notion of a constraint or objective doesn't really make sense in the context. +- AnalysisDriver will record all design vars, constraints, objectives, responses by default - those are probably what the user is keen in recording. + +### Driver runs will return a DriverResults object. The current return value of `failed` doesn't provide any information on the optimization and forces the user to go digging through the optimizers to find things like iteration counts, informs/exit status, Lagrange multipliers, etc. @@ -44,20 +51,18 @@ Any aspect that we expect to be common across several drivers should be an attri This will include: - `success`: Flag that is `True` if the optimization was successful. -- `f_eval`: The number of objective evaluations. -- `g_eval`: The number of gradient evaluations. -- `objectives`: A dictionary containing the objective name, units, and optimal value. -- `design_vars`: A dictionary of design variable names, units, and their optimal values. -- `constraints`: A dictionary of the constraint names, units, and their values at the optimal point. +- `message`: The driver-specific exit message. +- `model_evals`: The number of executions of model.solve_nonlinear() +- `deriv_evals`: The number of executions of compute_totals. `DriverResults` will contain an attribute/property `success` that is a boolean indicating whether the driver successfully ended. The meaning of this flag will vary from driver to driver (and optimizer to optmizer). For instance, SLSQP has a rather straight-forward success criteria, while SNOPT has multiple inform results that might indicate success. **Note: These changes are backwards incompatible and will impact anyone who is checking the return value of `run_driver`, since this object (as most Python objects), will evaluate to `True`. -4. OptimizationDrivers will support the notion of an `Autoscaler` that is called early in their `run`. The autoscaler will be set using `driver.set_autoscaler(AutoscalerClass())`. +### OptimizationDrivers will support the notion of an `Autoscaler` that is called early in their `run`. The autoscaler will be set using `driver.set_autoscaler(AutoscalerClass())`. -5. OpenMDAO will provide some default set of Autoscalers (discussed below), and allow users to implement their own. +### OpenMDAO will provide some default set of Autoscalers (discussed below), and allow users to implement their own. ## Changes to OptimizationDriver From 06dfb914fc35128a539b5f2c92117e90b125a605 Mon Sep 17 00:00:00 2001 From: Rob Falck Date: Thu, 16 May 2024 15:55:19 -0400 Subject: [PATCH 6/8] Update POEM_094.md --- POEM_094.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/POEM_094.md b/POEM_094.md index 021266cf..455e96f4 100644 --- a/POEM_094.md +++ b/POEM_094.md @@ -7,9 +7,9 @@ Associated implementation PR: N/A. Status: -- [x] Active +- [ ] Active - [ ] Requesting decision -- [ ] Accepted +- [x] Accepted - [ ] Rejected - [ ] Integrated @@ -53,7 +53,9 @@ This will include: - `success`: Flag that is `True` if the optimization was successful. - `message`: The driver-specific exit message. - `model_evals`: The number of executions of model.solve_nonlinear() +- `model_time`: Time spent evaluating model.solve_nonlinear() - `deriv_evals`: The number of executions of compute_totals. +- `deriv_time`: Time spent executing compute_totals. `DriverResults` will contain an attribute/property `success` that is a boolean indicating whether the driver successfully ended. The meaning of this flag will vary from driver to driver (and optimizer to optmizer). For instance, SLSQP has a rather straight-forward success criteria, while SNOPT has multiple inform results that might indicate success. From d3c2148fd2ac82c193057320fdbc05b98df0e14e Mon Sep 17 00:00:00 2001 From: Rob Falck Date: Sat, 20 Jul 2024 09:31:52 -0400 Subject: [PATCH 7/8] Update POEM_094.md --- POEM_094.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/POEM_094.md b/POEM_094.md index 455e96f4..60f51a6a 100644 --- a/POEM_094.md +++ b/POEM_094.md @@ -32,7 +32,7 @@ This POEM consists of a few thrusts. `AnalysisDriver` will support design exploration, but the notion of scaling doesn't apply here. -- AnalysisDriver will replace a deprecated DOEDriver. +- AnalysisDriver will allow changes to any variable, and potentially provide all inputs to `set_val` (for each name, provide an associated `val`, with optional `units`, and `indices`). - Different ways of providing run points to AnalysisDriver will dictate if it acts like a run-once driver, a DOE driver, a monte-carlo driver, etc. - AnalysisDriver will have a recorder attached by default. - Drivers will support `add_constraint`, `add_objective`, and `add_design_var` (just passed through to apply to the underlying model.) From efe33592b1472b0cfe1dc5e6e22f4dc730e55848 Mon Sep 17 00:00:00 2001 From: Rob Falck Date: Thu, 14 Aug 2025 09:37:36 -0400 Subject: [PATCH 8/8] Update POEM_094.md --- POEM_094.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/POEM_094.md b/POEM_094.md index 60f51a6a..45ed1db9 100644 --- a/POEM_094.md +++ b/POEM_094.md @@ -78,14 +78,18 @@ since this object (as most Python objects), will evaluate to `True`. ### New Methods -- `get_feasibility_tol` +- `_find_feasible` -Return the current feasibility tolerance for the optimizer. This provides a consistent optimizer-independent way of getting the feasibility tolerance for optimizers which support this concept. This is useful for scaling and potentially working out the active set of constraints from the OpenMDAO side of things. This can be obtained as option `Major Feasilibity Tol` from SNOPT or `constraint_viol_tol` from IPOPT, for instance. +Use a least-squares solver to minimize the constraint violations. I -- `get_lagrange_multipliers` +- `compute_lagrange_multipliers` Get the lagrange multipliers for optimizers which provide them, in an optimizer-independent way. This will be useful for evaluating post-optimality sensititivity. +- `compute_post_optimality_sensitivities` + +Provide the sensitivities/derivatives of the objective and design variable values _through_ the optimization. + ## Autoscale API Autoscaler will provide a `scale` method with a signaiture