Metadata-Version: 2.4
Name: alcoord
Version: 0.1.0
Summary: A Python implementation of Augmented Lagrangian Coordination for decomposition-based and multidisciplinary design optimization
License-Expression: MIT
License-File: LICENSE
Author: Masafumi Isaji
Author-email: masafumiisaji@outlook.com
Requires-Python: >=3.11.2, <4.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Provides-Extra: legacy
Requires-Dist: icecream (>=2.1.3,<3.0.0)
Requires-Dist: numpy (>=1.24,<2.0)
Requires-Dist: pandas (>=2.0,<3.0)
Requires-Dist: pygmo (>=2.19.5,<3.0.0) ; extra == "legacy"
Requires-Dist: pygmo-plugins-nonfree (>=0.24,<0.30) ; extra == "legacy"
Requires-Dist: pyomo (>=6.8,<7.0)
Requires-Dist: requests (>=2.31.0,<3.0.0)
Requires-Dist: scipy (>=1,<2)
Project-URL: Issues, https://github.com/masaisaji/alcoord/issues
Project-URL: Repository, https://github.com/masaisaji/alcoord
Description-Content-Type: text/markdown

Python implementation of Augmented Lagrangian Coordination framework for decomposition-based and multidisciplinary design optimization

# Installation

## PyPI Installation

The package can easily be installed from PyPI:

```sh
pip install alcoord
```

## Optimization Solver Installation

