def full_observer(system, poles_o, poles_s, debug=False) -> ObserverResult:
    """implementation of the complete observer for an autonomous system
    :param system : tuple (a, b, c) of system matrices
    :param poles_o: tuple of complex poles/eigenvalues of the observer
    dynamics
    :param poles_s: tuple of complex poles/eigenvalues of the closed
    system
    :param debug: output control for debugging in unittest(False:normal
    output,True: output local variables and normal output)
    :return: dataclass ObserverResult (controller function, feedback matrix, observer_gain, and pre-filter)
    """

    # systemically relevant matrices
    a = system[0]
    b = system[1]
    c = system[2]
    d = system[3]

    # ignore the PendingDeprecationWarning for built-in packet control
    warnings.filterwarnings('ignore', category=PendingDeprecationWarning)

    ctr_matrix = ctr.ctrb(a, b)  # controllabilty matrix
    assert np.linalg.det(ctr_matrix) != 0, 'this system is not controllable'

    # State feedback
    f_t = ctr.acker(a, b, poles_s)

    # pre-filter
    a1 = a - b * f_t
    v = (-1 * (c * a1**(-1) * b)**(-1))  # pre-filter

    obs_matrix = ctr.obsv(a, c)  # observability matrix
    assert np.linalg.det(obs_matrix) != 0, 'this system is unobservable'

    # calculate observer gain
    l_v = ctr.acker(a.T, c.T, poles_o).T

    # controller function
    # in this case controller function is: -1 * feedback * states + pre-filter * reference output
    # disturbance value ist not considered
    # t = sp.Symbol('t')
    # sys_input = -1 * (f_t * sys_state)[0]
    # input_func = sp.lambdify((sys_state, t), sys_input, modules='numpy')

    # this method returns controller function, feedback matrix, observer gain and pre-filter
    result = ObserverResult(f_t, l_v, v, None)

    # return internal variables for unittest
    if debug:
        c_locals = Container(fetch_locals=True)
        result.debug = c_locals

    return result
Beispiel #2
0
def resolve_keys(entity):
    """
    For quick progress almost all model fields are strings. This function converts those fields, which contains keys
    to contain the real reference (or list of references).
    :param entity:
    :return:
    """

    entity_type = type(entity)
    fields = entity_type.get_fields()

    # endow every entity with an object container:

    entity.oc = Container()

    for field in fields:
        if isinstance(field, models.EntityKeyField):

            # example: get the content of entity.predecessor_key
            refkey = getattr(entity, field.name)
            if refkey:
                try:
                    ref_entity = get_entity(refkey)
                except ValueError as ve:
                    msg = f"Bad refkey detected when processing field {field.name} of {entity}. " \
                        f"Original error: {ve.args[0]}"
                    raise InconsistentMetaDataError(msg)
            else:
                ref_entity = None

            # save the real object to the object container (allow later access)
            setattr(entity.oc, field.name, ref_entity)

        elif isinstance(field, models.EntityKeyListField):
            refkeylist_str = getattr(entity, field.name)

            if refkeylist_str is None:
                msg = f"There is a problem with the field {field.name} in entity {entity.key}."
                raise InconsistentMetaDataError(msg)

            refkeylist = yaml.load(refkeylist_str, Loader=yaml.FullLoader)
            if refkeylist in (None, [], [""]):
                refkeylist = []

            try:
                entity_list = [get_entity(refkey) for refkey in refkeylist]
            except ValueError as ve:
                msg = f"Bad refkey detected when processing field {field.name} of {entity}. "\
                      f"Original error: {ve.args[0]}"
                raise InconsistentMetaDataError(msg)
            setattr(entity.oc, field.name, entity_list)
def transformation(system, debug=False):
    """This function is used to divide system, input and
    output matrix into measurable and unmeasurable states
    or renumber them.

    :param system : tuple (a, b, c) of system matrices
    :param debug: output control for debugging in unittest(False:normal
    output,True: output local variables and normal output)
    :return: renumbered matrices a,b,c
    """

    a = np.array(system[0]).copy()
    b = np.array(system[1]).copy()
    c = np.array(system[2]).copy()
    d = np.array(system[3]).copy()

    index_list = []
    # find out which elements in C are equal to one.
    for i in range(c.shape[0]):
        for j in range(c.shape[1]):
            if c[i, j] == 1:
                index_list.append([i, j])

    # exchange row and column
    for [i1, j1] in index_list:
        if i1 != j1:
            c[:, [j1, i1]] = c[:, [i1, j1]]
            a[[[j1, i1]], :] = a[[[i1, j1]], :]
            a[:, [j1, i1]] = a[:, [i1, j1]]
            b[[j1, i1], :] = b[[i1, j1], :]

    if debug:
        c_locals = Container(fetch_locals=True)
        return c_locals

    return a, b, c, d
