├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── examplelaunch.pyw ├── pytunelogix ├── LICENSE.rst ├── README.md ├── __init__.py ├── _exampleCSVs │ ├── prc.csv │ └── prc_reverseaction.csv ├── clxpidsim │ ├── __init__.py │ └── clxsim.py ├── common │ ├── __init__.py │ └── generalclasses.py ├── pidlogger │ ├── __init__.py │ └── clxlogger.py ├── simulate │ ├── __init__.py │ └── simulator.py ├── stage1 │ ├── __init__.py │ └── csvtuner.py ├── stage2 │ ├── __init__.py │ └── openlooptune.py ├── stage3 │ ├── __init__.py │ └── closedlooptune.py └── stage4 │ ├── __init__.py │ └── adaptivetune.py ├── setup.cfg └── setup.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Destination2Unknown] 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Destination2Unknown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Note: Project Archived 2 | 3 | 4 | --- 5 | 6 | 7 | ### pytunelogix 8 | **Python PID Tuner for ControlLogix** 9 | 10 | ![PyPI](https://img.shields.io/pypi/v/pytunelogix?label=pypi%20package) 11 | ![PyPI - Downloads](https://img.shields.io/pypi/dm/pytunelogix) 12 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pytunelogix) 13 | ![GitHub repo size](https://img.shields.io/github/repo-size/destination2unknown/pytunelogix) 14 | ![PyPI - License](https://img.shields.io/pypi/l/pytunelogix) 15 | 16 | Windows Exe (no install required) -> https://github.com/Destination2Unknown/pytunelogix/releases 17 | 18 | To install use: 19 | 20 | ``` 21 | pip install pytunelogix 22 | ``` 23 | 24 | 25 | PID tuning in 4 Steps: 26 | ``` 27 | A-> Record PRC using Logger 28 | B-> Tune using PID Tuner 29 | C-> Refine tune using PID Simulator 30 | D-> Test tune with FOPDT Simulator for Logix PLC 31 | ``` 32 | 33 | 34 | To use, create a launch file: 35 | 36 | ``` 37 | examplelaunch.pyw #use pyw for no console 38 | ``` 39 | 40 | _________________________________________________________________________________________________________________________ 41 | **PID Logger** 42 | 43 | 44 | 45 | ![image](https://user-images.githubusercontent.com/92536730/175526532-df3cdb2c-1b42-4380-8b6f-d4f060a3194b.png) 46 | 47 | 48 | 49 | 50 | To launch, use: 51 | ``` 52 | from pytunelogix.pidlogger import clxlogger 53 | 54 | clxlogger.main() 55 | 56 | ``` 57 | 58 | 59 | _________________________________________________________________________________________________________________________ 60 | **Stage 1 - PID Tuner based on a CSV file of a Process Reaction Curve (PRC)** 61 | 62 | > ***Notes and Limitations:*** 63 | > 64 | > - _Assumes CV and PV data stored at 100ms intervals._ 65 | > 66 | > - _Assumes there is a single step in CV._ 67 | > 68 | > - _Ambient is calculated as an average of the PV prior to the step change._ 69 | > 70 | > - _Doesn't work correctly with a ramp in CV or with multiple CV steps._ 71 | > 72 | > 73 | > 74 | > ***N.B.*** 75 | > The PID tuning values are calculated for a PV with a standard range span of 100 in engineering units (e.g. 0-100 deg C or 50-150 deg F). 76 | > If the range of the PV has a different span the PID tuning values **may** need to be rescaled: 77 | > 78 | > Example 1: PV range of 200-400 deg C -> PID Gains x2 79 | > 80 | > Example 2: PV range of 75-100 deg C -> PID Gains x0.25 81 | > 82 | > 83 | 84 | 85 | 86 | To launch, use: 87 | ``` 88 | from pytunelogix.stage1 import csvtuner 89 | 90 | csvtuner.main() 91 | 92 | ``` 93 | 94 | Direct Acting: 95 | 96 | ![U_Tune](https://user-images.githubusercontent.com/92536730/179394923-8757a7b9-d1d6-482b-8bd3-8b4769937206.PNG) 97 | 98 | 99 | 100 | Reverse Acting: 101 | 102 | ![U_TuneR](https://user-images.githubusercontent.com/92536730/179394927-d35f3e2f-943c-41cc-bfff-cfee028a821f.PNG) 103 | 104 | 105 | 106 | 107 | _________________________________________________________________________________________________________________________ 108 | **Stage 2 - Open loop tune** 109 | 110 | 111 | 112 | 113 | 114 | https://user-images.githubusercontent.com/92536730/175918442-017d18a0-0bac-434d-aa44-b8cd3aebe231.mp4 115 | 116 | 117 | 118 | 119 | 120 | _________________________________________________________________________________________________________________________ 121 | **Stage 3 - Closed loop tune** 122 | 123 | 124 | 125 | https://user-images.githubusercontent.com/92536730/175920990-3fc2cb66-9d08-4c67-aff7-ff410345f9a5.mp4 126 | 127 | 128 | 129 | 130 | _________________________________________________________________________________________________________________________ 131 | **Stage 4 - Adaptive tuner** 132 | 133 | 134 | 135 | 136 | https://user-images.githubusercontent.com/92536730/175921177-86389b8f-2d3c-4dc7-8949-db4cdd782d84.mp4 137 | 138 | 139 | 140 | _________________________________________________________________________________________________________________________ 141 | **PID Simulator** 142 | 143 | 144 | Direct Acting: 145 | 146 | 147 | ![pidDirect_DEP](https://user-images.githubusercontent.com/92536730/179607882-859fc354-03c9-4c69-ab1f-6a47c6e74943.PNG) 148 | 149 | 150 | 151 | 152 | Reverse Acting: 153 | 154 | 155 | ![pidReverse_DEP](https://user-images.githubusercontent.com/92536730/179607844-f2728155-9c8a-43e7-8710-8e27b0bacc47.PNG) 156 | 157 | 158 | 159 | 160 | To launch, use: 161 | ``` 162 | from pytunelogix.simulate import simulator 163 | 164 | simulator.main() 165 | 166 | ``` 167 | 168 | 169 | _________________________________________________________________________________________________________________________ 170 | **ControlLogix FOPDT Process Simulator (PID Simulator)** 171 | 172 | 173 | Simulates a Process: 174 | 175 | 176 | ![UCLX_Sim](https://user-images.githubusercontent.com/92536730/179607984-aaea90ac-85dc-491c-8842-8aad5e23370a.png) 177 | 178 | 179 | 180 | Direct Acting: 181 | 182 | ![C](https://user-images.githubusercontent.com/92536730/179394941-54fdb56b-a777-4f8d-bde2-d7c2dd7c5a5f.PNG) 183 | 184 | 185 | 186 | Reverse Acting: 187 | 188 | 189 | ![C_R](https://user-images.githubusercontent.com/92536730/179394946-eb06bedd-3006-422f-91c2-66463b97bd0c.PNG) 190 | 191 | 192 | 193 | 194 | To launch, use: 195 | ``` 196 | from pytunelogix.clxpidsim import clxsim 197 | 198 | clxsim.main() 199 | 200 | ``` 201 | 202 | _________________________________________________________________________________________________________________________ 203 | 204 | 205 | **Windows Standalone Exe:** 206 | 207 | 208 | ![Pytunelogix](https://user-images.githubusercontent.com/92536730/183046630-5fb861b3-9824-4276-b7f5-1afa51b1236c.PNG) 209 | -------------------------------------------------------------------------------- /examplelaunch.pyw: -------------------------------------------------------------------------------- 1 | from pytunelogix.pidlogger import clxlogger 2 | from pytunelogix.stage1 import csvtuner 3 | from pytunelogix.clxpidsim import clxsim 4 | from pytunelogix.simulate import simulator 5 | 6 | #clxlogger.main() 7 | csvtuner.main() 8 | #clxsim.main() 9 | #simulator.main() -------------------------------------------------------------------------------- /pytunelogix/LICENSE.rst: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Destination2Unknown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pytunelogix/README.md: -------------------------------------------------------------------------------- 1 | # pytunelogix 2 | Python PID Tuner for ControlLogix 3 | -------------------------------------------------------------------------------- /pytunelogix/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Destination2Unknown/pytunelogix/b5dc835f8bdba87be9c4a78bdeba6208acd30c1e/pytunelogix/__init__.py -------------------------------------------------------------------------------- /pytunelogix/_exampleCSVs/prc_reverseaction.csv: -------------------------------------------------------------------------------- 1 | PV;CV;SP 2 | 82.19;0;25 3 | 82.19;0;25 4 | 82.2;0;25 5 | 82.2;0;25 6 | 82.2;0;25 7 | 82.2;0;25 8 | 82.2;0;25 9 | 82.2;0;25 10 | 82.21;0;25 11 | 82.21;0;25 12 | 82.21;0;25 13 | 82.21;0;25 14 | 82.21;0;25 15 | 82.22;0;25 16 | 82.22;0;25 17 | 82.22;0;25 18 | 82.22;0;25 19 | 82.22;0;25 20 | 82.23;0;25 21 | 82.23;0;25 22 | 82.23;0;25 23 | 82.23;0;25 24 | 82.23;0;25 25 | 82.24;0;25 26 | 82.24;0;25 27 | 82.24;0;25 28 | 82.24;0;25 29 | 82.24;0;25 30 | 82.25;0;25 31 | 82.25;0;25 32 | 82.25;0;25 33 | 82.25;0;25 34 | 82.25;0;25 35 | 82.26;0;25 36 | 82.26;0;25 37 | 82.26;0;25 38 | 82.26;0;25 39 | 82.26;0;25 40 | 82.27;0;25 41 | 82.27;0;25 42 | 82.27;0;25 43 | 82.27;0;25 44 | 82.28;0;25 45 | 82.28;0;25 46 | 82.28;0;25 47 | 82.28;0;25 48 | 82.28;0;25 49 | 82.28;0;25 50 | 82.28;0;25 51 | 82.28;0;25 52 | 82.28;0;25 53 | 82.28;0;25 54 | 82.28;0;25 55 | 82.28;0;25 56 | 82.28;0;25 57 | 82.28;0;25 58 | 82.28;0;25 59 | 82.28;0;25 60 | 82.28;0;25 61 | 82.28;0;25 62 | 82.29;0;25 63 | 82.29;0;25 64 | 82.29;0;25 65 | 82.29;0;25 66 | 82.29;0;25 67 | 82.29;0;25 68 | 82.29;0;25 69 | 82.29;0;25 70 | 82.29;0;25 71 | 82.29;0;25 72 | 82.29;0;25 73 | 82.3;0;25 74 | 82.3;0;25 75 | 82.3;0;25 76 | 82.3;0;25 77 | 82.3;0;25 78 | 82.3;0;25 79 | 82.3;0;25 80 | 82.3;0;25 81 | 82.31;0;25 82 | 82.31;0;25 83 | 82.31;0;25 84 | 82.31;0;25 85 | 82.31;0;25 86 | 82.31;0;25 87 | 82.31;0;25 88 | 82.31;0;25 89 | 82.31;0;25 90 | 82.31;0;25 91 | 82.31;0;25 92 | 82.31;0;25 93 | 82.31;0;25 94 | 82.31;0;25 95 | 82.31;0;25 96 | 82.31;20;25 97 | 82.31;20;25 98 | 82.31;20;25 99 | 82.31;20;25 100 | 82.31;20;25 101 | 82.31;20;25 102 | 82.31;20;25 103 | 82.31;20;25 104 | 82.31;20;25 105 | 82.31;20;25 106 | 82.31;20;25 107 | 82.31;20;25 108 | 82.31;20;25 109 | 82.31;20;25 110 | 82.32;20;25 111 | 82.32;20;25 112 | 82.32;20;25 113 | 82.32;20;25 114 | 82.32;20;25 115 | 82.32;20;25 116 | 82.32;20;25 117 | 82.32;20;25 118 | 82.32;20;25 119 | 82.32;20;25 120 | 82.32;20;25 121 | 82.32;20;25 122 | 82.32;20;25 123 | 82.33;20;25 124 | 82.33;20;25 125 | 82.33;20;25 126 | 82.32;20;25 127 | 82.32;20;25 128 | 82.32;20;25 129 | 82.32;20;25 130 | 82.32;20;25 131 | 82.32;20;25 132 | 82.32;20;25 133 | 82.32;20;25 134 | 82.32;20;25 135 | 82.32;20;25 136 | 82.32;20;25 137 | 82.32;20;25 138 | 82.32;20;25 139 | 82.32;20;25 140 | 82.32;20;25 141 | 82.32;20;25 142 | 82.32;20;25 143 | 82.32;20;25 144 | 82.32;20;25 145 | 82.32;20;25 146 | 82.32;20;25 147 | 82.32;20;25 148 | 82.32;20;25 149 | 82.32;20;25 150 | 82.32;20;25 151 | 82.32;20;25 152 | 82.32;20;25 153 | 82.32;20;25 154 | 82.32;20;25 155 | 82.32;20;25 156 | 82.32;20;25 157 | 82.32;20;25 158 | 82.32;20;25 159 | 82.33;20;25 160 | 82.33;20;25 161 | 82.33;20;25 162 | 82.33;20;25 163 | 82.33;20;25 164 | 82.34;20;25 165 | 82.34;20;25 166 | 82.34;20;25 167 | 82.34;20;25 168 | 82.34;20;25 169 | 82.34;20;25 170 | 82.34;20;25 171 | 82.34;20;25 172 | 82.34;20;25 173 | 82.34;20;25 174 | 82.35;20;25 175 | 82.35;20;25 176 | 82.35;20;25 177 | 82.35;20;25 178 | 82.35;20;25 179 | 82.35;20;25 180 | 82.35;20;25 181 | 82.35;20;25 182 | 82.35;20;25 183 | 82.35;20;25 184 | 82.35;20;25 185 | 82.35;20;25 186 | 82.35;20;25 187 | 82.35;20;25 188 | 82.35;20;25 189 | 82.35;20;25 190 | 82.35;20;25 191 | 82.35;20;25 192 | 82.35;20;25 193 | 82.35;20;25 194 | 82.35;20;25 195 | 82.35;20;25 196 | 82.35;20;25 197 | 82.35;20;25 198 | 82.35;20;25 199 | 82.35;20;25 200 | 82.35;20;25 201 | 82.35;20;25 202 | 82.35;20;25 203 | 82.35;20;25 204 | 82.35;20;25 205 | 82.35;20;25 206 | 82.35;20;25 207 | 82.35;20;25 208 | 82.36;20;25 209 | 82.36;20;25 210 | 82.36;20;25 211 | 82.36;20;25 212 | 82.36;20;25 213 | 82.37;20;25 214 | 82.37;20;25 215 | 82.37;20;25 216 | 82.37;20;25 217 | 82.37;20;25 218 | 82.37;20;25 219 | 82.37;20;25 220 | 82.37;20;25 221 | 82.38;20;25 222 | 82.38;20;25 223 | 82.38;20;25 224 | 82.38;20;25 225 | 82.38;20;25 226 | 82.38;20;25 227 | 82.38;20;25 228 | 82.39;20;25 229 | 82.39;20;25 230 | 82.39;20;25 231 | 82.39;20;25 232 | 82.39;20;25 233 | 82.4;20;25 234 | 82.4;20;25 235 | 82.4;20;25 236 | 82.4;20;25 237 | 82.4;20;25 238 | 82.4;20;25 239 | 82.4;20;25 240 | 82.4;20;25 241 | 82.41;20;25 242 | 82.41;20;25 243 | 82.41;20;25 244 | 82.41;20;25 245 | 82.41;20;25 246 | 82.41;20;25 247 | 82.41;20;25 248 | 82.41;20;25 249 | 82.41;20;25 250 | 82.4;20;25 251 | 82.4;20;25 252 | 82.4;20;25 253 | 82.4;20;25 254 | 82.4;20;25 255 | 82.4;20;25 256 | 82.4;20;25 257 | 82.4;20;25 258 | 82.4;20;25 259 | 82.4;20;25 260 | 82.4;20;25 261 | 82.4;20;25 262 | 82.4;20;25 263 | 82.4;20;25 264 | 82.4;20;25 265 | 82.4;20;25 266 | 82.4;20;25 267 | 82.4;20;25 268 | 82.4;20;25 269 | 82.4;20;25 270 | 82.4;20;25 271 | 82.4;20;25 272 | 82.4;20;25 273 | 82.4;20;25 274 | 82.4;20;25 275 | 82.41;20;25 276 | 82.41;20;25 277 | 82.41;20;25 278 | 82.41;20;25 279 | 82.41;20;25 280 | 82.41;20;25 281 | 82.41;20;25 282 | 82.42;20;25 283 | 82.42;20;25 284 | 82.42;20;25 285 | 82.42;20;25 286 | 82.42;20;25 287 | 82.42;20;25 288 | 82.42;20;25 289 | 82.42;20;25 290 | 82.42;20;25 291 | 82.42;20;25 292 | 82.42;20;25 293 | 82.42;20;25 294 | 82.42;20;25 295 | 82.42;20;25 296 | 82.43;20;25 297 | 82.43;20;25 298 | 82.43;20;25 299 | 82.43;20;25 300 | 82.43;20;25 301 | 82.43;20;25 302 | 82.44;20;25 303 | 82.44;20;25 304 | 82.44;20;25 305 | 82.44;20;25 306 | 82.44;20;25 307 | 82.44;20;25 308 | 82.44;20;25 309 | 82.44;20;25 310 | 82.44;20;25 311 | 82.44;20;25 312 | 82.44;20;25 313 | 82.44;20;25 314 | 82.45;20;25 315 | 82.45;20;25 316 | 82.45;20;25 317 | 82.45;20;25 318 | 82.45;20;25 319 | 82.45;20;25 320 | 82.45;20;25 321 | 82.45;20;25 322 | 82.45;20;25 323 | 82.45;20;25 324 | 82.45;20;25 325 | 82.45;20;25 326 | 82.45;20;25 327 | 82.45;20;25 328 | 82.45;20;25 329 | 82.45;20;25 330 | 82.45;20;25 331 | 82.45;20;25 332 | 82.45;20;25 333 | 82.45;20;25 334 | 82.45;20;25 335 | 82.45;20;25 336 | 82.45;20;25 337 | 82.45;20;25 338 | 82.46;20;25 339 | 82.46;20;25 340 | 82.46;20;25 341 | 82.46;20;25 342 | 82.46;20;25 343 | 82.46;20;25 344 | 82.46;20;25 345 | 82.46;20;25 346 | 82.46;20;25 347 | 82.46;20;25 348 | 82.46;20;25 349 | 82.46;20;25 350 | 82.46;20;25 351 | 82.46;20;25 352 | 82.46;20;25 353 | 82.46;20;25 354 | 82.46;20;25 355 | 82.46;20;25 356 | 82.46;20;25 357 | 82.46;20;25 358 | 82.46;20;25 359 | 82.46;20;25 360 | 82.46;20;25 361 | 82.46;20;25 362 | 82.46;20;25 363 | 82.46;20;25 364 | 82.46;20;25 365 | 82.46;20;25 366 | 82.46;20;25 367 | 82.46;20;25 368 | 82.45;20;25 369 | 82.45;20;25 370 | 82.45;20;25 371 | 82.45;20;25 372 | 82.45;20;25 373 | 82.45;20;25 374 | 82.45;20;25 375 | 82.45;20;25 376 | 82.45;20;25 377 | 82.45;20;25 378 | 82.46;20;25 379 | 82.46;20;25 380 | 82.46;20;25 381 | 82.46;20;25 382 | 82.46;20;25 383 | 82.46;20;25 384 | 82.46;20;25 385 | 82.46;20;25 386 | 82.46;20;25 387 | 82.46;20;25 388 | 82.46;20;25 389 | 82.45;20;25 390 | 82.45;20;25 391 | 82.45;20;25 392 | 82.45;20;25 393 | 82.45;20;25 394 | 82.45;20;25 395 | 82.45;20;25 396 | 82.45;20;25 397 | 82.45;20;25 398 | 82.45;20;25 399 | 82.45;20;25 400 | 82.46;20;25 401 | 82.46;20;25 402 | 82.46;20;25 403 | 82.46;20;25 404 | 82.46;20;25 405 | 82.46;20;25 406 | 82.46;20;25 407 | 82.46;20;25 408 | 82.46;20;25 409 | 82.46;20;25 410 | 82.46;20;25 411 | 82.45;20;25 412 | 82.45;20;25 413 | 82.45;20;25 414 | 82.45;20;25 415 | 82.45;20;25 416 | 82.45;20;25 417 | 82.45;20;25 418 | 82.45;20;25 419 | 82.45;20;25 420 | 82.45;20;25 421 | 82.45;20;25 422 | 82.45;20;25 423 | 82.45;20;25 424 | 82.45;20;25 425 | 82.45;20;25 426 | 82.45;20;25 427 | 82.45;20;25 428 | 82.45;20;25 429 | 82.45;20;25 430 | 82.45;20;25 431 | 82.45;20;25 432 | 82.45;20;25 433 | 82.45;20;25 434 | 82.45;20;25 435 | 82.45;20;25 436 | 82.45;20;25 437 | 82.45;20;25 438 | 82.45;20;25 439 | 82.45;20;25 440 | 82.45;20;25 441 | 82.45;20;25 442 | 82.45;20;25 443 | 82.45;20;25 444 | 82.45;20;25 445 | 82.45;20;25 446 | 82.45;20;25 447 | 82.45;20;25 448 | 82.45;20;25 449 | 82.45;20;25 450 | 82.45;20;25 451 | 82.46;20;25 452 | 82.46;20;25 453 | 82.46;20;25 454 | 82.46;20;25 455 | 82.46;20;25 456 | 82.46;20;25 457 | 82.46;20;25 458 | 82.46;20;25 459 | 82.46;20;25 460 | 82.46;20;25 461 | 82.45;20;25 462 | 82.45;20;25 463 | 82.45;20;25 464 | 82.45;20;25 465 | 82.45;20;25 466 | 82.45;20;25 467 | 82.45;20;25 468 | 82.45;20;25 469 | 82.45;20;25 470 | 82.45;20;25 471 | 82.45;20;25 472 | 82.45;20;25 473 | 82.45;20;25 474 | 82.45;20;25 475 | 82.45;20;25 476 | 82.45;20;25 477 | 82.45;20;25 478 | 82.45;20;25 479 | 82.45;20;25 480 | 82.45;20;25 481 | 82.45;20;25 482 | 82.45;20;25 483 | 82.45;20;25 484 | 82.45;20;25 485 | 82.44;20;25 486 | 82.44;20;25 487 | 82.44;20;25 488 | 82.44;20;25 489 | 82.44;20;25 490 | 82.44;20;25 491 | 82.44;20;25 492 | 82.44;20;25 493 | 82.44;20;25 494 | 82.44;20;25 495 | 82.44;20;25 496 | 82.44;20;25 497 | 82.44;20;25 498 | 82.44;20;25 499 | 82.44;20;25 500 | 82.44;20;25 501 | 82.44;20;25 502 | 82.44;20;25 503 | 82.44;20;25 504 | 82.44;20;25 505 | 82.44;20;25 506 | 82.44;20;25 507 | 82.44;20;25 508 | 82.44;20;25 509 | 82.44;20;25 510 | 82.44;20;25 511 | 82.45;20;25 512 | 82.45;20;25 513 | 82.45;20;25 514 | 82.45;20;25 515 | 82.45;20;25 516 | 82.45;20;25 517 | 82.45;20;25 518 | 82.45;20;25 519 | 82.45;20;25 520 | 82.45;20;25 521 | 82.45;20;25 522 | 82.46;20;25 523 | 82.46;20;25 524 | 82.46;20;25 525 | 82.46;20;25 526 | 82.46;20;25 527 | 82.46;20;25 528 | 82.46;20;25 529 | 82.46;20;25 530 | 82.46;20;25 531 | 82.46;20;25 532 | 82.46;20;25 533 | 82.46;20;25 534 | 82.46;20;25 535 | 82.46;20;25 536 | 82.46;20;25 537 | 82.46;20;25 538 | 82.46;20;25 539 | 82.46;20;25 540 | 82.46;20;25 541 | 82.46;20;25 542 | 82.46;20;25 543 | 82.46;20;25 544 | 82.46;20;25 545 | 82.46;20;25 546 | 82.46;20;25 547 | 82.46;20;25 548 | 82.46;20;25 549 | 82.46;20;25 550 | 82.46;20;25 551 | 82.46;20;25 552 | 82.46;20;25 553 | 82.46;20;25 554 | 82.45;20;25 555 | 82.45;20;25 556 | 82.46;20;25 557 | 82.46;20;25 558 | 82.46;20;25 559 | 82.46;20;25 560 | 82.46;20;25 561 | 82.46;20;25 562 | 82.46;20;25 563 | 82.46;20;25 564 | 82.46;20;25 565 | 82.46;20;25 566 | 82.46;20;25 567 | 82.46;20;25 568 | 82.46;20;25 569 | 82.46;20;25 570 | 82.47;20;25 571 | 82.47;20;25 572 | 82.47;20;25 573 | 82.47;20;25 574 | 82.47;20;25 575 | 82.47;20;25 576 | 82.47;20;25 577 | 82.47;20;25 578 | 82.47;20;25 579 | 82.47;20;25 580 | 82.47;20;25 581 | 82.47;20;25 582 | 82.47;20;25 583 | 82.47;20;25 584 | 82.48;20;25 585 | 82.48;20;25 586 | 82.48;20;25 587 | 82.48;20;25 588 | 82.48;20;25 589 | 82.48;20;25 590 | 82.48;20;25 591 | 82.48;20;25 592 | 82.48;20;25 593 | 82.48;20;25 594 | 82.48;20;25 595 | 82.48;20;25 596 | 82.49;20;25 597 | 82.49;20;25 598 | 82.49;20;25 599 | 82.49;20;25 600 | 82.49;20;25 601 | 82.49;20;25 602 | 82.49;20;25 603 | 82.49;20;25 604 | 82.5;20;25 605 | 82.5;20;25 606 | 82.5;20;25 607 | 82.5;20;25 608 | 82.5;20;25 609 | 82.5;20;25 610 | 82.5;20;25 611 | 82.5;20;25 612 | 82.5;20;25 613 | 82.5;20;25 614 | 82.51;20;25 615 | 82.51;20;25 616 | 82.51;20;25 617 | 82.51;20;25 618 | 82.51;20;25 619 | 82.51;20;25 620 | 82.51;20;25 621 | 82.51;20;25 622 | 82.51;20;25 623 | 82.51;20;25 624 | 82.51;20;25 625 | 82.51;20;25 626 | 82.51;20;25 627 | 82.51;20;25 628 | 82.51;20;25 629 | 82.51;20;25 630 | 82.51;20;25 631 | 82.51;20;25 632 | 82.51;20;25 633 | 82.51;20;25 634 | 82.5;20;25 635 | 82.5;20;25 636 | 82.51;20;25 637 | 82.51;20;25 638 | 82.51;20;25 639 | 82.51;20;25 640 | 82.51;20;25 641 | 82.51;20;25 642 | 82.51;20;25 643 | 82.51;20;25 644 | 82.51;20;25 645 | 82.51;20;25 646 | 82.51;20;25 647 | 82.51;20;25 648 | 82.51;20;25 649 | 82.51;20;25 650 | 82.51;20;25 651 | 82.5;20;25 652 | 82.5;20;25 653 | 82.5;20;25 654 | 82.5;20;25 655 | 82.5;20;25 656 | 82.5;20;25 657 | 82.5;20;25 658 | 82.5;20;25 659 | 82.5;20;25 660 | 82.5;20;25 661 | 82.5;20;25 662 | 82.5;20;25 663 | 82.5;20;25 664 | 82.5;20;25 665 | 82.5;20;25 666 | 82.5;20;25 667 | 82.5;20;25 668 | 82.5;20;25 669 | 82.5;20;25 670 | 82.51;20;25 671 | 82.51;20;25 672 | 82.51;20;25 673 | 82.51;20;25 674 | 82.51;20;25 675 | 82.51;20;25 676 | 82.51;20;25 677 | 82.51;20;25 678 | 82.52;20;25 679 | 82.52;20;25 680 | 82.52;20;25 681 | 82.52;20;25 682 | 82.52;20;25 683 | 82.52;20;25 684 | 82.52;20;25 685 | 82.52;20;25 686 | 82.52;20;25 687 | 82.52;20;25 688 | 82.52;20;25 689 | 82.52;20;25 690 | 82.52;20;25 691 | 82.52;20;25 692 | 82.52;20;25 693 | 82.52;20;25 694 | 82.52;20;25 695 | 82.52;20;25 696 | 82.53;20;25 697 | 82.53;20;25 698 | 82.53;20;25 699 | 82.53;20;25 700 | 82.53;20;25 701 | 82.53;20;25 702 | 82.54;20;25 703 | 82.54;20;25 704 | 82.54;20;25 705 | 82.54;20;25 706 | 82.54;20;25 707 | 82.54;20;25 708 | 82.54;20;25 709 | 82.54;20;25 710 | 82.54;20;25 711 | 82.54;20;25 712 | 82.54;20;25 713 | 82.53;20;25 714 | 82.53;20;25 715 | 82.53;20;25 716 | 82.54;20;25 717 | 82.54;20;25 718 | 82.54;20;25 719 | 82.54;20;25 720 | 82.54;20;25 721 | 82.54;20;25 722 | 82.54;20;25 723 | 82.54;20;25 724 | 82.54;20;25 725 | 82.54;20;25 726 | 82.54;20;25 727 | 82.54;20;25 728 | 82.54;20;25 729 | 82.54;20;25 730 | 82.54;20;25 731 | 82.54;20;25 732 | 82.54;20;25 733 | 82.53;20;25 734 | 82.53;20;25 735 | 82.53;20;25 736 | 82.53;20;25 737 | 82.53;20;25 738 | 82.52;20;25 739 | 82.52;20;25 740 | 82.52;20;25 741 | 82.51;20;25 742 | 82.51;20;25 743 | 82.51;20;25 744 | 82.5;20;25 745 | 82.5;20;25 746 | 82.49;20;25 747 | 82.48;20;25 748 | 82.48;20;25 749 | 82.47;20;25 750 | 82.46;20;25 751 | 82.46;20;25 752 | 82.45;20;25 753 | 82.44;20;25 754 | 82.43;20;25 755 | 82.42;20;25 756 | 82.41;20;25 757 | 82.4;20;25 758 | 82.39;20;25 759 | 82.38;20;25 760 | 82.37;20;25 761 | 82.35;20;25 762 | 82.34;20;25 763 | 82.33;20;25 764 | 82.31;20;25 765 | 82.3;20;25 766 | 82.29;20;25 767 | 82.27;20;25 768 | 82.26;20;25 769 | 82.24;20;25 770 | 82.23;20;25 771 | 82.21;20;25 772 | 82.19;20;25 773 | 82.18;20;25 774 | 82.16;20;25 775 | 82.14;20;25 776 | 82.13;20;25 777 | 82.11;20;25 778 | 82.09;20;25 779 | 82.08;20;25 780 | 82.06;20;25 781 | 82.04;20;25 782 | 82.02;20;25 783 | 82.01;20;25 784 | 81.99;20;25 785 | 81.97;20;25 786 | 81.95;20;25 787 | 81.93;20;25 788 | 81.91;20;25 789 | 81.89;20;25 790 | 81.87;20;25 791 | 81.85;20;25 792 | 81.83;20;25 793 | 81.81;20;25 794 | 81.79;20;25 795 | 81.77;20;25 796 | 81.75;20;25 797 | 81.73;20;25 798 | 81.7;20;25 799 | 81.68;20;25 800 | 81.65;20;25 801 | 81.63;20;25 802 | 81.6;20;25 803 | 81.58;20;25 804 | 81.56;20;25 805 | 81.53;20;25 806 | 81.51;20;25 807 | 81.48;20;25 808 | 81.46;20;25 809 | 81.43;20;25 810 | 81.4;20;25 811 | 81.38;20;25 812 | 81.35;20;25 813 | 81.33;20;25 814 | 81.3;20;25 815 | 81.27;20;25 816 | 81.25;20;25 817 | 81.22;20;25 818 | 81.19;20;25 819 | 81.17;20;25 820 | 81.14;20;25 821 | 81.11;20;25 822 | 81.09;20;25 823 | 81.03;20;25 824 | 81.03;20;25 825 | 81;20;25 826 | 80.97;20;25 827 | 80.94;20;25 828 | 80.91;20;25 829 | 80.88;20;25 830 | 80.85;20;25 831 | 80.81;20;25 832 | 80.78;20;25 833 | 80.72;20;25 834 | 80.72;20;25 835 | 80.69;20;25 836 | 80.66;20;25 837 | 80.62;20;25 838 | 80.59;20;25 839 | 80.56;20;25 840 | 80.53;20;25 841 | 80.5;20;25 842 | 80.46;20;25 843 | 80.4;20;25 844 | 80.4;20;25 845 | 80.37;20;25 846 | 80.33;20;25 847 | 80.3;20;25 848 | 80.26;20;25 849 | 80.23;20;25 850 | 80.19;20;25 851 | 80.16;20;25 852 | 80.12;20;25 853 | 80.09;20;25 854 | 80.05;20;25 855 | 80.02;20;25 856 | 79.98;20;25 857 | 79.94;20;25 858 | 79.91;20;25 859 | 79.87;20;25 860 | 79.83;20;25 861 | 79.8;20;25 862 | 79.76;20;25 863 | 79.72;20;25 864 | 79.68;20;25 865 | 79.64;20;25 866 | 79.61;20;25 867 | 79.57;20;25 868 | 79.53;20;25 869 | 79.49;20;25 870 | 79.45;20;25 871 | 79.41;20;25 872 | 79.37;20;25 873 | 79.33;20;25 874 | 79.29;20;25 875 | 79.26;20;25 876 | 79.22;20;25 877 | 79.18;20;25 878 | 79.14;20;25 879 | 79.11;20;25 880 | 79.07;20;25 881 | 79.03;20;25 882 | 78.99;20;25 883 | 78.95;20;25 884 | 78.91;20;25 885 | 78.87;20;25 886 | 78.83;20;25 887 | 78.79;20;25 888 | 78.75;20;25 889 | 78.71;20;25 890 | 78.66;20;25 891 | 78.62;20;25 892 | 78.58;20;25 893 | 78.49;20;25 894 | 78.49;20;25 895 | 78.45;20;25 896 | 78.41;20;25 897 | 78.37;20;25 898 | 78.33;20;25 899 | 78.29;20;25 900 | 78.24;20;25 901 | 78.2;20;25 902 | 78.16;20;25 903 | 78.12;20;25 904 | 78.07;20;25 905 | 78.03;20;25 906 | 77.99;20;25 907 | 77.94;20;25 908 | 77.9;20;25 909 | 77.85;20;25 910 | 77.81;20;25 911 | 77.76;20;25 912 | 77.72;20;25 913 | 77.67;20;25 914 | 77.63;20;25 915 | 77.58;20;25 916 | 77.54;20;25 917 | 77.5;20;25 918 | 77.45;20;25 919 | 77.41;20;25 920 | 77.37;20;25 921 | 77.32;20;25 922 | 77.28;20;25 923 | 77.24;20;25 924 | 77.19;20;25 925 | 77.15;20;25 926 | 77.1;20;25 927 | 77.05;20;25 928 | 77.01;20;25 929 | 76.96;20;25 930 | 76.91;20;25 931 | 76.86;20;25 932 | 76.82;20;25 933 | 76.77;20;25 934 | 76.72;20;25 935 | 76.67;20;25 936 | 76.63;20;25 937 | 76.58;20;25 938 | 76.53;20;25 939 | 76.48;20;25 940 | 76.44;20;25 941 | 76.39;20;25 942 | 76.34;20;25 943 | 76.29;20;25 944 | 76.24;20;25 945 | 76.19;20;25 946 | 76.15;20;25 947 | 76.1;20;25 948 | 76.06;20;25 949 | 76.01;20;25 950 | 75.96;20;25 951 | 75.92;20;25 952 | 75.87;20;25 953 | 75.82;20;25 954 | 75.78;20;25 955 | 75.73;20;25 956 | 75.68;20;25 957 | 75.63;20;25 958 | 75.58;20;25 959 | 75.54;20;25 960 | 75.49;20;25 961 | 75.44;20;25 962 | 75.39;20;25 963 | 75.34;20;25 964 | 75.3;20;25 965 | 75.25;20;25 966 | 75.2;20;25 967 | 75.15;20;25 968 | 75.1;20;25 969 | 75.05;20;25 970 | 75;20;25 971 | 74.95;20;25 972 | 74.9;20;25 973 | 74.85;20;25 974 | 74.8;20;25 975 | 74.75;20;25 976 | 74.7;20;25 977 | 74.65;20;25 978 | 74.6;20;25 979 | 74.55;20;25 980 | 74.49;20;25 981 | 74.39;20;25 982 | 74.39;20;25 983 | 74.34;20;25 984 | 74.29;20;25 985 | 74.24;20;25 986 | 74.19;20;25 987 | 74.13;20;25 988 | 74.08;20;25 989 | 74.03;20;25 990 | 73.98;20;25 991 | 73.93;20;25 992 | 73.88;20;25 993 | 73.82;20;25 994 | 73.77;20;25 995 | 73.72;20;25 996 | 73.67;20;25 997 | 73.62;20;25 998 | 73.56;20;25 999 | 73.51;20;25 1000 | 73.46;20;25 1001 | 73.41;20;25 1002 | 73.36;20;25 1003 | 73.31;20;25 1004 | 73.25;20;25 1005 | 73.2;20;25 1006 | 73.15;20;25 1007 | 73.1;20;25 1008 | 73.05;20;25 1009 | 73;20;25 1010 | 72.95;20;25 1011 | 72.9;20;25 1012 | 72.84;20;25 1013 | 72.79;20;25 1014 | 72.74;20;25 1015 | 72.69;20;25 1016 | 72.64;20;25 1017 | 72.59;20;25 1018 | 72.53;20;25 1019 | 72.48;20;25 1020 | 72.43;20;25 1021 | 72.38;20;25 1022 | 72.33;20;25 1023 | 72.28;20;25 1024 | 72.22;20;25 1025 | 72.17;20;25 1026 | 72.12;20;25 1027 | 72.07;20;25 1028 | 72.02;20;25 1029 | 71.96;20;25 1030 | 71.91;20;25 1031 | 71.86;20;25 1032 | 71.81;20;25 1033 | 71.76;20;25 1034 | 71.7;20;25 1035 | 71.65;20;25 1036 | 71.6;20;25 1037 | 71.55;20;25 1038 | 71.49;20;25 1039 | 71.44;20;25 1040 | 71.39;20;25 1041 | 71.33;20;25 1042 | 71.28;20;25 1043 | 71.23;20;25 1044 | 71.12;20;25 1045 | 71.12;20;25 1046 | 71.06;20;25 1047 | 71.01;20;25 1048 | 70.95;20;25 1049 | 70.9;20;25 1050 | 70.85;20;25 1051 | 70.79;20;25 1052 | 70.74;20;25 1053 | 70.68;20;25 1054 | 70.57;20;25 1055 | 70.57;20;25 1056 | 70.52;20;25 1057 | 70.46;20;25 1058 | 70.41;20;25 1059 | 70.36;20;25 1060 | 70.3;20;25 1061 | 70.25;20;25 1062 | 70.19;20;25 1063 | 70.14;20;25 1064 | 70.08;20;25 1065 | 70.03;20;25 1066 | 69.97;20;25 1067 | 69.92;20;25 1068 | 69.86;20;25 1069 | 69.81;20;25 1070 | 69.75;20;25 1071 | 69.7;20;25 1072 | 69.64;20;25 1073 | 69.59;20;25 1074 | 69.53;20;25 1075 | 69.48;20;25 1076 | 69.42;20;25 1077 | 69.37;20;25 1078 | 69.31;20;25 1079 | 69.26;20;25 1080 | 69.2;20;25 1081 | 69.15;20;25 1082 | 69.09;20;25 1083 | 69.04;20;25 1084 | 68.98;20;25 1085 | 68.93;20;25 1086 | 68.88;20;25 1087 | 68.82;20;25 1088 | 68.77;20;25 1089 | 68.71;20;25 1090 | 68.66;20;25 1091 | 68.61;20;25 1092 | 68.55;20;25 1093 | 68.5;20;25 1094 | 68.44;20;25 1095 | 68.39;20;25 1096 | 68.34;20;25 1097 | 68.28;20;25 1098 | 68.23;20;25 1099 | 68.17;20;25 1100 | 68.12;20;25 1101 | 68.06;20;25 1102 | 68.01;20;25 1103 | 67.96;20;25 1104 | 67.9;20;25 1105 | 67.85;20;25 1106 | 67.79;20;25 1107 | 67.74;20;25 1108 | 67.69;20;25 1109 | 67.63;20;25 1110 | 67.58;20;25 1111 | 67.52;20;25 1112 | 67.47;20;25 1113 | 67.41;20;25 1114 | 67.36;20;25 1115 | 67.31;20;25 1116 | 67.25;20;25 1117 | 67.2;20;25 1118 | 67.15;20;25 1119 | 67.09;20;25 1120 | 67.04;20;25 1121 | 66.98;20;25 1122 | 66.93;20;25 1123 | 66.88;20;25 1124 | 66.82;20;25 1125 | 66.77;20;25 1126 | 66.71;20;25 1127 | 66.66;20;25 1128 | 66.61;20;25 1129 | 66.55;20;25 1130 | 66.5;20;25 1131 | 66.44;20;25 1132 | 66.39;20;25 1133 | 66.33;20;25 1134 | 66.28;20;25 1135 | 66.22;20;25 1136 | 66.17;20;25 1137 | 66.12;20;25 1138 | 66.06;20;25 1139 | 66.01;20;25 1140 | 65.95;20;25 1141 | 65.95;20;25 1142 | 65.84;20;25 1143 | 65.84;20;25 1144 | 65.79;20;25 1145 | 65.68;20;25 1146 | 65.68;20;25 1147 | 65.62;20;25 1148 | 65.57;20;25 1149 | 65.52;20;25 1150 | 65.46;20;25 1151 | 65.41;20;25 1152 | 65.36;20;25 1153 | 65.3;20;25 1154 | 65.25;20;25 1155 | 65.14;20;25 1156 | 65.14;20;25 1157 | 65.09;20;25 1158 | 65.03;20;25 1159 | 64.98;20;25 1160 | 64.92;20;25 1161 | 64.87;20;25 1162 | 64.81;20;25 1163 | 64.76;20;25 1164 | 64.7;20;25 1165 | 64.59;20;25 1166 | 64.59;20;25 1167 | 64.54;20;25 1168 | 64.48;20;25 1169 | 64.43;20;25 1170 | 64.37;20;25 1171 | 64.32;20;25 1172 | 64.27;20;25 1173 | 64.21;20;25 1174 | 64.16;20;25 1175 | 64.05;20;25 1176 | 64.05;20;25 1177 | 63.99;20;25 1178 | 63.94;20;25 1179 | 63.88;20;25 1180 | 63.83;20;25 1181 | 63.78;20;25 1182 | 63.72;20;25 1183 | 63.67;20;25 1184 | 63.61;20;25 1185 | 63.56;20;25 1186 | 63.5;20;25 1187 | 63.45;20;25 1188 | 63.39;20;25 1189 | 63.34;20;25 1190 | 63.28;20;25 1191 | 63.23;20;25 1192 | 63.17;20;25 1193 | 63.12;20;25 1194 | 63.07;20;25 1195 | 63.01;20;25 1196 | 62.96;20;25 1197 | 62.9;20;25 1198 | 62.85;20;25 1199 | 62.8;20;25 1200 | 62.74;20;25 1201 | 62.69;20;25 1202 | 62.64;20;25 1203 | 62.58;20;25 1204 | 62.53;20;25 1205 | 62.48;20;25 1206 | 62.42;20;25 1207 | 62.37;20;25 1208 | 62.32;20;25 1209 | 62.27;20;25 1210 | 62.21;20;25 1211 | 62.16;20;25 1212 | 62.11;20;25 1213 | 62.06;20;25 1214 | 62;20;25 1215 | 61.95;20;25 1216 | 61.9;20;25 1217 | 61.85;20;25 1218 | 61.79;20;25 1219 | 61.74;20;25 1220 | 61.68;20;25 1221 | 61.63;20;25 1222 | 61.57;20;25 1223 | 61.52;20;25 1224 | 61.47;20;25 1225 | 61.41;20;25 1226 | 61.36;20;25 1227 | 61.31;20;25 1228 | 61.25;20;25 1229 | 61.2;20;25 1230 | 61.15;20;25 1231 | 61.09;20;25 1232 | 61.04;20;25 1233 | 60.99;20;25 1234 | 60.94;20;25 1235 | 60.88;20;25 1236 | 60.83;20;25 1237 | 60.78;20;25 1238 | 60.72;20;25 1239 | 60.67;20;25 1240 | 60.62;20;25 1241 | 60.56;20;25 1242 | 60.51;20;25 1243 | 60.46;20;25 1244 | 60.41;20;25 1245 | 60.35;20;25 1246 | 60.3;20;25 1247 | 60.25;20;25 1248 | 60.2;20;25 1249 | 60.14;20;25 1250 | 60.09;20;25 1251 | 60.04;20;25 1252 | 59.99;20;25 1253 | 59.93;20;25 1254 | 59.88;20;25 1255 | 59.83;20;25 1256 | 59.78;20;25 1257 | 59.73;20;25 1258 | 59.68;20;25 1259 | 59.62;20;25 1260 | 59.57;20;25 1261 | 59.52;20;25 1262 | 59.47;20;25 1263 | 59.42;20;25 1264 | 59.37;20;25 1265 | 59.31;20;25 1266 | 59.26;20;25 1267 | 59.21;20;25 1268 | 59.16;20;25 1269 | 59.11;20;25 1270 | 59.05;20;25 1271 | 59;20;25 1272 | 58.95;20;25 1273 | 58.9;20;25 1274 | 58.85;20;25 1275 | 58.8;20;25 1276 | 58.74;20;25 1277 | 58.7;20;25 1278 | 58.65;20;25 1279 | 58.6;20;25 1280 | 58.55;20;25 1281 | 58.5;20;25 1282 | 58.45;20;25 1283 | 58.4;20;25 1284 | 58.35;20;25 1285 | 58.3;20;25 1286 | 58.2;20;25 1287 | 58.2;20;25 1288 | 58.15;20;25 1289 | 58.1;20;25 1290 | 58.04;20;25 1291 | 57.99;20;25 1292 | 57.94;20;25 1293 | 57.89;20;25 1294 | 57.84;20;25 1295 | 57.79;20;25 1296 | 57.74;20;25 1297 | 57.69;20;25 1298 | 57.64;20;25 1299 | 57.59;20;25 1300 | 57.54;20;25 1301 | 57.49;20;25 1302 | 57.44;20;25 1303 | 57.39;20;25 1304 | 57.34;20;25 1305 | 57.3;20;25 1306 | 57.25;20;25 1307 | 57.2;20;25 1308 | 57.14;20;25 1309 | 57.09;20;25 1310 | 57.04;20;25 1311 | 56.99;20;25 1312 | 56.94;20;25 1313 | 56.89;20;25 1314 | 56.84;20;25 1315 | 56.79;20;25 1316 | 56.74;20;25 1317 | 56.69;20;25 1318 | 56.64;20;25 1319 | 56.59;20;25 1320 | 56.54;20;25 1321 | 56.49;20;25 1322 | 56.44;20;25 1323 | 56.39;20;25 1324 | 56.34;20;25 1325 | 56.3;20;25 1326 | 56.25;20;25 1327 | 56.2;20;25 1328 | 56.15;20;25 1329 | 56.1;20;25 1330 | 56.05;20;25 1331 | 56;20;25 1332 | 55.95;20;25 1333 | 55.9;20;25 1334 | 55.85;20;25 1335 | 55.8;20;25 1336 | 55.75;20;25 1337 | 55.7;20;25 1338 | 55.66;20;25 1339 | 55.61;20;25 1340 | 55.56;20;25 1341 | 55.52;20;25 1342 | 55.47;20;25 1343 | 55.42;20;25 1344 | 55.38;20;25 1345 | 55.33;20;25 1346 | 55.28;20;25 1347 | 55.23;20;25 1348 | 55.19;20;25 1349 | 55.14;20;25 1350 | 55.1;20;25 1351 | 55.05;20;25 1352 | 55;20;25 1353 | 54.96;20;25 1354 | 54.91;20;25 1355 | 54.86;20;25 1356 | 54.82;20;25 1357 | 54.77;20;25 1358 | 54.72;20;25 1359 | 54.67;20;25 1360 | 54.63;20;25 1361 | 54.58;20;25 1362 | 54.53;20;25 1363 | 54.48;20;25 1364 | 54.44;20;25 1365 | 54.39;20;25 1366 | 54.34;20;25 1367 | 54.29;20;25 1368 | 54.25;20;25 1369 | 54.2;20;25 1370 | 54.15;20;25 1371 | 54.11;20;25 1372 | 54.06;20;25 1373 | 54.02;20;25 1374 | 53.97;20;25 1375 | 53.92;20;25 1376 | 53.88;20;25 1377 | 53.83;20;25 1378 | 53.78;20;25 1379 | 53.73;20;25 1380 | 53.69;20;25 1381 | 53.64;20;25 1382 | 53.59;20;25 1383 | 53.55;20;25 1384 | 53.5;20;25 1385 | 53.45;20;25 1386 | 53.41;20;25 1387 | 53.36;20;25 1388 | 53.32;20;25 1389 | 53.27;20;25 1390 | 53.23;20;25 1391 | 53.18;20;25 1392 | 53.13;20;25 1393 | 53.09;20;25 1394 | 53.04;20;25 1395 | 53;20;25 1396 | 52.95;20;25 1397 | 52.91;20;25 1398 | 52.87;20;25 1399 | 52.82;20;25 1400 | 52.78;20;25 1401 | 52.73;20;25 1402 | 52.69;20;25 1403 | 52.64;20;25 1404 | 52.6;20;25 1405 | 52.56;20;25 1406 | 52.51;20;25 1407 | 52.47;20;25 1408 | 52.42;20;25 1409 | 52.38;20;25 1410 | 52.33;20;25 1411 | 52.29;20;25 1412 | 52.24;20;25 1413 | 52.2;20;25 1414 | 52.15;20;25 1415 | 52.11;20;25 1416 | 52.06;20;25 1417 | 52.02;20;25 1418 | 51.97;20;25 1419 | 51.93;20;25 1420 | 51.88;20;25 1421 | 51.84;20;25 1422 | 51.79;20;25 1423 | 51.74;20;25 1424 | 51.7;20;25 1425 | 51.65;20;25 1426 | 51.61;20;25 1427 | 51.57;20;25 1428 | 51.52;20;25 1429 | 51.48;20;25 1430 | 51.44;20;25 1431 | 51.39;20;25 1432 | 51.35;20;25 1433 | 51.31;20;25 1434 | 51.26;20;25 1435 | 51.22;20;25 1436 | 51.18;20;25 1437 | 51.13;20;25 1438 | 51.09;20;25 1439 | 51.05;20;25 1440 | 51.01;20;25 1441 | 50.96;20;25 1442 | 50.92;20;25 1443 | 50.88;20;25 1444 | 50.84;20;25 1445 | 50.79;20;25 1446 | 50.75;20;25 1447 | 50.71;20;25 1448 | 50.67;20;25 1449 | 50.63;20;25 1450 | 50.58;20;25 1451 | 50.54;20;25 1452 | 50.5;20;25 1453 | 50.46;20;25 1454 | 50.41;20;25 1455 | 50.37;20;25 1456 | 50.33;20;25 1457 | 50.29;20;25 1458 | 50.25;20;25 1459 | 50.21;20;25 1460 | 50.17;20;25 1461 | 50.13;20;25 1462 | 50.09;20;25 1463 | 50.05;20;25 1464 | 50;20;25 1465 | 49.96;20;25 1466 | 49.92;20;25 1467 | 49.88;20;25 1468 | 49.84;20;25 1469 | 49.8;20;25 1470 | 49.76;20;25 1471 | 49.72;20;25 1472 | 49.68;20;25 1473 | 49.64;20;25 1474 | 49.59;20;25 1475 | 49.55;20;25 1476 | 49.51;20;25 1477 | 49.47;20;25 1478 | 49.43;20;25 1479 | 49.38;20;25 1480 | 49.34;20;25 1481 | 49.3;20;25 1482 | 49.26;20;25 1483 | 49.22;20;25 1484 | 49.17;20;25 1485 | 49.13;20;25 1486 | 49.09;20;25 1487 | 49.05;20;25 1488 | 49.01;20;25 1489 | 48.97;20;25 1490 | 48.93;20;25 1491 | 48.89;20;25 1492 | 48.85;20;25 1493 | 48.81;20;25 1494 | 48.77;20;25 1495 | 48.72;20;25 1496 | 48.68;20;25 1497 | 48.64;20;25 1498 | 48.6;20;25 1499 | 48.56;20;25 1500 | 48.52;20;25 1501 | 48.48;20;25 1502 | 48.43;20;25 1503 | 48.39;20;25 1504 | 48.35;20;25 1505 | 48.31;20;25 1506 | 48.27;20;25 1507 | 48.23;20;25 1508 | 48.19;20;25 1509 | 48.15;20;25 1510 | 48.11;20;25 1511 | 48.07;20;25 1512 | 48.03;20;25 1513 | 47.99;20;25 1514 | 47.95;20;25 1515 | 47.91;20;25 1516 | 47.87;20;25 1517 | 47.83;20;25 1518 | 47.79;20;25 1519 | 47.75;20;25 1520 | 47.71;20;25 1521 | 47.67;20;25 1522 | 47.63;20;25 1523 | 47.59;20;25 1524 | 47.55;20;25 1525 | 47.51;20;25 1526 | 47.47;20;25 1527 | 47.43;20;25 1528 | 47.4;20;25 1529 | 47.36;20;25 1530 | 47.32;20;25 1531 | 47.28;20;25 1532 | 47.24;20;25 1533 | 47.21;20;25 1534 | 47.17;20;25 1535 | 47.13;20;25 1536 | 47.09;20;25 1537 | 47.05;20;25 1538 | 47.01;20;25 1539 | 46.97;20;25 1540 | 46.93;20;25 1541 | 46.9;20;25 1542 | 46.86;20;25 1543 | 46.82;20;25 1544 | 46.78;20;25 1545 | 46.74;20;25 1546 | 46.7;20;25 1547 | 46.67;20;25 1548 | 46.63;20;25 1549 | 46.59;20;25 1550 | 46.56;20;25 1551 | 46.52;20;25 1552 | 46.48;20;25 1553 | 46.44;20;25 1554 | 46.41;20;25 1555 | 46.37;20;25 1556 | 46.33;20;25 1557 | 46.3;20;25 1558 | 46.26;20;25 1559 | 46.22;20;25 1560 | 46.18;20;25 1561 | 46.15;20;25 1562 | 46.11;20;25 1563 | 46.07;20;25 1564 | 46.04;20;25 1565 | 46;20;25 1566 | 45.96;20;25 1567 | 45.93;20;25 1568 | 45.89;20;25 1569 | 45.86;20;25 1570 | 45.82;20;25 1571 | 45.78;20;25 1572 | 45.75;20;25 1573 | 45.71;20;25 1574 | 45.68;20;25 1575 | 45.64;20;25 1576 | 45.6;20;25 1577 | 45.57;20;25 1578 | 45.53;20;25 1579 | 45.5;20;25 1580 | 45.46;20;25 1581 | 45.42;20;25 1582 | 45.39;20;25 1583 | 45.35;20;25 1584 | 45.32;20;25 1585 | 45.28;20;25 1586 | 45.25;20;25 1587 | 45.21;20;25 1588 | 45.14;20;25 1589 | 45.14;20;25 1590 | 45.1;20;25 1591 | 45.07;20;25 1592 | 45.03;20;25 1593 | 44.99;20;25 1594 | 44.96;20;25 1595 | 44.92;20;25 1596 | 44.89;20;25 1597 | 44.85;20;25 1598 | 44.78;20;25 1599 | 44.78;20;25 1600 | 44.74;20;25 1601 | 44.71;20;25 1602 | 44.67;20;25 1603 | 44.64;20;25 1604 | 44.6;20;25 1605 | 44.57;20;25 1606 | 44.53;20;25 1607 | 44.5;20;25 1608 | 44.47;20;25 1609 | 44.43;20;25 1610 | 44.4;20;25 1611 | 44.37;20;25 1612 | 44.33;20;25 1613 | 44.3;20;25 1614 | 44.27;20;25 1615 | 44.23;20;25 1616 | 44.2;20;25 1617 | 44.17;20;25 1618 | 44.13;20;25 1619 | 44.1;20;25 1620 | 44.07;20;25 1621 | 44.03;20;25 1622 | 44;20;25 1623 | 43.97;20;25 1624 | 43.94;20;25 1625 | 43.9;20;25 1626 | 43.87;20;25 1627 | 43.83;20;25 1628 | 43.8;20;25 1629 | 43.77;20;25 1630 | 43.73;20;25 1631 | 43.7;20;25 1632 | 43.66;20;25 1633 | 43.63;20;25 1634 | 43.59;20;25 1635 | 43.56;20;25 1636 | 43.53;20;25 1637 | 43.5;20;25 1638 | 43.47;20;25 1639 | 43.43;20;25 1640 | 43.4;20;25 1641 | 43.37;20;25 1642 | 43.34;20;25 1643 | 43.31;20;25 1644 | 43.28;20;25 1645 | 43.25;20;25 1646 | 43.21;20;25 1647 | 43.18;20;25 1648 | 43.15;20;25 1649 | 43.12;20;25 1650 | 43.09;20;25 1651 | 43.06;20;25 1652 | 43.03;20;25 1653 | 43;20;25 1654 | 42.96;20;25 1655 | 42.93;20;25 1656 | 42.9;20;25 1657 | 42.87;20;25 1658 | 42.84;20;25 1659 | 42.81;20;25 1660 | 42.78;20;25 1661 | 42.75;20;25 1662 | 42.72;20;25 1663 | 42.69;20;25 1664 | 42.66;20;25 1665 | 42.62;20;25 1666 | 42.59;20;25 1667 | 42.56;20;25 1668 | 42.53;20;25 1669 | 42.5;20;25 1670 | 42.46;20;25 1671 | 42.43;20;25 1672 | 42.4;20;25 1673 | 42.37;20;25 1674 | 42.34;20;25 1675 | 42.3;20;25 1676 | 42.27;20;25 1677 | 42.24;20;25 1678 | 42.21;20;25 1679 | 42.18;20;25 1680 | 42.15;20;25 1681 | 42.12;20;25 1682 | 42.09;20;25 1683 | 42.06;20;25 1684 | 42.03;20;25 1685 | 42;20;25 1686 | 41.97;20;25 1687 | 41.94;20;25 1688 | 41.91;20;25 1689 | 41.88;20;25 1690 | 41.85;20;25 1691 | 41.82;20;25 1692 | 41.79;20;25 1693 | 41.76;20;25 1694 | 41.73;20;25 1695 | 41.7;20;25 1696 | 41.67;20;25 1697 | 41.64;20;25 1698 | 41.61;20;25 1699 | 41.58;20;25 1700 | 41.55;20;25 1701 | 41.52;20;25 1702 | 41.49;20;25 1703 | 41.46;20;25 1704 | 41.44;20;25 1705 | 41.41;20;25 1706 | 41.38;20;25 1707 | 41.35;20;25 1708 | 41.32;20;25 1709 | 41.29;20;25 1710 | 41.26;20;25 1711 | 41.23;20;25 1712 | 41.2;20;25 1713 | 41.17;20;25 1714 | 41.14;20;25 1715 | 41.11;20;25 1716 | 41.08;20;25 1717 | 41.05;20;25 1718 | 41.02;20;25 1719 | 40.99;20;25 1720 | 40.96;20;25 1721 | 40.93;20;25 1722 | 40.9;20;25 1723 | 40.87;20;25 1724 | 40.84;20;25 1725 | 40.81;20;25 1726 | 40.78;20;25 1727 | 40.75;20;25 1728 | 40.72;20;25 1729 | 40.69;20;25 1730 | 40.66;20;25 1731 | 40.63;20;25 1732 | 40.6;20;25 1733 | 40.57;20;25 1734 | 40.54;20;25 1735 | 40.51;20;25 1736 | 40.48;20;25 1737 | 40.45;20;25 1738 | 40.43;20;25 1739 | 40.4;20;25 1740 | 40.37;20;25 1741 | 40.34;20;25 1742 | 40.32;20;25 1743 | 40.29;20;25 1744 | 40.26;20;25 1745 | 40.23;20;25 1746 | 40.21;20;25 1747 | 40.18;20;25 1748 | 40.15;20;25 1749 | 40.12;20;25 1750 | 40.1;20;25 1751 | 40.07;20;25 1752 | 40.04;20;25 1753 | 40.01;20;25 1754 | 39.99;20;25 1755 | 39.96;20;25 1756 | 39.93;20;25 1757 | 39.9;20;25 1758 | 39.88;20;25 1759 | 39.82;20;25 1760 | 39.82;20;25 1761 | 39.79;20;25 1762 | 39.76;20;25 1763 | 39.74;20;25 1764 | 39.71;20;25 1765 | 39.68;20;25 1766 | 39.65;20;25 1767 | 39.62;20;25 1768 | 39.6;20;25 1769 | 39.54;20;25 1770 | 39.54;20;25 1771 | 39.51;20;25 1772 | 39.48;20;25 1773 | 39.46;20;25 1774 | 39.43;20;25 1775 | 39.4;20;25 1776 | 39.37;20;25 1777 | 39.34;20;25 1778 | 39.32;20;25 1779 | 39.29;20;25 1780 | 39.26;20;25 1781 | 39.24;20;25 1782 | 39.21;20;25 1783 | 39.18;20;25 1784 | 39.15;20;25 1785 | 39.13;20;25 1786 | 39.1;20;25 1787 | 39.08;20;25 1788 | 39.05;20;25 1789 | 39.03;20;25 1790 | 39;20;25 1791 | 38.98;20;25 1792 | 38.95;20;25 1793 | 38.92;20;25 1794 | 38.9;20;25 1795 | 38.87;20;25 1796 | 38.85;20;25 1797 | 38.82;20;25 1798 | 38.8;20;25 1799 | 38.77;20;25 1800 | 38.75;20;25 1801 | 38.72;20;25 1802 | 38.7;20;25 1803 | 38.67;20;25 1804 | 38.65;20;25 1805 | 38.62;20;25 1806 | 38.6;20;25 1807 | 38.57;20;25 1808 | 38.55;20;25 1809 | 38.52;20;25 1810 | 38.5;20;25 1811 | 38.47;20;25 1812 | 38.45;20;25 1813 | 38.42;20;25 1814 | 38.4;20;25 1815 | 38.37;20;25 1816 | 38.35;20;25 1817 | 38.32;20;25 1818 | 38.3;20;25 1819 | 38.28;20;25 1820 | 38.25;20;25 1821 | 38.23;20;25 1822 | 38.2;20;25 1823 | 38.18;20;25 1824 | 38.15;20;25 1825 | 38.13;20;25 1826 | 38.1;20;25 1827 | 38.08;20;25 1828 | 38.05;20;25 1829 | 38.03;20;25 1830 | 38;20;25 1831 | 37.98;20;25 1832 | 37.96;20;25 1833 | 37.93;20;25 1834 | 37.91;20;25 1835 | 37.88;20;25 1836 | 37.86;20;25 1837 | 37.83;20;25 1838 | 37.81;20;25 1839 | 37.78;20;25 1840 | 37.76;20;25 1841 | 37.73;20;25 1842 | 37.71;20;25 1843 | 37.68;20;25 1844 | 37.66;20;25 1845 | 37.64;20;25 1846 | 37.61;20;25 1847 | 37.59;20;25 1848 | 37.57;20;25 1849 | 37.54;20;25 1850 | 37.52;20;25 1851 | 37.49;20;25 1852 | 37.47;20;25 1853 | 37.45;20;25 1854 | 37.42;20;25 1855 | 37.4;20;25 1856 | 37.38;20;25 1857 | 37.35;20;25 1858 | 37.33;20;25 1859 | 37.3;20;25 1860 | 37.28;20;25 1861 | 37.26;20;25 1862 | 37.23;20;25 1863 | 37.21;20;25 1864 | 37.18;20;25 1865 | 37.16;20;25 1866 | 37.14;20;25 1867 | 37.11;20;25 1868 | 37.09;20;25 1869 | 37.06;20;25 1870 | 37.04;20;25 1871 | 37.01;20;25 1872 | 36.99;20;25 1873 | 36.97;20;25 1874 | 36.94;20;25 1875 | 36.92;20;25 1876 | 36.9;20;25 1877 | 36.88;20;25 1878 | 36.85;20;25 1879 | 36.83;20;25 1880 | 36.81;20;25 1881 | 36.79;20;25 1882 | 36.77;20;25 1883 | 36.75;20;25 1884 | 36.72;20;25 1885 | 36.7;20;25 1886 | 36.68;20;25 1887 | 36.66;20;25 1888 | 36.63;20;25 1889 | 36.61;20;25 1890 | 36.59;20;25 1891 | 36.56;20;25 1892 | 36.54;20;25 1893 | 36.52;20;25 1894 | 36.49;20;25 1895 | 36.47;20;25 1896 | 36.45;20;25 1897 | 36.42;20;25 1898 | 36.4;20;25 1899 | 36.38;20;25 1900 | 36.36;20;25 1901 | 36.33;20;25 1902 | 36.31;20;25 1903 | 36.29;20;25 1904 | 36.26;20;25 1905 | 36.24;20;25 1906 | 36.22;20;25 1907 | 36.2;20;25 1908 | 36.17;20;25 1909 | 36.15;20;25 1910 | 36.13;20;25 1911 | 36.1;20;25 1912 | 36.08;20;25 1913 | 36.06;20;25 1914 | 36.04;20;25 1915 | 36.01;20;25 1916 | 35.99;20;25 1917 | 35.97;20;25 1918 | 35.95;20;25 1919 | 35.93;20;25 1920 | 35.89;20;25 1921 | 35.89;20;25 1922 | 35.86;20;25 1923 | 35.84;20;25 1924 | 35.82;20;25 1925 | 35.8;20;25 1926 | 35.78;20;25 1927 | 35.76;20;25 1928 | 35.73;20;25 1929 | 35.71;20;25 1930 | 35.69;20;25 1931 | 35.67;20;25 1932 | 35.65;20;25 1933 | 35.62;20;25 1934 | 35.6;20;25 1935 | 35.58;20;25 1936 | 35.56;20;25 1937 | 35.54;20;25 1938 | 35.52;20;25 1939 | 35.5;20;25 1940 | 35.48;20;25 1941 | 35.46;20;25 1942 | 35.44;20;25 1943 | 35.42;20;25 1944 | 35.4;20;25 1945 | 35.38;20;25 1946 | 35.35;20;25 1947 | 35.33;20;25 1948 | 35.31;20;25 1949 | 35.29;20;25 1950 | 35.27;20;25 1951 | 35.25;20;25 1952 | 35.23;20;25 1953 | 35.21;20;25 1954 | 35.19;20;25 1955 | 35.17;20;25 1956 | 35.15;20;25 1957 | 35.13;20;25 1958 | 35.11;20;25 1959 | 35.09;20;25 1960 | 35.07;20;25 1961 | 35.05;20;25 1962 | 35.03;20;25 1963 | 35.01;20;25 1964 | 34.99;20;25 1965 | 34.98;20;25 1966 | 34.96;20;25 1967 | 34.94;20;25 1968 | 34.92;20;25 1969 | 34.9;20;25 1970 | 34.88;20;25 1971 | 34.86;20;25 1972 | 34.84;20;25 1973 | 34.82;20;25 1974 | 34.8;20;25 1975 | 34.78;20;25 1976 | 34.76;20;25 1977 | 34.74;20;25 1978 | 34.72;20;25 1979 | 34.7;20;25 1980 | 34.68;20;25 1981 | 34.66;20;25 1982 | 34.64;20;25 1983 | 34.62;20;25 1984 | 34.6;20;25 1985 | 34.58;20;25 1986 | 34.56;20;25 1987 | 34.54;20;25 1988 | 34.52;20;25 1989 | 34.5;20;25 1990 | 34.48;20;25 1991 | 34.46;20;25 1992 | 34.44;20;25 1993 | 34.42;20;25 1994 | 34.4;20;25 1995 | 34.39;20;25 1996 | 34.37;20;25 1997 | 34.35;20;25 1998 | 34.34;20;25 1999 | 34.32;20;25 2000 | 34.3;20;25 2001 | 34.29;20;25 2002 | 34.27;20;25 2003 | 34.25;20;25 2004 | 34.24;20;25 2005 | 34.22;20;25 2006 | 34.2;20;25 2007 | 34.18;20;25 2008 | 34.16;20;25 2009 | 34.14;20;25 2010 | 34.12;20;25 2011 | 34.1;20;25 2012 | 34.08;20;25 2013 | 34.07;20;25 2014 | 34.05;20;25 2015 | 34.03;20;25 2016 | 34.01;20;25 2017 | 33.99;20;25 2018 | 33.97;20;25 2019 | 33.96;20;25 2020 | 33.94;20;25 2021 | 33.92;20;25 2022 | 33.9;20;25 2023 | 33.88;20;25 2024 | 33.86;20;25 2025 | 33.85;20;25 2026 | 33.83;20;25 2027 | 33.81;20;25 2028 | 33.8;20;25 2029 | 33.78;20;25 2030 | 33.76;20;25 2031 | 33.75;20;25 2032 | 33.73;20;25 2033 | 33.71;20;25 2034 | 33.7;20;25 2035 | 33.68;20;25 2036 | 33.66;20;25 2037 | 33.65;20;25 2038 | 33.63;20;25 2039 | 33.61;20;25 2040 | 33.59;20;25 2041 | 33.57;20;25 2042 | 33.56;20;25 2043 | 33.54;20;25 2044 | 33.52;20;25 2045 | 33.5;20;25 2046 | 33.49;20;25 2047 | 33.47;20;25 2048 | 33.46;20;25 2049 | 33.44;20;25 2050 | 33.42;20;25 2051 | 33.41;20;25 2052 | 33.39;20;25 2053 | 33.37;20;25 2054 | 33.36;20;25 2055 | 33.34;20;25 2056 | 33.33;20;25 2057 | 33.31;20;25 2058 | 33.3;20;25 2059 | 33.28;20;25 2060 | 33.27;20;25 2061 | 33.25;20;25 2062 | 33.24;20;25 2063 | 33.23;20;25 2064 | 33.21;20;25 2065 | 33.19;20;25 2066 | 33.18;20;25 2067 | 33.16;20;25 2068 | 33.14;20;25 2069 | 33.13;20;25 2070 | 33.11;20;25 2071 | 33.09;20;25 2072 | 33.07;20;25 2073 | 33.06;20;25 2074 | 33.04;20;25 2075 | 33.02;20;25 2076 | 33.01;20;25 2077 | 32.99;20;25 2078 | 32.98;20;25 2079 | 32.96;20;25 2080 | 32.95;20;25 2081 | 32.92;20;25 2082 | 32.92;20;25 2083 | 32.91;20;25 2084 | 32.89;20;25 2085 | 32.88;20;25 2086 | 32.86;20;25 2087 | 32.84;20;25 2088 | 32.83;20;25 2089 | 32.81;20;25 2090 | 32.8;20;25 2091 | 32.77;20;25 2092 | 32.77;20;25 2093 | 32.75;20;25 2094 | 32.74;20;25 2095 | 32.72;20;25 2096 | 32.71;20;25 2097 | 32.69;20;25 2098 | 32.68;20;25 2099 | 32.66;20;25 2100 | 32.65;20;25 2101 | 32.62;20;25 2102 | 32.62;20;25 2103 | 32.6;20;25 2104 | 32.59;20;25 2105 | 32.57;20;25 2106 | 32.55;20;25 2107 | 32.54;20;25 2108 | 32.52;20;25 2109 | 32.5;20;25 2110 | 32.49;20;25 2111 | 32.47;20;25 2112 | 32.46;20;25 2113 | 32.44;20;25 2114 | 32.42;20;25 2115 | 32.41;20;25 2116 | 32.39;20;25 2117 | 32.38;20;25 2118 | 32.36;20;25 2119 | 32.35;20;25 2120 | 32.33;20;25 2121 | 32.32;20;25 2122 | 32.3;20;25 2123 | 32.29;20;25 2124 | 32.27;20;25 2125 | 32.26;20;25 2126 | 32.24;20;25 2127 | 32.22;20;25 2128 | 32.21;20;25 2129 | 32.19;20;25 2130 | 32.18;20;25 2131 | 32.16;20;25 2132 | 32.15;20;25 2133 | 32.13;20;25 2134 | 32.12;20;25 2135 | 32.1;20;25 2136 | 32.09;20;25 2137 | 32.07;20;25 2138 | 32.06;20;25 2139 | 32.05;20;25 2140 | 32.03;20;25 2141 | 32.02;20;25 2142 | 32;20;25 2143 | 31.99;20;25 2144 | 31.98;20;25 2145 | 31.96;20;25 2146 | 31.95;20;25 2147 | 31.93;20;25 2148 | 31.92;20;25 2149 | 31.9;20;25 2150 | 31.89;20;25 2151 | 31.87;20;25 2152 | 31.86;20;25 2153 | 31.85;20;25 2154 | 31.83;20;25 2155 | 31.82;20;25 2156 | 31.8;20;25 2157 | 31.79;20;25 2158 | 31.77;20;25 2159 | 31.76;20;25 2160 | 31.74;20;25 2161 | 31.73;20;25 2162 | 31.71;20;25 2163 | 31.7;20;25 2164 | 31.69;20;25 2165 | 31.67;20;25 2166 | 31.66;20;25 2167 | 31.64;20;25 2168 | 31.63;20;25 2169 | 31.61;20;25 2170 | 31.6;20;25 2171 | 31.58;20;25 2172 | 31.57;20;25 2173 | 31.56;20;25 2174 | 31.54;20;25 2175 | 31.53;20;25 2176 | 31.52;20;25 2177 | 31.51;20;25 2178 | 31.49;20;25 2179 | 31.48;20;25 2180 | 31.47;20;25 2181 | 31.46;20;25 2182 | 31.45;20;25 2183 | 31.44;20;25 2184 | 31.43;20;25 2185 | 31.41;20;25 2186 | 31.4;20;25 2187 | 31.39;20;25 2188 | 31.38;20;25 2189 | 31.37;20;25 2190 | 31.36;20;25 2191 | 31.34;20;25 2192 | 31.33;20;25 2193 | 31.32;20;25 2194 | 31.31;20;25 2195 | 31.3;20;25 2196 | 31.28;20;25 2197 | 31.27;20;25 2198 | 31.25;20;25 2199 | 31.24;20;25 2200 | 31.23;20;25 2201 | 31.21;20;25 2202 | 31.2;20;25 2203 | 31.19;20;25 2204 | 31.17;20;25 2205 | 31.16;20;25 2206 | 31.15;20;25 2207 | 31.14;20;25 2208 | 31.13;20;25 2209 | 31.11;20;25 2210 | 31.1;20;25 2211 | 31.09;20;25 2212 | 31.08;20;25 2213 | 31.07;20;25 2214 | 31.06;20;25 2215 | 31.04;20;25 2216 | 31.03;20;25 2217 | 31.02;20;25 2218 | 31.01;20;25 2219 | 30.99;20;25 2220 | 30.98;20;25 2221 | 30.97;20;25 2222 | 30.95;20;25 2223 | 30.94;20;25 2224 | 30.93;20;25 2225 | 30.92;20;25 2226 | 30.9;20;25 2227 | 30.89;20;25 2228 | 30.88;20;25 2229 | 30.87;20;25 2230 | 30.86;20;25 2231 | 30.85;20;25 2232 | 30.84;20;25 2233 | 30.83;20;25 2234 | 30.82;20;25 2235 | 30.81;20;25 2236 | 30.8;20;25 2237 | 30.79;20;25 2238 | 30.78;20;25 2239 | 30.77;20;25 2240 | 30.75;20;25 2241 | 30.74;20;25 2242 | 30.73;20;25 2243 | 30.72;20;25 2244 | 30.71;20;25 2245 | 30.7;20;25 2246 | 30.69;20;25 2247 | 30.68;20;25 2248 | 30.67;20;25 2249 | 30.65;20;25 2250 | 30.64;20;25 2251 | 30.63;20;25 2252 | 30.6;20;25 2253 | 30.6;20;25 2254 | 30.59;20;25 2255 | 30.58;20;25 2256 | 30.57;20;25 2257 | 30.56;20;25 2258 | 30.55;20;25 2259 | 30.54;20;25 2260 | 30.52;20;25 2261 | 30.51;20;25 2262 | 30.49;20;25 2263 | 30.49;20;25 2264 | 30.48;20;25 2265 | 30.47;20;25 2266 | 30.46;20;25 2267 | 30.44;20;25 2268 | 30.43;20;25 2269 | 30.42;20;25 2270 | 30.41;20;25 2271 | 30.39;20;25 2272 | 30.38;20;25 2273 | 30.37;20;25 2274 | 30.36;20;25 2275 | 30.35;20;25 2276 | 30.33;20;25 2277 | 30.32;20;25 2278 | 30.31;20;25 2279 | 30.3;20;25 2280 | 30.29;20;25 2281 | 30.27;20;25 2282 | 30.26;20;25 2283 | 30.25;20;25 2284 | 30.24;20;25 2285 | 30.23;20;25 2286 | 30.22;20;25 2287 | 30.21;20;25 2288 | 30.2;20;25 2289 | 30.19;20;25 2290 | 30.17;20;25 2291 | 30.16;20;25 2292 | 30.15;20;25 2293 | 30.14;20;25 2294 | 30.13;20;25 2295 | 30.12;20;25 2296 | 30.11;20;25 2297 | 30.1;20;25 2298 | 30.09;20;25 2299 | 30.08;20;25 2300 | 30.07;20;25 2301 | 30.06;20;25 2302 | 30.05;20;25 2303 | 30.04;20;25 2304 | 30.03;20;25 2305 | 30.02;20;25 2306 | 30.01;20;25 2307 | 30;20;25 2308 | 29.99;20;25 2309 | 29.98;20;25 2310 | 29.98;20;25 2311 | 29.97;20;25 2312 | 29.96;20;25 2313 | 29.95;20;25 2314 | 29.94;20;25 2315 | 29.93;20;25 2316 | 29.92;20;25 2317 | 29.91;20;25 2318 | 29.9;20;25 2319 | 29.89;20;25 2320 | 29.88;20;25 2321 | 29.88;20;25 2322 | 29.87;20;25 2323 | 29.86;20;25 2324 | 29.85;20;25 2325 | 29.84;20;25 2326 | 29.83;20;25 2327 | 29.82;20;25 2328 | 29.81;20;25 2329 | 29.8;20;25 2330 | 29.79;20;25 2331 | 29.78;20;25 2332 | 29.77;20;25 2333 | 29.76;20;25 2334 | 29.75;20;25 2335 | 29.74;20;25 2336 | 29.73;20;25 2337 | 29.72;20;25 2338 | 29.71;20;25 2339 | 29.7;20;25 2340 | 29.69;20;25 2341 | 29.68;20;25 2342 | 29.67;20;25 2343 | 29.66;20;25 2344 | 29.65;20;25 2345 | 29.64;20;25 2346 | 29.63;20;25 2347 | 29.62;20;25 2348 | 29.61;20;25 2349 | 29.6;20;25 2350 | 29.6;20;25 2351 | 29.59;20;25 2352 | 29.58;20;25 2353 | 29.57;20;25 2354 | 29.56;20;25 2355 | 29.55;20;25 2356 | 29.54;20;25 2357 | 29.53;20;25 2358 | 29.52;20;25 2359 | 29.52;20;25 2360 | 29.51;20;25 2361 | 29.5;20;25 2362 | 29.49;20;25 2363 | 29.48;20;25 2364 | 29.48;20;25 2365 | 29.47;20;25 2366 | 29.46;20;25 2367 | 29.45;20;25 2368 | 29.44;20;25 2369 | 29.43;20;25 2370 | 29.42;20;25 2371 | 29.41;20;25 2372 | 29.4;20;25 2373 | 29.39;20;25 2374 | 29.38;20;25 2375 | 29.37;20;25 2376 | 29.36;20;25 2377 | 29.35;20;25 2378 | 29.34;20;25 2379 | 29.33;20;25 2380 | 29.32;20;25 2381 | 29.31;20;25 2382 | 29.3;20;25 2383 | 29.29;20;25 2384 | 29.28;20;25 2385 | 29.27;20;25 2386 | 29.27;20;25 2387 | 29.26;20;25 2388 | 29.25;20;25 2389 | 29.24;20;25 2390 | 29.24;20;25 2391 | 29.23;20;25 2392 | 29.22;20;25 2393 | 29.21;20;25 2394 | 29.21;20;25 2395 | 29.2;20;25 2396 | 29.19;20;25 2397 | 29.18;20;25 2398 | 29.17;20;25 2399 | 29.16;20;25 2400 | 29.16;20;25 2401 | 29.15;20;25 2402 | 29.14;20;25 2403 | 29.13;20;25 2404 | 29.12;20;25 2405 | 29.11;20;25 2406 | 29.1;20;25 2407 | 29.1;20;25 2408 | 29.09;20;25 2409 | 29.08;20;25 2410 | 29.07;20;25 2411 | 29.06;20;25 2412 | 29.05;20;25 2413 | 29.04;20;25 2414 | 29.03;20;25 2415 | 29.03;20;25 2416 | 29.02;20;25 2417 | 29.01;20;25 2418 | 29;20;25 2419 | 28.99;20;25 2420 | 28.98;20;25 2421 | 28.97;20;25 2422 | 28.96;20;25 2423 | 28.96;20;25 2424 | 28.95;20;25 2425 | 28.94;20;25 2426 | 28.93;20;25 2427 | 28.92;20;25 2428 | 28.92;20;25 2429 | 28.91;20;25 2430 | 28.9;20;25 2431 | 28.9;20;25 2432 | 28.89;20;25 2433 | 28.88;20;25 2434 | 28.87;20;25 2435 | 28.87;20;25 2436 | 28.86;20;25 2437 | 28.85;20;25 2438 | 28.84;20;25 2439 | 28.83;20;25 2440 | 28.82;20;25 2441 | 28.81;20;25 2442 | 28.8;20;25 2443 | 28.8;20;25 2444 | 28.79;20;25 2445 | 28.78;20;25 2446 | 28.77;20;25 2447 | 28.77;20;25 2448 | 28.76;20;25 2449 | 28.75;20;25 2450 | 28.75;20;25 2451 | 28.74;20;25 2452 | 28.74;20;25 2453 | 28.72;20;25 2454 | 28.72;20;25 2455 | 28.72;20;25 2456 | 28.71;20;25 2457 | 28.7;20;25 2458 | 28.7;20;25 2459 | 28.69;20;25 2460 | 28.68;20;25 2461 | 28.68;20;25 2462 | 28.67;20;25 2463 | 28.66;20;25 2464 | 28.66;20;25 2465 | 28.65;20;25 2466 | 28.64;20;25 2467 | 28.63;20;25 2468 | 28.62;20;25 2469 | 28.62;20;25 2470 | 28.61;20;25 2471 | 28.6;20;25 2472 | 28.59;20;25 2473 | 28.58;20;25 2474 | 28.57;20;25 2475 | 28.56;20;25 2476 | 28.56;20;25 2477 | 28.55;20;25 2478 | 28.54;20;25 2479 | 28.53;20;25 2480 | 28.52;20;25 2481 | 28.52;20;25 2482 | 28.51;20;25 2483 | 28.5;20;25 2484 | 28.49;20;25 2485 | 28.48;20;25 2486 | 28.48;20;25 2487 | 28.47;20;25 2488 | 28.46;20;25 2489 | 28.45;20;25 2490 | 28.44;20;25 2491 | 28.43;20;25 2492 | 28.43;20;25 2493 | 28.42;20;25 2494 | 28.41;20;25 2495 | 28.4;20;25 2496 | 28.4;20;25 2497 | 28.39;20;25 2498 | 28.38;20;25 2499 | 28.38;20;25 2500 | 28.37;20;25 2501 | 28.36;20;25 2502 | 28.36;20;25 2503 | 28.35;20;25 2504 | 28.34;20;25 2505 | 28.34;20;25 2506 | 28.33;20;25 2507 | 28.33;20;25 2508 | 28.32;20;25 2509 | 28.32;20;25 2510 | 28.31;20;25 2511 | 28.3;20;25 2512 | 28.3;20;25 2513 | 28.29;20;25 2514 | 28.29;20;25 2515 | 28.28;20;25 2516 | 28.28;20;25 2517 | 28.27;20;25 2518 | 28.26;20;25 2519 | 28.26;20;25 2520 | 28.25;20;25 2521 | 28.24;20;25 2522 | 28.24;20;25 2523 | 28.23;20;25 2524 | 28.22;20;25 2525 | 28.22;20;25 2526 | 28.21;20;25 2527 | 28.2;20;25 2528 | 28.2;20;25 2529 | 28.19;20;25 2530 | 28.18;20;25 2531 | 28.18;20;25 2532 | 28.17;20;25 2533 | 28.17;20;25 2534 | 28.16;20;25 2535 | 28.15;20;25 2536 | 28.15;20;25 2537 | 28.14;20;25 2538 | 28.13;20;25 2539 | 28.12;20;25 2540 | 28.12;20;25 2541 | 28.11;20;25 2542 | 28.1;20;25 2543 | 28.09;20;25 2544 | 28.09;20;25 2545 | 28.08;20;25 2546 | 28.07;20;25 2547 | 28.07;20;25 2548 | 28.06;20;25 2549 | 28.06;20;25 2550 | 28.05;20;25 2551 | 28.05;20;25 2552 | 28.04;20;25 2553 | 28.04;20;25 2554 | 28.03;20;25 2555 | 28.03;20;25 2556 | 28.02;20;25 2557 | 28.02;20;25 2558 | 28.01;20;25 2559 | 28;20;25 2560 | 27.99;20;25 2561 | 27.99;20;25 2562 | 27.98;20;25 2563 | 27.97;20;25 2564 | 27.97;20;25 2565 | 27.96;20;25 2566 | 27.95;20;25 2567 | 27.95;20;25 2568 | 27.94;20;25 2569 | 27.93;20;25 2570 | 27.92;20;25 2571 | 27.92;20;25 2572 | 27.91;20;25 2573 | 27.9;20;25 2574 | 27.89;20;25 2575 | 27.89;20;25 2576 | 27.88;20;25 2577 | 27.87;20;25 2578 | 27.87;20;25 2579 | 27.86;20;25 2580 | 27.86;20;25 2581 | 27.85;20;25 2582 | 27.84;20;25 2583 | 27.84;20;25 2584 | 27.82;20;25 2585 | 27.82;20;25 2586 | 27.82;20;25 2587 | 27.82;20;25 2588 | 27.81;20;25 2589 | 27.81;20;25 2590 | 27.8;20;25 2591 | 27.8;20;25 2592 | 27.79;20;25 2593 | 27.79;20;25 2594 | 27.78;20;25 2595 | 27.78;20;25 2596 | 27.78;20;25 2597 | 27.77;20;25 2598 | 27.77;20;25 2599 | 27.76;20;25 2600 | 27.76;20;25 2601 | 27.75;20;25 2602 | 27.75;20;25 2603 | 27.74;20;25 2604 | 27.74;20;25 2605 | 27.73;20;25 2606 | 27.73;20;25 2607 | 27.72;20;25 2608 | 27.72;20;25 2609 | 27.71;20;25 2610 | 27.71;20;25 2611 | 27.7;20;25 2612 | 27.7;20;25 2613 | 27.69;20;25 2614 | 27.69;20;25 2615 | 27.68;20;25 2616 | 27.68;20;25 2617 | 27.67;20;25 2618 | 27.67;20;25 2619 | 27.66;20;25 2620 | 27.66;20;25 2621 | 27.65;20;25 2622 | 27.65;20;25 2623 | 27.64;20;25 2624 | 27.64;20;25 2625 | 27.63;20;25 2626 | 27.63;20;25 2627 | 27.63;20;25 2628 | 27.62;20;25 2629 | 27.62;20;25 2630 | 27.61;20;25 2631 | 27.61;20;25 2632 | 27.6;20;25 2633 | 27.6;20;25 2634 | 27.6;20;25 2635 | 27.59;20;25 2636 | 27.59;20;25 2637 | 27.58;20;25 2638 | 27.58;20;25 2639 | 27.57;20;25 2640 | 27.57;20;25 2641 | 27.56;20;25 2642 | 27.56;20;25 2643 | 27.56;20;25 2644 | 27.55;20;25 2645 | 27.55;20;25 2646 | 27.54;20;25 2647 | 27.54;20;25 2648 | 27.54;20;25 2649 | 27.53;20;25 2650 | 27.53;20;25 2651 | 27.52;20;25 2652 | 27.52;20;25 2653 | 27.52;20;25 2654 | 27.51;20;25 2655 | 27.51;20;25 2656 | 27.5;20;25 2657 | 27.49;20;25 2658 | 27.49;20;25 2659 | 27.48;20;25 2660 | 27.47;20;25 2661 | 27.47;20;25 2662 | 27.46;20;25 2663 | 27.45;20;25 2664 | 27.45;20;25 2665 | 27.44;20;25 2666 | 27.44;20;25 2667 | 27.43;20;25 2668 | 27.43;20;25 2669 | 27.42;20;25 2670 | 27.42;20;25 2671 | 27.41;20;25 2672 | 27.41;20;25 2673 | 27.4;20;25 2674 | 27.4;20;25 2675 | 27.39;20;25 2676 | 27.39;20;25 2677 | 27.39;20;25 2678 | 27.38;20;25 2679 | 27.38;20;25 2680 | 27.37;20;25 2681 | 27.37;20;25 2682 | 27.36;20;25 2683 | 27.36;20;25 2684 | 27.35;20;25 2685 | 27.35;20;25 2686 | 27.34;20;25 2687 | 27.34;20;25 2688 | 27.33;20;25 2689 | 27.33;20;25 2690 | 27.32;20;25 2691 | 27.32;20;25 2692 | 27.31;20;25 2693 | 27.31;20;25 2694 | 27.3;20;25 2695 | 27.3;20;25 2696 | 27.29;20;25 2697 | 27.29;20;25 2698 | 27.28;20;25 2699 | 27.28;20;25 2700 | 27.28;20;25 2701 | 27.27;20;25 2702 | 27.27;20;25 2703 | 27.26;20;25 2704 | 27.26;20;25 2705 | 27.26;20;25 2706 | 27.25;20;25 2707 | 27.25;20;25 2708 | 27.25;20;25 2709 | 27.24;20;25 2710 | 27.24;20;25 2711 | 27.23;20;25 2712 | 27.23;20;25 2713 | 27.23;20;25 2714 | 27.22;20;25 2715 | 27.22;20;25 2716 | 27.21;20;25 2717 | 27.21;20;25 2718 | 27.2;20;25 2719 | 27.2;20;25 2720 | 27.19;20;25 2721 | 27.18;20;25 2722 | 27.18;20;25 2723 | 27.17;20;25 2724 | 27.17;20;25 2725 | 27.16;20;25 2726 | 27.16;20;25 2727 | 27.15;20;25 2728 | 27.15;20;25 2729 | 27.14;20;25 2730 | 27.14;20;25 2731 | 27.13;20;25 2732 | 27.13;20;25 2733 | 27.12;20;25 2734 | 27.12;20;25 2735 | 27.11;20;25 2736 | 27.11;20;25 2737 | 27.1;20;25 2738 | 27.1;20;25 2739 | 27.09;20;25 2740 | 27.08;20;25 2741 | 27.08;20;25 2742 | 27.07;20;25 2743 | 27.07;20;25 2744 | 27.06;20;25 2745 | 27.06;20;25 2746 | 27.05;20;25 2747 | 27.05;20;25 2748 | 27.05;20;25 2749 | 27.04;20;25 2750 | 27.04;20;25 2751 | 27.04;20;25 2752 | 27.03;20;25 2753 | 27.03;20;25 2754 | 27.03;20;25 2755 | 27.02;20;25 2756 | 27.02;20;25 2757 | 27.02;20;25 2758 | 27.01;20;25 2759 | 27.01;20;25 2760 | 27;20;25 2761 | 27;20;25 2762 | 27;20;25 2763 | 26.99;20;25 2764 | 26.99;20;25 2765 | 26.98;20;25 2766 | 26.98;20;25 2767 | 26.97;20;25 2768 | 26.97;20;25 2769 | 26.97;20;25 2770 | 26.96;20;25 2771 | 26.96;20;25 2772 | 26.95;20;25 2773 | 26.95;20;25 2774 | 26.95;20;25 2775 | 26.94;20;25 2776 | 26.94;20;25 2777 | 26.93;20;25 2778 | 26.93;20;25 2779 | 26.92;20;25 2780 | 26.92;20;25 2781 | 26.91;20;25 2782 | 26.91;20;25 2783 | 26.9;20;25 2784 | 26.9;20;25 2785 | 26.89;20;25 2786 | 26.89;20;25 2787 | 26.89;20;25 2788 | 26.88;20;25 2789 | 26.88;20;25 2790 | 26.88;20;25 2791 | 26.87;20;25 2792 | 26.87;20;25 2793 | 26.86;20;25 2794 | 26.86;20;25 2795 | 26.86;20;25 2796 | 26.85;20;25 2797 | 26.85;20;25 2798 | 26.84;20;25 2799 | 26.83;20;25 2800 | 26.83;20;25 2801 | 26.82;20;25 2802 | 26.82;20;25 2803 | 26.81;20;25 2804 | 26.81;20;25 2805 | 26.81;20;25 2806 | 26.8;20;25 2807 | 26.8;20;25 2808 | 26.8;20;25 2809 | 26.8;20;25 2810 | 26.79;20;25 2811 | 26.79;20;25 2812 | 26.79;20;25 2813 | 26.79;20;25 2814 | 26.78;20;25 2815 | 26.78;20;25 2816 | 26.78;20;25 2817 | 26.77;20;25 2818 | 26.77;20;25 2819 | 26.76;20;25 2820 | 26.76;20;25 2821 | 26.75;20;25 2822 | 26.75;20;25 2823 | 26.74;20;25 2824 | 26.74;20;25 2825 | 26.73;20;25 2826 | 26.73;20;25 2827 | 26.73;20;25 2828 | 26.72;20;25 2829 | 26.72;20;25 2830 | 26.72;20;25 2831 | 26.71;20;25 2832 | 26.71;20;25 2833 | 26.71;20;25 2834 | 26.71;20;25 2835 | 26.7;20;25 2836 | 26.7;20;25 2837 | 26.69;20;25 2838 | 26.69;20;25 2839 | 26.69;20;25 2840 | 26.68;20;25 2841 | 26.68;20;25 2842 | 26.67;20;25 2843 | 26.67;20;25 2844 | 26.66;20;25 2845 | 26.66;20;25 2846 | 26.66;20;25 2847 | 26.65;20;25 2848 | 26.65;20;25 2849 | 26.65;20;25 2850 | 26.64;20;25 2851 | 26.64;20;25 2852 | 26.64;20;25 2853 | 26.63;20;25 2854 | 26.63;20;25 2855 | 26.62;20;25 2856 | 26.62;20;25 2857 | 26.62;20;25 2858 | 26.61;20;25 2859 | 26.61;20;25 2860 | 26.61;20;25 2861 | 26.6;20;25 2862 | 26.6;20;25 2863 | 26.6;20;25 2864 | 26.59;20;25 2865 | 26.59;20;25 2866 | 26.59;20;25 2867 | 26.58;20;25 2868 | 26.58;20;25 2869 | 26.58;20;25 2870 | 26.57;20;25 2871 | 26.57;20;25 2872 | 26.57;20;25 2873 | 26.56;20;25 2874 | 26.56;20;25 2875 | 26.56;20;25 2876 | 26.56;20;25 2877 | 26.55;20;25 2878 | 26.55;20;25 2879 | 26.55;20;25 2880 | 26.55;20;25 2881 | 26.55;20;25 2882 | 26.54;20;25 2883 | 26.54;20;25 2884 | 26.54;20;25 2885 | 26.54;20;25 2886 | 26.53;20;25 2887 | 26.53;20;25 2888 | 26.53;20;25 2889 | 26.52;20;25 2890 | 26.52;20;25 2891 | 26.52;20;25 2892 | 26.52;20;25 2893 | 26.51;20;25 2894 | 26.51;20;25 2895 | 26.51;20;25 2896 | 26.5;20;25 2897 | 26.5;20;25 2898 | 26.5;20;25 2899 | 26.5;20;25 2900 | 26.5;20;25 2901 | 26.49;20;25 2902 | 26.49;20;25 2903 | 26.49;20;25 2904 | 26.49;20;25 2905 | 26.48;20;25 2906 | 26.48;20;25 2907 | 26.48;20;25 2908 | 26.47;20;25 2909 | 26.47;20;25 2910 | 26.46;20;25 2911 | 26.46;20;25 2912 | 26.46;20;25 2913 | 26.45;20;25 2914 | 26.45;20;25 2915 | 26.44;20;25 2916 | 26.44;20;25 2917 | 26.43;20;25 2918 | 26.43;20;25 2919 | 26.43;20;25 2920 | 26.42;20;25 2921 | 26.42;20;25 2922 | 26.42;20;25 2923 | 26.41;20;25 2924 | 26.41;20;25 2925 | 26.4;20;25 2926 | 26.4;20;25 2927 | 26.4;20;25 2928 | 26.4;20;25 2929 | 26.39;20;25 2930 | 26.39;20;25 2931 | 26.39;20;25 2932 | 26.39;20;25 2933 | 26.38;20;25 2934 | 26.38;20;25 2935 | 26.38;20;25 2936 | 26.37;20;25 2937 | 26.37;20;25 2938 | 26.37;20;25 2939 | 26.37;20;25 2940 | 26.36;20;25 2941 | 26.36;20;25 2942 | 26.36;20;25 2943 | 26.35;20;25 2944 | 26.35;20;25 2945 | 26.35;20;25 2946 | 26.34;20;25 2947 | 26.34;20;25 2948 | 26.34;20;25 2949 | 26.34;20;25 2950 | 26.33;20;25 2951 | 26.33;20;25 2952 | 26.33;20;25 2953 | 26.32;20;25 2954 | 26.32;20;25 2955 | 26.32;20;25 2956 | 26.31;20;25 2957 | 26.31;20;25 2958 | 26.31;20;25 2959 | 26.31;20;25 2960 | 26.31;20;25 2961 | 26.3;20;25 2962 | 26.3;20;25 2963 | 26.3;20;25 2964 | 26.29;20;25 2965 | 26.29;20;25 2966 | 26.29;20;25 2967 | 26.29;20;25 2968 | 26.29;20;25 2969 | 26.29;20;25 2970 | 26.28;20;25 2971 | 26.28;20;25 2972 | 26.28;20;25 2973 | 26.28;20;25 2974 | 26.28;20;25 2975 | 26.28;20;25 2976 | 26.27;20;25 2977 | 26.27;20;25 2978 | 26.27;20;25 2979 | 26.26;20;25 2980 | 26.26;20;25 2981 | 26.26;20;25 2982 | 26.26;20;25 2983 | 26.25;20;25 2984 | 26.25;20;25 2985 | 26.25;20;25 2986 | 26.25;20;25 2987 | 26.25;20;25 2988 | 26.24;20;25 2989 | 26.24;20;25 2990 | 26.24;20;25 2991 | 26.24;20;25 2992 | 26.24;20;25 2993 | 26.24;20;25 2994 | 26.24;20;25 2995 | 26.23;20;25 2996 | 26.23;20;25 2997 | 26.23;20;25 2998 | 26.22;20;25 2999 | 26.22;20;25 3000 | 26.22;20;25 3001 | 26.22;20;25 3002 | 26.21;20;25 3003 | 26.21;20;25 3004 | 26.21;20;25 3005 | 26.2;20;25 3006 | 26.2;20;25 3007 | 26.2;20;25 3008 | 26.2;20;25 3009 | 26.19;20;25 3010 | 26.19;20;25 3011 | 26.19;20;25 3012 | 26.18;20;25 3013 | 26.18;20;25 3014 | 26.18;20;25 3015 | 26.18;20;25 3016 | 26.17;20;25 3017 | 26.17;20;25 3018 | 26.17;20;25 3019 | 26.17;20;25 3020 | 26.16;20;25 3021 | 26.16;20;25 3022 | 26.16;20;25 3023 | 26.16;20;25 3024 | 26.15;20;25 3025 | 26.15;20;25 3026 | 26.15;20;25 3027 | 26.14;20;25 3028 | 26.14;20;25 3029 | 26.14;20;25 3030 | 26.13;20;25 3031 | 26.13;20;25 3032 | 26.13;20;25 3033 | 26.12;20;25 3034 | 26.12;20;25 3035 | 26.12;20;25 3036 | 26.12;20;25 3037 | 26.11;20;25 3038 | 26.11;20;25 3039 | 26.11;20;25 3040 | 26.11;20;25 3041 | 26.11;20;25 3042 | 26.11;20;25 3043 | 26.1;20;25 3044 | 26.1;20;25 3045 | 26.1;20;25 3046 | 26.1;20;25 3047 | 26.09;20;25 3048 | 26.09;20;25 3049 | 26.09;20;25 3050 | 26.09;20;25 3051 | 26.09;20;25 3052 | 26.08;20;25 3053 | 26.08;20;25 3054 | 26.08;20;25 3055 | 26.08;20;25 3056 | 26.07;20;25 3057 | 26.07;20;25 3058 | 26.07;20;25 3059 | 26.06;20;25 3060 | 26.06;20;25 3061 | 26.06;20;25 3062 | 26.05;20;25 3063 | 26.05;20;25 3064 | 26.05;20;25 3065 | 26.05;20;25 3066 | 26.04;20;25 3067 | 26.04;20;25 3068 | 26.04;20;25 3069 | 26.04;20;25 3070 | 26.04;20;25 3071 | 26.04;20;25 3072 | 26.03;20;25 3073 | 26.03;20;25 3074 | 26.03;20;25 3075 | 26.03;20;25 3076 | 26.03;20;25 3077 | 26.02;20;25 3078 | 26.02;20;25 3079 | 26.01;20;25 3080 | 26.01;20;25 3081 | 26.01;20;25 3082 | 26;20;25 3083 | 26;20;25 3084 | 26;20;25 3085 | 25.99;20;25 3086 | 25.99;20;25 3087 | 25.99;20;25 3088 | 25.99;20;25 3089 | 25.98;20;25 3090 | 25.98;20;25 3091 | 25.98;20;25 3092 | 25.97;20;25 3093 | 25.97;20;25 3094 | 25.97;20;25 3095 | 25.97;20;25 3096 | 25.96;20;25 3097 | 25.96;20;25 3098 | 25.96;20;25 3099 | 25.95;20;25 3100 | 25.95;20;25 3101 | 25.95;20;25 3102 | 25.95;20;25 3103 | 25.94;20;25 3104 | 25.94;20;25 3105 | 25.94;20;25 3106 | 25.94;20;25 3107 | 25.94;20;25 3108 | 25.94;20;25 3109 | 25.94;20;25 3110 | 25.93;20;25 3111 | 25.93;20;25 3112 | 25.93;20;25 3113 | 25.93;20;25 3114 | 25.93;20;25 3115 | 25.93;20;25 3116 | 25.93;20;25 3117 | 25.92;20;25 3118 | 25.92;20;25 3119 | 25.92;20;25 3120 | 25.91;20;25 3121 | 25.91;20;25 3122 | 25.91;20;25 3123 | 25.9;20;25 3124 | 25.9;20;25 3125 | 25.9;20;25 3126 | 25.89;20;25 3127 | 25.89;20;25 3128 | 25.89;20;25 3129 | 25.88;20;25 3130 | 25.88;20;25 3131 | 25.88;20;25 3132 | 25.87;20;25 3133 | 25.87;20;25 3134 | 25.87;20;25 3135 | 25.87;20;25 3136 | 25.86;20;25 3137 | 25.86;20;25 3138 | 25.86;20;25 3139 | 25.86;20;25 3140 | 25.86;20;25 3141 | 25.86;20;25 3142 | 25.86;20;25 3143 | 25.85;20;25 3144 | 25.85;20;25 3145 | 25.85;20;25 3146 | 25.85;20;25 3147 | 25.85;20;25 3148 | 25.85;20;25 3149 | 25.85;20;25 3150 | 25.85;20;25 3151 | 25.84;20;25 3152 | 25.84;20;25 3153 | 25.84;20;25 3154 | 25.84;20;25 3155 | 25.84;20;25 3156 | 25.84;20;25 3157 | 25.84;20;25 3158 | 25.84;20;25 3159 | 25.83;20;25 3160 | 25.83;20;25 3161 | 25.83;20;25 3162 | 25.83;20;25 3163 | 25.83;20;25 3164 | 25.83;20;25 3165 | 25.83;20;25 3166 | 25.83;20;25 3167 | 25.82;20;25 3168 | 25.82;20;25 3169 | 25.82;20;25 3170 | 25.82;20;25 3171 | 25.82;20;25 3172 | 25.81;20;25 3173 | 25.81;20;25 3174 | 25.81;20;25 3175 | 25.81;20;25 3176 | 25.81;20;25 3177 | 25.8;20;25 3178 | 25.8;20;25 3179 | 25.8;20;25 3180 | 25.8;20;25 3181 | 25.79;20;25 3182 | 25.79;20;25 3183 | 25.79;20;25 3184 | 25.79;20;25 3185 | 25.78;20;25 3186 | 25.78;20;25 3187 | 25.78;20;25 3188 | 25.78;20;25 3189 | 25.78;20;25 3190 | 25.78;20;25 3191 | 25.78;20;25 3192 | 25.77;20;25 3193 | 25.77;20;25 3194 | 25.77;20;25 3195 | 25.77;20;25 3196 | 25.77;20;25 3197 | 25.77;20;25 3198 | 25.76;20;25 3199 | 25.76;20;25 3200 | 25.76;20;25 3201 | 25.76;20;25 3202 | 25.75;20;25 3203 | 25.75;20;25 3204 | 25.75;20;25 3205 | 25.75;20;25 3206 | 25.75;20;25 3207 | 25.75;20;25 3208 | 25.74;20;25 3209 | 25.74;20;25 3210 | 25.74;20;25 3211 | 25.74;20;25 3212 | 25.74;20;25 3213 | 25.74;20;25 3214 | 25.74;20;25 3215 | 25.73;20;25 3216 | 25.73;20;25 3217 | 25.73;20;25 3218 | 25.73;20;25 3219 | 25.73;20;25 3220 | 25.73;20;25 3221 | 25.73;20;25 3222 | 25.73;20;25 3223 | 25.73;20;25 3224 | 25.72;20;25 3225 | 25.72;20;25 3226 | 25.72;20;25 3227 | 25.72;20;25 3228 | 25.72;20;25 3229 | 25.72;20;25 3230 | 25.72;20;25 3231 | 25.72;20;25 3232 | 25.72;20;25 3233 | 25.72;20;25 3234 | 25.72;20;25 3235 | 25.72;20;25 3236 | 25.72;20;25 3237 | 25.72;20;25 3238 | 25.71;20;25 3239 | 25.71;20;25 3240 | 25.71;20;25 3241 | 25.71;20;25 3242 | 25.7;20;25 3243 | 25.7;20;25 3244 | 25.7;20;25 3245 | 25.7;20;25 3246 | 25.69;20;25 3247 | 25.69;20;25 3248 | 25.69;20;25 3249 | 25.69;20;25 3250 | 25.69;20;25 3251 | 25.68;20;25 3252 | 25.68;20;25 3253 | 25.68;20;25 3254 | 25.68;20;25 3255 | 25.68;20;25 3256 | 25.67;20;25 3257 | 25.67;20;25 3258 | 25.67;20;25 3259 | 25.67;20;25 3260 | 25.67;20;25 3261 | 25.66;20;25 3262 | 25.66;20;25 3263 | 25.66;20;25 3264 | 25.66;20;25 3265 | 25.66;20;25 3266 | 25.66;20;25 3267 | 25.65;20;25 3268 | 25.65;20;25 3269 | 25.65;20;25 3270 | 25.65;20;25 3271 | 25.64;20;25 3272 | 25.64;20;25 3273 | 25.64;20;25 3274 | 25.64;20;25 3275 | 25.64;20;25 3276 | 25.64;20;25 3277 | 25.64;20;25 3278 | 25.63;20;25 3279 | 25.63;20;25 3280 | 25.63;20;25 3281 | 25.63;20;25 3282 | 25.63;20;25 3283 | 25.63;20;25 3284 | 25.63;20;25 3285 | 25.63;20;25 3286 | 25.63;20;25 3287 | 25.63;20;25 3288 | 25.63;20;25 3289 | 25.62;20;25 3290 | 25.62;20;25 3291 | 25.62;20;25 3292 | 25.62;20;25 3293 | 25.62;20;25 3294 | 25.62;20;25 3295 | 25.62;20;25 3296 | 25.62;20;25 3297 | 25.62;20;25 3298 | 25.61;20;25 3299 | 25.61;20;25 3300 | 25.61;20;25 3301 | 25.61;20;25 3302 | 25.6;20;25 3303 | 25.6;20;25 3304 | 25.6;20;25 3305 | 25.6;20;25 3306 | 25.59;20;25 3307 | 25.59;20;25 3308 | 25.59;20;25 3309 | 25.59;20;25 3310 | 25.59;20;25 3311 | 25.58;20;25 3312 | 25.58;20;25 3313 | 25.58;20;25 3314 | 25.58;20;25 3315 | 25.57;20;25 3316 | 25.57;20;25 3317 | 25.57;20;25 3318 | 25.57;20;25 3319 | 25.57;20;25 3320 | 25.57;20;25 3321 | 25.57;20;25 3322 | 25.57;20;25 3323 | 25.57;20;25 3324 | 25.57;20;25 3325 | 25.57;20;25 3326 | 25.57;20;25 3327 | 25.57;20;25 3328 | 25.57;20;25 3329 | 25.57;20;25 3330 | 25.57;20;25 3331 | 25.57;20;25 3332 | 25.57;20;25 3333 | 25.57;20;25 3334 | 25.57;20;25 3335 | 25.57;20;25 3336 | 25.57;20;25 3337 | 25.57;20;25 3338 | 25.57;20;25 3339 | 25.57;20;25 3340 | 25.57;20;25 3341 | 25.57;20;25 3342 | 25.57;20;25 3343 | 25.56;20;25 3344 | 25.56;20;25 3345 | 25.56;20;25 3346 | 25.56;20;25 3347 | 25.56;20;25 3348 | 25.56;20;25 3349 | 25.56;20;25 3350 | 25.56;20;25 3351 | 25.56;20;25 3352 | 25.56;20;25 3353 | 25.56;20;25 3354 | 25.56;20;25 3355 | 25.55;20;25 3356 | 25.55;20;25 3357 | 25.55;20;25 3358 | 25.55;20;25 3359 | 25.55;20;25 3360 | 25.54;20;25 3361 | 25.54;20;25 3362 | 25.54;20;25 3363 | 25.54;20;25 3364 | 25.53;20;25 3365 | 25.53;20;25 3366 | 25.53;20;25 3367 | 25.53;20;25 3368 | 25.53;20;25 3369 | 25.53;20;25 3370 | 25.53;20;25 3371 | 25.53;20;25 3372 | 25.53;20;25 3373 | 25.53;20;25 3374 | 25.53;20;25 3375 | 25.53;20;25 3376 | 25.52;20;25 3377 | 25.52;20;25 3378 | 25.52;20;25 3379 | 25.52;20;25 3380 | 25.52;20;25 3381 | 25.51;20;25 3382 | 25.51;20;25 3383 | 25.51;20;25 3384 | 25.51;20;25 3385 | 25.51;20;25 3386 | 25.51;20;25 3387 | 25.51;20;25 3388 | 25.5;20;25 3389 | 25.5;20;25 3390 | 25.5;20;25 3391 | 25.5;20;25 3392 | 25.5;20;25 3393 | 25.5;20;25 3394 | 25.5;20;25 3395 | 25.5;20;25 3396 | 25.5;20;25 3397 | 25.5;20;25 3398 | 25.5;20;25 3399 | 25.5;20;25 3400 | 25.5;20;25 3401 | 25.49;20;25 3402 | 25.49;20;25 3403 | 25.49;20;25 3404 | 25.49;20;25 3405 | 25.49;20;25 3406 | 25.49;20;25 3407 | 25.49;20;25 3408 | 25.49;20;25 3409 | 25.49;20;25 3410 | 25.49;20;25 3411 | 25.49;20;25 3412 | 25.49;20;25 3413 | 25.49;20;25 3414 | 25.49;20;25 3415 | 25.49;20;25 3416 | 25.49;20;25 3417 | 25.49;20;25 3418 | 25.49;20;25 3419 | 25.49;20;25 3420 | 25.49;20;25 3421 | 25.49;20;25 3422 | 25.49;20;25 3423 | 25.49;20;25 3424 | 25.49;20;25 3425 | 25.49;20;25 3426 | 25.49;20;25 3427 | 25.49;20;25 3428 | 25.49;20;25 3429 | 25.49;20;25 3430 | 25.49;20;25 3431 | 25.49;20;25 3432 | 25.49;20;25 3433 | 25.49;20;25 3434 | 25.49;20;25 3435 | 25.49;20;25 3436 | 25.48;20;25 3437 | 25.48;20;25 3438 | 25.48;20;25 3439 | 25.48;20;25 3440 | 25.47;20;25 3441 | 25.47;20;25 3442 | 25.47;20;25 3443 | 25.47;20;25 3444 | 25.47;20;25 3445 | 25.46;20;25 3446 | 25.46;20;25 3447 | 25.46;20;25 3448 | 25.46;20;25 3449 | 25.46;20;25 3450 | 25.45;20;25 3451 | 25.45;20;25 3452 | 25.45;20;25 3453 | 25.45;20;25 3454 | 25.45;20;25 3455 | 25.45;20;25 3456 | 25.44;20;25 3457 | 25.44;20;25 3458 | 25.44;20;25 3459 | 25.44;20;25 3460 | 25.44;20;25 3461 | 25.44;20;25 3462 | 25.44;20;25 3463 | 25.44;20;25 3464 | 25.44;20;25 3465 | 25.43;20;25 3466 | 25.43;20;25 3467 | 25.43;20;25 3468 | 25.43;20;25 3469 | 25.43;20;25 3470 | 25.43;20;25 3471 | 25.43;20;25 3472 | 25.43;20;25 3473 | 25.43;20;25 3474 | 25.43;20;25 3475 | 25.43;20;25 3476 | 25.43;20;25 3477 | 25.43;20;25 3478 | 25.43;20;25 3479 | 25.43;20;25 3480 | 25.43;20;25 3481 | 25.43;20;25 3482 | 25.43;20;25 3483 | 25.43;20;25 3484 | 25.43;20;25 3485 | 25.43;20;25 3486 | 25.43;20;25 3487 | 25.43;20;25 3488 | 25.43;20;25 3489 | 25.43;20;25 3490 | 25.43;20;25 3491 | 25.43;20;25 3492 | 25.43;20;25 3493 | 25.43;20;25 3494 | 25.43;20;25 3495 | 25.43;20;25 3496 | 25.43;20;25 3497 | 25.43;20;25 3498 | 25.43;20;25 3499 | 25.43;20;25 3500 | 25.42;20;25 3501 | 25.42;20;25 3502 | 25.42;20;25 3503 | 25.42;20;25 3504 | 25.41;20;25 3505 | 25.41;20;25 3506 | 25.41;20;25 3507 | 25.41;20;25 3508 | 25.41;20;25 3509 | 25.41;20;25 3510 | 25.41;20;25 3511 | 25.41;20;25 3512 | 25.41;20;25 3513 | 25.41;20;25 3514 | 25.41;20;25 3515 | 25.41;20;25 3516 | 25.41;20;25 3517 | 25.41;20;25 3518 | 25.41;20;25 3519 | 25.41;20;25 3520 | 25.41;20;25 3521 | 25.41;20;25 3522 | 25.41;20;25 3523 | 25.41;20;25 3524 | 25.41;20;25 3525 | 25.41;20;25 3526 | 25.41;20;25 3527 | 25.41;20;25 3528 | 25.41;20;25 3529 | 25.41;20;25 3530 | 25.41;20;25 3531 | 25.41;20;25 3532 | 25.41;20;25 3533 | 25.41;20;25 3534 | 25.41;20;25 3535 | 25.41;20;25 3536 | 25.41;20;25 3537 | 25.4;20;25 3538 | 25.4;20;25 3539 | 25.4;20;25 3540 | 25.4;20;25 3541 | 25.4;20;25 3542 | 25.4;20;25 3543 | 25.39;20;25 3544 | 25.39;20;25 3545 | 25.39;20;25 3546 | 25.39;20;25 3547 | 25.39;20;25 3548 | 25.39;20;25 3549 | 25.38;20;25 3550 | 25.38;20;25 3551 | 25.38;20;25 3552 | 25.38;20;25 3553 | 25.38;20;25 3554 | 25.37;20;25 3555 | 25.37;20;25 3556 | 25.37;20;25 3557 | -------------------------------------------------------------------------------- /pytunelogix/clxpidsim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Destination2Unknown/pytunelogix/b5dc835f8bdba87be9c4a78bdeba6208acd30c1e/pytunelogix/clxpidsim/__init__.py -------------------------------------------------------------------------------- /pytunelogix/clxpidsim/clxsim.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Updated and maintained by destination0b10unknown@gmail.com 4 | Copyright 2022 destination2unknown 5 | 6 | Licensed under the MIT License; 7 | you may not use this file except in compliance with the License. 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | """ 16 | import tkinter as tk 17 | import numpy as np 18 | import matplotlib.pyplot as plt 19 | import random 20 | import threading 21 | import time 22 | from matplotlib import animation 23 | from scipy.integrate import odeint 24 | from pylogix import PLC 25 | from pytunelogix.common import generalclasses as g 26 | 27 | def main(): 28 | class data(object): 29 | def __init__(self): 30 | self.PV = np.zeros(0) 31 | self.CV = np.zeros(0) 32 | self.SP = np.zeros(0) 33 | self.livetrend=0 34 | self.scanCount=0 35 | def storereads(self,CV,SP): 36 | self.CV=np.append(self.CV,CV) 37 | self.SP=np.append(self.SP,SP) 38 | def storepv(self,PV): 39 | self.PV=np.append(self.PV,PV) 40 | def reset(self): 41 | self.PV = np.zeros(0) 42 | self.CV = np.zeros(0) 43 | self.SP = np.zeros(0) 44 | self.scanCount=0 45 | 46 | def fopdtsetup(): 47 | process.Gain=float(modelgain.get()) 48 | process.TimeConstant=float(modeltc.get())*10 49 | process.DeadTime=float(modeldt.get())*10 50 | process.Bias=float(ambient.get()) 51 | process.t=0 52 | gData.reset() 53 | gData.livetrend=1 54 | spstatus.set("") 55 | pvstatus.set("") 56 | cvstatus.set("") 57 | comm.IPAddress = ip.get() 58 | comm.ProcessorSlot = int(slot.get()) 59 | comm.SocketTimeout = 1 60 | button_start["state"] = "disabled" 61 | button_stop["state"] = "normal" 62 | button_trend["state"] = "normal" 63 | button_save["state"] = "normal" 64 | ip.configure(state="disabled") 65 | slot.configure(state="disabled") 66 | modelgain.configure(state="disabled") 67 | modeltc.configure(state="disabled") 68 | modeldt.configure(state="disabled") 69 | ambient.configure(state="disabled") 70 | 71 | def thread_start(): 72 | global looper 73 | looper = g.PeriodicInterval(start, 0.1) 74 | 75 | def start(): 76 | try: 77 | ret = comm.Read([cvtag.get(),sptag.get()]) 78 | if ret[0].Status=='Success': 79 | cvtext.set(round(ret[0].Value,2)) 80 | cvtag.configure(state="disabled") 81 | actualcv=ret[0].Value 82 | else: 83 | cvstatus.set(ret[0].Status) 84 | if gData.CV.size>1: 85 | actualcv=gData.CV[-1] 86 | else: 87 | actualcv=0 88 | 89 | if ret[1].Status=='Success': 90 | sptext.set(round(ret[1].Value,2)) 91 | sptag.configure(state="disabled") 92 | actualsp=ret[1].Value 93 | else: 94 | spstatus.set(ret[1].Status) 95 | if gData.SP.size>1: 96 | actualsp=gData.SP[-1] 97 | else: 98 | actualsp=0 99 | 100 | #Send CV to Process 101 | process.CV=gData.CV 102 | #Store Data when it is read 103 | gData.storereads(actualcv,actualsp) 104 | ts=[gData.scanCount,gData.scanCount+1] 105 | 106 | #Get new PV value 107 | if gData.PV.size>1: 108 | pv=process.update(gData.PV[-1],ts) 109 | else: 110 | pv=process.update(float(ambient.get()),ts) 111 | #Add Noise between -0.1 and 0.1 112 | noise=(random.randint(0,10)/100)-0.05 113 | #Store PV 114 | gData.storepv(pv[0]+noise) 115 | #Write PV to PLC 116 | write = comm.Write(pvtag.get(),gData.PV[-1]) 117 | if write.Status=='Success': 118 | pvtext.set(round(write.Value,2)) 119 | pvtag.configure(state="disabled") 120 | else: 121 | pvstatus.set(write.Status) 122 | gData.scanCount+=1 123 | 124 | except Exception as e: 125 | pvstatus.set('An exception occurred: {}'.format(e)) 126 | 127 | def stop(): 128 | button_start["state"] = "normal" 129 | button_stop["state"] = "disabled" 130 | ip.configure(state="normal") 131 | slot.configure(state="normal") 132 | modelgain.configure(state="normal") 133 | modeltc.configure(state="normal") 134 | modeldt.configure(state="normal") 135 | pvtag.configure(state="normal") 136 | cvtag.configure(state="normal") 137 | sptag.configure(state="normal") 138 | ambient.configure(state="normal") 139 | gData.livetrend=0 140 | if 'looper' in globals(): 141 | looper.stop() 142 | comm.Close() 143 | 144 | def livetrend(): 145 | #Set up the figure 146 | fig = plt.figure() 147 | ax = plt.axes(xlim=(0,100),ylim=(0, 100)) 148 | SP, = ax.plot([], [], lw=2, color="Red", label='SP') 149 | CV, = ax.plot([], [], lw=2, color="Green", label='CV') 150 | PV, = ax.plot([], [], lw=2, color="Blue", label='PV') 151 | 152 | #Setup Func 153 | def init(): 154 | SP.set_data([], []) 155 | PV.set_data([], []) 156 | CV.set_data([], []) 157 | plt.ylabel('EU') 158 | plt.xlabel("Time (min)") 159 | plt.suptitle("Live Data") 160 | plt.legend(loc='best') 161 | return SP,PV,CV, 162 | 163 | #Loop here 164 | def animate(i): 165 | x = np.arange(len(gData.SP),dtype=int) 166 | scale = int(60*1000/100) #Convert mS to Minutes 167 | x=x/scale 168 | ax.set_xlim(0,max(x)*1.1) 169 | max_y=max(max(gData.PV),max(gData.CV),max(gData.SP)) 170 | min_y=min(min(gData.PV),min(gData.CV),min(gData.SP)) 171 | ax.set_ylim(min_y-1,max_y+1) 172 | SP.set_data(x,gData.SP) 173 | CV.set_data(x,gData.CV) 174 | PV.set_data(x,gData.PV) 175 | return SP,PV,CV, 176 | 177 | #Live Data 178 | if gData.livetrend: 179 | anim = animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=1000) #, blit=True) # cant use blit with dynamic x-axis 180 | 181 | plt.show() 182 | 183 | def save(): 184 | if len(gData.PV): 185 | now=time.strftime("%Y%m%d_%H%M%S") 186 | np.savetxt("SimData_" + now + ".csv", np.transpose((gData.PV,gData.CV,gData.SP)), header="PV;CV;SP", comments='', fmt='%1.3f', delimiter = ";") 187 | else: 188 | button_save["state"] = "disabled" 189 | #Gui 190 | root = tk.Tk() 191 | root.title('CLX PID Process Simulator using a FOPDT Model') 192 | root.resizable(True, True) 193 | root.geometry('500x325') 194 | 195 | #Text tags setup 196 | pvtext = tk.StringVar() 197 | cvtext = tk.StringVar() 198 | sptext = tk.StringVar() 199 | pvstatus = tk.StringVar() 200 | cvstatus = tk.StringVar() 201 | spstatus = tk.StringVar() 202 | pvtag = tk.Entry(root,width=10) 203 | cvtag = tk.Entry(root,width=10) 204 | sptag = tk.Entry(root,width=10) 205 | ip = tk.Entry(root,width=15) 206 | slot = tk.Entry(root,width=5) 207 | modelgain = tk.Entry(root,width=5) 208 | modeltc = tk.Entry(root,width=5) 209 | modeldt = tk.Entry(root,width=5) 210 | ambient = tk.Entry(root,width=5) 211 | 212 | #Column 0 213 | #Labels 214 | tk.Label(root, text="Tag").grid(row=0,column=0,padx=10 ,pady=2,sticky="NESW") 215 | tk.Label(root, text="SP:").grid(row=1,column=0,padx=10 ,pady=2,sticky="NESW") 216 | tk.Label(root, text="PV:").grid(row=2,column=0,padx=10 ,pady=2,sticky="NESW") 217 | tk.Label(root, text="CV:").grid(row=3,column=0,padx=10 ,pady=2,sticky="NESW") 218 | #Row 4 = Button 219 | #Row 5 = Button 220 | tk.Label(root, text="PLC IP Address:").grid(row=6,column=0,padx=10 ,pady=2) 221 | tk.Label(root, text="PLC Slot:").grid(row=7,column=0,padx=10 ,pady=2) 222 | tk.Label(root, text="Model Gain:").grid(row=8,column=0,padx=10 ,pady=2,sticky="NESW") 223 | tk.Label(root, text="Model TimeConstant(s):").grid(row=9,column=0,padx=10 ,pady=2,sticky="NESW") 224 | tk.Label(root, text="Model DeadTime(s):").grid(row=10,column=0,padx=10 ,pady=2,sticky="NESW") 225 | tk.Label(root, text="Model Ambient:").grid(row=11,column=0,padx=10 ,pady=2,sticky="NESW") 226 | 227 | #Column 1 228 | #Labels 229 | tk.Label(root, text="Value").grid(row=0,column=1,padx=10 ,pady=2,sticky="NESW") 230 | tk.Label(root, textvariable=sptext).grid(row=1,column=1,padx=10 ,pady=2,sticky="NESW") 231 | tk.Label(root, textvariable=pvtext).grid(row=2,column=1,padx=10 ,pady=2,sticky="NESW") 232 | tk.Label(root, textvariable=cvtext).grid(row=3,column=1,padx=10 ,pady=2,sticky="NESW") 233 | #Row 4 = Button 234 | #Row 5 = Button 235 | ip.grid(row=6, column=1,padx=10 ,pady=2,sticky="NESW") 236 | slot.grid(row=7, column=1,padx=10 ,pady=2,sticky="NESW") 237 | modelgain.grid(row=8, column=1,padx=10 ,pady=2,sticky="NESW") 238 | modeltc.grid(row=9, column=1,padx=10 ,pady=2,sticky="NESW") 239 | modeldt.grid(row=10, column=1,padx=10 ,pady=2,sticky="NESW") 240 | ambient.grid(row=11, column=1,padx=10 ,pady=2,sticky="NESW") 241 | 242 | #Column 2 243 | #Actual PLC TagName 244 | tk.Label(root, text="PLC Tag").grid(row=0,column=2,padx=10 ,pady=2) 245 | sptag.grid(row=1, column=2,padx=10 ,pady=2,sticky="NESW") 246 | pvtag.grid(row=2, column=2,padx=10 ,pady=2,sticky="NESW") 247 | cvtag.grid(row=3, column=2,padx=10 ,pady=2,sticky="NESW") 248 | 249 | #Column 3 250 | #Status 251 | tk.Label(root, text="Last Error:").grid(row=0,column=3,padx=10,columnspan=3 ,pady=2,sticky="W") 252 | tk.Label(root, textvariable=spstatus).grid(row=1,column=3,padx=10,columnspan=3 ,pady=2,sticky="W") 253 | tk.Label(root, textvariable=pvstatus).grid(row=2,column=3,padx=10,columnspan=3 ,pady=2,sticky="W") 254 | tk.Label(root, textvariable=cvstatus).grid(row=3,column=3,padx=10,columnspan=3 ,pady=2,sticky="W") 255 | 256 | #Default Values 257 | sptag.insert(10, "SP") 258 | pvtag.insert(10, "PID_PV") 259 | cvtag.insert(10, "PID_CV") 260 | ip.insert(10, "192.168.123.100") 261 | slot.insert(5, "2") 262 | modelgain.insert(5, "1.75") 263 | modeltc.insert(5, "75.5") 264 | modeldt.insert(5, "17.66") 265 | ambient.insert(5, "13.5") 266 | 267 | #Buttons 268 | #Start Button Placement 269 | button_start = tk.Button(root, text="Start Simulator", command=lambda :[fopdtsetup(),thread_start()]) 270 | button_start.grid(row=4,column=0,columnspan=1,padx=10 ,pady=2,sticky="NESW") 271 | 272 | #Stop Button Placement 273 | button_stop = tk.Button(root, text="Stop Simulator", command=lambda :[stop()]) 274 | button_stop.grid(row=4,column=1,columnspan=1,padx=10 ,pady=2,sticky="NESW") 275 | button_stop["state"] = "disabled" 276 | 277 | #Trend Button Placement 278 | button_trend = tk.Button(root, text="Show Trend", command=lambda :[livetrend()]) 279 | button_trend.grid(row=5,column=0,columnspan=2,padx=10 ,pady=2,sticky="NESW") 280 | button_trend["state"] = "disabled" 281 | 282 | button_save = tk.Button(root, text="Save as CSV", command=lambda :[save()]) 283 | button_save.grid(row=11,column=2,columnspan=1,padx=10 ,pady=2,sticky="NESW") 284 | button_save["state"] = "disabled" 285 | 286 | #default setup 287 | params=0,0 288 | model= (modelgain.get(),modeltc.get()*10,modeldt.get()*10,13.1) 289 | process=g.FOPDTModel(params, model) 290 | gData=data() 291 | comm=PLC() 292 | 293 | root.mainloop() 294 | 295 | if __name__ == '__main__': 296 | main() -------------------------------------------------------------------------------- /pytunelogix/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Destination2Unknown/pytunelogix/b5dc835f8bdba87be9c4a78bdeba6208acd30c1e/pytunelogix/common/__init__.py -------------------------------------------------------------------------------- /pytunelogix/common/generalclasses.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Updated and maintained by destination0b10unknown@gmail.com 4 | Copyright 2022 destination2unknown 5 | 6 | Licensed under the MIT License; 7 | you may not use this file except in compliance with the License. 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | """ 16 | import threading 17 | import time 18 | from scipy.integrate import odeint 19 | import numpy as np 20 | 21 | class sthread(threading.Thread): 22 | def __init__(self,func): 23 | threading.Thread.__init__(self) 24 | self.func=func 25 | 26 | def run(self): 27 | self.func() 28 | 29 | class PeriodicInterval(threading.Thread): 30 | def __init__(self, task_function, period): 31 | super().__init__() 32 | self.daemon = True 33 | self.task_function = task_function 34 | self.period = period 35 | self.i = 0 36 | self.t0 = time.time() 37 | self.stopper=0 38 | self.start() 39 | 40 | def sleep(self): 41 | self.i += 1 42 | delta = self.t0 + self.period * self.i - time.time() 43 | if delta > 0: 44 | time.sleep(delta) 45 | 46 | def run(self): 47 | while self.stopper==0: 48 | self.task_function() 49 | self.sleep() 50 | 51 | def stop(self): 52 | self.stopper=1 53 | 54 | def starter(self): 55 | self.stopper=0 56 | self.i = 0 57 | self.t0 = time.time() 58 | 59 | class tunefinderFOPDT(object): 60 | def __init__(self): 61 | self.Gain, self.TimeConstant, self.DeadTime = 1.5,60.0,15.0 62 | self.CHRKp,self.CHRKi, self.CHRKd=0.1,0.01,0.001 63 | self.IMCKp,self.IMCKi,self.IMCKd=0.2,0.02,0.002 64 | self.AIMCKp,self.AIMCKi, self.AIMCKd=0.3,0.03,0.003 65 | 66 | def calc(self,ModelData): 67 | self.Gain, self.TimeConstant, self.DeadTime = ModelData 68 | if (self.TimeConstant<=0): 69 | self.TimeConstant=1 70 | if (self.DeadTime<=0): 71 | self.DeadTime=1 72 | #CHRKp 73 | num=0.35*self.TimeConstant 74 | den=abs(self.Gain)*self.DeadTime 75 | self.CHRKp=num/den 76 | #CHRKi 77 | ti=1.2*self.TimeConstant 78 | self.CHRKi=self.CHRKp/ti 79 | #CHRKd 80 | td=0.5*self.DeadTime 81 | self.CHRKd=(self.CHRKp*td)/60 82 | 83 | #IMC_Kp 84 | lmda=2.1*self.DeadTime 85 | num= self.TimeConstant+0.5*self.DeadTime 86 | den=abs(self.Gain)*(lmda) 87 | self.IMCKp = num/den 88 | #IMC_Ki 89 | ti=self.TimeConstant+0.5*self.DeadTime 90 | self.IMCKi = self.IMCKp / ti 91 | #IMC_Kd 92 | num=self.TimeConstant*self.DeadTime 93 | den=2*self.TimeConstant+self.DeadTime 94 | td=num/den 95 | self.IMCKd = (td*self.IMCKp)/60 96 | 97 | #AIMC Kp 98 | L=max(0.1*self.TimeConstant,0.8*self.DeadTime) 99 | self.AIMCKp=self.TimeConstant/(abs(self.Gain)*(self.DeadTime+L)) 100 | #AIMC Ki 101 | ti=self.TimeConstant/(1.03-0.165*(self.DeadTime/self.TimeConstant)) 102 | self.AIMCKi =self.AIMCKp/self.TimeConstant 103 | #AIMC Kd 104 | self.AIMCKd=(self.DeadTime/2)/60 105 | 106 | def calcFullFat(self,ModelData): 107 | self.Gain, self.TimeConstant, self.DeadTime = ModelData 108 | if (self.TimeConstant<=0): 109 | self.TimeConstant=1 110 | if (self.DeadTime<=0): 111 | self.DeadTime=1 112 | #CHRKp 113 | num=0.6*self.TimeConstant 114 | den=abs(self.Gain)*self.DeadTime 115 | self.CHRKp=num/den 116 | #CHRKi 117 | ti=1*self.TimeConstant 118 | self.CHRKi=self.CHRKp/ti 119 | #CHRKd 120 | td=0.5*self.DeadTime 121 | self.CHRKd=(self.CHRKp*td) 122 | 123 | #IMC_Kp 124 | lmda=2.1*self.DeadTime 125 | num= self.TimeConstant+0.5*self.DeadTime 126 | den=abs(self.Gain)*(lmda) 127 | self.IMCKp = 1.1*(num/den) 128 | #IMC_Ki 129 | ti=self.TimeConstant+0.5*self.DeadTime 130 | self.IMCKi = self.IMCKp / ti 131 | #IMC_Kd 132 | num=self.TimeConstant*self.DeadTime 133 | den=2*self.TimeConstant+self.DeadTime 134 | td=num/den 135 | self.IMCKd = 1.1*(td*self.IMCKp) 136 | 137 | #AIMC Kp 138 | L=max(0.1*self.TimeConstant,0.8*self.DeadTime) 139 | num=self.TimeConstant + 0.5*self.DeadTime 140 | den=abs(self.Gain)*(L+0.5*self.DeadTime) 141 | self.AIMCKp=num/den 142 | #AIMC Ki 143 | ti=self.TimeConstant+0.5*self.DeadTime 144 | self.AIMCKi=self.AIMCKp/ti 145 | #AIMC Kd 146 | num=self.TimeConstant*self.DeadTime 147 | den=2*self.TimeConstant+self.DeadTime 148 | td=num/den 149 | self.AIMCKd = td*self.AIMCKp 150 | 151 | class PID(object): 152 | def __init__(self,Kp=1.0,Ki=0.1,Kd=0.01,setpoint=50,output_limits=(0, 100)): 153 | self.Kp, self.Ki, self.Kd = Kp, Ki, Kd 154 | self.setpoint = setpoint 155 | self._min_output, self._max_output = 0, 100 156 | self._proportional = 0 157 | self._integral = 0 158 | self._derivative = 0 159 | self.output_limits = output_limits 160 | self._last_eD =0 161 | self._lastCV=0 162 | self._d_init=0 163 | self.reset() 164 | 165 | def _clamp(self, value, limits): 166 | lower, upper = limits 167 | if value is None: 168 | return None 169 | elif (upper is not None) and (value > upper): 170 | return upper 171 | elif (lower is not None) and (value < lower): 172 | return lower 173 | return value 174 | 175 | def __call__(self,PV=0,SP=0,action="Direct",dFilter=1,pvt=1): 176 | #PID calculations 177 | #P term 178 | if action=="Direct": 179 | e = SP - PV 180 | else: 181 | e = PV - SP 182 | self._proportional = self.Kp * e 183 | 184 | #I Term 185 | if self._lastCV<100 and self._lastCV >0: 186 | self._integral += self.Ki * e 187 | #Allow I Term to change when Kp is set to Zero 188 | if self.Kp==0 and self._lastCV==100 and self.Ki * e<0: 189 | self._integral += self.Ki * e 190 | if self.Kp==0 and self._lastCV==0 and self.Ki * e>0: 191 | self._integral += self.Ki * e 192 | 193 | #D term 194 | if action=="Direct": 195 | eD=-PV 196 | else: 197 | eD=PV 198 | self._derivative = dFilter*self.Kd*(eD - self._last_eD) 199 | #init D term 200 | if self._d_init==0: 201 | self._derivative=0 202 | self._d_init=1 203 | 204 | #pv tracking 205 | if pvt==0: 206 | self._integral=-(self.Kp*e)+0.00001 207 | 208 | #Controller Output 209 | CV = self._proportional + self._integral + self._derivative 210 | CV = self._clamp(CV, self.output_limits) 211 | 212 | # update stored data for next iteration 213 | self._last_eD = eD 214 | self._lastCV=CV 215 | return CV 216 | 217 | @property 218 | def components(self): 219 | return self._proportional, self._integral, self._derivative 220 | 221 | @property 222 | def tunings(self): 223 | return self.Kp, self.Ki, self.Kd 224 | 225 | @tunings.setter 226 | def tunings(self, tunings): 227 | self.Kp, self.Ki, self.Kd = tunings 228 | 229 | @property 230 | def output_limits(self): 231 | return self._min_output, self._max_output 232 | 233 | @output_limits.setter 234 | def output_limits(self, limits): 235 | if limits is None: 236 | self._min_output, self._max_output = 0, 100 237 | return 238 | 239 | min_output, max_output = limits 240 | self._min_output = min_output 241 | self._max_output = max_output 242 | self._integral = self._clamp(self._integral, self.output_limits) 243 | 244 | def reset(self): 245 | #Reset 246 | self._proportional = 0 247 | self._integral = 0 248 | self._derivative = 0 249 | self._integral = self._clamp(self._integral, self.output_limits) 250 | self._last_eD = 0 251 | self._lastCV = 0 252 | self._last_eD = 0 253 | self._d_init = 0 254 | 255 | class FOPDTModel(object): 256 | def __init__(self, CV, ModelData): 257 | self.CV= CV 258 | self.Gain, self.TimeConstant, self.DeadTime, self.Bias = ModelData 259 | 260 | def calc(self,PV,ts): 261 | if (ts-self.DeadTime) <= 0: 262 | um=0 263 | elif int(ts-self.DeadTime)>=len(self.CV): 264 | um=self.CV[-1] 265 | else: 266 | um=self.CV[int(ts-self.DeadTime)] 267 | dydt = (-(PV-self.Bias) + self.Gain * um)/(self.TimeConstant) 268 | return dydt 269 | 270 | def update(self,PV,ts): 271 | y=odeint(self.calc,PV,ts) 272 | return y[-1] -------------------------------------------------------------------------------- /pytunelogix/pidlogger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Destination2Unknown/pytunelogix/b5dc835f8bdba87be9c4a78bdeba6208acd30c1e/pytunelogix/pidlogger/__init__.py -------------------------------------------------------------------------------- /pytunelogix/pidlogger/clxlogger.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Updated and maintained by destination0b10unknown@gmail.com 4 | Copyright 2022 destination2unknown 5 | 6 | Licensed under the MIT License; 7 | you may not use this file except in compliance with the License. 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | """ 16 | import sys 17 | import time 18 | import tkinter as tk 19 | import csv 20 | import os 21 | import pandas as pd 22 | import numpy as np 23 | import matplotlib.pyplot as plt 24 | import threading 25 | from matplotlib import animation 26 | from datetime import datetime 27 | from pylogix import PLC 28 | from pytunelogix.common import generalclasses as g 29 | 30 | def main(): 31 | class data(object): 32 | def __init__(self): 33 | self.reset() 34 | 35 | def update(self,PV,CV,SP): 36 | self.PV=np.append(self.PV,PV) 37 | self.CV=np.append(self.CV,CV) 38 | self.SP=np.append(self.SP,SP) 39 | 40 | def reset(self): 41 | self.PV = np.zeros(0) 42 | self.CV = np.zeros(0) 43 | self.SP = np.zeros(0) 44 | self.ErrCount=0 45 | self.ReadCount=0 46 | self.SetupFlag=False 47 | self.RunNowFlag=False 48 | self.CSVFile=object 49 | self.CSVFileWriter=object 50 | 51 | def thread_record(): 52 | global loop_record 53 | loop_record = g.PeriodicInterval(Record, int(deltat.get())/1000) 54 | 55 | def Record(): 56 | if GData.SetupFlag==False: 57 | #Setup communnication object 58 | comm.IPAddress = ip.get() 59 | comm.ProcessorSlot = int(slot.get()) 60 | comm.SocketTimeout = sorted([100, int(deltat.get())/1000, 5000])[1] 61 | spstatus.set("") 62 | pvstatus.set("") 63 | cvstatus.set("") 64 | GData.ErrCount=0 65 | GData.ReadCount=0 66 | GData.SetupFlag=True 67 | GData.RunNowFlag=True 68 | button_record.configure(bg = "Black") 69 | button_record["state"] = "disabled" 70 | try: 71 | #Write new data to csv if read was successful, if not write last value, Open File or create if it doesn't exist 72 | GData.CSVFile = open(fname.get(), 'a') 73 | GData.CSVFileWriter = csv.writer(GData.CSVFile, delimiter=';', lineterminator='\n', quotechar='/', quoting=csv.QUOTE_MINIMAL) 74 | #Write headers if its a new file 75 | if os.stat(fname.get()).st_size == 0: 76 | GData.CSVFileWriter.writerow(('PV','CV','SP','TimeStamp')) 77 | 78 | except Exception as e: 79 | spstatus.set('File Error: ' + str(e)) 80 | cvstatus.set('File Error: ' + str(e)) 81 | pvstatus.set('File Error: ' + str(e)) 82 | 83 | current_date_time = datetime.utcnow().strftime('%d-%m-%Y %H:%M:%S.%f') 84 | 85 | try: 86 | #Setup tags to read 87 | tag_list = [pvtexttag.get(), cvtexttag.get(), sptexttag.get()] 88 | ret = comm.Read(tag_list) 89 | 90 | #Update gui data if read is successful, if not update last error 91 | if ret[0].Status=='Success': 92 | actualpv=round(ret[0].Value,2) 93 | pvtext.set(actualpv) 94 | pvtexttag.configure(state="disabled") 95 | else: 96 | pvstatus.set(ret[0].Status) 97 | if len(GData.PV>0): 98 | actualpv=GData.PV[-1] 99 | else: 100 | actualpv=0 101 | 102 | #Update gui data if read is successful, if not update last error 103 | if ret[1].Status=='Success': 104 | actualcv=round(ret[1].Value,2) 105 | cvtext.set(actualcv) 106 | cvtexttag.configure(state="disabled") 107 | else: 108 | cvstatus.set(ret[1].Status) 109 | if len(GData.CV>0): 110 | actualcv=GData.CV[-1] 111 | else: 112 | actualcv=0 113 | 114 | #Update gui data if read is successful, if not update last error 115 | if ret[2].Status=='Success': 116 | actualsp=round(ret[2].Value,2) 117 | sptext.set(actualsp) 118 | sptexttag.configure(state="disabled") 119 | else: 120 | spstatus.set(ret[2].Status) 121 | if len(GData.SP>0): 122 | actualsp=GData.SP[-1] 123 | else: 124 | actualsp=0 125 | 126 | #Disable inputs if tag read is successful 127 | if ret[0].Status=='Success' or ret[1].Status=='Success' or ret[2].Status=='Success': 128 | deltat.configure(state="disabled") 129 | ip.configure(state="disabled") 130 | slot.configure(state="disabled") 131 | fname.configure(state="disabled") 132 | button_record.configure(bg = "Green") 133 | button_livetrend["state"] = "normal" 134 | 135 | #Write all values to csv file 136 | row = [actualpv,actualcv,actualsp] 137 | GData.update(actualpv,actualcv,actualsp) 138 | row.append(current_date_time) 139 | GData.CSVFileWriter.writerow(row) 140 | 141 | #If read fails update error counter, if successful increment read counter 142 | if ret[0].Status=='Success' or ret[1].Status=='Success' or ret[2].Status=='Success': 143 | GData.ReadCount+=1 144 | if ret[0].Status!='Success' and ret[1].Status!='Success' and ret[2].Status!='Success': 145 | GData.ErrCount+=1 146 | ec='Errors: '+ str(GData.ErrCount) 147 | rc='Reads: '+ str(GData.ReadCount) 148 | errorcount.set(ec) 149 | readcount.set(rc) 150 | 151 | except Exception as e: 152 | spstatus.set('Error: ' + str(e)) 153 | cvstatus.set('Error: ' + str(e)) 154 | pvstatus.set('Error: ' + str(e)) 155 | 156 | def Write(): 157 | try: 158 | #Setup comms 159 | comms = PLC() 160 | comms.IPAddress = ip.get() 161 | comms.ProcessorSlot = int(slot.get()) 162 | comms.SocketTimeout = 1 163 | 164 | #Setup tags to read back data 165 | tag_list = [cvtexttag.get(), sptexttag.get()] 166 | 167 | #Don't write data if empty, reads back data after write 168 | if spsend.get(): 169 | sp = float(spsend.get()) 170 | comms.Write(sptexttag.get(), sp) 171 | sptext.set(round(comms.Read(sptexttag.get()).Value,2)) 172 | if cvsend.get(): 173 | cv=float(cvsend.get()) 174 | comms.Write(cvtexttag.get(), cv) 175 | cvtext.set(round(comms.Read(cvtexttag.get()).Value,2)) 176 | 177 | except Exception as e: 178 | spstatus.set('Write Error: ' + str(e)) 179 | cvstatus.set('Write Error: ' + str(e)) 180 | 181 | finally: 182 | comms.Close() 183 | 184 | def TrendFileData(): 185 | try: 186 | if GData.RunNowFlag: 187 | GData.CSVFile.flush() 188 | df = pd.read_csv(fname.get(), sep=';',quoting=csv.QUOTE_NONE, escapechar="\\", encoding="utf-8") 189 | headers=list(df) 190 | df['TimeStamp'] = pd.to_datetime(df['TimeStamp'],format='%d-%m-%Y %H:%M:%S.%f') 191 | plt.figure() 192 | plt.plot(df['TimeStamp'],df[headers[0]], color="#1f77b4", linewidth=2, label=headers[0]) 193 | plt.plot(df['TimeStamp'],df[headers[1]], color="#ff7f0e",linewidth=2,label=headers[1]) 194 | plt.plot(df['TimeStamp'],df[headers[2]], color="#2ca02c",linewidth=2,label=headers[2]) 195 | plt.ylabel('EU') 196 | plt.xlabel("Time") 197 | plt.title(fname.get()) 198 | plt.legend(loc='best') 199 | plt.gcf().autofmt_xdate() 200 | plt.show() 201 | 202 | except Exception as e: 203 | pvstatus.set('CSV Read Error: ' + str(e)) 204 | 205 | 206 | def LiveTrend(): 207 | #Set up the figure 208 | fig = plt.figure() 209 | ax = plt.axes(xlim=(0,100),ylim=(0, 100)) 210 | SP, = ax.plot([], [], lw=2, label='SP') 211 | PV, = ax.plot([], [], lw=2, label='PV') 212 | CV, = ax.plot([], [], lw=2, label='CV') 213 | 214 | #Setup Func 215 | def init(): 216 | SP.set_data([], []) 217 | PV.set_data([], []) 218 | CV.set_data([], []) 219 | plt.ylabel('EU') 220 | plt.xlabel("Time (min)") 221 | plt.suptitle("Live Data") 222 | plt.legend(loc='best') 223 | return SP,PV,CV, 224 | 225 | #Loop here 226 | def animate(i): 227 | x = np.arange(len(GData.SP),dtype=int) 228 | scale = int(60*1000/int(deltat.get())) #Convert mS to Minutes 229 | x=x/scale 230 | ax.set_xlim(0,max(x)*1.1) 231 | SP.set_data(x,GData.SP) 232 | CV.set_data(x,GData.CV) 233 | PV.set_data(x,GData.PV) 234 | return SP,PV,CV, 235 | 236 | #Live Data 237 | if GData.RunNowFlag: 238 | anim = animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=1000) #, blit=True) # cant use blit with dynamic x-axis 239 | 240 | plt.show() 241 | 242 | def Stop(): 243 | if 'loop_record' in globals(): 244 | loop_record.stop() 245 | #Enable text box entry 246 | sptexttag.configure(state="normal") 247 | pvtexttag.configure(state="normal") 248 | cvtexttag.configure(state="normal") 249 | deltat.configure(state="normal") 250 | ip.configure(state="normal") 251 | slot.configure(state="normal") 252 | fname.configure(state="normal") 253 | button_record.configure(bg = "#f0f0f0") 254 | button_livetrend["state"] = "disabled" 255 | button_record["state"] = "normal" 256 | comm.Close() 257 | if not GData.CSVFile.closed: 258 | GData.CSVFile.close() 259 | GData.reset() 260 | plt.close('all') 261 | 262 | #Gui 263 | root = tk.Tk() 264 | root.title('CLX PID Data Logger -> CSV') 265 | root.resizable(True, True) 266 | root.geometry('510x175') 267 | 268 | #Text tags setup 269 | pvtext = tk.StringVar() 270 | cvtext = tk.StringVar() 271 | sptext = tk.StringVar() 272 | pvstatus = tk.StringVar() 273 | cvstatus = tk.StringVar() 274 | spstatus = tk.StringVar() 275 | errorcount = tk.StringVar() 276 | readcount = tk.StringVar() 277 | sptexttag = tk.Entry(root,width=10) 278 | pvtexttag = tk.Entry(root,width=10) 279 | cvtexttag = tk.Entry(root,width=10) 280 | spsend = tk.Entry(root,width=5) 281 | cvsend = tk.Entry(root,width=5) 282 | deltat = tk.Entry(root,width=5) 283 | ip = tk.Entry(root,width=15) 284 | slot = tk.Entry(root,width=5) 285 | fname = tk.Entry(root,width=5) 286 | 287 | #Column 0 288 | #Labels 289 | tk.Label(root, text=" ").grid(row=0,column=0,padx=10 ,pady=2) 290 | tk.Label(root, text="Tag").grid(row=0,column=0,padx=10 ,pady=2) 291 | tk.Label(root, text="SP:").grid(row=1,column=0,padx=10 ,pady=2) 292 | tk.Label(root, text="PV:").grid(row=2,column=0,padx=10 ,pady=2) 293 | tk.Label(root, text="CV:").grid(row=3,column=0,padx=10 ,pady=2) 294 | 295 | #Column 1 296 | #Label positions - Read 297 | tk.Label(root, text="Value").grid(row=0,column=1,padx=10 ,pady=2) 298 | tk.Label(root, textvariable=sptext).grid(row=1,column=1,padx=10 ,pady=2) 299 | tk.Label(root, textvariable=pvtext).grid(row=2,column=1,padx=10 ,pady=2) 300 | tk.Label(root, textvariable=cvtext).grid(row=3,column=1,padx=10 ,pady=2) 301 | 302 | #Column 2 303 | #Send - Write 304 | tk.Label(root, text="Write").grid(row=0,column=2,padx=10 ,pady=2) 305 | spsend.grid(row=1, column=2,padx=10 ,pady=2,sticky="NESW") 306 | cvsend.grid(row=3, column=2,padx=10 ,pady=2,sticky="NESW") 307 | 308 | #Column 3 309 | #Actual PLC TagName 310 | tk.Label(root, text="PLC Tag").grid(row=0,column=3,padx=10 ,pady=2) 311 | sptexttag.grid(row=1, column=3,padx=10 ,pady=2,sticky="NESW") 312 | pvtexttag.grid(row=2, column=3,padx=10 ,pady=2,sticky="NESW") 313 | cvtexttag.grid(row=3, column=3,padx=10 ,pady=2,sticky="NESW") 314 | deltat.grid(row=4, column=3,columnspan=1,padx=10 ,pady=2,sticky="NESW") 315 | tk.Label(root, text="mS",bg='#F0F0F0').grid(row=4,column=3,padx=10 ,pady=2,sticky="E") 316 | 317 | #Column 4 318 | #Actual PLC IP address 319 | tk.Label(root, text="PLC IP Address:").grid(row=0,column=4,padx=10 ,pady=2) 320 | ip.grid(row=1, column=4,padx=10 ,pady=2,sticky="NESW") 321 | tk.Label(root, text="PLC Slot:").grid(row=2,column=4,padx=10 ,pady=2) 322 | slot.grid(row=3, column=4,padx=10 ,pady=2,sticky="NESW") 323 | fname.grid(row=4, column=4,padx=10 ,pady=2,sticky="NESW",columnspan=1) 324 | 325 | #Column 5 326 | #Status 327 | tk.Label(root, text="Last Error:").grid(row=0,column=5,padx=10,columnspan=2 ,pady=2,sticky="NESW") 328 | tk.Label(root, textvariable=spstatus).grid(row=1,column=5,padx=10,columnspan=2 ,pady=2,sticky="NESW") 329 | tk.Label(root, textvariable=pvstatus).grid(row=2,column=5,padx=10,columnspan=2 ,pady=2,sticky="NESW") 330 | tk.Label(root, textvariable=cvstatus).grid(row=3,column=5,padx=10,columnspan=2 ,pady=2,sticky="NESW") 331 | tk.Label(root, textvariable=errorcount).grid(row=4,column=5,padx=10,columnspan=2 ,pady=2,sticky="NESW") 332 | tk.Label(root, textvariable=readcount).grid(row=5,column=5,padx=10,columnspan=2 ,pady=2,sticky="NESW") 333 | 334 | #Default Values 335 | sptexttag.insert(10, "SP") 336 | pvtexttag.insert(10, "PID_PV") 337 | cvtexttag.insert(10, "PID_CV") 338 | deltat.insert(5, "100") 339 | ip.insert(10, "192.168.123.100") 340 | slot.insert(5, "2") 341 | fname.insert(10,"D:\Trend.csv") 342 | 343 | #Buttons 344 | #Record Button Placement 345 | button_record = tk.Button(root, text="Record Data",disabledforeground="white", command=lambda :[thread_record()]) 346 | button_record.grid(row=4,column=0,columnspan=2,padx=10 ,pady=2,sticky="NESW") 347 | 348 | #Write Button Placement 349 | button_write = tk.Button(root, text="Write", command=lambda :[Write()]) 350 | button_write.grid(row=4,column=2,columnspan=1,padx=10 ,pady=2,sticky="NESW") 351 | 352 | #Live Trend Button Placement 353 | button_livetrend = tk.Button(root, text="Live Plot", command=lambda :[LiveTrend()]) 354 | button_livetrend.grid(row=5,column=3,columnspan=1,padx=10 ,pady=2,sticky="NESW") 355 | button_livetrend["state"] = "disabled" 356 | 357 | #Stop Trends Button Placement 358 | button_stop = tk.Button(root, text="Stop Recording", command=lambda :[Stop()]) 359 | button_stop.grid(row=5,column=0,columnspan=3,padx=10 ,pady=2,sticky="NESW") 360 | 361 | #Trend Button Placement 362 | button_TrendFileData = tk.Button(root, text="Plot Data From CSV", command=lambda :[TrendFileData()]) 363 | button_TrendFileData.grid(row=5,column=4,columnspan=1,padx=10 ,pady=2,sticky="NESW") 364 | 365 | #Class init 366 | GData=data() 367 | comm = PLC() 368 | 369 | root.mainloop() 370 | 371 | if __name__ == '__main__': 372 | main() -------------------------------------------------------------------------------- /pytunelogix/simulate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Destination2Unknown/pytunelogix/b5dc835f8bdba87be9c4a78bdeba6208acd30c1e/pytunelogix/simulate/__init__.py -------------------------------------------------------------------------------- /pytunelogix/simulate/simulator.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Updated and maintained by destination0b10unknown@gmail.com 4 | Copyright 2022 destination2unknown 5 | 6 | Licensed under the MIT License; 7 | you may not use this file except in compliance with the License. 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | """ 16 | import numpy as np 17 | import matplotlib.pyplot as plt 18 | from scipy.integrate import odeint 19 | import tkinter as tk 20 | from pytunelogix.common import generalclasses as g 21 | 22 | #Random Noise between -0.25 and 0.25, same set used for each run. Created once at runtime. 23 | minsize=600 24 | maxsize=25200 25 | noise= np.random.rand(maxsize)/2 26 | noise-=0.25 27 | 28 | def main(): 29 | def refresh(): 30 | #get values from tkinter 31 | if str(ind_button['state']) =='disabled' and str(sec_button['state'])=='disabled': 32 | calcKp.set(tKp.get()) 33 | calcKi.set(tKi.get()) 34 | calcKd.set(tKd.get()) 35 | elif str(ind_button['state']) =='disabled' and str(sec_button['state'])=='normal': 36 | calcKp.set(tKp.get()) 37 | calcKi.set(float(tKi.get())/60) 38 | calcKd.set(float(tKd.get())*60) 39 | elif str(ind_button['state']) =='normal' and str(sec_button['state'])=='disabled': 40 | calcKp.set(tKp.get()) 41 | calcKi.set(float(tKp.get())/(float(tKi.get()))) 42 | calcKd.set(float(tKp.get())*float(tKd.get())) 43 | elif str(ind_button['state']) =='normal' and str(sec_button['state'])=='normal': 44 | calcKp.set(tKp.get()) 45 | calcKi.set(float(tKp.get())/(float(tKi.get())*60)) 46 | calcKd.set(float(tKp.get())*float(tKd.get())*60) 47 | igain,itau,ideadtime=float(tK.get()),float(ttau.get()),float(tdt.get()) 48 | ikp,iki,ikd = float(calcKp.get()),float(calcKi.get()),float(calcKd.get()) 49 | 50 | #Find the size of the range needed 51 | if (ideadtime+itau)*7 < minsize: 52 | rangesize = minsize 53 | elif (ideadtime+itau)*7 >maxsize: 54 | rangesize = maxsize 55 | else: 56 | rangesize = int((ideadtime+itau)*7) 57 | 58 | #setup time intervals 59 | t = np.arange(start=0, stop=rangesize, step=1) 60 | 61 | #Setup data arrays 62 | SP = np.zeros(len(t)) 63 | PV = np.zeros(len(t)) 64 | CV = np.zeros(len(t)) 65 | pterm = np.zeros(len(t)) 66 | iterm = np.zeros(len(t)) 67 | dterm = np.zeros(len(t)) 68 | global noise 69 | noise=np.resize(noise, len(t)) 70 | 71 | #defaults 72 | ibias=float(tAmb.get()) 73 | startofstep=10 74 | 75 | #Packup data 76 | PIDGains=(ikp,iki,ikd) 77 | ModelData=(igain,itau,ideadtime,ibias) 78 | #Filter for D term 79 | if radioFilter.get()=='Filter': 80 | tc=max(0.1*itau,0.8,ideadtime) 81 | num=tc*(itau+0.5*ideadtime) 82 | den=itau*(tc+ideadtime) 83 | dFilter=num/den 84 | else: 85 | dFilter=1 86 | direction=radioDirection.get() 87 | 88 | #Process Variable Tracking 89 | if radioPvT.get()=='PvT': 90 | PvT=1 91 | else: 92 | PvT=0 93 | 94 | #PID Instantiation 95 | pid = g.PID(ikp, iki, ikd, SP[0]) 96 | pid.output_limits = (0, 100) 97 | pid.tunings=(PIDGains) 98 | 99 | #plant Instantiation 100 | plant=g.FOPDTModel(CV, ModelData) 101 | 102 | #Start Value 103 | PV[0]=ibias+noise[0] 104 | 105 | #Loop through to find PID output and Process value 106 | for i in t: 107 | if i df['PV'].iloc[-1]: 87 | if df['CV'].idxmin()< df['CV'].idxmax(): 88 | direction="Reverse" 89 | else: 90 | direction="Direct" 91 | else: 92 | if df['CV'].idxmin()< df['CV'].idxmax(): 93 | direction="Direct" 94 | else: 95 | direction="Reverse" 96 | 97 | #Find basic parameters 98 | if df['CV'].idxmin()< df['CV'].idxmax(): 99 | i_start=df['CV'].idxmax() 100 | else: 101 | i_start=df['CV'].idxmin() 102 | ambient.set(round(df['PV'].iloc[:i_start+onesecwindow].mean(axis = 0),2)) 103 | StartPV=df['PV'].iloc[i_start] 104 | InitCV=df['CV'].iloc[0] 105 | StartCV=df['CV'].iloc[i_start] 106 | 107 | #DeadTime Range 108 | RangeU=round(float(ambient.get()),2)*hihilimit 109 | RangeL=round(float(ambient.get()),2)*lololimit 110 | 111 | #Find DeadTime 112 | if (direction=="Reverse" and df['CV'].idxmin()< df['CV'].idxmax()) or (direction=="Direct" and df['CV'].idxmin()> df['CV'].idxmax()): 113 | for x in range(i_start,(len(df['PV'])-window)): 114 | if(df['PV'].iloc[x:x+twosecwindow:1].mean(axis = 0)RangeU): 120 | modeldt.set(round((x-i_start)*deltaT,2)) 121 | break 122 | 123 | #Find Gain 124 | if (direction=="Reverse" and df['CV'].idxmin()< df['CV'].idxmax()) or (direction=="Direct" and df['CV'].idxmin()> df['CV'].idxmax()): 125 | j=df['PV'].idxmin() 126 | min_peak=df['PV'].min() 127 | else: 128 | j=df['PV'].idxmax() 129 | max_peak=df['PV'].max() 130 | 131 | peak = df['PV'].iloc[j-onesecwindow:j+onesecwindow:1].mean(axis = 0) 132 | modelgain.set(round((peak-round(float(ambient.get()),2))/(StartCV-InitCV),2)) 133 | 134 | #Time Constant 135 | tc_value=0.63*(peak-float(ambient.get()))+float(ambient.get()) 136 | tc_upp=tc_value*1.01 137 | tc_low=tc_value*0.99 138 | 139 | #Find Time Constant 140 | if (direction=="Reverse" and df['CV'].idxmin()< df['CV'].idxmax()) or (direction=="Direct" and df['CV'].idxmin()> df['CV'].idxmax()): 141 | z=df[df['PV']<=StartPV-(peak*0.1)].first_valid_index() 142 | for x in range(z,(len(df['PV'])-window)): 143 | if(df['PV'].iloc[x-twosecwindow:x+twosecwindow:1].mean(axis = 0)=(peak*0.5)].first_valid_index() 151 | for x in range(z,(len(df['PV'])-window)): 152 | if(df['PV'].iloc[x-twosecwindow:x+twosecwindow:1].mean(axis = 0)>tc_value): 153 | modeltc.set(round(((x-i_start)*deltaT)-float(modeldt.get()),2)) 154 | break 155 | else: 156 | modeltc.set(-1) 157 | 158 | #Setup System Model 159 | num = [float(modelgain.get())] 160 | den = [float(modeltc.get()),1] 161 | sys1 = signal.TransferFunction(num,den) 162 | tune() 163 | 164 | #Find Step Response based on Rough Model 165 | t1,y1 = signal.step(sys1, N=int(df['PV'].count()/10)) 166 | t1=t1[::10] 167 | y1=y1[::10] 168 | 169 | #Rescale 170 | plotpv=df['PV'].iloc[::10].reset_index(drop=True) 171 | plotcv=df['CV'].iloc[::10].reset_index(drop=True) 172 | 173 | #plot 174 | plt.figure() 175 | plt.xlim(0,df['PV'].count()*0.11) 176 | plt.plot(plotpv, color="blue", linewidth=3, label='PV') 177 | plt.plot(t1+(i_start/10+(float(modeldt.get()))),((StartCV-InitCV)*y1)+round(float(ambient.get()),2),color="red",linewidth=3,label='Model') 178 | plt.plot(plotcv, color="green", linewidth=3, label='CV') 179 | plt.hlines(round(float(ambient.get()),2), 0, i_start/10+float(modeldt.get()),colors='red', linestyles='solid',linewidth=3,label='') 180 | plt.ylabel('Engineering Units') 181 | plt.xlabel('Seconds') 182 | plt.suptitle("Rough Model v Actual Data") 183 | plt.title(f"ModelGain: {round(float(modelgain.get()),2)} ModelTc: {round(float(modeltc.get()),2)} ModelDT: {round(float(modeldt.get()),2)}") 184 | plt.legend(loc='best') 185 | plt.show(block=False) 186 | 187 | def step3(filename): 188 | if bool(fname.get())==True: 189 | #Read File 190 | df = pd.read_csv(filename, sep=';') 191 | 192 | #Find CV and PV Columns 193 | CV_cols = [col for col in df.columns if 'CV' in col.upper()] 194 | PV_cols = [col for col in df.columns if 'PV' in col.upper()] 195 | df['CV'] = df[CV_cols[0]] 196 | df['PV'] = df[PV_cols[0]] 197 | 198 | #Find Step Size 199 | CVStep=df['CV'].max()-df['CV'].min() 200 | 201 | #start of step 202 | if df['CV'].idxmin()< df['CV'].idxmax(): 203 | indexofstart=df['CV'].idxmax() 204 | else: 205 | indexofstart=df['CV'].idxmin() 206 | 207 | ambient.set(round(df['PV'].iloc[:indexofstart].mean(axis = 0),2)) 208 | 209 | #produces a model based on the parameters 210 | def fopdt_func(t_fopdt, K=1, tau=1, deadtime=0): 211 | deadtime = max(0,deadtime) 212 | tau = max(0,tau) 213 | return np.array([K*(1-np.exp(-(t_fopdt-deadtime)/tau)) if t_fopdt >= deadtime else 0 for t_fopdt in t_fopdt]) 214 | 215 | #Difference between model and actual 216 | def err(Xe,te,ye): 217 | Ke,tau,DeadTime = Xe 218 | z = CVStep*fopdt_func(te,Ke,tau,DeadTime)+float(ambient.get()) 219 | iae = sum(abs(z-ye))*(max(te)-min(te))/len(te) 220 | return iae 221 | 222 | #Trim Timescale 223 | actualPV=df['PV'].iloc[max(0,indexofstart-1)::10].reset_index(drop=True) 224 | actualCV=df['CV'].iloc[max(0,indexofstart-1)::10].reset_index(drop=True) 225 | t=actualPV.index.values 226 | 227 | #Model Starting Point 228 | if modelgain.get() =="...": 229 | ModelValues = 1.1,60,10 230 | else: 231 | ModelValues = float(modelgain.get()),float(modeltc.get()), float(modeldt.get()) 232 | 233 | #minimize difference between model and actual 234 | bounds = [(None, None), (0, None), (0,None)] 235 | Gain,Tau,DeadTime = minimize(err,ModelValues,args=(t, actualPV.values),bounds=bounds, method='Nelder-Mead').x 236 | 237 | #Update holder 238 | modelgain.set(round(Gain,2)) 239 | modeltc.set(round(Tau,2)) 240 | modeldt.set(round(DeadTime,2)) 241 | tune() 242 | 243 | #Get data to plot new model 244 | ymodel=CVStep*fopdt_func(t,float(modelgain.get()),float(modeltc.get()), float(modeldt.get()))+float(ambient.get()) 245 | 246 | #Invert sign on Negative Step 247 | if df['CV'].idxmin()> df['CV'].idxmax(): 248 | modelgain.set(round(-Gain,2)) 249 | 250 | #Plot 251 | plt.figure() 252 | plt.plot(actualPV,color="blue",linewidth=3,label='PV') 253 | plt.plot(actualCV,color="green",linewidth=3,label='CV') 254 | plt.plot(ymodel,color="red",linewidth=3,label='Model') 255 | plt.xlabel('Seconds') 256 | plt.ylabel('Engineering Units') 257 | plt.suptitle('Refined Model v Actual Data') 258 | plt.title(f"ModelGain: {round(float(modelgain.get()),2)} ModelTc: {round(float(modeltc.get()),2)} ModelDT: {round(float(modeldt.get()),2)}") 259 | plt.legend(loc='best') 260 | plt.show(block=False) 261 | 262 | def runthesim(processmodel,tune): 263 | #EntryPoint 264 | minsize=300 265 | maxsize=7200 266 | 267 | #unpack values 268 | igain,itau,ideadtime=processmodel 269 | ikp,iki,ikd = tune 270 | 271 | #Find the size of the range needed 272 | if ideadtime*2+itau*10 < minsize: 273 | rangesize = minsize 274 | elif ideadtime*2+itau*10 >maxsize: 275 | rangesize = maxsize 276 | else: 277 | rangesize = int(ideadtime*2+itau*10) 278 | 279 | #setup time intervals 280 | t = np.arange(rangesize) 281 | #Random Noise between -0.1 and 0.1 282 | noise= 0.2*np.random.rand(rangesize) 283 | noise-=0.1 284 | #noise=np.zeros(rangesize) #no noise 285 | 286 | #Setup data arrays 287 | SP = np.zeros(len(t)) 288 | PV = np.zeros(len(t)) 289 | CV = np.zeros(len(t)) 290 | pterm = np.zeros(len(t)) 291 | iterm = np.zeros(len(t)) 292 | dterm = np.zeros(len(t)) 293 | 294 | #defaults 295 | startofstep=10 296 | 297 | #Packup data 298 | PIDGains=(ikp,iki,ikd) 299 | ModelData=(igain,itau,ideadtime,float(ambient.get())) 300 | 301 | #PID Instantiation 302 | pid = g.PID(ikp, iki, ikd, SP[0]) 303 | pid.output_limits = (0, 100) 304 | pid.tunings=(PIDGains) 305 | 306 | #plant Instantiation 307 | plant=g.FOPDTModel(CV, ModelData) 308 | 309 | #Start Value 310 | PV[0]=float(ambient.get())+noise[0] 311 | 312 | #Loop through timestamps 313 | for i in t: 314 | if i<(len(t)-1): 315 | if i < startofstep: 316 | SP[i] = float(ambient.get()) 317 | elif (i > startofstep and i< rangesize/2): 318 | if float(modelgain.get()) > 0: 319 | SP[i]= 50 + float(ambient.get()) 320 | else: 321 | SP[i]= float(ambient.get()) - 50 322 | else: 323 | if float(modelgain.get()) > 0: 324 | SP[i]=40 + float(ambient.get()) 325 | else: 326 | SP[i]= float(ambient.get()) - 40 327 | #Find current controller output 328 | CV[i]=pid(PV[i], SP[i], "Direct" if float(modelgain.get()) > 0 else "Reverse") 329 | ts = [i,i+1] 330 | #Send step data 331 | plant.CV=CV 332 | #Find calculated PV 333 | PV[i+1] = plant.update(PV[i],ts) 334 | PV[i+1]+=noise[i] 335 | else: 336 | #cleanup endpoint 337 | SP[i]=SP[i-1] 338 | CV[i]=CV[i-1] 339 | itae = 0 if i < startofstep else itae+(i-startofstep)*abs(SP[i]-PV[i]) 340 | 341 | #Display itae value 342 | itae=(round(itae/len(t),2)) #measure PID performance 343 | dataout=SP,PV,CV,itae 344 | return dataout 345 | 346 | def step4(): 347 | if modelgain.get()!="..." and tCHRKp.get()!="...": 348 | processmodel=float(modelgain.get()),float(modeltc.get()), float(modeldt.get()) 349 | CHRtune=tuner.CHRKp,tuner.CHRKi,tuner.CHRKd 350 | IMCtune= tuner.IMCKp,tuner.IMCKi,tuner.IMCKd 351 | AIMCtune=tuner.AIMCKp,tuner.AIMCKi,tuner.AIMCKd 352 | 353 | #store pid data array from simulation 354 | CHR=runthesim(processmodel,CHRtune) 355 | IMC=runthesim(processmodel,IMCtune) 356 | AIMC=runthesim(processmodel,AIMCtune) 357 | 358 | #Unpack the data returned 359 | CHRSP,CHRPV,CHRCV,CHRitae=CHR 360 | IMCSP,IMCPV,IMAIMCV,IMCitae=IMC 361 | AIMCSP,AIMCPV,AIMCCV,AIMCitae=AIMC 362 | 363 | plt.figure() 364 | plt.plot(CHRSP, color="goldenrod", linewidth=3, label='SP') 365 | plt.plot(CHRPV,color="darkgreen",linewidth=2,label='CHR PV') 366 | plt.plot(IMCPV,color="blue",linewidth=2,label='IMC PV') 367 | plt.plot(AIMCPV, color="red", linewidth=2, label='AIMC PV') 368 | 369 | plt.ylabel('EU') 370 | plt.xlabel('Seconds') 371 | plt.suptitle("PID Tune Comparison") 372 | plt.title("CHR ITAE:%s IMC ITAE:%s AIMC ITAE:%s" % (CHRitae, IMCitae, AIMCitae),fontsize=10) 373 | plt.legend(loc='best') 374 | plt.show(block=False) 375 | 376 | def tune(selector="C"): 377 | if modelgain.get()!="...": 378 | model=float(modelgain.get()),float(modeltc.get()), float(modeldt.get()) 379 | if selector=="C": 380 | con_button['state']='disabled' 381 | agg_button['state']='normal' 382 | tuner.calc(model) 383 | elif selector=='A': 384 | con_button['state']='normal' 385 | agg_button['state']='disabled' 386 | tuner.calcFullFat(model) 387 | if selector == "S": 388 | sec_button['state']='disabled' 389 | min_button['state']='normal' 390 | elif selector == "M": 391 | sec_button['state']='normal' 392 | min_button['state']='disabled' 393 | elif selector == "D": 394 | ind_button['state']='normal' 395 | dep_button['state']='disabled' 396 | elif selector == "I": 397 | ind_button['state']='disabled' 398 | dep_button['state']='normal' 399 | 400 | if str(dep_button['state']) =='normal' and str(sec_button['state'])=='disabled': 401 | tUnitKp.set("Kp") 402 | tUnitKi.set(" Ki (1/s) ") 403 | tUnitKd.set(" Kd (s) ") 404 | tCHRKp.set(round(tuner.CHRKp,4)) 405 | tCHRKi.set(round(tuner.CHRKi,4)) 406 | tCHRKd.set(round(tuner.CHRKd,4)) 407 | tIMCKp.set(round(tuner.IMCKp,4)) 408 | tIMCKi.set(round(tuner.IMCKi,4)) 409 | tIMCKd.set(round(tuner.IMCKd,4)) 410 | tAIMCKp.set(round(tuner.AIMCKp,4)) 411 | tAIMCKi.set(round(tuner.AIMCKi,4)) 412 | tAIMCKd.set(round(tuner.AIMCKd,4)) 413 | 414 | elif str(dep_button['state']) =='normal' and str(sec_button['state'])=='normal': 415 | tUnitKp.set("Kp") 416 | tUnitKi.set(" Ki (1/min) ") 417 | tUnitKd.set(" Kd (min) ") 418 | tCHRKp.set(round(tuner.CHRKp,4)) 419 | tCHRKi.set(round(tuner.CHRKi*60,4)) 420 | tCHRKd.set(round(tuner.CHRKd/60,4)) 421 | tIMCKp.set(round(tuner.IMCKp,4)) 422 | tIMCKi.set(round(tuner.IMCKi*60,4)) 423 | tIMCKd.set(round(tuner.IMCKd/60,4)) 424 | tAIMCKp.set(round(tuner.AIMCKp,4)) 425 | tAIMCKi.set(round(tuner.AIMCKi*60,4)) 426 | tAIMCKd.set(round(tuner.AIMCKd/60,4)) 427 | 428 | elif str(dep_button['state']) =='disabled' and str(sec_button['state'])=='disabled': 429 | tUnitKp.set("Kc") 430 | tUnitKi.set(" Ti (sec/repeat) ") 431 | tUnitKd.set("Td (sec)") 432 | tCHRKp.set(round(tuner.CHRKp,4)) 433 | tCHRKi.set(round(tuner.CHRKp/(tuner.CHRKi),4)) 434 | tCHRKd.set(round(tuner.CHRKd/(tuner.CHRKp),4)) 435 | tIMCKp.set(round(tuner.IMCKp,4)) 436 | tIMCKi.set(round(tuner.IMCKp/(tuner.IMCKi),4)) 437 | tIMCKd.set(round(tuner.IMCKd/(tuner.IMCKp),4)) 438 | tAIMCKp.set(round(tuner.AIMCKp,4)) 439 | tAIMCKi.set(round(tuner.AIMCKp/(tuner.AIMCKi),4)) 440 | tAIMCKd.set(round(tuner.AIMCKd/(tuner.AIMCKp),4)) 441 | 442 | else: 443 | tUnitKp.set("Kc") 444 | tUnitKi.set("Ti (min/repeat)") 445 | tUnitKd.set("Td (min)") 446 | tCHRKp.set(round(tuner.CHRKp,4)) 447 | tCHRKi.set(round(tuner.CHRKp/(tuner.CHRKi*60),4)) 448 | tCHRKd.set(round(tuner.CHRKd/(tuner.CHRKp*60),4)) 449 | tIMCKp.set(round(tuner.IMCKp,4)) 450 | tIMCKi.set(round(tuner.IMCKp/(tuner.IMCKi*60),4)) 451 | tIMCKd.set(round(tuner.IMCKd/(tuner.IMCKp*60),4)) 452 | tAIMCKp.set(round(tuner.AIMCKp,4)) 453 | tAIMCKi.set(round(tuner.AIMCKp/(tuner.AIMCKi*60),4)) 454 | tAIMCKd.set(round(tuner.AIMCKd/(tuner.AIMCKp*60),4)) 455 | 456 | tuner=g.tunefinderFOPDT() 457 | modeldt.set('...') 458 | modeltc.set('...') 459 | modelgain.set('...') 460 | ambient.set('...') 461 | tCHRKp.set("...") 462 | tCHRKi.set("...") 463 | tCHRKd.set("...") 464 | tIMCKp.set("...") 465 | tIMCKi.set("...") 466 | tIMCKd.set("...") 467 | tAIMCKp.set("...") 468 | tAIMCKi.set("...") 469 | tAIMCKd.set("...") 470 | tUnitKp.set(" Kp ") 471 | tUnitKi.set(" Ki (1/s) ") 472 | tUnitKd.set(" Kd (s) ") 473 | 474 | step1_button = ttk.Button(root,text='Step 1: Open CSV File ',command=lambda :[step1()]) 475 | step1_button.grid(row=0,column=0,padx=5,pady=4,sticky="NESW") 476 | 477 | step2_button = ttk.Button(root,text='Step 2: Estimate Model ',command=lambda :[step2(fname.get())]) 478 | step2_button.grid(row=1,column=0,padx=5,pady=4,sticky="NESW") 479 | 480 | step3_button = ttk.Button(root,text='Step 3: Refine Model ', command=lambda :[step3(fname.get())]) 481 | step3_button.grid(row=2,column=0,padx=5,pady=4,sticky="NESW") 482 | 483 | step4_button = ttk.Button(root,text='Step 4: Run PID Sim ',command=lambda :[step4()]) 484 | step4_button.grid(row=3,column=0,padx=5,pady=4,sticky="NESW") 485 | 486 | con_button = ttk.Button(root,text='Conservative',command=lambda :[tune("C")]) 487 | con_button.grid(row=6,column=0,padx=5,pady=4,sticky="NESW") 488 | con_button['state']='disabled' 489 | 490 | agg_button = ttk.Button(root,text='Aggressive',command=lambda :[tune("A")]) 491 | agg_button.grid(row=7,column=0,padx=5,pady=4,sticky="NESW") 492 | 493 | ind_button = ttk.Button(root,text='Independant',command=lambda :[tune("I")]) 494 | ind_button.grid(row=8,column=0,padx=5,pady=4,sticky="NESW") 495 | ind_button['state']='disabled' 496 | 497 | dep_button = ttk.Button(root,text='Dependant',command=lambda :[tune("D")]) 498 | dep_button.grid(row=9,column=0,padx=5,pady=4,sticky="NESW") 499 | 500 | sec_button = ttk.Button(root,text='Seconds',command=lambda :[tune("S")]) 501 | sec_button.grid(row=10,column=0,padx=5,pady=4,sticky="NESW") 502 | sec_button['state']='disabled' 503 | 504 | min_button = ttk.Button(root,text='Minutes',command=lambda :[tune("M")]) 505 | min_button.grid(row=11,column=0,padx=5,pady=4,sticky="NESW") 506 | 507 | tk.Label(root, textvariable=fname).grid(row=0,column=1,columnspan=5,padx=1,pady=1,sticky="W") 508 | tk.Label(root, text="Model Gain:").grid(row=1,column=1,columnspan=2,padx=1,pady=1,sticky="W") 509 | tk.Label(root, text="TimeConstant (s):").grid(row=2,column=1,columnspan=1,padx=1,pady=1,sticky="W") 510 | tk.Label(root, text="DeadTime (s):").grid(row=3,column=1,columnspan=1,padx=1,pady=1,sticky="W") 511 | tk.Label(root, text="Ambient:").grid(row=4,column=1,columnspan=1,padx=1,pady=1,sticky="W") 512 | tk.Label(root, textvariable=modelgain).grid(row=1,column=2,padx=1,pady=1,sticky="NESW") 513 | tk.Label(root, textvariable=modeltc).grid(row=2,column=2,padx=1,pady=1,sticky="NESW") 514 | tk.Label(root, textvariable=modeldt).grid(row=3,column=2,padx=1,pady=1,sticky="NESW") 515 | tk.Label(root, textvariable=ambient).grid(row=4,column=2,padx=1,pady=1,sticky="NESW") 516 | spacer="____________________________________________________" 517 | tk.Label(root, text=spacer).grid(row=5,column=1,columnspan=4) 518 | tk.Label(root, text="PID Gains ->").grid(row=6,column=1,sticky="W") 519 | tk.Label(root, textvariable=tUnitKp).grid(row=6,column=2,sticky="NESW") 520 | tk.Label(root, textvariable=tUnitKi).grid(row=6,column=3,sticky="NESW") 521 | tk.Label(root, textvariable=tUnitKd).grid(row=6,column=4,sticky="NESW") 522 | tk.Label(root, text="CHR Method").grid(row=7,column=1,sticky="W") 523 | tk.Label(root, text="IMC Method").grid(row=8,column=1,sticky="W") 524 | tk.Label(root, text="AIMC Method").grid(row=9,column=1,sticky="W") 525 | tk.Label(root, textvariable=tCHRKp).grid(row=7,column=2,padx=4,pady=4,sticky="NESW") 526 | tk.Label(root, textvariable=tCHRKi).grid(row=7,column=3,padx=4,pady=4,sticky="NESW") 527 | tk.Label(root, textvariable=tCHRKd).grid(row=7,column=4,padx=4,pady=4,sticky="NESW") 528 | tk.Label(root, textvariable=tIMCKp).grid(row=8,column=2,padx=4,pady=4,sticky="NESW") 529 | tk.Label(root, textvariable=tIMCKi).grid(row=8,column=3,padx=4,pady=4,sticky="NESW") 530 | tk.Label(root, textvariable=tIMCKd).grid(row=8,column=4,padx=4,pady=4,sticky="NESW") 531 | tk.Label(root, textvariable=tAIMCKp).grid(row=9,column=2,padx=4,pady=4,sticky="NESW") 532 | tk.Label(root, textvariable=tAIMCKi).grid(row=9,column=3,padx=4,pady=4,sticky="NESW") 533 | tk.Label(root, textvariable=tAIMCKd).grid(row=9,column=4,padx=4,pady=4,sticky="NESW") 534 | 535 | # run the gui 536 | root.mainloop() 537 | 538 | if __name__ == '__main__': 539 | main() -------------------------------------------------------------------------------- /pytunelogix/stage2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Destination2Unknown/pytunelogix/b5dc835f8bdba87be9c4a78bdeba6208acd30c1e/pytunelogix/stage2/__init__.py -------------------------------------------------------------------------------- /pytunelogix/stage2/openlooptune.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | pass 3 | 4 | if __name__ == '__main__': 5 | main() -------------------------------------------------------------------------------- /pytunelogix/stage3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Destination2Unknown/pytunelogix/b5dc835f8bdba87be9c4a78bdeba6208acd30c1e/pytunelogix/stage3/__init__.py -------------------------------------------------------------------------------- /pytunelogix/stage3/closedlooptune.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | pass 3 | 4 | if __name__ == '__main__': 5 | main() -------------------------------------------------------------------------------- /pytunelogix/stage4/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Destination2Unknown/pytunelogix/b5dc835f8bdba87be9c4a78bdeba6208acd30c1e/pytunelogix/stage4/__init__.py -------------------------------------------------------------------------------- /pytunelogix/stage4/adaptivetune.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | pass 3 | 4 | if __name__ == '__main__': 5 | main() -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file=README.md 3 | license_files=LICENSE.rst -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='pytunelogix', 5 | version='1.1.19', 6 | license='MIT', 7 | author="Destination2Unknown", 8 | author_email='destination0b10unknown@gmail.com', 9 | description='PID tuner, logger and simulator', 10 | long_description='PID tuner, logger and simulator, with multiple tuning methods', 11 | packages=find_packages(), 12 | url='https://github.com/Destination2Unknown/pytunelogix', 13 | keywords='PID Tuner', 14 | classifiers= [ 15 | "Development Status :: 5 - Production/Stable", 16 | "Intended Audience :: Information Technology", 17 | "Programming Language :: Python :: 3.7", 18 | "Programming Language :: Python :: 3.8", 19 | "Programming Language :: Python :: 3.9", 20 | "Programming Language :: Python :: 3.10", 21 | "Operating System :: MacOS :: MacOS X", 22 | "Operating System :: Microsoft :: Windows", 23 | ], 24 | install_requires=[ 25 | 'scipy', 26 | 'numpy', 27 | 'matplotlib', 28 | 'pylogix', 29 | 'pandas', 30 | ], 31 | ) --------------------------------------------------------------------------------