The Isoperimetric Problem

For a description of the isoperimetric problem, see the JupyterLab notebook documentation for this problem.

The python script in this example can be executed from the command line with:

$ python -m yapss.examples.isoperimetric

Functions

YAPSS solution of the isoperimetric problem.

main() None[source]

Demonstrate the solution to the isoperimetric problem.

plot_solution(solution: Solution) None[source]

Plot the solution to the isoperimetric problem.

The collocation points are shown as dots, and the curve is interpolated between points with a cubic spline.

Parameters:
solution: Solution

The solution to the isoperimetric problem.

setup() Problem[source]

Set up the isoperimetric optimization problem.

Returns:
Problem

The isoperimetric optimization problem.

Code

"""

YAPSS solution of the isoperimetric problem.

"""

__all__ = ["main", "plot_solution", "setup"]

# standard library imports
import math

# package imports
import numpy as np

# third party imports
from matplotlib import pyplot as plt
from scipy.interpolate import interp1d

from yapss import ContinuousArg, DiscreteArg, ObjectiveArg, Problem, Solution


def setup() -> Problem:
    """Set up the isoperimetric optimization problem.

    Returns
    -------
    Problem
        The isoperimetric optimization problem.
    """
    # problem has 1 phase, with 2 states, 2 controls, 1 path constraint, and 3
    # integrals. There are four constraints: 2 to constrain the curved to be closed,
    # and 2 to constrain the centroid of the curve to be at the origin.

    ocp = Problem(name="Isoperimetric Problem", nx=[2], nu=[2], nq=[3], nh=[1], nd=4)

    def objective(arg: ObjectiveArg) -> None:
        """Objective callback function."""
        arg.objective = arg.phase[0].integral[0]

    def continuous(arg: ContinuousArg) -> None:
        """Continuous callback function."""
        x, y = arg.phase[0].state
        ux, uy = arg.phase[0].control
        arg.phase[0].dynamics[:] = ux, uy
        arg.phase[0].path[0] = ux**2 + uy**2
        arg.phase[0].integrand[0] = (y * ux - x * uy) / 2
        arg.phase[0].integrand[1] = x
        arg.phase[0].integrand[2] = y

    def discrete(arg: DiscreteArg) -> None:
        """Discrete callback function."""
        arg.discrete[:2] = arg.phase[0].final_state - arg.phase[0].initial_state
        arg.discrete[2:4] = arg.phase[0].integral[1:]

    ocp.functions.objective = objective
    ocp.functions.continuous = continuous
    ocp.functions.discrete = discrete

    # bounds
    bounds = ocp.bounds.phase[0]
    bounds.path.lower[0] = bounds.path.upper[0] = 1
    bounds.initial_time.lower = bounds.initial_time.upper = 0.0
    bounds.final_time.lower = bounds.final_time.upper = 1.0
    bounds.state.lower[:] = -10
    bounds.state.upper[:] = +10
    bounds.control.lower[:] = -2
    bounds.control.upper[:] = +2
    ocp.bounds.discrete.lower = ocp.bounds.discrete.upper = [0, 0, 0, 0]
    ocp.bounds.discrete.lower[2:] = -1e-4
    ocp.bounds.discrete.upper[2:] = +1e-4

    # guess
    guess = ocp.guess.phase[0]
    guess.time = [0.0, 0.25, 0.5, 0.75, 1.0]
    guess.state = [
        [1.0, 0.0, -1.0, 0.0, 1.0],
        [0.0, -1.0, 0.0, 1.0, 0.0],
    ]

    # mesh
    m, n = 3, 15
    ocp.mesh.phase[0].collocation_points = m * (n,)
    ocp.mesh.phase[0].fraction = m * (1.0 / m,)

    # set objective scale to maximize objective
    ocp.scale.objective = -1

    # yapss and ipopt options
    ocp.derivatives.method = "auto"
    ocp.derivatives.order = "second"
    ocp.ipopt_options.tol = 1e-20
    ocp.ipopt_options.print_level = 3

    return ocp


def plot_solution(solution: Solution) -> None:
    """Plot the solution to the isoperimetric problem.

    The collocation points are shown as dots, and the curve is interpolated between
    points with a cubic spline.

    Parameters
    ----------
    solution: Solution
        The solution to the isoperimetric problem.
    """
    plt.figure(1)
    plt.clf()
    x, y = solution.phase[0].state
    s = solution.phase[0].time
    sp = np.linspace(0, 1, 500)
    xp = interp1d(s, x, kind="cubic")(sp)
    yp = interp1d(s, y, kind="cubic")(sp)
    plt.plot(xp, yp)
    plt.plot(x, y, ".", markersize=10)
    plt.xlabel("$x$")
    plt.ylabel("$y$")
    plt.axis("square")
    plt.tight_layout()


def main() -> None:
    """Demonstrate the solution to the isoperimetric problem."""
    problem = setup()
    solution = problem.solve()

    # print the solution
    area = solution.objective
    area_ideal = 1 / (4 * math.pi)
    print(f"\n\nMaximum area = {area} (Should be 1 / (4 pi) = {area_ideal})")
    print(f"Relative error in solution = {abs(area - area_ideal) / area_ideal}")

    # plot the solution
    plot_solution(solution)
    plt.show()


if __name__ == "__main__":
    main()

Text Output


******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit https://github.com/coin-or/Ipopt
******************************************************************************

Total number of variables............................:      181
                     variables with only lower bounds:        0
                variables with lower and upper bounds:      172
                     variables with only upper bounds:        0
Total number of equality constraints.................:      138
Total number of inequality constraints...............:        3
        inequality constraints with only lower bounds:        1
   inequality constraints with lower and upper bounds:        2
        inequality constraints with only upper bounds:        0


Number of Iterations....: 68

                                   (scaled)                 (unscaled)
Objective...............:  -7.9577471545947673e-02    7.9577471545947673e-02
Dual infeasibility......:   2.8897348699173663e-17    2.8897348699173663e-17
Constraint violation....:   6.9271303103357607e-15    6.9271303103357607e-15
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   5.0000000000000012e-21    5.0000000000000012e-21
Overall NLP error.......:   6.9271303103357607e-15    6.9271303103357607e-15


Number of objective function evaluations             = 121
Number of objective gradient evaluations             = 69
Number of equality constraint evaluations            = 121
Number of inequality constraint evaluations          = 121
Number of equality constraint Jacobian evaluations   = 69
Number of inequality constraint Jacobian evaluations = 69
Number of Lagrangian Hessian evaluations             = 68
Total seconds in IPOPT (w/o function evaluations)    =      0.351
Total seconds in NLP function evaluations            =      0.173

EXIT: Solved To Acceptable Level.


Maximum area = 0.07957747154594767 (Should be 1 / (4 pi) = 0.07957747154594767)
Relative error in solution = 0.0

Plots

../_images/isoperimetric_plot_1.png