pypid.backend: Fix 'abmient' -> 'ambient' typo in get_ambient_pv docstring
[pypid.git] / pypid / test.py
1 # Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of pypid.
4 #
5 # pypid is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 3 of the License, or (at your option) any later
8 # version.
9 #
10 # pypid is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # pypid.  If not, see <http://www.gnu.org/licenses/>.
16
17 "Basic testing for `Controller`\s and `Backend`\s"
18
19 import time as _time
20
21 from . import LOG as _LOG
22 from . import rules as _rules
23 from .backend import get_backend as _get_backend
24 from controller import Controller as _Controller
25
26
27 def test_backend(backend=None):
28     internal_backend = False
29     if not backend:
30         internal_backend = True
31         backend = _get_backend('test')()
32     try:
33         sp = backend.get_setpoint()
34         _LOG.info('PV = {:n} {}'.format(backend.get_pv(), backend.pv_units))
35         _LOG.info('SP = {:n} {}'.format(sp, backend.pv_units))
36         _LOG.info('MV = {:n} {}'.format(backend.get_mv(), backend.mv_units))
37
38         _set_and_check_setpoint(backend=backend, setpoint=5.0)
39         _check_max_mv(backend=backend)
40         _set_and_check_setpoint(backend=backend, setpoint=50.0)
41         _check_max_mv(backend=backend)
42         _set_and_check_setpoint(backend=backend, setpoint=sp)
43     finally:
44         if internal_backend:
45             backend.cleanup()
46
47 def _set_and_check_setpoint(backend, setpoint):
48     _LOG.info('setting setpoint to {:n} {}'.format(setpoint, backend.pv_units))
49     c.set_setpoint(setpoint)
50     sp = c.get_setpoint()
51     _LOG.info('SP = {:n} {}'.format(sp, backend.pv_units))
52     if sp != setpoint:
53         msg = 'read setpoint {:n} != written setpoint {:n}'.format(
54             sp, setpoint)
55         _LOG.error(msg)
56         raise Exception(msg)
57
58 def _check_max_current(backend):
59     # give the backend some time to overcome any integral gain
60     _time.sleep(10)
61     MV = c.get_mv()
62     _LOG.info('MV = {:n} {}'.format(MV, backend.mv_units))
63     mMV = c.get_max_mv()
64     if mv != mMV:
65         PV = backend.get_pv()
66         SP = backend.get_setpoint()
67         PVu = backend.pv_units
68         MVu = backend.mv_units
69         msg = ('current of {:n} {} is not the max {:n} {}, but the system is '
70                'at {:n} {} while the setpoint is at {:n} {}').format(
71             MV, MVu, mMV, MVu, PV, PVu, SP, PVu)
72         _LOG.error(msg)
73         raise Exception(msg)
74
75 def test_controller_step_response(backend=None, setpoint=1):
76     internal_backend = False
77     if not backend:
78         internal_backend = True
79         backend = _get_backend('test')()
80     try:
81         backend.set_mode('PID')
82         c = _Controller(backend=backend)
83         max_MV = backend.get_max_mv()
84         MVa = 0.4 * max_MV
85         MVb = 0.5 * max_MV
86         step_response = c.get_step_response(
87             mv_a=MVa, mv_b=MVb, tolerance=0.5, stable_time=4.)
88         if False:
89             with open('step_response.dat', 'w') as d:
90                 s = step_response[0][0]
91                 for t,PV in step_response:
92                     d.write('{:n}\t{:n}\n'.format(t-s, PV))
93         process_gain,dead_time,decay_time,max_rate = c.analyze_step_response(
94             step_response, mv_shift=MVb-MVa)
95         _LOG.debug(('step response: process gain {:n}, dead time {:n}, decay '
96                     'time {:n}, max-rate {:n}').format(
97                 process_gain, dead_time, decay_time, max_rate))
98         r = rules
99         for name,response_fn,modes in [
100             ('Zeigler-Nichols', r.ziegler_nichols_step_response,
101              ['P', 'PI', 'PID']),
102             ('Cohen-Coon', r.cohen_coon_step_response,
103              ['P', 'PI', 'PID']), # 'PD'
104             ('Wang-Juan-Chan', r.wang_juang_chan_step_response,
105              ['PID']),
106             ]:
107             for mode in modes:
108                 p,i,d = response_fn(
109                     process_gain=process_gain, dead_time=dead_time,
110                     decay_time=decay_time, mode=mode)
111                 _LOG.debug(
112                     '{} step response {}: p {:n}, i {:n}, d {:n}'.format(
113                         name, mode, p, i, d))
114     finally:
115         if internal_backend:
116             backend.cleanup()
117
118 def test_controller_bang_bang_response(backend=None, setpoint=1):
119     internal_backend = False
120     if not backend:
121         internal_backend = True
122         backend = _get_backend('test')()
123     try:
124         backend.set_setpoint(setpoint)
125         c = _Controller(backend=backend)
126         dead_band = 3*c.estimate_pv_sensitivity()
127         bang_bang_response = c.get_bang_bang_response(dead_band=dead_band)
128         if False:
129             with open('bang_bang_response.dat', 'w') as d:
130                 s = bang_bang_response[0][0]
131                 for t,T in bang_bang_response:
132                     d.write('{:n}\t{:n}\n'.format(t-s, T))
133         amplitude,period = c.analyze_bang_bang_response(bang_bang_response)
134         _LOG.debug('bang-bang response: amplitude {:n}, period {:n}'.format(
135                 amplitude,period))
136         for name,response_fn,modes in [
137             ('Zeigler-Nichols', r.ziegler_nichols_bang_bang_response,
138              ['P', 'PI', 'PID']),
139             ]:
140             for mode in modes:
141                 p,i,d = response_fn(
142                     amplitude=amplitude, period=period, mode=mode)
143                 _LOG.debug(
144                     '{} bang-bang response {}: p {:n}, i {:n}, d {:n}'.format(
145                         name, mode, p, i, d))
146     finally:
147         if internal_backend:
148             backend.cleanup()