# CONVEKS - Copyright (C) 2018 E. Kuci, C. Geuzaine and P. Duysinx, University of Liege
#
# See the LICENSE.txt file for license information. Please report all
# bugs and problems to the public mailing list <conveks@onelab.info>.

# This file defines the CONVEKS Python API (v1.1).
#
# Do not edit it directly: it is automatically generated by `api/gen.py'.
#
# By design, the CONVEKS Python API is purely functional, and only uses elementary
# types (as well as `numpy' arrays if `numpy' is avaiable). See `demos/api' for
# examples.

from ctypes import *
import signal
import os
import platform
from math import pi

CONVEKS_API_VERSION = "1.1"

signal.signal(signal.SIGINT, signal.SIG_DFL)
libdir = os.path.dirname(os.path.realpath(__file__))
if platform.system() == 'Windows':
    lib = CDLL(os.path.join(libdir, "conveks-1.1.dll"))
elif platform.system() == 'Darwin':
    lib = CDLL(os.path.join(libdir, "libconveks.dylib"))
else:
    lib = CDLL(os.path.join(libdir, "libconveks.so"))

use_numpy = False
try:
    import numpy
    try:
        from weakref import finalize as weakreffinalize
    except:
        from backports.weakref import finalize as weakreffinalize
    use_numpy = True
except:
    pass

def _ostring(s):
    sp = s.value.decode("utf-8")
    lib.conveksFree(s)
    return sp