Depending on the optimization models you solve, you may need external optimization solvers such as Gurobi, BARON, CPLEX, SCIP, and IPOPT.
Optimization problems are modeled using [`Pyomo`](https://pyomo.readthedocs.io/en/stable/index.html), so solver availability depends on your environment and Pyomo configuration.
To see available solvers, run:

```sh
pyomo help --solvers
```

Note that solvers need to be visible to Pyomo in addition to being installed system-wide, which may require configuring environment variables.

## Development Installation

This project uses [`poetry`](https://python-poetry.org/docs/#installation) for development.
If you want to work on the source code, you can install from source (e.g., with `conda`):

```sh
git clone https://github.com/masaisaji/alcoord.git
cd alcoord
conda create -n alcoord python=3.11
conda activate alcoord
poetry install
```

If `virtualenv` or similar is preferred, run `poetry install` in the newly created virtual environment with Python version 3.11 or later.
During development, it is recommended to run tests to ensure that the code works correctly after modifications:

```sh
poetry run pytest
```

# Usage Pattern

The following skeleton illustrates the expected structure.
Let's say you have the following problem you want to decompose

$$
\begin{aligned}
\min & f_1(x_1,x_2,x_3) + f_2(x_2,x_3,x_4,x_5)\\
\text{s.t.} & g_1(x_1, x_2, x_3) \leq 0\\
& g_{21}(x_2, x_5) \leq 0 \\
& g_{22}(x_3, x_4) \leq 0
\end{aligned}
$$

into two subproblems such as

$$
\begin{aligned}
\min & f_1(x_1,x_2,x_3) + \phi(x_1,x_2,x_3)\\
\text{s.t.} & g_1(x_1, x_2, x_3) \leq 0
\end{aligned}
$$
$$
\begin{aligned}
\min & f_2(x_2,x_3,x_4,x_5) + \phi(x_2,x_3,x_4,x_5)\\
\text{s.t.} & g_{21}(x_2, x_5) \leq 0 \\
& g_{22}(x_3, x_4) \leq 0
\end{aligned}
$$

where $\phi$ is the augmented Lagrangian term for the corresponding subproblem.
To solve such problems with the augmented Lagrangian coordination method implemented in this codebase, you need:

- a method that builds and solves each subproblem modeled with `pyomo`
- an initial guess (for nonlinear programming (NLP) subproblems)

## High-Level Flow and a Skeleton Code

Assuming that you already have the subproblem solve methods and an initial guess, the overall usage flow is as follows

1. Create a "dependency matrix" with 1/0 or True/False. For example, the dependency of subproblem 1 above would be `[1, 1, 1, 0, 0]` since it depends on $x_1, x_2, x_3$ but not on $x_4, x_5$.
2. In a `dict`, specify the following for each subproblem:
   - the optimization type ("MIP" or "NLP"),
   - the function that returns the subproblem solve result (to be detailed later), and
   - optional arguments needed for the solve function
3. Build instances of the `alcoord` classes (`DimensionConverter`, `InnerLoop`, and `OuterLoop`)
4. Run the algorithm and retrieve the results

A skeleton script for the above example:

```py
from alcoord import (
    InnerLoop,
    OuterLoop,
    DimensionConverter,
    AllSubpDict,
)
import numpy as np
from example_subproblem import solve_subproblem_1, solve_subproblem_2

initial_guess: np.ndarray = np.array(
    [
        # define initial guess here
    ]
)

# Indicate subproblem's variable dependency with 1/0 or True/False
depen_matrix: list[list[int]] = [
    [1, 1, 1, 0, 0],
    [0, 1, 1, 1, 1],
]
dim_all_var = 5

# Create a dict including:
# the optimization type ("MIP" or "NLP"),
# function returning subproblem solve results,
# and optional arguments
# for each subproblem
all_subprob_dict: AllSubpDict = {
    0: {"optim type": "NLP", "function": solve_subproblem_1, "args": None},
    1: {"optim type": "NLP", "function": solve_subproblem_2, "args": None},
}

# Build necessary objects/instances
# See docs of each class for additional parameter settings
dim_conv = DimensionConverter(
    dependency_matrix=depen_matrix,
    dim_all_var=dim_all_var,
)
inner_loop = InnerLoop(
    dc=dim_conv,
    all_subprob_dict=all_subprob_dict,
    initial_guess=initial_guess,
)
outer_loop = OuterLoop(
    inner_loop=inner_loop,
)

# Run the algorithm/routine and retrieve results
res = outer_loop.run()
obj = res["objectives"]
var = res["design vars"]
```

## Subproblem Solve Function

A function that solves a subproblem and returns its solution is expected to take the following arguments:

- `local_shared_var_target`
- `lagrange_est`
- `penalty_weight`
- `local_var_idx`
- `local_shared_var_idx`
- `args`
- `initial_guess`

Except for `args` and `initial_guess`, these arguments are mostly used to reflect the subproblem's relation with the other subproblems, which is captured through the augmented Lagrangian terms in the objective.
The augmented Lagrangian term can be obtained using a helper function `get_aug_lag_term_for_pyomo`, which returns a Pyomo expression you can use directly when defining the objective.

Once the subproblem optimization model is built and solved, the function is expected to return a `SubproblemResult` object, which is a simple `dict` with `"objective"` and `"design var"` keys.

A skeleton script for the above example is as follows:

```py
from alcoord import (
    SubproblemResult,
    get_aug_lag_term_for_pyomo,
)
import pyomo.environ as pyo
import numpy as np


def solve_subproblem_1(
    local_shared_var_target: np.ndarray,
    lagrange_est: np.ndarray,
    penalty_weight: np.ndarray,
    local_var_idx: list[int],
    local_shared_var_idx: list[int],
    args=None,
    initial_guess=None,
) -> SubproblemResult:
    model = pyo.ConcreteModel()
    #
    # define indices, variables, constraints, etc
    #

    penalty_term = get_aug_lag_term_for_pyomo(
        local_shared_var_target=local_shared_var_target,
        lagrange_est=lagrange_est,
        penalty_weight=penalty_weight,
        pyomo_local_shared_var_ls=[
            model.x2, model.x3
        ],
    )
    original_objective = ... # objective expression w/o penalty
    model.obj = pyo.Objective(
        expr=original_objective + penalty_term
    )

    pyo.SolverFactory("ipopt").solve(model, tee=False)
    obj = float(pyo.value(model.obj))
    design_var = np.array(
        [
            # extract design variable values...
        ]
    )

    subp_res: SubproblemResult = {"objective": obj, "design var": design_var}
    return subp_res
```

# Module Dependencies Map

Note: $\fbox{calling module} \rightarrow \fbox{called module}$

```mermaid
graph TD
    inner_loop.py --> dimension_converter.py
    inner_loop.py --> subproblems.py
    inner_loop.py --> type_defs.py
    subproblems.py --> type_defs.py
    outer_loop.py --> inner_loop.py
    outer_loop.py --> type_defs.py
```

# Legacy version for Linux

On Linux, the legacy version lets you use `pygmo` and its compatible solvers for continuous nonlinear programming (NLP) problems.
To install the dependency for this legacy version, run

```sh
poetry install -E legacy
```

for development, or

```sh
pip install "alcoord[legacy]"
```

for PyPI installation.

A separate set of tests for the legacy version can be run with:

```sh
poetry run pytest -m legacy
```

This version is unsupported on other platforms.

# References

- [Augmented Lagrangian coordination for distributed optimal design in MDO](http://dx.doi.org/10.1002/nme.2158)
- [MDO Approach to Integrated Space Mission Planning and Spacecraft Design](http://dx.doi.org/10.2514/1.A35284)

# Acknowledgment

This material is based upon work supported by the National Science Foundation under Award No. 1942559.
Any opinions, findings and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.

