Exemplo n.º 1
0
def _constrained_l0_branch_and_bound(l,
                                     Q,
                                     target_mean,
                                     max_total_current,
                                     max_el_current,
                                     max_l0,
                                     eps_bb=1e-1,
                                     max_bb_iter=100,
                                     start_inactive=[],
                                     start_active=[],
                                     log_level=20):
    ''' Solves the constrained L0 problem using a branch-and-bound algorithm '''
    logger.log(log_level, "Starting BB")
    max_l0 = int(max_l0)
    l = copy.copy(np.atleast_2d(l))
    if max_total_current is None and max_el_current is None:
        raise ValueError(
            'at least one of max_l1 or max_el_current must not be None')
    if max_el_current is not None:
        if max_total_current is not None:
            max_total_current = min(max_total_current,
                                    max_l0 * max_el_current / 2.)
        else:
            max_total_current = max_l0 * max_el_current
    else:
        max_el_current = max_total_current

    n = l.shape[1]
    # Define the initial state
    if len(start_inactive) > 0:
        assert min(start_inactive) >= 0 and max(start_inactive) < n, \
            'Invalid electrode number in start_inactive'
    if len(start_active) > 0:
        assert min(start_active) >= 0 and max(start_active) < n, \
            'Invalid electrode number in start_active'
    unassigned = [
        i for i in range(n) if i not in start_inactive + start_active
    ]
    init = bb_state(start_active, start_inactive, unassigned)
    # partilize _bb_bounds_function
    bf = functools.partial(_bb_bounds_function, l, Q, target_mean,
                           max_total_current, max_el_current, max_l0)
    # do the branch and bound
    final_state = _branch_and_bound(init,
                                    bf,
                                    eps_bb,
                                    max_bb_iter,
                                    log_level=log_level)
    x = final_state.x_ub
    return x
Exemplo n.º 2
0
def _branch_and_bound(init, function, eps, max_k, log_level=20):
    '''Brach and Bound Algorithm
    Parameters:
    --------
    init: object
        initial state
    function: func
        Function which takes the state and returns an upper bound, lower bound and 2
        child states
    eps: float
        Tolerance between upper and lower bound
    max_k: int
        Maximum depth
    '''
    active_nodes = [bb_node(init, function)]
    k = 0
    return_val = None
    while True:
        lb = np.array([n.lb_val for n in active_nodes])
        ub = np.array([n.ub_val for n in active_nodes])
        # Prune
        keep = lb <= ub.min()
        keep[ub.argmin()] = True
        lb = lb[keep]
        ub = ub[keep]
        active_nodes = [n for i, n in enumerate(active_nodes) if keep[i]]
        logger.log(
            log_level, "{0} Upper Bound: {1}, Lower Bound: {2}".format(
                k, ub.min(), lb.min()))
        if ub.min() - lb.min() <= eps * np.abs(lb.min()) or k >= max_k:
            if ub.min() - lb.min() <= eps * np.abs(lb.min()):
                logger.log(log_level, 'Tolerance reached, returning')
            else:
                logger.log(log_level,
                           'Maximum number of iterations reached, retunning')
            return_val = active_nodes[ub.argmin()].state
            break
        q = active_nodes.pop(lb.argmin())
        c1, c2 = q.split()
        active_nodes.append(c1)
        active_nodes.append(c2)
        k += 1

    return return_val
Exemplo n.º 3
0
def _constrained_l0(l,
                    Q,
                    target_mean,
                    max_total_current,
                    max_el_current,
                    max_l0,
                    eps=1e-5,
                    method='bb_compact',
                    log_level=20):
    logger.log(log_level,
               'Running optimization with constrained number of electrodes')
    max_l0 = int(max_l0)
    if max_total_current is None and max_el_current is None:
        raise ValueError(
            'at least one of max_total_current or max_el_current must not be None'
        )

    if max_el_current is not None:
        if max_total_current is not None:
            max_total_current = min(max_total_current,
                                    max_l0 * max_el_current / 2.)
        else:
            max_total_current = max_l0 * max_el_current
    else:
        max_el_current = max_total_current

    if method == 'proj':
        x = optimize_focality(l,
                              Q,
                              target_mean,
                              max_total_current,
                              max_el_current,
                              log_level=10)
        active = np.argsort(np.abs(x))[-max_l0:]
        x_active = optimize_focality(l[:, active],
                                     Q[np.ix_(active, active)],
                                     target_mean,
                                     max_total_current,
                                     max_el_current,
                                     log_level=10)
        x = np.zeros(l.shape[1])
        x[active] = x_active
        return x

    elif method == 'bb_full':
        x = _constrained_l0_branch_and_bound(l,
                                             Q,
                                             target_mean,
                                             max_total_current,
                                             max_el_current,
                                             max_l0,
                                             eps_bb=1e-1,
                                             max_bb_iter=100,
                                             log_level=log_level)

    elif method == 'bb_compact':
        logger.log(log_level, 'Using the compact Branch-and-bound method')
        x = optimize_focality(l,
                              Q,
                              target_mean,
                              3 * max_total_current,
                              max_el_current,
                              log_level=10)
        active = np.abs(x) > 1e-3 * max_el_current
        x_active = _constrained_l0_branch_and_bound(l[:, active],
                                                    Q[np.ix_(active, active)],
                                                    target_mean,
                                                    max_total_current,
                                                    max_el_current,
                                                    max_l0,
                                                    eps_bb=1e-1,
                                                    max_bb_iter=100,
                                                    log_level=log_level)

        x = np.zeros(l.shape[1])
        x[active] = x_active

    else:
        raise Exception('Uknown method')

    return x