def _ovectorpair(ptr, size):
    v = list((ptr[i * 2], ptr[i * 2 + 1]) for i in range(size//2))
    lib.conveksFree(ptr)
    return v

def _ovectorint(ptr, size):
    if use_numpy:
        v = numpy.ctypeslib.as_array(ptr, (size, ))
        weakreffinalize(v, lib.conveksFree, ptr)
    else:
        v = list(ptr[i] for i in range(size))
        lib.conveksFree(ptr)
    return v

def _ovectordouble(ptr, size):
    if use_numpy:
        v = numpy.ctypeslib.as_array(ptr, (size, ))
        weakreffinalize(v, lib.conveksFree, ptr)
    else:
        v = list(ptr[i] for i in range(size))
        lib.conveksFree(ptr)
    return v

def _ovectorstring(ptr, size):
    v = list(_ostring(cast(ptr[i], c_char_p)) for i in range(size))
    lib.conveksFree(ptr)
    return v

def _ovectorvectorint(ptr, size, n):
    v = [_ovectorint(pointer(ptr[i].contents), size[i]) for i in range(n.value)]
    lib.conveksFree(size)
    lib.conveksFree(ptr)
    return v

def _ovectorvectordouble(ptr, size, n):
    v = [_ovectordouble(pointer(ptr[i].contents), size[i]) for i in range(n.value)]
    lib.conveksFree(size)
    lib.conveksFree(ptr)
    return v

def _ovectorvectorpair(ptr, size, n):
    v = [_ovectorpair(pointer(ptr[i].contents), size[i]) for i in range(n.value)]
    lib.conveksFree(size)
    lib.conveksFree(ptr)
    return v

def _ivectorint(o):
    if use_numpy:
        return numpy.ascontiguousarray(o, numpy.int32).ctypes, c_size_t(len(o))
    else:
        return (c_int * len(o))(*o), c_size_t(len(o))

def _ivectorvectorint(os):
    n = len(os)
    parrays = [_ivectorint(o) for o in os]
    sizes = (c_size_t * n)(*(a[1] for a in parrays))
    arrays = (POINTER(c_int) * n)(*(cast(a[0], POINTER(c_int)) for a in parrays))
    arrays.ref = [a[0] for a in parrays]
    size = c_size_t(n)
    return arrays, sizes, size

def _ivectorvectordouble(os):
    n = len(os)
    parrays = [_ivectordouble(o) for o in os]
    sizes = (c_size_t * n)(*(a[1] for a in parrays))
    arrays = (POINTER(c_double) * n)(*(cast(a[0], POINTER(c_double)) for a in parrays))
    arrays.ref = [a[0] for a in parrays]
    size = c_size_t(n)
    return arrays, sizes, size

def _ivectordouble(o):
    if use_numpy:
        array = numpy.ascontiguousarray(o, numpy.float64)
        ct = array.ctypes
        ct.array = array
        return  ct, c_size_t(len(o))
    else:
        return (c_double * len(o))(*o), c_size_t(len(o))

def _ivectorpair(o):
    if use_numpy:
        array = numpy.ascontiguousarray(o, numpy.int32)
        ct = array.ctypes
        ct.array = array
        return  ct, c_size_t(len(o) * 2)
    else:
        return ((c_int * 2) * len(o))(*o), c_size_t(len(o) * 2)

def _iargcargv(o):
    return c_int(len(o)), (c_char_p * len(o))(*(s.encode() for s in o))


def initialize(type="none"):
    """
    Initialize Conveks.
    """
    ierr = c_int()
    lib.conveksInitialize(
        c_char_p(type.encode()),
        byref(ierr))
    if ierr.value != 0:
        raise ValueError(
            "conveksInitialize returned non-zero error code: ",
            ierr.value)

def run():
    """
    Solve the optimization problem.
    """
    ierr = c_int()
    lib.conveksRun(
        byref(ierr))
    if ierr.value != 0:
        raise ValueError(
            "conveksRun returned non-zero error code: ",
            ierr.value)

def addObjective(func):
    """
    Add the objective function.
    """
    ierr = c_int()
    lib.conveksAddObjective(
        c_void_p(func),
        byref(ierr))
    if ierr.value != 0:
        raise ValueError(
            "conveksAddObjective returned non-zero error code: ",
            ierr.value)

def addConstraint():
    """
    Solve the optimization problem.
    """
    ierr = c_int()
    lib.conveksAddConstraint(
        byref(ierr))
    if ierr.value != 0:
        raise ValueError(
            "conveksAddConstraint returned non-zero error code: ",
            ierr.value)

def addGroupOfConstraints():
    """
    Solve the optimization problem.
    """
    ierr = c_int()
    lib.conveksAddGroupOfConstraints(
        byref(ierr))
    if ierr.value != 0:
        raise ValueError(
            "conveksAddGroupOfConstraints returned non-zero error code: ",
            ierr.value)

def addVariable():
    """
    Solve the optimization problem.
    """
    ierr = c_int()
    lib.conveksAddVariable(
        byref(ierr))
    if ierr.value != 0:
        raise ValueError(
            "conveksAddVariable returned non-zero error code: ",
            ierr.value)

def addGroupOfVariables():
    """
    Solve the optimization problem.
    """
    ierr = c_int()
    lib.conveksAddGroupOfVariables(
        byref(ierr))
    if ierr.value != 0:
        raise ValueError(
            "conveksAddGroupOfVariables returned non-zero error code: ",
            ierr.value)

def computeObjective():
    """
    Compute the objective function.
    """
    ierr = c_int()
    lib.conveksComputeObjective(
        byref(ierr))
    if ierr.value != 0:
        raise ValueError(
            "conveksComputeObjective returned non-zero error code: ",
            ierr.value)

def finalize():
    """
    Finalize Conveks.
    """
    ierr = c_int()
    lib.conveksFinalize(
        byref(ierr))
    if ierr.value != 0:
        raise ValueError(
            "conveksFinalize returned non-zero error code: ",
            ierr.value)


class option:
    """
    Global option handling functions
    """

    @staticmethod
    def setNumber(name, value):
        """
        Set a numerical option to `value'. `name'. Available categories and options
        are listed in the Conveks reference manual.
        """
        ierr = c_int()
        lib.conveksOptionSetNumber(
            c_char_p(name.encode()),
            c_double(value),
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksOptionSetNumber returned non-zero error code: ",
                ierr.value)

    @staticmethod
    def getNumber(name):
        """
        Get the `value' of a numerical option.

        Return `value'.
        """
        api_value_ = c_double()
        ierr = c_int()
        lib.conveksOptionGetNumber(
            c_char_p(name.encode()),
            byref(api_value_),
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksOptionGetNumber returned non-zero error code: ",
                ierr.value)
        return api_value_.value

    @staticmethod
    def getList():
        """
        Get all the options.

        Return `names'.
        """
        api_names_, api_names_n_ = POINTER(POINTER(c_char))(), c_size_t()
        ierr = c_int()
        lib.conveksOptionGetList(
            byref(api_names_), byref(api_names_n_),
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksOptionGetList returned non-zero error code: ",
                ierr.value)
        return _ovectorstring(api_names_, api_names_n_.value)

    @staticmethod
    def show():
        """
        Print the numerical options.
        """
        ierr = c_int()
        lib.conveksOptionShow(
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksOptionShow returned non-zero error code: ",
                ierr.value)


class mma:
    """
    MMA optimizer
    """

    @staticmethod
    def initialize(currentPoint, lowerBound, upperBound, restart=False):
        """
        Initialize MMA.
        """
        api_currentPoint_, api_currentPoint_n_ = _ivectordouble(currentPoint)
        api_lowerBound_, api_lowerBound_n_ = _ivectordouble(lowerBound)
        api_upperBound_, api_upperBound_n_ = _ivectordouble(upperBound)
        ierr = c_int()
        lib.conveksMmaInitialize(
            api_currentPoint_, api_currentPoint_n_,
            api_lowerBound_, api_lowerBound_n_,
            api_upperBound_, api_upperBound_n_,
            c_int(bool(restart)),
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaInitialize returned non-zero error code: ",
                ierr.value)

    @staticmethod
    def getCurrentPoint():
        """
        Get current point.

        Return `currentPoint'.
        """
        api_currentPoint_, api_currentPoint_n_ = POINTER(c_double)(), c_size_t()
        ierr = c_int()
        lib.conveksMmaGetCurrentPoint(
            byref(api_currentPoint_), byref(api_currentPoint_n_),
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaGetCurrentPoint returned non-zero error code: ",
                ierr.value)
        return _ovectordouble(api_currentPoint_, api_currentPoint_n_.value)

    @staticmethod
    def updateCurrentPoint(objective, gradientOfObjective, constraints=[], gradientOfConstraints=[], diagHessianOfObjective=[], objectiveAtUpdatedPoint=float('nan'), constraintsAtUpdatedPoint=[]):
        """
        Update current point (based on first order derivatives and possibly the
        diagonal entries of the objective function hessian).
        """
        api_gradientOfObjective_, api_gradientOfObjective_n_ = _ivectordouble(gradientOfObjective)
        api_constraints_, api_constraints_n_ = _ivectordouble(constraints)
        api_gradientOfConstraints_, api_gradientOfConstraints_n_ = _ivectordouble(gradientOfConstraints)
        api_diagHessianOfObjective_, api_diagHessianOfObjective_n_ = _ivectordouble(diagHessianOfObjective)
        api_constraintsAtUpdatedPoint_, api_constraintsAtUpdatedPoint_n_ = _ivectordouble(constraintsAtUpdatedPoint)
        ierr = c_int()
        lib.conveksMmaUpdateCurrentPoint(
            c_double(objective),
            api_gradientOfObjective_, api_gradientOfObjective_n_,
            api_constraints_, api_constraints_n_,
            api_gradientOfConstraints_, api_gradientOfConstraints_n_,
            api_diagHessianOfObjective_, api_diagHessianOfObjective_n_,
            c_double(objectiveAtUpdatedPoint),
            api_constraintsAtUpdatedPoint_, api_constraintsAtUpdatedPoint_n_,
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaUpdateCurrentPoint returned non-zero error code: ",
                ierr.value)

    @staticmethod
    def getKKTNorm(constraints, gradientOfObjective, gradientOfConstraints):
        """
        Get the norm of KKT at current point.

        Return `norm'.
        """
        api_norm_ = c_double()
        api_constraints_, api_constraints_n_ = _ivectordouble(constraints)
        api_gradientOfObjective_, api_gradientOfObjective_n_ = _ivectordouble(gradientOfObjective)
        api_gradientOfConstraints_, api_gradientOfConstraints_n_ = _ivectordouble(gradientOfConstraints)
        ierr = c_int()
        lib.conveksMmaGetKKTNorm(
            byref(api_norm_),
            api_constraints_, api_constraints_n_,
            api_gradientOfObjective_, api_gradientOfObjective_n_,
            api_gradientOfConstraints_, api_gradientOfConstraints_n_,
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaGetKKTNorm returned non-zero error code: ",
                ierr.value)
        return api_norm_.value

    @staticmethod
    def isConservativeSubProblem(objective, constraints=[]):
        """
        Check if the submodel is conservative and return 1 if it is the case,
        otherwise return 0.

        Return `conservative'.
        """
        api_conservative_ = c_int()
        api_constraints_, api_constraints_n_ = _ivectordouble(constraints)
        ierr = c_int()
        lib.conveksMmaIsConservativeSubProblem(
            byref(api_conservative_),
            c_double(objective),
            api_constraints_, api_constraints_n_,
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaIsConservativeSubProblem returned non-zero error code: ",
                ierr.value)
        return api_conservative_.value

    @staticmethod
    def getOuterIteration():
        """
        Get outer iteration.

        Return `iteration'.
        """
        api_iteration_ = c_int()
        ierr = c_int()
        lib.conveksMmaGetOuterIteration(
            byref(api_iteration_),
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaGetOuterIteration returned non-zero error code: ",
                ierr.value)
        return api_iteration_.value

    @staticmethod
    def countNonlinearIterations():
        """
        Get total number of nonlinear iterations.

        Return `iteration'.
        """
        api_iteration_ = c_int()
        ierr = c_int()
        lib.conveksMmaCountNonlinearIterations(
            byref(api_iteration_),
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaCountNonlinearIterations returned non-zero error code: ",
                ierr.value)
        return api_iteration_.value

    @staticmethod
    def getDesignChange():
        """
        Get the infinite norm between two successive designs.

        Return `change'.
        """
        api_change_ = c_double()
        ierr = c_int()
        lib.conveksMmaGetDesignChange(
            byref(api_change_),
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaGetDesignChange returned non-zero error code: ",
                ierr.value)
        return api_change_.value

    @staticmethod
    def setMoveLimits(lowerBound, upperBound, move):
        """
        Set the outer move limits.
        """
        api_lowerBound_, api_lowerBound_n_ = _ivectordouble(lowerBound)
        api_upperBound_, api_upperBound_n_ = _ivectordouble(upperBound)
        ierr = c_int()
        lib.conveksMmaSetMoveLimits(
            api_lowerBound_, api_lowerBound_n_,
            api_upperBound_, api_upperBound_n_,
            c_double(move),
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaSetMoveLimits returned non-zero error code: ",
                ierr.value)

    @staticmethod
    def finalize():
        """
        Finalize MMA.
        """
        ierr = c_int()
        lib.conveksMmaFinalize(
            byref(ierr))
        if ierr.value != 0:
            raise ValueError(
                "conveksMmaFinalize returned non-zero error code: ",
                ierr.value)


    class option:
        """
        Global option handling functions
        """

        @staticmethod
        def setNumber(name, value):
            """
            Set a numerical option to `value'. `name'. Available categories and options
            are listed in the MMA reference manual.
            """
            ierr = c_int()
            lib.conveksMmaOptionSetNumber(
                c_char_p(name.encode()),
                c_double(value),
                byref(ierr))
            if ierr.value != 0:
                raise ValueError(
                    "conveksMmaOptionSetNumber returned non-zero error code: ",
                    ierr.value)

        @staticmethod
        def getNumber(name):
            """
            Get the `value' of a numerical option.

            Return `value'.
            """
            api_value_ = c_double()
            ierr = c_int()
            lib.conveksMmaOptionGetNumber(
                c_char_p(name.encode()),
                byref(api_value_),
                byref(ierr))
            if ierr.value != 0:
                raise ValueError(
                    "conveksMmaOptionGetNumber returned non-zero error code: ",
                    ierr.value)
            return api_value_.value

        @staticmethod
        def show():
            """
            Print the numerical options.
            """
            ierr = c_int()
            lib.conveksMmaOptionShow(
                byref(ierr))
            if ierr.value != 0:
                raise ValueError(
                    "conveksMmaOptionShow returned non-zero error code: ",
                    ierr.value)
