<jupyter_start><jupyter_text>Formulazione del controllore PIDNel seguente esempio, esamineremo i componenti e i vari metodi di definizione del controllore PID (Proporzionale-Integrale-Derivativo). Come suggerisce il nome, il sistema è composto da tre componenti distinti (un integratore, un differenziatore e un guadagno) e l'uscita è la somma ponderata di questi segnali.$$y_{PID}(t) = P\cdot u(t) + I\cdot\int u(t)dt + D\cdot\frac{d}{dt}u(t)$$Applicata la trasformata di Laplace al modello, è possibile assegnare al controllore la seguente funzione di trasferimento:$$G_{PID}(s)=P+\frac{I}{s}+D\cdot s$$Questa forma ideale, in cui i componenti sono separati e indipendenti, viene definita la forma ideale del controllore. Una forma alternativa di questo controllore può essere formata calcolando il guadagno comune delle tre componenti (Kp). In questo caso, i coefficienti rimanenti rappresentano le caratteristiche di frequenza dei componenti. Questa forma viene solitamente definita forma parallela:$$G_{PID} = K_p(1 + \frac{1}{T_is} + T_ds)$$Occasionalmente viene utilizzata una versione alternativa in cui i componenti sono collegati in serie anziché in parallelo. In questo caso, l'equazione risultante è molto simile, ma il guadagno per la componente P dipende anche dalle costanti di tempo.$$G_{PID} = K_p(1 + \frac{1}{T_is})(1 + T_ds) = K_p(1 + \frac{T_d}{T_i} + \frac{1}{T_is} + T_ds)$$C'è un problema con questi modelli PID quando si tratta di implementazioni elettroniche analogiche. È impossibile implementare la componente derivativa ideale poiché i componenti realistici hanno un taglio alle alte frequenze (il guadagno infinito non può essere implementato). Questa proprietà può essere modellata includendo un componente di primo ordine nella funzione di trasferimento. Con questa modifica, il modello parallelo può essere espresso come:$$G_{PID} = K_p(1 + \frac{1}{T_is} + \frac{T_ds}{{T_d}'s + 1}) \qquad {T_d}'=\frac{T_d}{F_d} \qquad F_d \substack{\in\\ \sim} [8,\;20]$$Seleziona un tipo di modello!<jupyter_code># System type selector
typeSelect = w.ToggleButtons(
    options=[('Indipendente', 0), ('Parallelo', 1), ('Serie', 2), ('Reale', 3)],
    description='PID: ', layout=w.Layout(width='100%'))

display(typeSelect)<jupyter_output><empty_output><jupyter_text>Scegli i valori del PID! Osserva i cambiamenti nelle caratteristiche frequenziali!<jupyter_code>fig1, ((f1_ax1), (f1_ax2)) = plt.subplots(2, 1)
fig1.set_size_inches((9.8, 5))
fig1.set_tight_layout(True)

f1_line1, = f1_ax1.plot([], [], lw=1, color='blue')
f1_line2, = f1_ax2.plot([], [], lw=1, color='blue')

f1_ax1.grid(which='both', axis='both', color='lightgray')
f1_ax2.grid(which='both', axis='both', color='lightgray')

f1_ax1.autoscale(enable=True, axis='x', tight=True)
f1_ax2.autoscale(enable=True, axis='x', tight=True)
f1_ax1.autoscale(enable=True, axis='y', tight=False)
f1_ax2.autoscale(enable=True, axis='y', tight=False)

f1_ax1.set_title('Diagramma del modulo', fontsize=11)
f1_ax1.set_xscale('log')
f1_ax1.set_xlabel(r'$\omega\/[\frac{rad}{s}]$', labelpad=0, fontsize=10)
f1_ax1.set_ylabel(r'$A\/$[dB]', labelpad=0, fontsize=10)
f1_ax1.tick_params(axis='both', which='both', pad=0, labelsize=8)

f1_ax2.set_title('Diagramma della fase', fontsize=11)
f1_ax2.set_xscale('log')
f1_ax2.set_xlabel(r'$\omega\/[\frac{rad}{s}]$', labelpad=0, fontsize=10)
f1_ax2.set_ylabel(r'$\phi\/$[°]', labelpad=0, fontsize=10)
f1_ax2.tick_params(axis='both', which='both', pad=0, labelsize=8)


