def split_solve(self):
            '''
            Mehtod to solve the splitting problem
            function takes the weight_available_matrix of the split instance
            and find the optimum number of weights types in the candidated holes_available
            that minimize the error.
            Args:
                split model instance
            Returns:
                S: numpy array of the same shape as weight_available_matrix of a model
            '''

            # define mixed integer matrix in cvxpy
            _S = cp.Variable(self.weight_available_matrix.shape, integer=True)
            # define objective function
            _Real = cp.sum(
                cp.multiply(np.real(self.weight_available_matrix),
                            _S)) - np.real(self.weight)
            _Imag = cp.sum(
                cp.multiply(np.imag(self.weight_available_matrix),
                            _S)) - np.imag(self.weight)
            _Residuals = cp.norm(cp.hstack([_Real, _Imag]))
            _obj_split = cp.Minimize(_Residuals)
            # constraints
            _const_splitting = [_S >= 0]
            if self.max_number_weights_per_hole:
                if isinstance(self.max_number_weights_per_hole, int):
                    _const_splitting += [
                        cp.sum(_S, axis=0) <= self.max_number_weights_per_hole
                    ]
                else:
                    raise tools.CustomError(
                        '`max_number_weights_per_hole` should be integer number'
                    )
            if self.max_weight_per_plane:
                if isinstance(self.max_weight_per_plane, (float, int)):
                    _const_splitting += [
                        cp.sum(_S.T @ self.weights_available, axis=0) <=
                        self.max_weight_per_plane
                    ]
                else:
                    raise tools.CustomError(
                        '`max_weight_per_plane` should be float number')

            # solve
            _prob_S = cp.Problem(_obj_split, _const_splitting)
            _prob_S.solve(solver=cp.XPRESS
                          )  # TODO make solver options PYTHON_MIP(), ECOS_BB
            _S = np.array(np.round(_S.value))
            self._S = _S
            self._prob_S = _prob_S.value
            _W_equ_split = np.sum(self.weight_available_matrix * self._S)
            self._W_equ_split = _W_equ_split
            return _S
 def solve(self, solver='OLE'):
     '''
     Method to solve the model
     Args:
         solver:'OLE' Ordinary Least Squares method
         'Huber': Uses Huber smoother to down estimate the outliers.
     '''
     W = cp.Variable((self.N, 1), complex=True)
     if solver.upper() == 'OLE':  # Ordinary least squares
         _objective = cp.Minimize(cp.sum_squares(self.ALPHA @ W + self.A))
     elif solver.upper(
     ) == 'HUBER':  # TODO test Huber solver for robust optimization
         _real = cp.real(self.ALPHA @ W + self.A)
         _imag = cp.imag(self.ALPHA @ W + self.A)
         _objective = cp.Minimize(
             cp.sum_squares(cp.huber(cp.hstack([_real, _imag]), M=0)))
     elif solver.upper() == 'WLS':  #  TODO test weighted least squares
         _objective = cp.Minimize(
             cp.sum_squares(cp.diag(self.C) @ (self.ALPHA @ W + self.A)))
     else:
         raise tools.CustomError('Unrecognized Solver name')
     prob = cp.Problem(_objective)
     prob.solve()
     self.W = W.value
     return W.value
        def error(self, options='error'):
            '''
            Method to determine different ways of error in the splitting.
            Args:
                Instance of split model
                options: kwarg to define the type of error.
                'error': default value returns the relative value of error.
                'problem_error': returns the accuracy of the solution.
                'equ': returns the equivilant weight of splitting solution
                    in math form.
            '''

            # Error equivilant weight after splitting
            _error = abs(self._W_equ_split - self.weight)
            # Estimate the error generated by using minmax instead of norm2 in the objective function
            _problem_error = (self._prob_S - _error) / abs(self.weight)
            if options == 'error':
                return _error
            elif options == 'problem_error':
                return _problem_error
            elif options == 'equ':
                return tools.convert_cart_math(self._W_equ_split)
            else:
                raise tools.CustomError(
                    'Invalid options. Choose options="error" for solution error'
                    'caused by splitting, options="problem_error" for solution'
                    ' accuracy and options="equ" for equivilant weight.')
 def solve(self, solver=None):
     '''
     Method to solve the Minmax model
     '''
     W = cp.Variable((self.N, 1), complex=True)
     _objective = cp.Minimize(cp.norm((self.ALPHA @ W + self.A), "inf"))
     # Define weight constraints
     _constrains = []
     if self.weight_const != {}:
         try:
             for key, value in self.weight_const.items():
                 _constrains += [cp.norm(W[key]) <= value]
         except NameError:
             raise tools.CustomError('Invalid weight constraint format')
     else:
         pass
     prob = cp.Problem(_objective, _constrains)
     prob.solve()
     self.W = W.value
     return W.value
    def solve(self, solver=None):
        '''
        solving the LMI model
        returns solution matrix W
        '''

        # Weight constraints
        N = self.N
        M = self.M
        _wc = np.zeros(N)
        if self.weight_const != {}:
            try:
                for key, value in self.weight_const.items():
                    _wc[key] = value
            except NameError:
                raise tools.CustomError('Invalid weight constraint format')
        else:
            pass

        # Identify critical planes
        if self.V_max:
            _Vm = self.V_max
        else:
            raise tools.CustomError('V_max is not specified')

        if self.critical_planes and len(self.critical_planes) > 0:
            _list_cr = self.critical_planes
        else:
            raise tools.CustomError('Critical Planes are not set.')

        _ALPHA = self.ALPHA.copy()
        A = self.A.copy()
        _ALPHAcr = _ALPHA[_list_cr]
        Acr = A[_list_cr]
        _ALPHAncr = np.delete(_ALPHA, _list_cr, axis=0)
        _Ancr = np.delete(A, _list_cr, axis=0)
        # assign cvxpy variables
        _WR = cp.Variable((N, 1))
        _WI = cp.Variable((N, 1))
        _Vc = cp.Variable()

        _RRfcr = cp.diag(
            np.real(Acr) + np.real(_ALPHAcr) @ _WR - np.imag(_ALPHAcr) @ _WI)
        _IRfcr = cp.diag(
            np.imag(Acr) + np.imag(_ALPHAcr) @ _WR + np.real(_ALPHAcr) @ _WI)

        _RRfNcr = cp.diag(
            np.real(_Ancr) + np.real(_ALPHAncr) @ _WR -
            np.imag(_ALPHAncr) @ _WI)
        _IRfNcr = cp.diag(
            np.imag(_Ancr) + np.imag(_ALPHAncr) @ _WR +
            np.real(_ALPHAncr) @ _WI)
        _zcr = np.zeros((_RRfcr.shape[0], _RRfcr.shape[1]))
        _Icr = np.eye(_RRfcr.shape[0])
        _zncr = np.zeros((_RRfNcr.shape[0], _RRfNcr.shape[1]))
        _Incr = np.eye(_RRfNcr.shape[0])

        _objective = cp.Minimize(_Vc)
        _LMI_real = cp.bmat([[_Vc * _Icr, _RRfcr, _zcr, -_IRfcr],
                             [_RRfcr, _Icr, _IRfcr, _zcr],
                             [_zcr, _IRfcr, _Vc * _Icr, _RRfcr],
                             [-_IRfcr, _zcr, _RRfcr, _Icr]])
        _LMI_imag = cp.bmat([[_Vm**2 * _Incr, _RRfNcr, _zncr, -_IRfNcr],
                             [_RRfNcr, _Incr, _IRfNcr, _zncr],
                             [_zncr, _IRfNcr, _Vm**2 * _Incr, _RRfNcr],
                             [-_IRfNcr, _zncr, _RRfNcr, _Incr]])
        # Model weight constraints
        _const_LMI_w = []
        for i in range(N):
            _LMI_weight = cp.bmat([[_wc[i]**2, _WR[i], 0, -_WI[i]],
                                   [_WR[i], 1, _WI[i], 0],
                                   [0, _WI[i], _wc[i]**2, _WR[i]],
                                   [-_WI[i], 0, _WR[i], 1]])
            _const_LMI_w.append(_LMI_weight >> 0)
        _const_LMI_w.append(_LMI_real >> 0)
        _const_LMI_w.append(_LMI_imag >> 0)
        # Stating the problem
        prob = cp.Problem(_objective, _const_LMI_w)
        prob.solve(cp.CVXOPT, kktsolver=cp.ROBUST_KKTSOLVER)
        W = _WR + _WI * 1j
        self.W = W.value
        return self.W
    def __init__(self,
                 A: 'initial_vibration' = None,
                 alpha: 'instance of Alpha class' = None,
                 conditions: 'list of condition instances' = None,
                 name: 'string' = ''):
        """ Instantiate the model
        Args:
        A: Initial vibration vector -> np.ndarray
        ALPHA: Influence coefficient matrix -> class Alpha
        conditions: List of conditions instance that express the model various
                    setpoints for balancing -> list of `class Condition`
        name: optional name of the model -> string
        """
        self.name = name
        self.alpha = alpha
        self.W = None
        self.split_instance = [
        ]  # List of all related splits that has modified the solution
        self.conditions = conditions
        if self.conditions is None:
            if A is None or alpha is None:
                raise TypeError(
                    'Either (A and Alpha) or `conditions` should be assigned.')
            try:
                if A.shape[1] == 1:
                    self.A = A
            except AttributeError:
                raise tools.CustomError('Either direct_matrix or (A,B,U) '
                                        'should be passed "numpy arrays"')
            except IndexError:
                raise tools.CustomError(
                    '`A` should be a column vector (Mx1) dimension')

            # Test if Alpha is an instance of Alpha class
            if not isinstance(alpha, Alpha):
                raise tools.CustomError(
                    'Please create an `Alpha instance` first --> ex. alpha = Alpha()'
                )
            else:
                self.alpha = alpha
                self.ALPHA = alpha.value
        elif isinstance(self.conditions, list) == False:
            raise TypeError('Conditions should be a list')
        else:
            if all(
                    isinstance(condition, Condition)
                    for condition in self.conditions):
                self.ALPHA = np.vstack(
                    [condition.alpha.value for condition in self.conditions])
                self.A = np.vstack(
                    [condition.A for condition in self.conditions])
            else:
                raise TypeError(
                    '''`conditions` should be a list of `Condition class` try condition
                                = Condition()''')

        try:
            _ = self.ALPHA.shape  # Test that alpha.value returns np.array
            self.N = self.ALPHA.shape[1]  # Get N number of balancing_planes
            self.M = self.ALPHA.shape[0]  # Get M number of measuring points
        except AttributeError:
            raise tools.CustomError('Missing valid ALPHA value')
        def split_setup(self,
                        balancing_plane_index,
                        holes_available,
                        weights_available,
                        max_number_weights_per_hole=None,
                        max_weight_per_plane=None):
            """
            Split method to determine the optimized splitted weight
            over available holes and with some availabe weight types
            the optimization is mixed integer quadratic type that
            minimize the residule of the caluclated model weight from
            a combination of availabe weights on the availble holes
            Args:
                balancing_plane_index: the index of weight to be split
                    from the model. (model.W[index]) -> complex class
                holes_available: list of holes available, this can be
                    entered as numpy array (ex. np.arange(0, 100, 10) represents
                    the holes from 0 deg to 100 deg spaced 10 degress each)-> list
                weights_available: list of type of weights available
                    ex. [185, 90] represents that factory weights available
                    are 185 and 90 grams for instance. -> list
                max_number_weights_per_hole: maximum number of weights premissible
                    to be added in each hole. (ex. 1 -> only one weight is allowable
                    per hole) -> int
                max_weight_per_plane: max weight allowable per plane.
                    (ex 2000 -> maximum weight allowable per plane is 2 kg) -> float
            Returns: weight_available_matrix which represents all possible weights at
                    all possible holes.

            """
            self.balancing_plane_index = balancing_plane_index
            self.holes_available = holes_available
            self.weights_available = weights_available
            self.max_number_weights_per_hole = max_number_weights_per_hole
            self.max_weight_per_plane = max_weight_per_plane
            self.weight = self.model.W[balancing_plane_index]

            # Vectorizing cmath `rect` function to be applied on holes
            vrect = np.vectorize(cmath.rect)
            # list to np.array
            if isinstance(holes_available, list):
                if holes_available:
                    holes_available = np.array(holes_available)
                else:
                    raise tools.CustomError(
                        'holes_available should contian at least one element')
            else:
                raise tools.CustomError('holes_available should be a list')
            if isinstance(weights_available, list):
                if weights_available:
                    weights_available = np.array(weights_available)
                else:
                    raise tools.CustomError(
                        'weights_available should contain at least one element'
                    )
            else:
                raise tools.CustomError('weights_available should be a list')
            # Transfer weights from row to column
            weights_available = weights_available[:, np.newaxis]
            # Make weight matrix in complex form
            weight_available_matrix = weights_available * vrect(
                1, holes_available * cmath.pi / 180)  # from deg to rad
            self.weight_available_matrix = weight_available_matrix
            pass