def reduced_observer(system, poles_o, poles_s, x0, tt, debug=False):
    """
    :param system: tuple (A, B, C) of system matrices
    :param poles_o: tuple of complex poles/eigenvalues of the
                    observer dynamics
    :param poles_s: tuple of complex poles/eigenvalues of the
                    closed system
    :param x0: initial condition
    :param tt: vector for the time axis
    :param debug: output control for debugging in unittest
                  (False:normal output,True: output local variables
                  and normal output)
    :return yy: output of the system
    :return xx: states of the system
    :return tt: time of the simulation
    """

    # renumber the matrices to construct the matrices
    # in the form of the reduced observer
    a, b, c, d = ob_func.transformation(system, debug=False)

    # row rank of the output matrix
    rank = np.linalg.matrix_rank(c[0])

    # submatrices of the system matrix A
    # and the input matrix
    a_11 = a[0:rank, 0:rank]

    a_12 = a[0:rank, rank:a.shape[1]]
    a_21 = a[rank:a.shape[1], 0:rank]
    a_22 = a[rank:a.shape[1], rank:a.shape[1]]
    b_1 = b[0:rank, :]
    b_2 = b[rank:b.shape[0], :]

    # construct the observability matrix
    # q_1 = a_12
    # q_2 = a_12 * a_22
    # q = np.vstack([q_1, q_2])

    obs_matrix = ctr.obsv(a_22, a_12)  # observability matrix
    rank_q = np.linalg.matrix_rank(obs_matrix)
    assert np.linalg.det(obs_matrix) != 0, 'this system is unobservable'

    # ignore the PendingDeprecationWarning for built-in packet control
    warnings.filterwarnings('ignore', category=PendingDeprecationWarning)

    # state feedback
    f_t = ctr.acker(a, b, poles_s)
    c_ = c - d * f_t
    f_1 = f_t[:, 0:1]
    f_2 = f_t[:, 1:]
    l_ = ctr.acker(a_22.T, a_12.T, poles_o).T

    # State representation of the entire system
    # Original system and observer error system,
    # Dimension: 4 x 4)
    sys = ob_func.state_space_func(a, a_11, a_12, a_21, a_22, b, b_1, b_2, c_,
                                   l_, f_1, f_2)

    # simulation for the proper movement
    yy, tt, xx = ctr.matlab.initial(sys, tt, x0, return_x=True)
    result = yy, xx, tt, sys

    # return internal variables for unittest
    if debug:
        c_locals = Container(fetch_locals=True)
        return result, c_locals

    return result
Beispiel #5
0
def check_solution(key):
    """

    :param key:                 entity key of the ProblemSolution
    :return:
    """

    sol_entity = get_entity(key)
    resolve_keys(sol_entity)

    assert isinstance(sol_entity, models.ProblemSolution)

    # get path for solution
    solution_file = sol_entity.solution_file

    if solution_file != "solution.py":
        msg = "Arbitrary filename will be supported in the future"
        raise NotImplementedError(msg)

    c = Container(
    )  # this will be our easily accessible context dict for the template

    # TODO: handle the filename (see also template)
    c.solution_path = os.path.join(root_path, sol_entity.base_path)

    c.ackrep_core_path = core_pkg_path

    # noinspection PyUnresolvedReferences
    assert isinstance(sol_entity.oc, ObjectContainer)

    assert len(sol_entity.oc.solved_problem_list) >= 1

    if sol_entity.oc.solved_problem_list == 0:
        msg = f"{sol_entity}: Expected at least one solved problem."
        raise InconsistentMetaDataError(msg)

    elif sol_entity.oc.solved_problem_list == 1:
        problem_spec = sol_entity.oc.solved_problem_list[0]
    else:
        print(
            "Applying a solution to multiple problems is not yet supported. Taking the last one."
        )
        problem_spec = sol_entity.oc.solved_problem_list[-1]

    if problem_spec.problem_file != "problem.py":
        msg = "Arbitrary filename will be supported in the future"
        raise NotImplementedError(msg)

    # TODO: handle the filename (see also template)
    c.problem_spec_path = os.path.join(root_path, problem_spec.base_path)

    # list of the build_paths
    c.method_package_list = []
    for mp in sol_entity.oc.method_package_list:
        full_build_path = make_method_build(mp, accept_existing=True)
        assert os.path.isdir(full_build_path)
        c.method_package_list.append(full_build_path)

    context = dict(c.item_list())

    print("  ... Creating exec-script ... ")

    scriptname = "execscript.py"

    assert not sol_entity.base_path.startswith(os.path.sep)

    # determine whether the entity comes from ackrep_data or ackrep_data_for_unittests ore ackrep_data_import
    data_repo_path = pathlib.Path(sol_entity.base_path).parts[0]
    scriptpath = os.path.join(root_path, data_repo_path, scriptname)
    render_template("templates/execscript.py.template",
                    context,
                    target_path=scriptpath)

    print(f"  ... running exec-script {scriptpath} ... ")

    # TODO: plug in containerization here:
    # Note: this hangs on any interactive element inside the script (such as IPS)
    res = subprocess.run(["python", scriptpath], capture_output=True)
    res.exited = res.returncode
    res.stdout = res.stdout.decode("utf8")
    res.stderr = res.stderr.decode("utf8")

    return res
