Novel Distributed Optimization Method¶
While Tutorial > Problem Definition and Algorithm Execution shows how to use an existing coordination method to solve a distributed design optimization problem, this section is written for developers who want to extend the DDO framework with a novel distributed optimization method.
The framework is built around a single Unified Algorithmic Structure: every coordination method — ALC, ALADIN, SBDP, and Consensus ALC — is a specialization of it. Adding a new method therefore means providing method-specific classes that plug into the shared machinery, rather than writing the entire method from scratch. Further conceptual background is given in Framework Architecture > Core Components and Unified Algorithmic Structure.
Overview of the building blocks¶
A novel distributed optimization method, referred to as <Method> below, is assembled from the following method-specific classes (while some existing classes may be reused — see Section 6):
Distributed_Design_Optimizer/
├── middlelevel/ > shared interface storage exchanged between subsystems (Section 2)
│ └── <method>/
│ ├── MiddleLevelDataStorage<Method>
│ ├── MiddleLevelCoupling<Method>
│ └── InConsistencySize
├── subsystem/
│ ├── LocalSubSystem<Method> (Section 3)
│ ├── ControllerSubSystem<Method> (only if a controller exists) (Section 3)
│ └── couplingparameters/ (Section 2)
│ ├── CouplingParameters<Method>
│ └── LocalToController_CouplingParameters<Method> (only if a controller exists)
└── coordination/
├── convergence/ > convergence criteria (Section 4)
├── innerloop_iterationscheme/ > inner-loop scheduling (Section 4)
├── updatecouplingparametermethod/ > couplingparameter update rules (Section 4)
└── coordinationmethod/ > the entry point that wires everything together (Section 5)
└── <Method>
1. Casting the novel distributed optimization method into the Unified Algorithmic Structure¶
Before writing any code, the novel distributed optimization method is expressed in terms of the Unified Algorithmic Structure.
When casting the novel distributed optimization method into the unified structure, the following aspects specifically need to be considered, as each determines which of the later building blocks actually need to be defined:
- The coupling information \(u\) that is exchanged. The framework already supports mapped responses \({}^{j}_{i}\)\(H\), coupling variables \({}^{j}_{i}\)\(h\), shared design variables \({}^{i}_{j}\)\(z\), and target shared design variables. A novel distributed optimization method may additionally exchange penalty weights, Lagrange multipliers, or auxiliary variables etc. This drives Section 2.
- Whether each coupling is symmetric or asymmetric. A peer coupling between two subsystems is symmetric — both ends play the same role and exchange the same kind of information, so a single coupling class (subclassing
SubSysCouplingParametersBasis) and a single middlelevel coupling class (subclassingSubSysMiddleLevelCouplingBasis) serve both sides, as in ALC and Consensus ALC. A coupling to a controller may be asymmetric — the information a subsystem sends to the controller differs from what the controller returns, requiring separate classes per direction that are combined in one storage class, as in ALADIN (see Section 2). - Whether there is a controller problem. Methods such as ALADIN add a central controller; methods such as ALC do not. This decides whether controller-side classes are needed in Sections 2, 3 and 5.
- Where the coupling parameters are updated. In the inner loop, the outer loop, or both — this governs which update hooks are overridden in Section 3 and which update strategy is selected in Section 4.
2. Defining the middlelevel and couplingparameters classes¶
A coupling has two complementary representations. The middlelevel package implements the interface storage through which subsystems exchange coupling information across process boundaries, while the couplingparameters package holds the subsystem-local view of each coupling. Every subsystem keeps a list of CouplingParameters<Method> instances — one per pairwise coupling with a neighbor — and copies values to and from the shared MiddleLevelCoupling<Method>. This division of labor, and the copy mechanics that connect the two, are described in detail in Framework Architecture > Information Sharing via CouplingParameters and MiddleLevelDataStorage. The full inheritance structure of the classes subclassed below is shown on middlelevel and couplingparameters API reference pages.
For a novel distributed optimization method, a sub-package middlelevel/<method>/ is added, containing:
MiddleLevelDataStorage<Method>— the shared storage slot for one pairwise coupling, subclassingMiddleLevelDataStorageBasis. Its__init__populates a_couplingdatalist with oneMiddleLevelCoupling<Method>per side of the coupling.MiddleLevelCoupling<Method>— the container holding the actual values exchanged for one side of a symmetric peer coupling between two local subsystems, subclassingSubSysMiddleLevelCouplingBasis(in turn aMiddleLevelCouplingBasis). The base class already stores the four standard quantities (mapped responses, coupling variables, shared and target shared design variables).- If the method has a controller, the controller-side middlelevel coupling classes need to be defined. When the controller exchange is asymmetric (as in ALADIN), this splits into separate directional classes — a local-to-controller
LocalToController_MiddleLevelCouplingALADINand a controller-to-localControllerToLocal_MiddleLevelCouplingALADIN, both subclassingLocalController_MiddleLevelCouplingBasis(in turn aMiddleLevelCouplingBasis) — combined in oneLocalController_MiddleLevelDataStorageALADINstorage class, mirroring the directional couplingparameters classes below. InConsistencySize— declares the sizes of the inconsistency vectors for this method, subclassingInConsistencySizeBasis.
and, in the couplingparameters/<method>/ package, the following are defined:
CouplingParameters<Method>— the subsystem-local view of a symmetric peer coupling between two local subsystems, subclassingSubSysCouplingParametersBasis(in turn aCouplingParametersBasis/CouplingParametersInterface).- If the method has a controller, the controller-side coupling parameters need to be defined. When the controller exchange is asymmetric (as in ALADIN), this splits into separate directional classes — a local-side
LocalToController_CouplingParametersALADINfor what a subsystem sends to the controller, and a controller-sideControllerCouplingParametersALADIN(subclassingControllerCouplingParametersBasis) — mirroring the directional middlelevel classes above.
The four standard coupling quantities — mapped responses, coupling variables, shared design variables, and target shared design variables — are defined once in SubSysCouplingParametersBasis (and mirrored in SubSysMiddleLevelCouplingBasis), with every method-specific subclass only adding to this base rather than redeclaring them. These four quantities are exactly what is needed to express and post-process the inconsistencies between neighboring local subsystems (see Data Logging and Processing), so they are common to every coordination method; a novel distributed optimization method adds only the extra information (penalty weights, Lagrange multipliers, auxiliary or sensitivity quantities) that its own coordination scheme requires on top.
By convention the couplingparameters attributes mirror the naming of the matching MiddleLevelCoupling<Method> class. In addition, each CouplingParameters<Method> holds a set of _copy_* attributes that store the data pulled from the middle level (i.e. the neighbor's values). The mapping between the two is realized by subclassing CouplingParametersBasis with a method-specific class and implementing the two copy procedures:
CopyFromMiddleLevelCoupling(middlelevelcouplingIn)— read the neighbor's shared values out of the middle level into the local_copy_*attributes.CopyToMiddleLevelCoupling(middlelevelcouplingIn)— write this subsystem's own values into the middle level for the neighbor to read.
A key design point is that a CouplingParameters<Method> class may hold information that is never written to the shared middle level — it stays local to the subsystem but remains associated with a specific neighbor. What is and is not communicated is decided entirely by CopyToMiddleLevelCoupling() and CopyFromMiddleLevelCoupling().
The three existing methods illustrate how much a method's classes add beyond their basis parent, and whether that extra information is shared:
- ALC relies on a symmetric coupling and information exchange with its neighboring subsystems. Its
CouplingParametersALCextendsSubSysCouplingParametersBasiswith penalty weights and Lagrange multipliers, which are — however — not communicated to neighbors. Consequently itsMiddleLevelCouplingALCadds nothing beyondSubSysMiddleLevelCouplingBasis. - Consensus ALC relies on a symmetric coupling and information exchange with its neighboring subsystems. Its
CouplingParametersConsensusALCextendsSubSysCouplingParametersBasiswith several quantities, some of which are communicated to neighbors. Consequently, itsCouplingParametersConsensusALCholds corresponding_copy_attributes and itsMiddleLevelCouplingConsensusALCextendsSubSysMiddleLevelCouplingBasiswith the same quantities. - ALADIN relies on an asymmetric coupling and information exchange with the controller subsystems, so a single class cannot represent both ends: a subsystem-to-controller class packages the sensitivity data sent to the controller (
LocalToController_CouplingParametersALADIN/LocalToController_MiddleLevelCouplingALADIN), while a controller-to-subsystem class returns the coordination step (ControllerCouplingParametersALADIN/ControllerToLocal_MiddleLevelCouplingALADIN). Both directions are combined in one storage class (LocalController_MiddleLevelDataStorageALADIN). Crucially, ALADIN still also usesSubSysCouplingParametersBasisandSubSysMiddleLevelCouplingBasisfor coupling with neighboring local subsystems. This is not necessary for the coordination method itself, but enables computing and post-processing the inconsistencies between neighboring local subsystems (see Data Logging and Processing). A novel distributed optimization method should therefore keep these peer middlelevel and couplingparameters classes even when — like ALADIN — its algorithm does not otherwise communicate between local subsystems.
3. Defining the coordination-specific subsystem classes¶
The subsystem package represents an individual processing unit. For a novel distributed optimization method, the following are defined:
-
LocalSubSystem<Method>, subclassingLocalSubSystemBasis(in turn aSubSystemBasis/SubSystemInterface). Its__init__must set the attributes that the base classes leave uninitialized (marked to be initialized in child-class in the base__init__):_couplingparameters— the list ofCouplingParameters<Method>instances (one per neighbor) defined in Section 2. This must be assigned before callingsuper().__init__(), because the base initialization of the optimization data already relies on it._inconsistencies— a list ofInConsistencySize(one per coupling parameter), assigned aftersuper().__init__()._local_convergenceindicator_innerloopand_local_convergenceindicator_outerloop— the inner- and outer-loop convergence indicators, taken from the__init__arguments (see Section 4)._updatecouplingparametermethod_outerloop— the outer-loop coupling-parameter update method, also taken from the__init__arguments (see Section 4).
It is furthermore strongly recommended that the
__init__also declares compatibility lists and validates the inputs using a newly definedvalidate_inputs().self.validate_inputs()is called as the last statement of__init__, once all the attributes it inspects are set.It must also implement the two methods that
LocalSubSystemBasisleaves abstract — and that only local subsystems provide — namelyreturn_initialized_Inconsistencies()andappend_Controller(). -
If the method utilizes a single controller, additionally a
ControllerSubSystem<Method>subclassingControllerSubSystemBasis— as done byControllerSubSystemALADIN.
Furthermore the LocalSubSystem<Method> and ControllerSubSystem<Method> (if existing) need to define the following methods, each of which implements a specific step of Algorithm 5 and of the per-subsystem solve run_IterativeOptimization():
prepare_OptimizationProblem()andpostprocess_Optimization()— the "Prepare optimization problem formulation" and "Post-process the optimization" steps that bracket theargminin Algorithm 5.prepare_OptimizationProblem()assembles the concrete problem to be solved from the coupling parameters just read from the middle level and other necessary information available in theLocalSubSystem<Method>orControllerSubSystem<Method>.evaluateCoordinationObjective(),evaluateCoordinationEqualityConstraint(),evaluateCoordinationInequalityConstraint()— the \({}^{i}\)\(P\) and \({}^{i}\)\(Q\) terms of Algorithm 5. These are the coordination-specific additions thatrun_IterativeOptimization()combines with the local objective \({}^{i}\)\(v_f\) and constraints \({}^{i}\)\(v_g\) & \({}^{i}\)\(v_h\) (in case ofLocalSubSystem<Method>);ControllerSubSystem<Method>only has coordination-related terms.evaluate_Inconsistencies()(only forLocalSubSystem<Method>, notControllerSubSystem<Method>) — computes the inconsistency \(^{i}_{j}\)\(c\) and \(^{j}_{i}\)\(c\) and other related quantities.mapToController()(only relevant when the method has a controller) — in aLocalSubSystem<Method>, it is implemented to map the subsystem's own information into the local-to-controller coupling parameters \({}^{i}_{C}\)\(u\) sent to the controller (see Section 2). If the method has no controller, it is left a no-op (pass). AControllerSubSystem<Method>never maps to itself, so it does not need to implement this at all — the required no-op is already inherited fromControllerSubSystemBasis.initializeCouplingParameters_before_CopyToMiddleLevel(),initializeCouplingParameters_after_CopyFromMiddleLevel(),initializeCouplingParameters_after_Second_CopyFromMiddleLevel()— populate the coupling-parameter containers at start-up so that the very first exchange through the middle level is well-defined (the "initial relevant coupling parameters in interface storage" required by Algorithm 5).prepare_updateCouplingParameters(),updateCouplingParameters_innerLoop(),updateCouplingParameters_outerLoop()— the "Update relevant coupling parameters" steps of Algorithm 5. They advance the method's relevant coupling parameters (e.g. penalty weights and Lagrange multipliers). Which of these are implemented is dictated by where the coupling parameters are updated (Section 1).
The parent classes SubSystemBasis, LocalSubSystemBasis, and ControllerSubSystemBasis already provide standardized attributes and methods to realize the following functionalities:
- History saving and data logging —
appendtohistory(),savesubsystemhistory(). See Data Logging. - Derivative information — finite-difference Jacobians as well as gradients, Jacobians, and Hessians of the local/total objective and constraints. See Derivative Computation.
- Lagrange multipliers of the local and coordination constraints —
compute_KKT_system_matrix_and_bounds(),compute_KKT_multipliers(),compute_ApproximateKKT_multipliers(),decompose_KKT_multipliers(). See SubSystem Optimization > Multiplier reconstruction when the solver does not provide them. - Iterative optimization — the full local solve loop
run_IterativeOptimization(),runAnalysis(),mapToCouplingParameters(), and total-objective/constraint evaluation. See SubSystem Optimization.
This is why a new method only needs to supply the coordination-specific methods above.
4. Reusing or defining convergence, iteration scheme, and update classes¶
Three coordination/ sub-packages provide interchangeable strategies for convergence criteria, innerloop iteration schemes and updating penalty weights or multipliers etc. In most cases an existing class can be reused and simply passed to the coordination method in Section 5. A bespoke class is only defined when the novel distributed optimization method genuinely needs new behavior, in which case the relevant interface is subclassed, following the existing sister classes.
convergence/— inner- and outer-loop convergence criteria. New criteria implement the factoryConvergenceIndicator_Innerloop_Interface/ConvergenceIndicator_Outerloop_Interface(methodscreateLocalConvergenceIndicator(),createCentralizedConvergenceIndicator(),validate_inputs(),print_startup_summary(),print_termination_summary()).innerloop_iterationscheme/— how executions of local subsystems (and a controller) are scheduled within the inner loop. New schemes implementIterationSchemeInterface(run_innerloop_jobs_multiprocessing(), theprint_*hooks). Existing schemes include variousParallel*andSequential*strategies.updatecouplingparametermethod/— how penalty weights and Lagrange multipliers are initialized and updated. New update rules implementUpdateCouplingParameterMethodInterface(validate_inputs(),update_state(), theprint_*hooks). Existing rules includeUpdateCouplingParameterMethod_AdaptiveWeights,_AugLagMultipliersAdaptiveWeights,_AugLagMultipliersFixedWeights,_SubgradientMultipliers,_NoOp, and others.
5. Implementing the coordination method entry point¶
Finally, everything is tied together in the coordinationmethod package with a <Method> class subclassing CoordinationMethodBasis and implementing CoordinationMethodInterface. This is the class the user selects as _coordinationmethod in their InputFile (see Tutorial > Problem Definition and Algorithm Execution).
Its constructor receives all method-specific hyperparameters available for the user to choose. This may include some strategies chosen in Section 4.
Interface methods to implement (see CoordinationMethodInterface):
validate_inputs(), together withget_IterationScheme(),get_AllowedIterationSchemes(),get_RecommendedIterationSchemes(), andget_UnrecommendedIterationSchemes(). This mirrors the_allowed*/_recommended*validation pattern recommended forLocalSubSystem<Method>in Section 3, applied at coordination-method level for method specific hyperparameters chosen by the user in theirInputFile.createSubSystems(id_list, level_list, neighborid_list, ...)— build the list ofLocalSubSystem<Method>instances (Section 3), one per subsystem, wiring in theirCouplingParameters<Method>(Section 2) and the convergence/update strategies of Section 4.createControllerSubSystem(subsystemsIn)— build theControllerSubSystem<Method>that coordinates the local subsystems, or returnNonewhen the method has no controller (Section 1).createMiddleLevel(idparent, idchild, multiprocessing_lock)andcreateControllerMiddleLevel(...)— build the sharedMiddleLevelDataStorage<Method>interface storage (Section 2) for information exchange among local subsystems and with the controller (if existing). The latter returnsNonewhen there is no controller.get_Convergence_Indicator_Innerloop(),get_Convergence_Indicator_Outerloop()— return the inner- and outer-loop convergence indicator factories chosen in Section 4.centralized_prepare_updateCouplingParameters(subsystemsIn)— perform any method-specific centralized operation on the coupling parameters after the inner loop completes and before the decentralized "Update relevant coupling parameters" hooks of Section 3; leave it a no-op for fully decentralized methods.print_startup_summary(),print_termination_summary(), andprint_beginning_of_centralized_prepare_updateCouplingParameters()— print the method configuration at start-up and termination, and a banner before the centralized preparation step.
6. Reusing an existing method without the full tutorial¶
Not every novel distributed optimization method requires all building blocks mentioned throughout this tutorial. Because the framework is composed of interchangeable parts, many variations reuse most of an existing method and only replace a single component:
For example, to run standard ALC with a different scheme for updating the multipliers and weights, or with a different inner-/outer-loop convergence criterion, only those classes in Section 4 are defined and passed to the existing ALC coordination method and LocalSubSystemALC — everything else remains untouched.