Exemplo n.º 4
0
def optimize_focality(l,
                      Q,
                      target_mean,
                      max_total_current=None,
                      max_el_current=None,
                      Qin=None,
                      max_angle=None,
                      max_active_electrodes=None,
                      log_level=20):
    ''' Optimizes the focality, with the specifield mean field in the target area

    Parameters
    -----------
        l: np.ndarray
            Linear objective, obtained from target_matrices
        Q: np.ndarray
            Quadratic objective, obtained from energy_matrix
        target_mean: float or Nx1 array of floats
            Mean field component that we will try to reach (l^t currents = target_mean)
        max_total_current: float (optional)
            Maximal current flow though all electrodes. Default: No maximum
        max_el_current: float (optional)
            Maximal current flow though each electrode. Default: No maximum
        Qin: np.ndarray (optional)
            Q_in matrix from target_matrices. Needed when constraining angle
        max_angle: float (optional)
            Maximum angle between the average taget component and the average field in
            the region, in degrees. Default: No Maximum
        max_active_electrodes: int (optional)
            Maximum number of active electrodes
    Returns
    ----------
        x: np.ndarray
            Optimal electrode currents
    '''
    if max_total_current is None and max_el_current is None:
        raise ValueError(
            'Please define a maximal total current or maximal electrode ' +
            'current')

    if max_angle is not None and Qin is None:
        raise ValueError('When setting a max angle, a Qin must also be given')

    if max_el_current is not None:
        eps = 1e-3 * max_el_current

    elif max_total_current is not None:
        eps = 1e-3 * max_total_current

    else:
        eps = 1e-5

    l = np.copy(np.atleast_2d(np.array(l)))
    assert np.all(np.array(target_mean) > 0), \
        'The target mean values have to be positive'

    logger.log(log_level, 'Began to run optimization')
    if max_angle is None and max_active_electrodes is None:
        target_mean = np.atleast_1d(np.array(target_mean))
        assert l.shape[0] == target_mean.shape[0], \
            "Please specify one target mean per target"
        n = l.shape[1]
        l_avg, A_ub, b_ub, A_eq, b_eq, bounds = \
            _lp_variables(l, target_mean, max_total_current, max_el_current)
        sol = scipy.optimize.linprog(-l_avg,
                                     A_ub,
                                     b_ub,
                                     A_eq,
                                     b_eq,
                                     bounds=bounds)
        x_ = sol.x
        f = l.dot(x_[:n] - x_[n:])
        if np.any(np.abs(f - target_mean) >= np.abs(1e-2 * target_mean)):
            logger.log(log_level, 'Could not reach target intensities')
            return x_[:n] - x_[n:]

        else:
            logger.log(log_level,
                       'Target intensity reached, optimizing focality')
            # Do the QP
            a_, Q_, C_, d_, A_, b_, eps = \
                _qp_variables(l, Q, f, max_total_current, max_el_current)
            x_ = _active_set_QP(a_, Q_, C_, d_, x_, A=A_, b=b_, eps=eps)
            x = x_[:n] - x_[n:]
            return x

    elif max_angle is not None:
        assert l.shape[0] == 1,\
                'Angle constraint only avaliable for single targets'
        max_angle = np.deg2rad(max_angle)
        x = _constrained_angle(l,
                               Q,
                               target_mean,
                               max_total_current,
                               max_el_current,
                               Qin,
                               max_angle,
                               max_active_electrodes=max_active_electrodes,
                               eps=eps,
                               log_level=log_level)
        return x

    elif max_active_electrodes is not None:
        target_mean = np.atleast_1d(np.array(target_mean))
        assert l.shape[0] == target_mean.shape[0], \
            "Please specify one target mean per target"
        n = l.shape[1]
        x = _constrained_l0(l,
                            Q,
                            target_mean,
                            max_total_current,
                            max_el_current,
                            max_active_electrodes,
                            eps=eps,
                            log_level=log_level)
        return x

    else:
        raise NotImplementedError(
            'Cant handle angle constraints and maximum number of '
            'electrodes simultaneouly')
