Skip to content

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 (subclassing SubSysMiddleLevelCouplingBasis) 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:

and, in the couplingparameters/<method>/ package, the following are defined:

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:

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>, subclassing LocalSubSystemBasis (in turn a SubSystemBasis / 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 of CouplingParameters<Method> instances (one per neighbor) defined in Section 2. This must be assigned before calling super().__init__(), because the base initialization of the optimization data already relies on it.
    • _inconsistencies — a list of InConsistencySize (one per coupling parameter), assigned after super().__init__().
    • _local_convergenceindicator_innerloop and _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 defined validate_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 LocalSubSystemBasis leaves abstract — and that only local subsystems provide — namely return_initialized_Inconsistencies() and append_Controller().

  • If the method utilizes a single controller, additionally a ControllerSubSystem<Method> subclassing ControllerSubSystemBasis — as done by ControllerSubSystemALADIN.

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() and postprocess_Optimization() — the "Prepare optimization problem formulation" and "Post-process the optimization" steps that bracket the argmin in 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 the LocalSubSystem<Method> or ControllerSubSystem<Method>.
  • evaluateCoordinationObjective(), evaluateCoordinationEqualityConstraint(), evaluateCoordinationInequalityConstraint() — the \({}^{i}\)\(P\) and \({}^{i}\)\(Q\) terms of Algorithm 5. These are the coordination-specific additions that run_IterativeOptimization() combines with the local objective \({}^{i}\)\(v_f\) and constraints \({}^{i}\)\(v_g\) & \({}^{i}\)\(v_h\) (in case of LocalSubSystem<Method>); ControllerSubSystem<Method> only has coordination-related terms.
  • evaluate_Inconsistencies() (only for LocalSubSystem<Method>, not ControllerSubSystem<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 a LocalSubSystem<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). A ControllerSubSystem<Method> never maps to itself, so it does not need to implement this at all — the required no-op is already inherited from ControllerSubSystemBasis.
  • 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 factory ConvergenceIndicator_Innerloop_Interface / ConvergenceIndicator_Outerloop_Interface (methods createLocalConvergenceIndicator(), 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 implement IterationSchemeInterface (run_innerloop_jobs_multiprocessing(), the print_* hooks). Existing schemes include various Parallel* and Sequential* strategies.
  • updatecouplingparametermethod/ — how penalty weights and Lagrange multipliers are initialized and updated. New update rules implement UpdateCouplingParameterMethodInterface (validate_inputs(), update_state(), the print_* hooks). Existing rules include UpdateCouplingParameterMethod_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 with get_IterationScheme(), get_AllowedIterationSchemes(), get_RecommendedIterationSchemes(), and get_UnrecommendedIterationSchemes(). This mirrors the _allowed*/_recommended* validation pattern recommended for LocalSubSystem<Method> in Section 3, applied at coordination-method level for method specific hyperparameters chosen by the user in their InputFile.
  • createSubSystems(id_list, level_list, neighborid_list, ...) — build the list of LocalSubSystem<Method> instances (Section 3), one per subsystem, wiring in their CouplingParameters<Method> (Section 2) and the convergence/update strategies of Section 4.
  • createControllerSubSystem(subsystemsIn) — build the ControllerSubSystem<Method> that coordinates the local subsystems, or return None when the method has no controller (Section 1).
  • createMiddleLevel(idparent, idchild, multiprocessing_lock) and createControllerMiddleLevel(...) — build the shared MiddleLevelDataStorage<Method> interface storage (Section 2) for information exchange among local subsystems and with the controller (if existing). The latter returns None when 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(), and print_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.