"""
Top-drive controller factories (Aarsnes, di Meglio & Shor 2018, Sec. 3).
All three industrial controllers share one block diagram: a PI speed loop,
optionally with impedance feedback of band-pass filtered pipe torque. The
factories compute gains from the drill-pipe characteristic impedance
``zeta_p = J_p sqrt(G rho)`` and the top-drive inertia.
Controllers that do not fit this structure can be written in Python and run in
the simulator's external-torque mode (see :meth:`Simulation.step`).
"""
import numpy as np
from .parameters import ControllerParameters
[docs]
def pipe_impedance(grid):
"""Torsional characteristic impedance of the top cell, zeta_p = J sqrt(G rho) (N m s)."""
return float(grid.J[0] * np.sqrt(grid.G[0] * grid.rho[0]))
[docs]
def stiff(zeta_p, inertia):
"""Industry-standard stiff PI: Kp = 100 zeta_p, Ki = 5 I_TD (eq. 8)."""
return ControllerParameters(kp=100.0 * zeta_p, ki=5.0 * inertia, impedance=0.0, name='stiff')
[docs]
def soft_torque(zeta_p, inertia, f_c):
"""
SoftTorque/SoftSpeed: Kp = 4 zeta_p, Ki = (2 pi f_c)^2 I_TD (eqs. 11-12),
where ``f_c`` (Hz) is the frequency at which the reflection coefficient is
minimised, normally the first torsional mode of the string.
"""
return ControllerParameters(kp=4.0 * zeta_p, ki=(2 * np.pi * f_c) ** 2 * inertia,
impedance=0.0, name='soft_torque')
[docs]
def z_torque(zeta_p, inertia, t_hp=10.0, t_lp=0.001, t_en=0.001, kp=None, ki=None):
"""
ZTorque: a stiff inner speed loop whose reference is corrected by the
band-pass filtered pipe torque, Z = 1/zeta_p (eqs. 14-15), so the top drive
behaves like a matched impedance. The inner loop must be stiff for the top
drive to actually follow the corrected reference; pairing the impedance
feedback with soft PI gains is unstable. Defaults follow the paper (1 ms
speed and low-pass filters, 10 s high-pass). Convergence to the set point
is governed by ``t_hp``.
"""
c = stiff(zeta_p, inertia)
if kp is not None:
c.kp = kp
if ki is not None:
c.ki = ki
c.impedance = 1.0 / zeta_p
c.t_hp, c.t_lp, c.t_en = t_hp, t_lp, t_en
c.name = 'z_torque'
return c
[docs]
def custom(kp, ki, impedance=0.0, t_hp=10.0, t_lp=0.005, t_en=0.001, name='custom'):
"""Any controller with the same block structure."""
return ControllerParameters(kp=kp, ki=ki, impedance=impedance, t_hp=t_hp,
t_lp=t_lp, t_en=t_en, name=name)
[docs]
def reflection_coefficient(c: ControllerParameters, zeta_p, inertia, freqs):
"""
Top-side torsional reflection coefficient ``|(C(s)+I s - zeta)/(C(s)+I s + zeta)|``
at frequencies ``freqs`` (Hz), ignoring the impedance-feedback path (eq. 10).
"""
s = 2j * np.pi * np.asarray(freqs)
C = c.kp + c.ki / s + inertia * s
return np.abs((C - zeta_p) / (C + zeta_p))