Exemplo n.º 5
0
def _constrained_angle(l,
                       Q,
                       target_mean,
                       max_total_current,
                       max_el_current,
                       Qin,
                       max_angle,
                       max_active_electrodes=None,
                       eps=1e-5,
                       eps_angle=1e-1,
                       log_level=20):
    logger.log(log_level, 'Running optimization with angle constraint')
    max_iter = 20
    # Try to find where we can find values bellow and above the target
    it = 0
    above = 0

    def angle(x):
        tan = np.sqrt(np.abs(x.dot(Qin).dot(x) - l.dot(x)**2))
        # the abs is here for numerical stability
        return np.abs(np.arctan2(tan, l.dot(x)))

    # Check if the maximal focality solution alreay fulfills the constraint
    x = optimize_focality(l,
                          Q,
                          target_mean,
                          max_total_current,
                          max_el_current,
                          max_active_electrodes=max_active_electrodes,
                          log_level=10)

    if angle(x) <= max_angle:
        logger.log(log_level, 'Max focality field fullfills angle constraint')
        return x

    x_above = np.copy(x)
    target_field = l.dot(x)

    # calculate the smallest angle, given a fixed intensity
    def calc_smallest_angle(alpha):
        x_l = optimize_focality(l,
                                Qin,
                                alpha * target_field,
                                max_total_current,
                                max_el_current,
                                max_active_electrodes=max_active_electrodes,
                                log_level=10)
        return x_l

    # if the target field is not reached, we don't need to calculate the smallest angle,
    # as the set of feasible solutions only contains one element
    if target_field >= target_mean * (1 - eps):
        x = calc_smallest_angle(1.)

    f = angle(x)

    # if we cannot reduce the angle to the target while keeting l^t x at the target
    # intensity, reduce the target intensity untill it's achievable.
    if f > max_angle:
        logger.log(log_level, "Target intensity can't be reached, reducing it")
        above = 1.
        f_above = f
        bellow = None
        f_bellow = None
        # lowers the target mean untill the desired angle can be achieved
        alpha = 1.
        it = 0
        # find lower bound
        while not (f > max_angle * (1 - eps_angle) and f < max_angle):
            if bellow is not None:
                alpha = above + \
                    (max_angle * (1 - eps_angle * .5) - f_above) * \
                    (bellow - above)/(f_bellow - f_above)
            else:
                alpha *= .8
            x = calc_smallest_angle(alpha)
            f = angle(x)
            logger.log(
                log_level, '{0} alpha: {1}, angle: {2}, max_angle: {3}'.format(
                    it, alpha, f, max_angle))
            if f < max_angle:
                bellow = alpha
                f_bellow = f
                x_bellow = np.copy(x)
            else:
                above = alpha
                f_above = f
            it += 1
            if it > max_iter:
                if bellow is None:
                    return x
                else:
                    return x_bellow
        return x

    # In this case, we know that by minimizing x^t Qin x while keeping l^t x = t, we can
    # achieve the bound

    # find a combination between Q and Qin that maximizes focality while keeping Qin in
    # the bound
    else:
        logger.log(
            log_level,
            "Target intensity reached, optimizing focality with angle constraint"
        )
        f_bellow = f
        bellow = 1.
        x_bellow = np.copy(x)
        f_above = angle(x_above)
        above = 0

        # Start bisection
        it = 0
        alpha = 1
        while not (f > max_angle * (1 - eps_angle) and f < max_angle):
            alpha = above + \
                (max_angle * (1 - eps_angle * .5) - f_above) * \
                (bellow - above)/(f_bellow - f_above)
            x = optimize_focality(l, (1 - alpha) * Q + alpha * Qin,
                                  target_field,
                                  max_total_current,
                                  max_el_current,
                                  max_active_electrodes=max_active_electrodes,
                                  log_level=10)
            f = angle(x)
            logger.log(
                log_level, '{0} alpha: {1}, angle: {2}, max_angle: {3}'.format(
                    it, alpha, f, max_angle))
            if f > max_angle:
                above = alpha
                f_above = f
            else:
                bellow = alpha
                f_bellow = f
                x_bellow = np.copy(x)

            it += 1
            if it > max_iter:
                return x_bellow

        return x