def state_feedback(system: tuple,
                   poles_o: list,
                   sys_state: sp.Matrix,
                   eqrt: list,
                   yr,
                   debug=False) -> StateFeedbackResult:
    """
    :param system : tuple (a, b, c, d) of system matrices
    :param poles_o: tuple of closed-loop poles of the system
    :param sys_state: states of nonlinear system
    :param eqrt: equilibrium points of the system
    :param yr: reference output
    :param debug: output control for debugging in unittest(False:normal
    output,True: output local variables and normal output)
    :return: dataclass StateFeedbackResult (controller function, feedback matrix and pre-filter)
    """

    # ignore the PendingDeprecationWarning for built-in packet control
    warnings.filterwarnings('ignore', category=PendingDeprecationWarning)

    # system matrices
    a = system[0]
    b = system[1]
    c = system[2]
    d = system[3]

    ctr_matrix = ctr.ctrb(a, b)  # controllabilty matrix
    assert np.linalg.det(ctr_matrix) != 0, 'this system is not controllable'

    # full state feedback
    f_t = ctr.acker(a, b, poles_o)

    # pre-filter
    a1 = a - b * f_t
    v = -1 * (c * a1**(-1) * b)**(-1)  # pre-filter
    '''Since the controller, which is designed on the basis of a linearized system, 
    is a small signal model, the states have to be converted from the large signal model 
    to the small signal model. i.e. the equilibrium points of the original non-linear system must 
    be subtracted from the returned states. 

    x' = x - x0 
    x: states of nonlinear system (large signal model)
    x0: equilibrium points
    x': states of controller (small signal model)

    And for the same reason, the equilibrium position of the 
    input must be added to controller function.
    '''

    t = sp.Symbol('t')

    # convert states to small signal model
    small_state = sys_state - sp.Matrix(
        [eqrt[i][1] for i in range(len(sys_state))])

    # creat controller function
    # in this case controller function is: -1 * feedback * states + pre-filter * reference output
    # disturbance value ist not considered
    sys_input = -1 * (f_t *
                      small_state)[0] + v[0] * yr + eqrt[len(sys_state)][1]
    input_func = sp.lambdify((sys_state, t), sys_input, modules='numpy')

    # this method returns controller function, feedback matrix and pre-filter
    result = StateFeedbackResult(input_func, f_t, v, None)

    # return internal variables for unittest
    if debug:
        c_locals = Container(fetch_locals=True)
        result.debug = c_locals

    return result
Beispiel #7
0
def lqr_method(system, q_matrix, r_matrix, sys_state, eqrt, yr, debug=False):
    """
    :param system : tuple (a, b, c, d) of system matrices
    :param q_matrix: State weights matrix
    :param r_matrix: input weights matrix
    :param sys_state: states of nonlinear system
    :param eqrt: equilibrium points of the system
    :param yr: reference output
    :param debug: output control for debugging in unittest(False:normal
    output,True: output local variables and normal output)
    :return result: data class LQR_Result (controller function, feedback matrix pre-filter
                    and poles of closed loop)

    """

    # ignore the PendingDeprecationWarning for built-in packet control
    warnings.filterwarnings('ignore', category=PendingDeprecationWarning)

    # system matrices
    a = system[0]
    b = system[1]
    c = system[2]
    d = system[3]

    # computes the optimal state feedback controller that minimizes the quadratic cost function
    res = ctr.lqr(a, b, q_matrix, r_matrix)

    # full state feedback
    f_t = res[0]
    a1 = a - b * f_t
    v = -1 * (c * a1**(-1) * b)**(-1)  # pre-filter
    '''Since the controller, which is designed on the basis of a linearized system, 
    is a small signal model, the states have to be converted from the large signal model 
    to the small signal model. i.e. the equilibrium points of the original non-linear system must 
    be subtracted from the returned states. 
    
    x' = x - x0 
    x: states of nonlinear system (large signal model)
    x0: equilibrium points
    x': states of controller (small signal model)
    
    And for the same reason, the equilibrium position of the 
    input must be added to controller function.
    '''

    t = sp.Symbol('t')
    # convert states to small signal model
    small_state = sys_state - sp.Matrix(
        [eqrt[i][1] for i in range(len(sys_state))])

    # creat controller function
    # in this case controller function is: -1 * feedback * states + pre-filter * reference output +
    # equilibrium point of input
    # disturbance value ist not considered
    sys_input = -1 * (f_t *
                      small_state)[0] + v[0] * yr + eqrt[len(sys_state)][1]
    input_func = sp.lambdify((sys_state, t), sys_input, modules='numpy')

    poles_lqr = res[2]

    result = LQR_Result(input_func, f_t, v, poles_lqr, None)

    # return internal variables for unittest
    if debug:
        c_locals = Container(fetch_locals=True)
        result.debug = c_locals

    return result