Esempio n. 8
0
    def add(
            self,
            direct_matrix: 'np.array' = None,
            A: 'initial_vibration numpy.array' = None,
            B: 'trial matrix numpy.array' = None,
            U: 'trial weight row vector numpy.array' = None,
            keep_trial:
        'optional keep the previous trial weight in every succeeding trial' = False,
            name: 'string' = ''):
        '''
        Method to add new values for Alpha instance
        either the direct_matrix is needed or ALL of (A, B, U)
        Args:
            direct_matrix: numpy array M rows -> measuring points,
                        N columns -> balancing planes
            A: Initial vibration column array -> numpy array
            B: Trial matrix MxN array -> numpy array
        '''
        self.A = A
        self.B = B
        self.U = U
        self.keep_trial = keep_trial
        try:  # test if direct input
            _ = direct_matrix.shape
            if direct_matrix.ndim < 2:
                raise IndexError(
                    'Influence coefficient matrix should be of more than 1 dimensions.'
                )
            if direct_matrix.shape[0] >= direct_matrix.shape[1]:
                self.value = direct_matrix
            else:
                raise tools.CustomError(
                    'Number of rows(measuring points) should be '
                    'equal or  more than the number of columns '
                    '(balancing planes)!')
            if self.A is not None or self.B is not None or self.U is not None:
                raise ValueError(
                    'Either (direct Matrix) or (A, B, U) should be input, but not both.'
                )

        except AttributeError:
            # if direct matrix is not input calculate it from A, B, U
            # test the exstiance of A, A0, B, U to calculate ALPHA
            try:
                all([A.shape, B.shape, U.shape])
                # Test dimensions
                if A.shape[1] > 1:
                    raise tools.CustomError('`A` should be column vector')
                elif U.ndim > 1:
                    raise tools.CustomError('`U` should be row vector')
                elif B.shape[0] != A.shape[0] or B.shape[1] != U.shape[0]:
                    raise tools.CustomError(
                        '`B` dimensions should match `A`and `U`')
                else:
                    self.A = A
                    self.B = B
                    self.U = U
                    if not keep_trial:
                        self.value = (self.B - self.A) / self.U
                    else:
                        _A_keep_trial = np.delete(
                            (np.insert(self.B, [0], self.A, axis=1)),
                            -1,
                            axis=1)
                        self.value = (self.B - _A_keep_trial) / self.U
            except AttributeError:
                raise tools.CustomError('Either direct_matrix or (A,B,U) '
                                        'should be passed "numpy arrays"')
        if self.value is not None:
            self.M = self.value.shape[0]
            self.N = self.value.shape[1]