def calculate_tf(P, I, D, I0, D0, model, F=1):   
    
    global I_slider, D_slider
    
    if I0:
        I_slider.disabled=False
    else:
        I_slider.disabled=True
        
    if D0:
        D_slider.disabled=False
    else:
        D_slider.disabled=True
    
    
    if model == 0: # Ideal
        
        W = c.parallel(c.tf([P], [1]),
                       c.tf([I * I0], [1 * I0, 1 * (not I0)]),
                       c.tf([D * D0, 0], [1]))
        
    elif model == 1: # Parallel
        
        W = c.parallel(c.tf([P], [1]),
                       c.tf([P / I * I0], [1 * I0, 1 * (not I0)]),
                       c.tf([P * D * D0, 0], [1]))
        
    elif model == 2: # Series
        
        W = c.parallel(c.tf([P + (D * D0 + 1 * (not D0)) / (I * I0 + 1 * (not I0))], [1]),
                       c.tf([P / I * I0], [1 * I0, 1 * (not I0)]),
                       c.tf([P * D * D0, 0], [1]))
        
    else: # Realistic
        
        W = c.parallel(c.tf([P], [1]),
                       c.tf([P / I * I0], [1 * I0, 1 * (not I0)]),
                       c.tf([P * D * D0, 0], [D / F * D0, 1]))
        
    print('Funzione di trasferimento del PID:')
    print(W)
    
    global f1_line1, f1_line2
    
    f1_ax1.lines.remove(f1_line1)
    f1_ax2.lines.remove(f1_line2)
    
    mag, phase, omega = c.bode_plot(W, Plot=False)   # Bode-plot 
    
    f1_line1, = f1_ax1.plot(omega, 20*np.log10(mag), lw=1, color='blue')
    f1_line2, = f1_ax2.plot(omega, phase*180/np.pi, lw=1, color='blue')
    
    f1_ax1.relim()
    f1_ax2.relim()
    f1_ax1.autoscale_view()
    f1_ax2.autoscale_view()        

def draw_controllers(model):
    
    global P_slider, I_slider, D_slider, I_button, D_button, F_slider
    
    if model == 0: # Ideal
        
        P_slider = w.FloatLogSlider(value=0.5, base=10, min=-4, max=4, description='$P$', continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))
        I_slider = w.FloatLogSlider(value=0.1, base=10, min=-4, max=4, description='$I$', continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))
        D_slider = w.FloatLogSlider(value=1, base=10, min=-4, max=4, description='$D$', continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))

        I_button = w.ToggleButton(value=True, description='',
                                  layout=w.Layout(width='auto', flex='1 1 0%'))
        D_button = w.ToggleButton(value=False, description='',
                                  layout=w.Layout(width='auto', flex='1 1 0%'))

        input_data = w.interactive_output(calculate_tf, {'P': P_slider, 'I': I_slider, 'D': D_slider,
                                                         'I0' : I_button, 'D0': D_button,
                                                         'model': typeSelect})

        display(w.HBox([P_slider, I_button, I_slider, D_button, D_slider]), input_data)
        
    elif model in [1, 2]: # Series and Parallel
        
        P_slider = w.FloatLogSlider(value=0.5, base=10, min=-1, max=4, description='$K_p$', continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))
        I_slider = w.FloatLogSlider(value=0.0035, base=10, min=-4, max=1, description='$T_i$', continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))
        D_slider = w.FloatLogSlider(value=1, base=10, min=-4, max=1, description='$T_d$', continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))

        I_button = w.ToggleButton(value=True, description='',
                                  layout=w.Layout(width='auto', flex='1 1 0%'))
        D_button = w.ToggleButton(value=False, description='',
                                  layout=w.Layout(width='auto', flex='1 1 0%'))

        input_data = w.interactive_output(calculate_tf, {'P': P_slider, 'I': I_slider, 'D': D_slider,
                                                         'I0' : I_button, 'D0': D_button,
                                                         'model': typeSelect})

        display(w.HBox([P_slider, I_button, I_slider, D_button, D_slider]), input_data)
        
    else: # Realistic
        
        P_slider = w.FloatLogSlider(value=0.5, base=10, min=-1, max=4, description='$K_p$', continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))
        I_slider = w.FloatLogSlider(value=0.0035, base=10, min=-4, max=1, description='$T_i$', continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))
        D_slider = w.FloatLogSlider(value=1, base=10, min=-4, max=1, description='$T_d$', continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))
        F_slider = w.FloatLogSlider(value=1, base=10, min=0, max=3, description="${T_d}'=T_d/ $", continuous_update=False,
                                    layout=w.Layout(width='auto', flex='10 10 auto'))

        I_button = w.ToggleButton(value=True, description='',
                                  layout=w.Layout(width='auto', flex='1 1 0%'))
        D_button = w.ToggleButton(value=False, description='',
                                  layout=w.Layout(width='auto', flex='1 1 0%'))

        input_data = w.interactive_output(calculate_tf, {'P': P_slider, 'I': I_slider, 'D': D_slider,
                                                         'F': F_slider, 'I0' : I_button, 'D0': D_button,
                                                         'model': typeSelect})

        display(w.HBox([P_slider, I_button, I_slider, D_button, D_slider, F_slider]), input_data)
    
    
w.interactive_output(draw_controllers, {'model': typeSelect})<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.14763966627943817, 'fraction_numerical': 0.03284401731967473, 'mean_word_length': 2.5830495648883844, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '285100', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 3407, 'n_tokens_neox': 3269, 'n_words': 718, 'path': 'ICCT_it/examples/03/FD-06_Controllore_PID.ipynb', 'repo_name': 'ICCTerasmus/ICCT', 'size': '9469'}