Quickstart Optimization

An optimization process can be presented as following:

../_images/optiScheme.svg
  • Optimization algorithm: AlgorithmInterface. This is the algorithm that performs the optimization, and outputs a vector of variables between [0, 1[.
  • Maths to physics: InterfaceMathsToPhysics. Transforms the output vector of the optimization algorithm to the variables of a InterfaceDevice. The usage of this block becomes meaningful for more complex optimization problem, such as optimizing a BLDC motor while keeping the outer diameter constant. In this case, a good implementation of the M2P block automatically scales the inner dimensions of the motor to comply with this constraint.
  • Characterization: InterfaceCharacterization. Based on the attributes of the device, performs some computation. This block is nearly useless for simple optimization problems (when the objective function is easily computed) but becomes interesting for more complex problems, where many things need to be precalculated before obtaining the objective functions and constraints. This for example can hold an analytical or a FEM magnetic model. A sub-optimization could also be performed there.
  • Objective and constraints: InterfaceObjCons. These classes correspond to either what has to be minimized, or which constraints <=0 has to be complied with.

Quick example: \(\min_{x, y \in [0, 2]} f(x) = \sqrt{ 1 + (y+3) \cdot x^2 }, g(x) = 4 + 2 \sqrt{y+3} \cdot \sqrt{1+(x-1)^2}\), under the constrained that \(x \leq 0.55\). This is a bi-objective problem and will lead to a pareto front.

"""Now that you know everything about data and visualization, let's get started with optimization!
Optimeed provides high-level interface to perform optimization with visualization and data storage.
The Wiki gives more details about the optimization. To get started, you need the following key ingredients:

    - A device that contains the variables to be optimized ("Device") and other parameters you would like to save
    - A list of optimization variables ("OptimizationVariable")
    - An evaluation function ("Characterization")
    - One or more objective functions ("Objectives")
    - (optional) Eventual constraints ("Constraints")
    - An optimization algorithm ("Optimization Algorithm")
    - Something that will fill the "Device" object with the optimization variables coming from the optimization algorithm. ("MathsToPhysics")
        Don't get scared with this one, if you do not know how it can be useful, the proposition by default works perfectly fine.
    - Something that will link all the blocks together ("Optimizer")
"""


# These are what we need for the optimization
from optimeed.optimize.optiAlgorithms import MultiObjective_GA as OptimizationAlgorithm
from optimeed.optimize import Real_OptimizationVariable, InterfaceObjCons, InterfaceCharacterization, OptiHistoric
from optimeed.optimize.optimizer import OptimizerSettings, run_optimization

# These are the high-level visualization tools
from optimeed.visualize.displayOptimization import OptimizationDisplayer
from optimeed.visualize import Onclick_representDevice, Represent_brut_attributes, start_qt_mainloop
import time


class Device:
    """Define the Device to optimize."""
    x: float  # Type hinted -> will be automatically saved
    y: float  # Type hinted -> will be automatically saved

    def __init__(self):
        self.x = 1
        self.y = 1


class Characterization(InterfaceCharacterization):
    """Define the Characterization scheme. In this case nothing is performed,
     but this is typically where model code will be executed and results saved inside 'theDevice'."""
    def compute(self, thedevice):
        time.sleep(0.0001)


class MyObjective1(InterfaceObjCons):
    """First objective function (to be minimized)"""
    def compute(self, thedevice):
        return (1 + (thedevice.y+3)*thedevice.x**2)**0.5


class MyObjective2(InterfaceObjCons):
    """Second objective function (to be minimized)"""
    def compute(self, thedevice):
        return 4 + 2*(thedevice.y+3)**0.5*(1+(thedevice.x-1)**2)**0.5


class MyConstraint(InterfaceObjCons):
    """Constraints, that needs to be <= 0"""
    def compute(self, thedevice):
        return thedevice.x - 0.55


if __name__ == "__main__":  # This line is necessary to spawn new processes
    """Start the main code. Instantiate previously defined classes."""
    theDevice = Device()
    theAlgo = OptimizationAlgorithm()
    theAlgo.set_option(theAlgo.OPTI_ALGORITHM, "NSGAII")  # You can change the algorithm if you need ;)
    # theAlgo.set_option(theAlgo.NUMBER_OF_CORES, 2)  # Toggle this line to use more cores. Default is 1 (single core).

    # Careful that it generates overhead -> only helpful when the characterization is computationally expensive.

    theCharacterization = Characterization()

    """Variable to be optimized"""
    optimizationVariables = list()
    optimizationVariables.append(Real_OptimizationVariable('x', 0, 2))  #
    optimizationVariables.append(Real_OptimizationVariable('y', 1, 2))

    """Objective and constraints"""
    listOfObjectives = [MyObjective1(), MyObjective2()]
    listOfConstraints = [MyConstraint()]

    """Set the optimizer"""
    theOptiParameters = OptimizerSettings(theDevice, listOfObjectives, listOfConstraints, optimizationVariables,
                                          theOptimizationAlgorithm=theAlgo, theCharacterization=theCharacterization)

    """The logger (to automatically save the points)"""
    theOptiHistoric = OptiHistoric(optiname="opti", autosave_timer=10, autosave=True, create_new_directory=True)

    """Start the optimization"""
    max_opti_time_sec = 10

    display_opti = True
    if display_opti:  # Display real-time graphs
        optiDisplayer = OptimizationDisplayer(theOptiParameters, theOptiHistoric, light_background=True)
        _, theDataLink, _ = optiDisplayer.generate_optimizationGraphs()

        # Here we set the actions on click.
        theActionsOnClick = list()
        theActionsOnClick.append(Onclick_representDevice(theDataLink, [Represent_brut_attributes()]))
        optiDisplayer.set_actionsOnClick(theActionsOnClick)

        resultsOpti, convergence = optiDisplayer.launch_optimization([theOptiParameters, theOptiHistoric], {"max_opti_time_sec": max_opti_time_sec},
                                                                     refresh_time=0.1, max_nb_points_convergence=None)  # Refresh the graphs each nth seconds

    else:  # Otherwise just focus on results ... That can be helpful if you are confident the optimizations will converge and you need to launch several optimizations.
        resultsOpti, convergence = run_optimization(theOptiParameters, theOptiHistoric, max_opti_time_sec=max_opti_time_sec)

    """Gather results"""
    # Pro hint: you would probably never work with these next few lines of code, instead you would move to the next tutorial
    # to retrieve the results from the automatically saved files.
    print("Best individuals :")
    for device in resultsOpti:
        print("x : {} \t y : {}". format(device.x, device.y))

    if display_opti:
        start_qt_mainloop()  # To keep windows alive

    """Note that the results are automatically saved if KWARGS_OPTIHISTO autosaved=True.
    In this case, optimization folder is automatically generated in Workspace/optiX. It contains five files:
    -> autosaved: contains all the devices evaluated during the optimization
    -> logopti: contains all the information relating to the optimization itself: objectives, constraints, evaluation time.
    -> opticonvergence: contains all the information relative to the convergence of the optimization (saved only at the end)
    -> results: all the best devices as decided by the optimization algorithm
    -> optimization_parameters: the class OptimizationParameters that can be reloaded using SingleObjectSaveLoad.load 
    -> summary.html: a summary of the optimization problem
    See other tutorials on how to save/load these information.
    """