File size: 3,114 Bytes
145f794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import numpy as np
import pandas as pd
from scipy.optimize import minimize
from scipy.interpolate import interp1d
from scipy.optimize import differential_evolution


def objective_function(params, t, pv_hist, cv_hist):
    # Unpack parameters
    Kp, tau, theta = params

    # Basic constraints to keep parameters realistic
    if Kp <= 0 or tau <= 0 or theta < 0:
        return 1e9  # Return a large number for invalid parameters

    # Simulate the model response
    pv_sim = simulate_fopdt(params, t, cv_hist)

    # Calculate Sum of Squared Errors (SSE)
    error = np.sum((pv_hist - pv_sim) ** 2)
    return error


def simulate_fopdt(params, t, cv):
    Kp, tau, theta = params
    dt = t[1] - t[0]
    pv_sim = np.zeros_like(t)

    # Calculate the number of discrete steps for the dead time
    delay_steps = int(round(theta / dt))

    # Create a delayed version of the CV signal
    cv_delayed = np.zeros_like(cv)
    if delay_steps > 0 and delay_steps < len(cv):
        cv_delayed[delay_steps:] = cv[:-delay_steps]
    else:
        cv_delayed = cv

    for i in range(1, len(t)):
        # First-order dynamics using the pre-calculated delayed CV
        d_pv = (Kp * cv_delayed[i] - pv_sim[i - 1]) / tau
        pv_sim[i] = pv_sim[i - 1] + d_pv * dt

    return pv_sim


def identify_process_model(t, pv, cv):
    bounds = [(0.1, 5.0), (1, 20.0), (0.5, 5.0)]  # Adjusted bounds for robustness

    # Run the optimization using differential_evolution.
    result = differential_evolution(
        objective_function,
        bounds,
        args=(t, pv, cv),
        popsize=30,
        maxiter=1000,
        tol=0.01,
        recombination=0.7,
        strategy="best1bin",
    )

    if result.success:
        identified_params = result.x
        return identified_params
    else:
        print("Error: System identification failed.")
        print(result.message)
        return None


def calculate_tuning_params(Kp, tau, theta):
    if Kp is None or tau is None or theta is None or theta == 0:
        print("Cannot calculate tuning rules with invalid model parameters.")
        return {}

    # --- Ziegler-Nichols ---
    zn_kc = (1.2 * tau) / (Kp * theta)
    zn_ti = 2.0 * theta
    zn_td = 0.5 * theta

    zn_kp = zn_kc
    zn_ki = zn_kc / zn_ti
    zn_kd = zn_kc * zn_td

    # --- Cohen-Coon ---
    cc_kc = (tau / (Kp * theta)) * (4 / 3 + theta / (4 * tau))
    cc_ti = theta * (32 + 6 * theta / tau) / (13 + 8 * theta / tau)
    cc_td = theta * 4 / (11 + 2 * theta / tau)

    cc_kp = cc_kc
    cc_ki = cc_kc / cc_ti
    cc_kd = cc_kc * cc_td

    # --- IMC (Lambda) Tuning ---
    lambda_val = tau
    imc_kc = (tau + 0.5 * theta) / (Kp * (lambda_val + 0.5 * theta))
    imc_ti = tau + 0.5 * theta
    imc_td = (tau * theta) / (2 * tau + theta)

    imc_kp = imc_kc
    imc_ki = imc_kc / imc_ti
    imc_kd = imc_kc * imc_td

    tuning_results = {
        "Ziegler-Nichols": {"Kp": zn_kp, "Ki": zn_ki, "Kd": zn_kd},
        "Cohen-Coon": {"Kp": cc_kp, "Ki": cc_ki, "Kd": cc_kd},
        "IMC (Lambda)": {"Kp": imc_kp, "Ki": imc_ki, "Kd": imc_kd},
    }
    return tuning_results