|
13 | 13 | "- Assembly of a *System* with specific instances of *Analysis* classes\n", |
14 | 14 | "- Formulating an optimization problem with a *Flume* optimizer interface and executing the optimization script\n", |
15 | 15 | "\n", |
16 | | - "To address these points, this notebook will provide sections of code with accompanying descriptions regarding the use of the various base classes and how to set up an optimization problem.\n", |
| 16 | + "To address these points, this notebook will provide sections of code with accompanying descriptions regarding the use of the primary classes and how to set up an optimization problem.\n", |
17 | 17 | "\n", |
18 | 18 | "## Framework Architecture\n", |
19 | 19 | "\n", |
20 | | - "*Flume* defines three base classes, which are the key aspects that define the framework's architecture. A brief summary of each is provided below. \n", |
| 20 | + "*Flume* defines three primary classes, which are the key aspects that define the framework's architecture. A brief summary of each is provided below. \n", |
21 | 21 | "\n", |
22 | | - "- *State*: The base class that is used to wrap variables and outputs. Each state object comprises a numeric value, a string description, a derivative value, and source information. The numeric value can be a float or NumPy array, and the type and shape information are ascertained from the provided value. The source information denotes the *Analysis* object that the *State* data is associated with. When creating a *State*, the user should provide the value `value`, description `desc`, and source `source` information. The source is always set to be `self`, which links the *State* to a specific *Analysis* object. The derivative value `deriv` is updated within the adjoint analysis and is not provided when the *State* is created.\n", |
23 | | - "- *Analysis*: The base class that defines the structure for analysis disciplines within the framework. It establishes a set of common methods, such as setting variable values, extracting outputs, and adjoint tests, that are utilized to define the structure of the framework. Internally, this also establishes the connections between *Analysis* classes that denote a transfer of information. All classes that inherit from the *Analysis* base class must have arguments for the object's unique name `obj_name`, a list of sub-analyses `sub_analyses`, and keyword arguments. This will be explained in context below for the Rosenbrock example.\n", |
24 | | - "- *System*: The base class that wraps multiple *Analysis* objects into a system, which can then be utilized to perform optimization using one of *Flume*'s optimizer interfaces. When constructing a *System* object, the user must provide a name for the system `sys_name` and define a list of top-level *Analysis* objects `top_level_analysis_list`. This list contains the *Analysis* objects that define output *State* objects that will be used for defining the objective and constraint functions that characterize the optimization problem. The user can optionally provide arguments for `log_name` and `log_prefix`, which specify the file name to use for the log file and the output directory for file management.\n", |
| 22 | + "- *State*: The class that is used to wrap variables and outputs. Each state object comprises a numeric value, a string description, a derivative value, and source information. The numeric value can be a float or NumPy array, and the type and shape information are ascertained from the provided value. The source information denotes the *Analysis* object that the *State* data is associated with. When creating a *State*, the user should provide the value `value`, description `desc`, and source `source` information. The source is always set to be `self`, which links the *State* to a specific *Analysis* object. The derivative value `deriv` is updated within the adjoint analysis and is not provided when the *State* is created.\n", |
| 23 | + "- *Analysis*: The abstract base class that defines the structure for analysis disciplines within the framework. It establishes a set of common methods, such as setting variable values, extracting outputs, and adjoint tests, that are utilized to define the structure of the framework. Internally, this also establishes the connections between *Analysis* classes that denote a transfer of information. All classes that inherit from the *Analysis* base class must have arguments for the object's unique name `obj_name`, a list of sub-analyses `sub_analyses`, and keyword arguments. This will be explained in context below for the Rosenbrock example.\n", |
| 24 | + "- *System*: The class that serves as a container to specify an analysis sequence using *Analysis* objects, which can then be utilized to perform optimization using one of *Flume*'s optimizer interfaces. When constructing a *System* object, the user must provide a name for the system `sys_name` and define a list of top-level *Analysis* objects `top_level_analysis_list`. This list contains the *Analysis* objects that define output *State* objects that will be used for defining the objective and constraint functions that characterize the optimization problem. The user can optionally provide arguments for `log_name` and `log_prefix`, which specify the file name to use for the log file and the output directory for file management.\n", |
25 | 25 | "\n", |
26 | 26 | "The structure of *Flume* is visualized in the image below, which depicts an abstracted *System* that encapsulates four distinct _Analysis_ objects.\n", |
27 | 27 | "Arrows that link _Analysis_ objects denote _State_ objects that connect outputs of one discipline to variables of another.\n", |
28 | 28 | "_Analysis_ objects that are outlined in red and are labeled with \"Top-level **_Analysis_** Object\" are those that define output _States_ which are utilized for optimization.\n", |
29 | 29 | "Thus, the arrows that extend beyond the _System_ boundary are _States_ that define design variables, the objective function, or constraint functions for an optimization problem that is wrapped within the framework.\n", |
30 | 30 | "\n", |
31 | | - "\n", |
| 31 | + "\n", |
32 | 32 | "\n", |
33 | 33 | "## Framework Nomenclature\n", |
34 | 34 | "Before including any code, it is important to clarify the nomenclature that is used within the framework. All *Analysis* classes contain at least one variable and output, and they may optionally include parameters. In the context of *Flume*, these are defined as follows.\n", |
|
91 | 91 | " # Compute the value of the Rosenbrock function\n", |
92 | 92 | " f = (a - x) ** 2 + b * (y - x**2) ** 2\n", |
93 | 93 | "\n", |
94 | | - " # Update the analyzed attribute\n", |
95 | | - " self.analyzed = True\n", |
96 | | - "\n", |
97 | 94 | " # Store the outputs\n", |
98 | 95 | " self.outputs = {}\n", |
99 | 96 | "\n", |
|
125 | 122 | " # Compute yb\n", |
126 | 123 | " yb += (2 * b * (y - x**2)) * fb\n", |
127 | 124 | "\n", |
128 | | - " # Update the analyzed adjoint attribute\n", |
129 | | - " self.adjoint_analyzed = True\n", |
130 | | - "\n", |
131 | 125 | " # Assign the derivative values\n", |
132 | 126 | " self.variables[\"x\"].set_deriv_value(deriv_val=xb)\n", |
133 | 127 | " self.variables[\"y\"].set_deriv_value(deriv_val=yb)\n", |
|
173 | 167 | "source": [ |
174 | 168 | "### `_analyze` Method\n", |
175 | 169 | "\n", |
176 | | - "The primary intent of this method is to compute the outputs of interest using the input values for the parameters and variables. To do so, the variable values and parameters are extracted with\n", |
| 170 | + "For the _Analysis_ base class, the \"Template Method\" behavioral design pattern is used (see _Design Patterns: Elements of Reusable Object-Oriented Software_ by Gamma et al. for additional details). This means that the user is responsible for defining the private, helper functions, such as `_analyze`, that define the internal hooks used by other methods defined in the _Analysis_ base class. For the `_analyze` method, its intent is to compute the outputs of interest using the input values for the parameters and variables. To do so, the variable values and parameters are extracted with\n", |
177 | 171 | "```python\n", |
178 | 172 | "# Extract the variable values\n", |
179 | 173 | "x = self.variables[\"x\"].value\n", |
|
187 | 181 | "```python\n", |
188 | 182 | "f = (a - x) ** 2 + b * (y - x**2) ** 2\n", |
189 | 183 | "```\n", |
190 | | - "After the outputs are computed, the user should update the `analyzed` attribute to denote that computations have concluded for the current *Analysis*. \n", |
191 | | - "```python\n", |
192 | | - "self.analyzed = True\n", |
193 | | - "```\n", |
194 | | - "This is used internally to avoid recomputing data if an *Analysis* appears multiple times within a *System* before variable values are updated. Then, the outputs are stored within the `outputs` dictionary, where the values are again wrapped in *State* objects.\n", |
| 184 | + "Then, the outputs are stored within the `outputs` dictionary, where the values are again wrapped in *State* objects.\n", |
195 | 185 | "```python\n", |
196 | 186 | "# Store the outputs\n", |
197 | 187 | "self.outputs = {}\n", |
|
236 | 226 | "# Compute contributions to yb\n", |
237 | 227 | "yb += (2 * b * (y - x**2)) * fb\n", |
238 | 228 | "```\n", |
239 | | - "After computing the derivatives, the user should update the `adjoint_analyzed` attribute to denote that computations for the derivatives have concluded, similar to the behavior for the `_analyze` method.\n", |
240 | | - "```python\n", |
241 | | - "# Update the analyzed adjoint attribute\n", |
242 | | - "self.adjoint_analyzed = True\n", |
243 | | - "```\n", |
244 | | - "Finally, the derivative values are assigned to the *State* objects stored within the `variables` dictionary, which ensures that these quantities are stored before concluding the current `_analyze_adjoint` method.\n", |
| 229 | + "After computing the derivatives the derivative values are assigned to the *State* objects stored within the `variables` dictionary, which ensures that these quantities are stored before concluding the current `_analyze_adjoint` method.\n", |
245 | 230 | "```python\n", |
246 | 231 | "# Assign the derivative values\n", |
247 | 232 | "self.variables[\"x\"].set_deriv_value(deriv_val=xb)\n", |
|
383 | 368 | " x_dv = self.variables[\"x_dv\"].value\n", |
384 | 369 | " y_dv = self.variables[\"y_dv\"].value\n", |
385 | 370 | "\n", |
386 | | - " # Update the analyzed attribute\n", |
387 | | - " self.analyzed = True\n", |
388 | | - "\n", |
389 | 371 | " # Store the outputs in the outputs dictionary\n", |
390 | 372 | " self.outputs = {}\n", |
391 | 373 | "\n", |
|
412 | 394 | " x_dvb += xb\n", |
413 | 395 | " y_dvb += yb\n", |
414 | 396 | "\n", |
415 | | - " # Update the analyzed adjoint attribute\n", |
416 | | - " self.adjoint_analyzed = True\n", |
417 | | - "\n", |
418 | 397 | " # Set the derivative values\n", |
419 | 398 | " self.variables[\"x_dv\"].set_deriv_value(deriv_val=x_dvb)\n", |
420 | 399 | "\n", |
|
526 | 505 | " # Compute the value of the constraint\n", |
527 | 506 | " g = x**2 + y**2\n", |
528 | 507 | "\n", |
529 | | - " # Update the analyzed attribute\n", |
530 | | - " self.analyzed = True\n", |
531 | | - "\n", |
532 | 508 | " # Store the outputs in the outputs dictionary\n", |
533 | 509 | " self.outputs = {}\n", |
534 | 510 | "\n", |
|
560 | 536 | " xb += gb * 2 * x\n", |
561 | 537 | " yb += gb * 2 * y\n", |
562 | 538 | "\n", |
563 | | - " # Update the analyzed adjoint attribute\n", |
564 | | - " self.adjoint_analyzed = True\n", |
565 | | - "\n", |
566 | 539 | " # Assign the derivative values\n", |
567 | 540 | " self.variables[\"x\"].set_deriv_value(deriv_val=xb)\n", |
568 | 541 | " self.variables[\"y\"].set_deriv_value(deriv_val=yb)\n", |
|
695 | 668 | " message: Optimization terminated successfully\n", |
696 | 669 | " success: True\n", |
697 | 670 | " status: 0\n", |
698 | | - " fun: 0.04567480871937527\n", |
| 671 | + " fun: 0.04567480871950009\n", |
699 | 672 | " x: [ 7.864e-01 6.177e-01]\n", |
700 | | - " nit: 9\n", |
| 673 | + " nit: 18\n", |
701 | 674 | " jac: [-1.911e-01 -1.501e-01]\n", |
702 | | - " nfev: 12\n", |
703 | | - " njev: 9\n" |
| 675 | + " nfev: 26\n", |
| 676 | + " njev: 18\n" |
704 | 677 | ] |
705 | 678 | }, |
706 | 679 | { |
|
798 | 771 | ], |
799 | 772 | "metadata": { |
800 | 773 | "kernelspec": { |
801 | | - "display_name": "venv", |
| 774 | + "display_name": "venv (3.10.8)", |
802 | 775 | "language": "python", |
803 | 776 | "name": "python3" |
804 | 777 | }, |
|
0 commit comments