From 1664568cfba429118e2f38a0c4d1d41d1ec63991 Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:28:56 -0400 Subject: [PATCH 01/15] Delete SEP_tuning_files directory --- SEP_tuning_files/2D_sweep_RF_DC.csv | 2602 --------------------------- 1 file changed, 2602 deletions(-) delete mode 100644 SEP_tuning_files/2D_sweep_RF_DC.csv diff --git a/SEP_tuning_files/2D_sweep_RF_DC.csv b/SEP_tuning_files/2D_sweep_RF_DC.csv deleted file mode 100644 index 34b0f93..0000000 --- a/SEP_tuning_files/2D_sweep_RF_DC.csv +++ /dev/null @@ -1,2602 +0,0 @@ -Vrf [V],Vdc [V],I [nA] -0.63,0.63,0.0011091 -0.632,0.63,0.00075889 -0.634,0.63,0.0002919 -0.636,0.63,0.000175 -0.638,0.63,0.00034974 -0.64,0.63,0.00087415 -0.642,0.63,0.00093229 -0.644,0.63,0.00058273 -0.646,0.63,0.00046622 -0.648,0.63,0.00046622 -0.65,0.63,0.00040797 -0.652,0.63,0.00029143 -0.654,0.63,0.00058294 -0.656,0.63,0.00058251 -0.658,0.63,0.0005826 -0.66,0.63,0.00058256 -0.662,0.63,0.00058281 -0.664,0.63,0.00046649 -0.666,0.63,0 -0.668,0.63,0.00029145 -0.67,0.63,0.00046653 -0.672,0.63,0.0005248 -0.674,0.63,0.0004667 -0.676,0.63,0.00058342 -0.678,0.63,0.00052469 -0.68,0.63,0.00040806 -0.682,0.63,0.00046642 -0.684,0.63,0.00034989 -0.686,0.63,0.00046656 -0.688,0.63,0.00035 -0.69,0.63,0.00046656 -0.692,0.63,0.00052508 -0.694,0.63,0.00058367 -0.696,0.63,0.00058381 -0.698,0.63,0.00064223 -0.7,0.63,0.00075929 -0.702,0.63,0.00075973 -0.704,0.63,0.00093471 -0.706,0.63,0.00075889 -0.708,0.63,0.00093395 -0.71,0.63,0.00081726 -0.712,0.63,0.00081726 -0.714,0.63,0.00075889 -0.716,0.63,0.00093409 -0.718,0.63,0.00058372 -0.72,0.63,0.00046646 -0.722,0.63,0.00034984 -0.724,0.63,0.00017486 -0.726,0.63,0.00017484 -0.728,0.63,0.00046625 -0.73,0.63,0.00040797 -0.63,0.632,0.00023309 -0.632,0.632,0.00011658 -0.634,0.632,0.00023318 -0.636,0.632,0.00023325 -0.638,0.632,0.00046642 -0.64,0.632,0.00034977 -0.642,0.632,0.00029151 -0.644,0.632,0.00029171 -0.646,0.632,0.000175 -0.648,0.632,0.00035015 -0.65,0.632,0.00040857 -0.652,0.632,0.00046698 -0.654,0.632,0.00087577 -0.656,0.632,0.00070051 -0.658,0.632,0.00046712 -0.66,0.632,0.0001168 -0.662,0.632,0.00035046 -0.664,0.632,0.00058462 -0.666,0.632,0.00087661 -0.668,0.632,0.00093513 -0.67,0.632,0.0010523 -0.672,0.632,0.00087668 -0.674,0.632,0.00075996 -0.676,0.632,0.00046746 -0.678,0.632,0.00029205 -0.68,0.632,0.00029203 -0.682,0.632,0.00058393 -0.684,0.632,0.00070088 -0.686,0.632,0.00075934 -0.688,0.632,0.0006429 -0.69,0.632,0.00058398 -0.692,0.632,0.00052546 -0.694,0.632,0.0004086 -0.696,0.632,0.00058342 -0.698,0.632,0.00075883 -0.7,0.632,0.00058354 -0.702,0.632,0.00046653 -0.704,0.632,0.00040821 -0.706,0.632,0.00040812 -0.708,0.632,0.000408 -0.71,0.632,0.00040797 -0.712,0.632,0.00046622 -0.714,0.632,0.0002914 -0.716,0.632,0.00017487 -0.718,0.632,0.00011654 -0.72,0.632,0.00034964 -0.722,0.632,0.00058268 -0.724,0.632,0.00029145 -0.726,0.632,0.00069979 -0.728,0.632,0.00052488 -0.73,0.632,0.00046646 -0.63,0.634,0.00070078 -0.632,0.634,0.0007595 -0.634,0.634,0.00070098 -0.636,0.634,0.00075962 -0.638,0.634,0.00070114 -0.64,0.634,0.00052573 -0.642,0.634,0.00023356 -0.644,0.634,0.00017508 -0.646,0.634,0.00029177 -0.648,0.634,0.0002918 -0.65,0.634,0.0002918 -0.652,0.634,0.00023347 -0.654,0.634,0.0004086 -0.656,0.634,0.00029192 -0.658,0.634,0.0005255 -0.66,0.634,0.00064223 -0.662,0.634,0.00052538 -0.664,0.634,0.00052543 -0.666,0.634,0.00058398 -0.668,0.634,0.00058393 -0.67,0.634,0.00046712 -0.672,0.634,0.00052543 -0.674,0.634,0.00070051 -0.676,0.634,0.00075877 -0.678,0.634,0.00064214 -0.68,0.634,0.00064223 -0.682,0.634,0.00075895 -0.684,0.634,0.00087584 -0.686,0.634,0.00093402 -0.688,0.634,0.00058312 -0.69,0.634,0.00029134 -0.692,0.634,0.00034964 -0.694,0.634,0.00023318 -0.696,0.634,5.83E-05 -0.698,0.634,0.00017506 -0.7,0.634,0.00035023 -0.702,0.634,0.00035026 -0.704,0.634,0.00040866 -0.706,0.634,0.00046701 -0.708,0.634,0.00040875 -0.71,0.634,0.00035036 -0.712,0.634,0.00023354 -0.714,0.634,0.00029197 -0.716,0.634,0.0002919 -0.718,0.634,0.00064238 -0.72,0.634,0.00040872 -0.722,0.634,0.00064219 -0.724,0.634,0.00040866 -0.726,0.634,0.00070051 -0.728,0.634,0.00093409 -0.73,0.634,0.000759 -0.63,0.636,0.00093416 -0.632,0.636,0.00093402 -0.634,0.636,0.0010508 -0.636,0.636,0.0010509 -0.638,0.636,0.00093423 -0.64,0.636,0.00099269 -0.642,0.636,0.0011096 -0.644,0.636,0.0010511 -0.646,0.636,0.00099262 -0.648,0.636,0.0011091 -0.65,0.636,0.001109 -0.652,0.636,0.00046705 -0.654,0.636,0.00029199 -0.656,0.636,0.00075917 -0.658,0.636,0.00093451 -0.66,0.636,0.00093437 -0.662,0.636,0.00099291 -0.664,0.636,0.0011099 -0.666,0.636,0.00099328 -0.668,0.636,0.00099313 -0.67,0.636,0.00093478 -0.672,0.636,0.0010514 -0.674,0.636,0.0011679 -0.676,0.636,0.001168 -0.678,0.636,0.0012264 -0.68,0.636,0.0013429 -0.682,0.636,0.0011094 -0.684,0.636,0.00099262 -0.686,0.636,0.00093409 -0.688,0.636,0.00058381 -0.69,0.636,0.00075877 -0.692,0.636,0.0011088 -0.694,0.636,0.0012259 -0.696,0.636,0.0011091 -0.698,0.636,0.0011091 -0.7,0.636,0.0011091 -0.702,0.636,0.0012838 -0.704,0.636,0.0012841 -0.706,0.636,0.0015748 -0.708,0.636,0.001459 -0.71,0.636,0.0011675 -0.712,0.636,0.00099166 -0.714,0.636,0.0011082 -0.716,0.636,0.0012246 -0.718,0.636,0.0011664 -0.72,0.636,0.0012255 -0.722,0.636,0.0011665 -0.724,0.636,0.0011082 -0.726,0.636,0.001109 -0.728,0.636,0.0012257 -0.73,0.636,0.0012252 -0.63,0.638,0.00017503 -0.632,0.638,0.00017509 -0.634,0.638,0.0001167 -0.636,0.638,0.00029184 -0.638,0.638,0.00029186 -0.64,0.638,0.00023349 -0.642,0.638,0 -0.644,0.638,5.84E-05 -0.646,0.638,0.00029184 -0.648,0.638,0.00046701 -0.65,0.638,0.00058381 -0.652,0.638,0.00046701 -0.654,0.638,0.00029188 -0.656,0.638,0.0002919 -0.658,0.638,0.00023349 -0.66,0.638,0.00058354 -0.662,0.638,0.00075877 -0.664,0.638,0.00058337 -0.666,0.638,0.00075867 -0.668,0.638,0.00058346 -0.67,0.638,0.00046694 -0.672,0.638,0.00046718 -0.674,0.638,0.0009935 -0.676,0.638,0.0012269 -0.678,0.638,0.0014026 -0.68,0.638,0.0014033 -0.682,0.638,0.0014035 -0.684,0.638,0.0014034 -0.686,0.638,0.0011689 -0.688,0.638,0.0014037 -0.69,0.638,0.0017545 -0.692,0.638,0.0016959 -0.694,0.638,0.0016374 -0.696,0.638,0.0018715 -0.698,0.638,0.0018715 -0.7,0.638,0.0022224 -0.702,0.638,0.0022811 -0.704,0.638,0.0024562 -0.706,0.638,0.0026316 -0.708,0.638,0.0026316 -0.71,0.638,0.0026318 -0.712,0.638,0.0027485 -0.714,0.638,0.0028655 -0.716,0.638,0.0031579 -0.718,0.638,0.0034496 -0.72,0.638,0.003507 -0.722,0.638,0.0028617 -0.724,0.638,0.0023936 -0.726,0.638,0.0023934 -0.728,0.638,0.0027443 -0.73,0.638,0.0031558 -0.63,0.64,0.00011674 -0.632,0.64,0.00011671 -0.634,0.64,0.00046687 -0.636,0.64,0.00070041 -0.638,0.64,0.0004668 -0.64,0.64,0.00029184 -0.642,0.64,0.00040836 -0.644,0.64,0.0005835 -0.646,0.64,0.00093409 -0.648,0.64,0.0012844 -0.65,0.64,0.0016353 -0.652,0.64,0.0016355 -0.654,0.64,0.0019284 -0.656,0.64,0.0020453 -0.658,0.64,0.0014611 -0.66,0.64,0.0012263 -0.662,0.64,0.001401 -0.664,0.64,0.001751 -0.666,0.64,0.001868 -0.668,0.64,0.0018683 -0.67,0.64,0.0019854 -0.672,0.64,0.0019857 -0.674,0.64,0.0020445 -0.676,0.64,0.0022201 -0.678,0.64,0.0025115 -0.68,0.64,0.0025111 -0.682,0.64,0.0023963 -0.684,0.64,0.0025729 -0.686,0.64,0.0025737 -0.688,0.64,0.0028691 -0.69,0.64,0.0028666 -0.692,0.64,0.0025139 -0.694,0.64,0.0022209 -0.696,0.64,0.0022211 -0.698,0.64,0.0022201 -0.7,0.64,0.0028645 -0.702,0.64,0.0036274 -0.704,0.64,0.0039211 -0.706,0.64,0.003976 -0.708,0.64,0.0040348 -0.71,0.64,0.0036823 -0.712,0.64,0.0036818 -0.714,0.64,0.0039766 -0.716,0.64,0.0043863 -0.718,0.64,0.0047964 -0.72,0.64,0.0052644 -0.722,0.64,0.0054399 -0.724,0.64,0.0056759 -0.726,0.64,0.0057413 -0.728,0.64,0.00591 -0.73,0.64,0.0063824 -0.63,0.642,0.0018687 -0.632,0.642,0.0023963 -0.634,0.642,0.0027484 -0.636,0.642,0.0025708 -0.638,0.642,0.002338 -0.64,0.642,0.0023977 -0.642,0.642,0.0028655 -0.644,0.642,0.0031595 -0.646,0.642,0.0030425 -0.648,0.642,0.0026905 -0.65,0.642,0.002807 -0.652,0.642,0.0031582 -0.654,0.642,0.0028056 -0.656,0.642,0.0022196 -0.658,0.642,0.0020448 -0.66,0.642,0.0021609 -0.662,0.642,0.0027467 -0.664,0.642,0.0029207 -0.666,0.642,0.0023347 -0.668,0.642,0.0025689 -0.67,0.642,0.0028608 -0.672,0.642,0.0032708 -0.674,0.642,0.0036239 -0.676,0.642,0.0038597 -0.678,0.642,0.0039772 -0.68,0.642,0.0039772 -0.682,0.642,0.0039784 -0.684,0.642,0.0041536 -0.686,0.642,0.0045645 -0.688,0.642,0.0050965 -0.69,0.642,0.0053315 -0.692,0.642,0.0056245 -0.694,0.642,0.0060355 -0.696,0.642,0.0061527 -0.698,0.642,0.0061523 -0.7,0.642,0.0065644 -0.702,0.642,0.007094 -0.704,0.642,0.0072117 -0.706,0.642,0.0072736 -0.708,0.642,0.0078056 -0.71,0.642,0.008159 -0.712,0.642,0.0083944 -0.714,0.642,0.0089834 -0.716,0.642,0.0093392 -0.718,0.642,0.0098112 -0.72,0.642,0.010289 -0.722,0.642,0.011113 -0.724,0.642,0.011344 -0.726,0.642,0.011524 -0.728,0.642,0.011828 -0.73,0.642,0.01254 -0.63,0.644,0.0025115 -0.632,0.644,0.0033896 -0.634,0.644,0.0031556 -0.636,0.644,0.0025111 -0.638,0.644,0.0023363 -0.64,0.644,0.0035633 -0.642,0.644,0.0046781 -0.644,0.644,0.0044431 -0.646,0.644,0.0036812 -0.648,0.644,0.0035046 -0.65,0.644,0.0038012 -0.652,0.644,0.0044471 -0.654,0.644,0.004387 -0.656,0.644,0.0042118 -0.658,0.644,0.0045066 -0.66,0.644,0.0051551 -0.662,0.644,0.0059783 -0.664,0.644,0.0059192 -0.666,0.644,0.0059201 -0.668,0.644,0.0061527 -0.67,0.644,0.006622 -0.672,0.644,0.0070354 -0.674,0.644,0.0073887 -0.676,0.644,0.0076296 -0.678,0.644,0.0077475 -0.68,0.644,0.0082764 -0.682,0.644,0.0093343 -0.684,0.644,0.010393 -0.686,0.644,0.010511 -0.688,0.644,0.010455 -0.69,0.644,0.010984 -0.692,0.644,0.011696 -0.694,0.644,0.012171 -0.696,0.644,0.012524 -0.698,0.644,0.013115 -0.7,0.644,0.013644 -0.702,0.644,0.014177 -0.704,0.644,0.01484 -0.706,0.644,0.015008 -0.708,0.644,0.015842 -0.71,0.644,0.016495 -0.712,0.644,0.016971 -0.714,0.644,0.017155 -0.716,0.644,0.017464 -0.718,0.644,0.017877 -0.72,0.644,0.018765 -0.722,0.644,0.019653 -0.724,0.644,0.020485 -0.726,0.644,0.020846 -0.728,0.644,0.021036 -0.73,0.644,0.022002 -0.63,0.646,0.0035686 -0.632,0.646,0.0042735 -0.634,0.646,0.0037455 -0.636,0.646,0.0044511 -0.638,0.646,0.0066855 -0.64,0.646,0.0086924 -0.642,0.646,0.0086937 -0.644,0.646,0.007572 -0.646,0.646,0.0072785 -0.648,0.646,0.0088106 -0.65,0.646,0.010292 -0.652,0.646,0.010646 -0.654,0.646,0.0098774 -0.656,0.646,0.0094587 -0.658,0.646,0.010638 -0.66,0.646,0.011586 -0.662,0.646,0.011836 -0.664,0.646,0.011954 -0.666,0.646,0.01231 -0.668,0.646,0.013257 -0.67,0.646,0.014496 -0.672,0.646,0.015324 -0.674,0.646,0.015144 -0.676,0.646,0.015383 -0.678,0.646,0.015497 -0.68,0.646,0.015786 -0.682,0.646,0.016615 -0.684,0.646,0.017205 -0.686,0.646,0.018087 -0.688,0.646,0.019282 -0.69,0.646,0.020314 -0.692,0.646,0.020695 -0.694,0.646,0.021296 -0.696,0.646,0.022077 -0.698,0.646,0.023645 -0.7,0.646,0.024729 -0.702,0.646,0.025634 -0.704,0.646,0.026237 -0.706,0.646,0.027093 -0.708,0.646,0.028062 -0.71,0.646,0.029498 -0.712,0.646,0.030658 -0.714,0.646,0.031208 -0.716,0.646,0.03228 -0.718,0.646,0.034103 -0.72,0.646,0.035883 -0.722,0.646,0.03712 -0.724,0.646,0.038198 -0.726,0.646,0.039741 -0.728,0.646,0.041385 -0.73,0.646,0.042596 -0.63,0.648,0.0047323 -0.632,0.648,0.0044431 -0.634,0.648,0.0062011 -0.636,0.648,0.010443 -0.638,0.648,0.015009 -0.64,0.648,0.015197 -0.642,0.648,0.01212 -0.644,0.648,0.011643 -0.646,0.648,0.015783 -0.648,0.648,0.018714 -0.65,0.648,0.0186 -0.652,0.648,0.017059 -0.654,0.648,0.017051 -0.656,0.648,0.018309 -0.658,0.648,0.021413 -0.66,0.648,0.022736 -0.662,0.648,0.022328 -0.664,0.648,0.022388 -0.666,0.648,0.024199 -0.668,0.648,0.026011 -0.67,0.648,0.02757 -0.672,0.648,0.027224 -0.674,0.648,0.027523 -0.676,0.648,0.028912 -0.678,0.648,0.0302 -0.68,0.648,0.03141 -0.682,0.648,0.032367 -0.684,0.648,0.033589 -0.686,0.648,0.035612 -0.688,0.648,0.036883 -0.69,0.648,0.037732 -0.692,0.648,0.038281 -0.694,0.648,0.039816 -0.696,0.648,0.041146 -0.698,0.648,0.043285 -0.7,0.648,0.044781 -0.702,0.648,0.045858 -0.704,0.648,0.047445 -0.706,0.648,0.049332 -0.708,0.648,0.050751 -0.71,0.648,0.052432 -0.712,0.648,0.054871 -0.714,0.648,0.057338 -0.716,0.648,0.059418 -0.718,0.648,0.060633 -0.72,0.648,0.061113 -0.722,0.648,0.064068 -0.724,0.648,0.06646 -0.726,0.648,0.069084 -0.728,0.648,0.070372 -0.73,0.648,0.07199 -0.63,0.65,0.0075743 -0.632,0.65,0.0067487 -0.634,0.65,0.01314 -0.636,0.65,0.021262 -0.638,0.65,0.02348 -0.64,0.65,0.019879 -0.642,0.65,0.019872 -0.644,0.65,0.024848 -0.646,0.65,0.030994 -0.648,0.65,0.032637 -0.65,0.65,0.029848 -0.652,0.65,0.028202 -0.654,0.65,0.033044 -0.656,0.65,0.038557 -0.658,0.65,0.040714 -0.66,0.65,0.039224 -0.662,0.65,0.038838 -0.664,0.65,0.041707 -0.666,0.65,0.0457 -0.668,0.65,0.04667 -0.67,0.65,0.045556 -0.672,0.65,0.046053 -0.674,0.65,0.046804 -0.676,0.65,0.048713 -0.678,0.65,0.050441 -0.68,0.65,0.052782 -0.682,0.65,0.054198 -0.684,0.65,0.055948 -0.686,0.65,0.058134 -0.688,0.65,0.060214 -0.69,0.65,0.061608 -0.692,0.65,0.064501 -0.694,0.65,0.067674 -0.696,0.65,0.069712 -0.698,0.65,0.071984 -0.7,0.65,0.073946 -0.702,0.65,0.076031 -0.704,0.65,0.078124 -0.706,0.65,0.081659 -0.708,0.65,0.085289 -0.71,0.65,0.086991 -0.712,0.65,0.087607 -0.714,0.65,0.088731 -0.716,0.65,0.091383 -0.718,0.65,0.09601 -0.72,0.65,0.099264 -0.722,0.65,0.1024 -0.724,0.65,0.10451 -0.726,0.65,0.10828 -0.728,0.65,0.1113 -0.73,0.65,0.11378 -0.63,0.652,0.012468 -0.632,0.652,0.012118 -0.634,0.652,0.022256 -0.636,0.652,0.029243 -0.638,0.652,0.027912 -0.64,0.652,0.027965 -0.642,0.652,0.039212 -0.644,0.652,0.051412 -0.646,0.652,0.057093 -0.648,0.652,0.051962 -0.65,0.652,0.048844 -0.652,0.652,0.056367 -0.654,0.652,0.064339 -0.656,0.652,0.066577 -0.658,0.652,0.064866 -0.66,0.652,0.063617 -0.662,0.652,0.067959 -0.664,0.652,0.073566 -0.666,0.652,0.076815 -0.668,0.652,0.076199 -0.67,0.652,0.075337 -0.672,0.652,0.078369 -0.674,0.652,0.082909 -0.676,0.652,0.086375 -0.678,0.652,0.088189 -0.68,0.652,0.089465 -0.682,0.652,0.092357 -0.684,0.652,0.09663 -0.686,0.652,0.10038 -0.688,0.652,0.10273 -0.69,0.652,0.10542 -0.692,0.652,0.10719 -0.694,0.652,0.10811 -0.696,0.652,0.11114 -0.698,0.652,0.11513 -0.7,0.652,0.11749 -0.702,0.652,0.12225 -0.704,0.652,0.1284 -0.706,0.652,0.13238 -0.708,0.652,0.13386 -0.71,0.652,0.13295 -0.712,0.652,0.12964 -0.714,0.652,0.13212 -0.716,0.652,0.13531 -0.718,0.652,0.13851 -0.72,0.652,0.14086 -0.722,0.652,0.14574 -0.724,0.652,0.15183 -0.726,0.652,0.15559 -0.728,0.652,0.1589 -0.73,0.652,0.16293 -0.63,0.654,0.016504 -0.632,0.654,0.014301 -0.634,0.654,0.024522 -0.636,0.654,0.030614 -0.638,0.654,0.031406 -0.64,0.654,0.038486 -0.642,0.654,0.058602 -0.644,0.654,0.077396 -0.646,0.654,0.08011 -0.648,0.654,0.071219 -0.65,0.654,0.06936 -0.652,0.654,0.080238 -0.654,0.654,0.094294 -0.656,0.654,0.09541 -0.658,0.654,0.089546 -0.66,0.654,0.08989 -0.662,0.654,0.097264 -0.664,0.654,0.10496 -0.666,0.654,0.1084 -0.668,0.654,0.10744 -0.67,0.654,0.10846 -0.672,0.654,0.11436 -0.674,0.654,0.12008 -0.676,0.654,0.12184 -0.678,0.654,0.12412 -0.68,0.654,0.12537 -0.682,0.654,0.1308 -0.684,0.654,0.13445 -0.686,0.654,0.13952 -0.688,0.654,0.14159 -0.69,0.654,0.14495 -0.692,0.654,0.14927 -0.694,0.654,0.15596 -0.696,0.654,0.16015 -0.698,0.654,0.16344 -0.7,0.654,0.16468 -0.702,0.654,0.16831 -0.704,0.654,0.1726 -0.706,0.654,0.17826 -0.708,0.654,0.18105 -0.71,0.654,0.18299 -0.712,0.654,0.18973 -0.714,0.654,0.19642 -0.716,0.654,0.20262 -0.718,0.654,0.20611 -0.72,0.654,0.21028 -0.722,0.654,0.21279 -0.724,0.654,0.21491 -0.726,0.654,0.21776 -0.728,0.654,0.22405 -0.73,0.654,0.23086 -0.63,0.656,0.022017 -0.632,0.656,0.0174 -0.634,0.656,0.023682 -0.636,0.656,0.028098 -0.638,0.656,0.038227 -0.64,0.656,0.064196 -0.642,0.656,0.097293 -0.644,0.656,0.10935 -0.646,0.656,0.10034 -0.648,0.656,0.096876 -0.65,0.656,0.11505 -0.652,0.656,0.13975 -0.654,0.656,0.14638 -0.656,0.656,0.13765 -0.658,0.656,0.13413 -0.66,0.656,0.14369 -0.662,0.656,0.15683 -0.664,0.656,0.16067 -0.666,0.656,0.15842 -0.668,0.656,0.15687 -0.67,0.656,0.16304 -0.672,0.656,0.17132 -0.674,0.656,0.17699 -0.676,0.656,0.17806 -0.678,0.656,0.17885 -0.68,0.656,0.18537 -0.682,0.656,0.19242 -0.684,0.656,0.19862 -0.686,0.656,0.20111 -0.688,0.656,0.20406 -0.69,0.656,0.20695 -0.692,0.656,0.21051 -0.694,0.656,0.2173 -0.696,0.656,0.22171 -0.698,0.656,0.22624 -0.7,0.656,0.22929 -0.702,0.656,0.23535 -0.704,0.656,0.2436 -0.706,0.656,0.24776 -0.708,0.656,0.25246 -0.71,0.656,0.25671 -0.712,0.656,0.26237 -0.714,0.656,0.27024 -0.716,0.656,0.27451 -0.718,0.656,0.27688 -0.72,0.656,0.28303 -0.722,0.656,0.28804 -0.724,0.656,0.2928 -0.726,0.656,0.29851 -0.728,0.656,0.30299 -0.73,0.656,0.30729 -0.63,0.658,0.029209 -0.632,0.658,0.017425 -0.634,0.658,0.020773 -0.636,0.658,0.030532 -0.638,0.658,0.057244 -0.64,0.658,0.096566 -0.642,0.658,0.12143 -0.644,0.658,0.11825 -0.646,0.658,0.12004 -0.648,0.658,0.14688 -0.65,0.658,0.1847 -0.652,0.658,0.20114 -0.654,0.658,0.19331 -0.656,0.658,0.18789 -0.658,0.658,0.19989 -0.66,0.658,0.21879 -0.662,0.658,0.22785 -0.664,0.658,0.22121 -0.666,0.658,0.21877 -0.668,0.658,0.22804 -0.67,0.658,0.24069 -0.672,0.658,0.24659 -0.674,0.658,0.24478 -0.676,0.658,0.24544 -0.678,0.658,0.25362 -0.68,0.658,0.26187 -0.682,0.658,0.27027 -0.684,0.658,0.27032 -0.686,0.658,0.27147 -0.688,0.658,0.27806 -0.69,0.658,0.28318 -0.692,0.658,0.29073 -0.694,0.658,0.29819 -0.696,0.658,0.30481 -0.698,0.658,0.30833 -0.7,0.658,0.31242 -0.702,0.658,0.31494 -0.704,0.658,0.31899 -0.706,0.658,0.3235 -0.708,0.658,0.32705 -0.71,0.658,0.33144 -0.712,0.658,0.33652 -0.714,0.658,0.34383 -0.716,0.658,0.34714 -0.718,0.658,0.35413 -0.72,0.658,0.35908 -0.722,0.658,0.36323 -0.724,0.658,0.3676 -0.726,0.658,0.37026 -0.728,0.658,0.37726 -0.73,0.658,0.38381 -0.63,0.66,0.030908 -0.632,0.66,0.013909 -0.634,0.66,0.021728 -0.636,0.66,0.041442 -0.638,0.66,0.077291 -0.64,0.66,0.10709 -0.642,0.66,0.11906 -0.644,0.66,0.12926 -0.646,0.66,0.16267 -0.648,0.66,0.21445 -0.65,0.66,0.24682 -0.652,0.66,0.24143 -0.654,0.66,0.23301 -0.656,0.66,0.25036 -0.658,0.66,0.2771 -0.66,0.66,0.29269 -0.662,0.66,0.28908 -0.664,0.66,0.28379 -0.666,0.66,0.29212 -0.668,0.66,0.30412 -0.67,0.66,0.31125 -0.672,0.66,0.30914 -0.674,0.66,0.30965 -0.676,0.66,0.31328 -0.678,0.66,0.32273 -0.68,0.66,0.3334 -0.682,0.66,0.33668 -0.684,0.66,0.33912 -0.686,0.66,0.34466 -0.688,0.66,0.3532 -0.69,0.66,0.35797 -0.692,0.66,0.36277 -0.694,0.66,0.36713 -0.696,0.66,0.37318 -0.698,0.66,0.37856 -0.7,0.66,0.38378 -0.702,0.66,0.38924 -0.704,0.66,0.39716 -0.706,0.66,0.40304 -0.708,0.66,0.40603 -0.71,0.66,0.40618 -0.712,0.66,0.41278 -0.714,0.66,0.41977 -0.716,0.66,0.42424 -0.718,0.66,0.42776 -0.72,0.66,0.43172 -0.722,0.66,0.43608 -0.724,0.66,0.4406 -0.726,0.66,0.44516 -0.728,0.66,0.44902 -0.73,0.66,0.4532 -0.63,0.662,0.033294 -0.632,0.662,0.013709 -0.634,0.662,0.024646 -0.636,0.662,0.050416 -0.638,0.662,0.08081 -0.64,0.662,0.1013 -0.642,0.662,0.12041 -0.644,0.662,0.16305 -0.646,0.662,0.22637 -0.648,0.662,0.27357 -0.65,0.662,0.28153 -0.652,0.662,0.27658 -0.654,0.662,0.30073 -0.656,0.662,0.33337 -0.658,0.662,0.35728 -0.66,0.662,0.3549 -0.662,0.662,0.34571 -0.664,0.662,0.34957 -0.666,0.662,0.36738 -0.668,0.662,0.38113 -0.67,0.662,0.38298 -0.672,0.662,0.3793 -0.674,0.662,0.3806 -0.676,0.662,0.39157 -0.678,0.662,0.40384 -0.68,0.662,0.40642 -0.682,0.662,0.41027 -0.684,0.662,0.416 -0.686,0.662,0.42292 -0.688,0.662,0.42779 -0.69,0.662,0.43373 -0.692,0.662,0.43554 -0.694,0.662,0.43748 -0.696,0.662,0.445 -0.698,0.662,0.45151 -0.7,0.662,0.45626 -0.702,0.662,0.45992 -0.704,0.662,0.46151 -0.706,0.662,0.46432 -0.708,0.662,0.46809 -0.71,0.662,0.47391 -0.712,0.662,0.47777 -0.714,0.662,0.4823 -0.716,0.662,0.48741 -0.718,0.662,0.48999 -0.72,0.662,0.49379 -0.722,0.662,0.49728 -0.724,0.662,0.50214 -0.726,0.662,0.50556 -0.728,0.662,0.50788 -0.73,0.662,0.51131 -0.63,0.664,0.037712 -0.632,0.664,0.019742 -0.634,0.664,0.033455 -0.636,0.664,0.057662 -0.638,0.664,0.074882 -0.64,0.664,0.097549 -0.642,0.664,0.14136 -0.644,0.664,0.20733 -0.646,0.664,0.26804 -0.648,0.664,0.29119 -0.65,0.664,0.2962 -0.652,0.664,0.32082 -0.654,0.664,0.37233 -0.656,0.664,0.39935 -0.658,0.664,0.39946 -0.66,0.664,0.39288 -0.662,0.664,0.40236 -0.664,0.664,0.42583 -0.666,0.664,0.43642 -0.668,0.664,0.43586 -0.67,0.664,0.43141 -0.672,0.664,0.43583 -0.674,0.664,0.44781 -0.676,0.664,0.45604 -0.678,0.664,0.45904 -0.68,0.664,0.46102 -0.682,0.664,0.46656 -0.684,0.664,0.47302 -0.686,0.664,0.47971 -0.688,0.664,0.48575 -0.69,0.664,0.48941 -0.692,0.664,0.49394 -0.694,0.664,0.49884 -0.696,0.664,0.5034 -0.698,0.664,0.50615 -0.7,0.664,0.51055 -0.702,0.664,0.51488 -0.704,0.664,0.51878 -0.706,0.664,0.523 -0.708,0.664,0.52538 -0.71,0.664,0.52619 -0.712,0.664,0.53043 -0.714,0.664,0.53557 -0.716,0.664,0.53919 -0.718,0.664,0.54064 -0.72,0.664,0.54165 -0.722,0.664,0.5449 -0.724,0.664,0.54918 -0.726,0.664,0.55095 -0.728,0.664,0.55234 -0.73,0.664,0.55597 -0.63,0.666,0.042528 -0.632,0.666,0.0218 -0.634,0.666,0.033445 -0.636,0.666,0.049118 -0.638,0.666,0.070641 -0.64,0.666,0.10947 -0.642,0.666,0.17375 -0.644,0.666,0.23596 -0.646,0.666,0.2727 -0.648,0.666,0.29187 -0.65,0.666,0.32938 -0.652,0.666,0.38611 -0.654,0.666,0.42494 -0.656,0.666,0.43381 -0.658,0.666,0.4301 -0.66,0.666,0.44074 -0.662,0.666,0.46212 -0.664,0.666,0.47858 -0.666,0.666,0.48134 -0.668,0.666,0.47782 -0.67,0.666,0.4812 -0.672,0.666,0.493 -0.674,0.666,0.50363 -0.676,0.666,0.50889 -0.678,0.666,0.5095 -0.68,0.666,0.51155 -0.682,0.666,0.51949 -0.684,0.666,0.52738 -0.686,0.666,0.53124 -0.688,0.666,0.53374 -0.69,0.666,0.53602 -0.692,0.666,0.53999 -0.694,0.666,0.54665 -0.696,0.666,0.55212 -0.698,0.666,0.55515 -0.7,0.666,0.55745 -0.702,0.666,0.56006 -0.704,0.666,0.56458 -0.706,0.666,0.56802 -0.708,0.666,0.5705 -0.71,0.666,0.57314 -0.712,0.666,0.57595 -0.714,0.666,0.57879 -0.716,0.666,0.58204 -0.718,0.666,0.58453 -0.72,0.666,0.58711 -0.722,0.666,0.5896 -0.724,0.666,0.59072 -0.726,0.666,0.59113 -0.728,0.666,0.5935 -0.73,0.666,0.5961 -0.63,0.668,0.046282 -0.632,0.668,0.022884 -0.634,0.668,0.03188 -0.636,0.668,0.049031 -0.638,0.668,0.081071 -0.64,0.668,0.13315 -0.642,0.668,0.18997 -0.644,0.668,0.23471 -0.646,0.668,0.26995 -0.648,0.668,0.31451 -0.65,0.668,0.37639 -0.652,0.668,0.42883 -0.654,0.668,0.4478 -0.656,0.668,0.45011 -0.658,0.668,0.46203 -0.66,0.668,0.48709 -0.662,0.668,0.50829 -0.664,0.668,0.51239 -0.666,0.668,0.50856 -0.668,0.668,0.5129 -0.67,0.668,0.52536 -0.672,0.668,0.53385 -0.674,0.668,0.53801 -0.676,0.668,0.54048 -0.678,0.668,0.54465 -0.68,0.668,0.55205 -0.682,0.668,0.55984 -0.684,0.668,0.56463 -0.686,0.668,0.56736 -0.688,0.668,0.57111 -0.69,0.668,0.57635 -0.692,0.668,0.58174 -0.694,0.668,0.58486 -0.696,0.668,0.58652 -0.698,0.668,0.58868 -0.7,0.668,0.59191 -0.702,0.668,0.59555 -0.704,0.668,0.59885 -0.706,0.668,0.60118 -0.708,0.668,0.60404 -0.71,0.668,0.60667 -0.712,0.668,0.60909 -0.714,0.668,0.61117 -0.716,0.668,0.61346 -0.718,0.668,0.61556 -0.72,0.668,0.61833 -0.722,0.668,0.62004 -0.724,0.668,0.62091 -0.726,0.668,0.62218 -0.728,0.668,0.62431 -0.73,0.668,0.62576 -0.63,0.67,0.047295 -0.632,0.67,0.02044 -0.634,0.67,0.02975 -0.636,0.67,0.052963 -0.638,0.67,0.089627 -0.64,0.67,0.13654 -0.642,0.67,0.18212 -0.644,0.67,0.22638 -0.646,0.67,0.27982 -0.648,0.67,0.35041 -0.65,0.67,0.40981 -0.652,0.67,0.43944 -0.654,0.67,0.45235 -0.656,0.67,0.4725 -0.658,0.67,0.50112 -0.66,0.67,0.5251 -0.662,0.67,0.5313 -0.664,0.67,0.53175 -0.666,0.67,0.53766 -0.668,0.67,0.54936 -0.67,0.67,0.56142 -0.672,0.67,0.56728 -0.674,0.67,0.56979 -0.676,0.67,0.57417 -0.678,0.67,0.58262 -0.68,0.67,0.5892 -0.682,0.67,0.59378 -0.684,0.67,0.59636 -0.686,0.67,0.60031 -0.688,0.67,0.60614 -0.69,0.67,0.6104 -0.692,0.67,0.61469 -0.694,0.67,0.61721 -0.696,0.67,0.61965 -0.698,0.67,0.62344 -0.7,0.67,0.62631 -0.702,0.67,0.62949 -0.704,0.67,0.63126 -0.706,0.67,0.63326 -0.708,0.67,0.63566 -0.71,0.67,0.63853 -0.712,0.67,0.63966 -0.714,0.67,0.64128 -0.716,0.67,0.6432 -0.718,0.67,0.64454 -0.72,0.67,0.64618 -0.722,0.67,0.64636 -0.724,0.67,0.64846 -0.726,0.67,0.65061 -0.728,0.67,0.65133 -0.73,0.67,0.65244 -0.63,0.672,0.040532 -0.632,0.672,0.019774 -0.634,0.672,0.032003 -0.636,0.672,0.056807 -0.638,0.672,0.093607 -0.64,0.672,0.13632 -0.642,0.672,0.18313 -0.644,0.672,0.23277 -0.646,0.672,0.30126 -0.648,0.672,0.36712 -0.65,0.672,0.41426 -0.652,0.672,0.43868 -0.654,0.672,0.4654 -0.656,0.672,0.49925 -0.658,0.672,0.52593 -0.66,0.672,0.54059 -0.662,0.672,0.54492 -0.664,0.672,0.55178 -0.666,0.672,0.5634 -0.668,0.672,0.57863 -0.67,0.672,0.58605 -0.672,0.672,0.58916 -0.674,0.672,0.59394 -0.676,0.672,0.60101 -0.678,0.672,0.60839 -0.68,0.672,0.614 -0.682,0.672,0.61731 -0.684,0.672,0.6209 -0.686,0.672,0.62579 -0.688,0.672,0.63115 -0.69,0.672,0.63623 -0.692,0.672,0.63885 -0.694,0.672,0.64085 -0.696,0.672,0.64477 -0.698,0.672,0.64794 -0.7,0.672,0.65037 -0.702,0.672,0.65321 -0.704,0.672,0.65638 -0.706,0.672,0.65878 -0.708,0.672,0.66088 -0.71,0.672,0.6626 -0.712,0.672,0.66394 -0.714,0.672,0.66582 -0.716,0.672,0.6681 -0.718,0.672,0.66999 -0.72,0.672,0.67072 -0.722,0.672,0.67143 -0.724,0.672,0.67279 -0.726,0.672,0.67405 -0.728,0.672,0.67615 -0.73,0.672,0.67782 -0.63,0.674,0.048681 -0.632,0.674,0.021601 -0.634,0.674,0.038282 -0.636,0.674,0.060064 -0.638,0.674,0.093302 -0.64,0.674,0.13269 -0.642,0.674,0.18087 -0.644,0.674,0.24122 -0.646,0.674,0.30705 -0.648,0.674,0.37237 -0.65,0.674,0.41678 -0.652,0.674,0.44641 -0.654,0.674,0.47888 -0.656,0.674,0.51257 -0.658,0.674,0.54021 -0.66,0.674,0.55259 -0.662,0.674,0.56 -0.664,0.674,0.5715 -0.666,0.674,0.58473 -0.668,0.674,0.59717 -0.67,0.674,0.60438 -0.672,0.674,0.6091 -0.674,0.674,0.61605 -0.676,0.674,0.62472 -0.678,0.674,0.63228 -0.68,0.674,0.63636 -0.682,0.674,0.63948 -0.684,0.674,0.64373 -0.686,0.674,0.64951 -0.688,0.674,0.65502 -0.69,0.674,0.65928 -0.692,0.674,0.66153 -0.694,0.674,0.6656 -0.696,0.674,0.66768 -0.698,0.674,0.67102 -0.7,0.674,0.67362 -0.702,0.674,0.67811 -0.704,0.674,0.68069 -0.706,0.674,0.68265 -0.708,0.674,0.68542 -0.71,0.674,0.6878 -0.712,0.674,0.69 -0.714,0.674,0.69289 -0.716,0.674,0.69474 -0.718,0.674,0.69525 -0.72,0.674,0.69582 -0.722,0.674,0.69673 -0.724,0.674,0.69785 -0.726,0.674,0.69914 -0.728,0.674,0.70085 -0.73,0.674,0.70196 -0.63,0.676,0.052943 -0.632,0.676,0.023917 -0.634,0.676,0.036525 -0.636,0.676,0.05977 -0.638,0.676,0.094558 -0.64,0.676,0.13446 -0.642,0.676,0.19058 -0.644,0.676,0.25666 -0.646,0.676,0.32397 -0.648,0.676,0.37689 -0.65,0.676,0.41543 -0.652,0.676,0.45479 -0.654,0.676,0.49588 -0.656,0.676,0.52848 -0.658,0.676,0.54691 -0.66,0.676,0.55924 -0.662,0.676,0.57261 -0.664,0.676,0.58776 -0.666,0.676,0.60284 -0.668,0.676,0.61356 -0.67,0.676,0.62114 -0.672,0.676,0.62943 -0.674,0.676,0.63849 -0.676,0.676,0.64727 -0.678,0.676,0.65303 -0.68,0.676,0.65731 -0.682,0.676,0.66157 -0.684,0.676,0.6682 -0.686,0.676,0.67437 -0.688,0.676,0.67795 -0.69,0.676,0.68138 -0.692,0.676,0.68528 -0.694,0.676,0.6882 -0.696,0.676,0.69091 -0.698,0.676,0.69358 -0.7,0.676,0.69768 -0.702,0.676,0.7002 -0.704,0.676,0.70302 -0.706,0.676,0.70506 -0.708,0.676,0.7076 -0.71,0.676,0.70946 -0.712,0.676,0.71153 -0.714,0.676,0.71366 -0.716,0.676,0.71552 -0.718,0.676,0.71659 -0.72,0.676,0.71888 -0.722,0.676,0.72048 -0.724,0.676,0.72197 -0.726,0.676,0.7233 -0.728,0.676,0.72405 -0.73,0.676,0.72545 -0.63,0.678,0.054042 -0.632,0.678,0.024664 -0.634,0.678,0.0379 -0.636,0.678,0.061594 -0.638,0.678,0.09496 -0.64,0.678,0.14157 -0.642,0.678,0.20267 -0.644,0.678,0.27748 -0.646,0.678,0.326 -0.648,0.678,0.37484 -0.65,0.678,0.42662 -0.652,0.678,0.47729 -0.654,0.678,0.51257 -0.656,0.678,0.5355 -0.658,0.678,0.55132 -0.66,0.678,0.56904 -0.662,0.678,0.58898 -0.664,0.678,0.60433 -0.666,0.678,0.61607 -0.668,0.678,0.62624 -0.67,0.678,0.63738 -0.672,0.678,0.65038 -0.674,0.678,0.6608 -0.676,0.678,0.66697 -0.678,0.678,0.67307 -0.68,0.678,0.67931 -0.682,0.678,0.6862 -0.684,0.678,0.69286 -0.686,0.678,0.69743 -0.688,0.678,0.7006 -0.69,0.678,0.70399 -0.692,0.678,0.7082 -0.694,0.678,0.71149 -0.696,0.678,0.71565 -0.698,0.678,0.71923 -0.7,0.678,0.72276 -0.702,0.678,0.72429 -0.704,0.678,0.7267 -0.706,0.678,0.72912 -0.708,0.678,0.73191 -0.71,0.678,0.73427 -0.712,0.678,0.73655 -0.714,0.678,0.73914 -0.716,0.678,0.74024 -0.718,0.678,0.74076 -0.72,0.678,0.74169 -0.722,0.678,0.74399 -0.724,0.678,0.74566 -0.726,0.678,0.74811 -0.728,0.678,0.74955 -0.73,0.678,0.75126 -0.63,0.68,0.045119 -0.632,0.68,0.026144 -0.634,0.68,0.040652 -0.636,0.68,0.061874 -0.638,0.68,0.097605 -0.64,0.68,0.148 -0.642,0.68,0.20574 -0.644,0.68,0.26242 -0.646,0.68,0.31961 -0.648,0.68,0.37735 -0.65,0.68,0.43514 -0.652,0.68,0.48313 -0.654,0.68,0.51801 -0.656,0.68,0.53999 -0.658,0.68,0.56046 -0.66,0.68,0.58258 -0.662,0.68,0.60394 -0.664,0.68,0.62041 -0.666,0.68,0.6328 -0.668,0.68,0.64436 -0.67,0.68,0.65774 -0.672,0.68,0.67085 -0.674,0.68,0.67942 -0.676,0.68,0.68666 -0.678,0.68,0.69305 -0.68,0.68,0.70098 -0.682,0.68,0.70734 -0.684,0.68,0.71417 -0.686,0.68,0.71893 -0.688,0.68,0.72273 -0.69,0.68,0.72747 -0.692,0.68,0.73204 -0.694,0.68,0.73534 -0.696,0.68,0.73836 -0.698,0.68,0.74187 -0.7,0.68,0.74593 -0.702,0.68,0.7484 -0.704,0.68,0.74977 -0.706,0.68,0.75265 -0.708,0.68,0.75556 -0.71,0.68,0.75836 -0.712,0.68,0.76101 -0.714,0.68,0.76314 -0.716,0.68,0.76538 -0.718,0.68,0.76771 -0.72,0.68,0.76934 -0.722,0.68,0.77236 -0.724,0.68,0.77417 -0.726,0.68,0.77529 -0.728,0.68,0.77658 -0.73,0.68,0.77806 -0.63,0.682,0.056647 -0.632,0.682,0.026186 -0.634,0.682,0.039217 -0.636,0.682,0.066897 -0.638,0.682,0.10377 -0.64,0.682,0.15335 -0.642,0.682,0.21071 -0.644,0.682,0.26862 -0.646,0.682,0.33037 -0.648,0.682,0.39043 -0.65,0.682,0.44596 -0.652,0.682,0.48834 -0.654,0.682,0.51842 -0.656,0.682,0.54394 -0.658,0.682,0.56912 -0.66,0.682,0.59399 -0.662,0.682,0.61172 -0.664,0.682,0.62761 -0.666,0.682,0.64427 -0.668,0.682,0.66016 -0.67,0.682,0.67551 -0.672,0.682,0.6881 -0.674,0.682,0.6988 -0.676,0.682,0.70819 -0.678,0.682,0.71748 -0.68,0.682,0.72523 -0.682,0.682,0.73282 -0.684,0.682,0.73831 -0.686,0.682,0.74381 -0.688,0.682,0.74793 -0.69,0.682,0.75209 -0.692,0.682,0.75721 -0.694,0.682,0.76082 -0.696,0.682,0.76381 -0.698,0.682,0.76785 -0.7,0.682,0.77174 -0.702,0.682,0.77425 -0.704,0.682,0.77633 -0.706,0.682,0.77981 -0.708,0.682,0.78399 -0.71,0.682,0.78644 -0.712,0.682,0.7881 -0.714,0.682,0.79021 -0.716,0.682,0.79206 -0.718,0.682,0.79444 -0.72,0.682,0.79779 -0.722,0.682,0.79938 -0.724,0.682,0.80115 -0.726,0.682,0.80213 -0.728,0.682,0.80257 -0.73,0.682,0.80336 -0.63,0.684,0.059288 -0.632,0.684,0.027848 -0.634,0.684,0.043861 -0.636,0.684,0.069108 -0.638,0.684,0.10788 -0.64,0.684,0.16376 -0.642,0.684,0.21512 -0.644,0.684,0.27357 -0.646,0.684,0.34073 -0.648,0.684,0.40821 -0.65,0.684,0.45487 -0.652,0.684,0.48994 -0.654,0.684,0.52611 -0.656,0.684,0.55487 -0.658,0.684,0.58176 -0.66,0.684,0.60708 -0.662,0.684,0.62313 -0.664,0.684,0.64051 -0.666,0.684,0.65719 -0.668,0.684,0.67564 -0.67,0.684,0.69225 -0.672,0.684,0.70609 -0.674,0.684,0.71763 -0.676,0.684,0.72825 -0.678,0.684,0.7393 -0.68,0.684,0.74731 -0.682,0.684,0.75463 -0.684,0.684,0.762 -0.686,0.684,0.76758 -0.688,0.684,0.77329 -0.69,0.684,0.77772 -0.692,0.684,0.78172 -0.694,0.684,0.78644 -0.696,0.684,0.78986 -0.698,0.684,0.79249 -0.7,0.684,0.7954 -0.702,0.684,0.79937 -0.704,0.684,0.80292 -0.706,0.684,0.80495 -0.708,0.684,0.80682 -0.71,0.684,0.80994 -0.712,0.684,0.81262 -0.714,0.684,0.81604 -0.716,0.684,0.81827 -0.718,0.684,0.8205 -0.72,0.684,0.82285 -0.722,0.684,0.82475 -0.724,0.684,0.82628 -0.726,0.684,0.82857 -0.728,0.684,0.83011 -0.73,0.684,0.83112 -0.63,0.686,0.058237 -0.632,0.686,0.028756 -0.634,0.686,0.046822 -0.636,0.686,0.072234 -0.638,0.686,0.10992 -0.64,0.686,0.15682 -0.642,0.686,0.21503 -0.644,0.686,0.28647 -0.646,0.686,0.34947 -0.648,0.686,0.40487 -0.65,0.686,0.45121 -0.652,0.686,0.491 -0.654,0.686,0.52866 -0.656,0.686,0.56236 -0.658,0.686,0.58774 -0.66,0.686,0.61045 -0.662,0.686,0.62891 -0.664,0.686,0.64854 -0.666,0.686,0.66943 -0.668,0.686,0.69024 -0.67,0.686,0.70632 -0.672,0.686,0.72103 -0.674,0.686,0.73465 -0.676,0.686,0.74829 -0.678,0.686,0.75977 -0.68,0.686,0.77026 -0.682,0.686,0.77841 -0.684,0.686,0.78609 -0.686,0.686,0.79215 -0.688,0.686,0.79665 -0.69,0.686,0.80223 -0.692,0.686,0.8065 -0.694,0.686,0.81079 -0.696,0.686,0.81528 -0.698,0.686,0.81897 -0.7,0.686,0.82203 -0.702,0.686,0.82441 -0.704,0.686,0.82883 -0.706,0.686,0.83325 -0.708,0.686,0.83549 -0.71,0.686,0.83823 -0.712,0.686,0.84082 -0.714,0.686,0.84263 -0.716,0.686,0.8448 -0.718,0.686,0.84841 -0.72,0.686,0.85021 -0.722,0.686,0.85251 -0.724,0.686,0.85295 -0.726,0.686,0.8547 -0.728,0.686,0.856 -0.73,0.686,0.85758 -0.63,0.688,0.057861 -0.632,0.688,0.028644 -0.634,0.688,0.044661 -0.636,0.688,0.075305 -0.638,0.688,0.11441 -0.64,0.688,0.16497 -0.642,0.688,0.2243 -0.644,0.688,0.28513 -0.646,0.688,0.34953 -0.648,0.688,0.40602 -0.65,0.688,0.45604 -0.652,0.688,0.49745 -0.654,0.688,0.53578 -0.656,0.688,0.56722 -0.658,0.688,0.59257 -0.66,0.688,0.61591 -0.662,0.688,0.63516 -0.664,0.688,0.65857 -0.666,0.688,0.67939 -0.668,0.688,0.69865 -0.67,0.688,0.71687 -0.672,0.688,0.7344 -0.674,0.688,0.75118 -0.676,0.688,0.76554 -0.678,0.688,0.77772 -0.68,0.688,0.78793 -0.682,0.688,0.79814 -0.684,0.688,0.80604 -0.686,0.688,0.81257 -0.688,0.688,0.81971 -0.69,0.688,0.82554 -0.692,0.688,0.82992 -0.694,0.688,0.83375 -0.696,0.688,0.83859 -0.698,0.688,0.84189 -0.7,0.688,0.84573 -0.702,0.688,0.84997 -0.704,0.688,0.85413 -0.706,0.688,0.85683 -0.708,0.688,0.86035 -0.71,0.688,0.86419 -0.712,0.688,0.8672 -0.714,0.688,0.86972 -0.716,0.688,0.87159 -0.718,0.688,0.87434 -0.72,0.688,0.87687 -0.722,0.688,0.87859 -0.724,0.688,0.8805 -0.726,0.688,0.88175 -0.728,0.688,0.88277 -0.73,0.688,0.88384 -0.63,0.69,0.061426 -0.632,0.69,0.02894 -0.634,0.69,0.043151 -0.636,0.69,0.070563 -0.638,0.69,0.11597 -0.64,0.69,0.1596 -0.642,0.69,0.21901 -0.644,0.69,0.28274 -0.646,0.69,0.3446 -0.648,0.69,0.40369 -0.65,0.69,0.45747 -0.652,0.69,0.50213 -0.654,0.69,0.54101 -0.656,0.69,0.56931 -0.658,0.69,0.59461 -0.66,0.69,0.61935 -0.662,0.69,0.64342 -0.664,0.69,0.66687 -0.666,0.69,0.69028 -0.668,0.69,0.71029 -0.67,0.69,0.73094 -0.672,0.69,0.7502 -0.674,0.69,0.76891 -0.676,0.69,0.78478 -0.678,0.69,0.79813 -0.68,0.69,0.81039 -0.682,0.69,0.82141 -0.684,0.69,0.82873 -0.686,0.69,0.83545 -0.688,0.69,0.84205 -0.69,0.69,0.84829 -0.692,0.69,0.85364 -0.694,0.69,0.85818 -0.696,0.69,0.862 -0.698,0.69,0.86558 -0.7,0.69,0.87018 -0.702,0.69,0.87375 -0.704,0.69,0.87695 -0.706,0.69,0.88019 -0.708,0.69,0.88305 -0.71,0.69,0.8864 -0.712,0.69,0.8893 -0.714,0.69,0.89231 -0.716,0.69,0.89593 -0.718,0.69,0.89864 -0.72,0.69,0.89989 -0.722,0.69,0.90137 -0.724,0.69,0.90362 -0.726,0.69,0.90471 -0.728,0.69,0.90539 -0.73,0.69,0.90578 -0.63,0.692,0.066451 -0.632,0.692,0.032699 -0.634,0.692,0.048534 -0.636,0.692,0.076641 -0.638,0.692,0.11822 -0.64,0.692,0.17206 -0.642,0.692,0.23326 -0.644,0.692,0.29518 -0.646,0.692,0.35826 -0.648,0.692,0.41983 -0.65,0.692,0.46972 -0.652,0.692,0.51439 -0.654,0.692,0.55005 -0.656,0.692,0.57696 -0.658,0.692,0.60404 -0.66,0.692,0.6297 -0.662,0.692,0.65476 -0.664,0.692,0.6782 -0.666,0.692,0.70083 -0.668,0.692,0.72232 -0.67,0.692,0.74444 -0.672,0.692,0.76655 -0.674,0.692,0.7846 -0.676,0.692,0.80206 -0.678,0.692,0.81608 -0.68,0.692,0.83007 -0.682,0.692,0.84159 -0.684,0.692,0.84984 -0.686,0.692,0.85671 -0.688,0.692,0.86352 -0.69,0.692,0.869 -0.692,0.692,0.87491 -0.694,0.692,0.87938 -0.696,0.692,0.8836 -0.698,0.692,0.88772 -0.7,0.692,0.89136 -0.702,0.692,0.89571 -0.704,0.692,0.8985 -0.706,0.692,0.90186 -0.708,0.692,0.90519 -0.71,0.692,0.90956 -0.712,0.692,0.91211 -0.714,0.692,0.91485 -0.716,0.692,0.91709 -0.718,0.692,0.91816 -0.72,0.692,0.92047 -0.722,0.692,0.92178 -0.724,0.692,0.92319 -0.726,0.692,0.92376 -0.728,0.692,0.92458 -0.73,0.692,0.92593 -0.63,0.694,0.062771 -0.632,0.694,0.032404 -0.634,0.694,0.048704 -0.636,0.694,0.080462 -0.638,0.694,0.1244 -0.64,0.694,0.17613 -0.642,0.694,0.23676 -0.644,0.694,0.29667 -0.646,0.694,0.36092 -0.648,0.694,0.41883 -0.65,0.694,0.47067 -0.652,0.694,0.51338 -0.654,0.694,0.54843 -0.656,0.694,0.57859 -0.658,0.694,0.60791 -0.66,0.694,0.63382 -0.662,0.694,0.65853 -0.664,0.694,0.68241 -0.666,0.694,0.70675 -0.668,0.694,0.72911 -0.67,0.694,0.75424 -0.672,0.694,0.77528 -0.674,0.694,0.79453 -0.676,0.694,0.81194 -0.678,0.694,0.82815 -0.68,0.694,0.84339 -0.682,0.694,0.85427 -0.684,0.694,0.86455 -0.686,0.694,0.87303 -0.688,0.694,0.87998 -0.69,0.694,0.88715 -0.692,0.694,0.89297 -0.694,0.694,0.89711 -0.696,0.694,0.90103 -0.698,0.694,0.90567 -0.7,0.694,0.90979 -0.702,0.694,0.91368 -0.704,0.694,0.91748 -0.706,0.694,0.92101 -0.708,0.694,0.92545 -0.71,0.694,0.92858 -0.712,0.694,0.93002 -0.714,0.694,0.93184 -0.716,0.694,0.93404 -0.718,0.694,0.93699 -0.72,0.694,0.93921 -0.722,0.694,0.94099 -0.724,0.694,0.9421 -0.726,0.694,0.94208 -0.728,0.694,0.94221 -0.73,0.694,0.94271 -0.63,0.696,0.06464 -0.632,0.696,0.032196 -0.634,0.696,0.049498 -0.636,0.696,0.081938 -0.638,0.696,0.12491 -0.64,0.696,0.17814 -0.642,0.696,0.23819 -0.644,0.696,0.30367 -0.646,0.696,0.37294 -0.648,0.696,0.42936 -0.65,0.696,0.4753 -0.652,0.696,0.51737 -0.654,0.696,0.55272 -0.656,0.696,0.58438 -0.658,0.696,0.6135 -0.66,0.696,0.63867 -0.662,0.696,0.66308 -0.664,0.696,0.68737 -0.666,0.696,0.71256 -0.668,0.696,0.73764 -0.67,0.696,0.7612 -0.672,0.696,0.78407 -0.674,0.696,0.80553 -0.676,0.696,0.82538 -0.678,0.696,0.84377 -0.68,0.696,0.85735 -0.682,0.696,0.86934 -0.684,0.696,0.87991 -0.686,0.696,0.88857 -0.688,0.696,0.89638 -0.69,0.696,0.90299 -0.692,0.696,0.90803 -0.694,0.696,0.91267 -0.696,0.696,0.91732 -0.698,0.696,0.92155 -0.7,0.696,0.92546 -0.702,0.696,0.9293 -0.704,0.696,0.93191 -0.706,0.696,0.93518 -0.708,0.696,0.93908 -0.71,0.696,0.94213 -0.712,0.696,0.94499 -0.714,0.696,0.94708 -0.716,0.696,0.9499 -0.718,0.696,0.95198 -0.72,0.696,0.9536 -0.722,0.696,0.95466 -0.724,0.696,0.95623 -0.726,0.696,0.95714 -0.728,0.696,0.9585 -0.73,0.696,0.9592 -0.63,0.698,0.07051 -0.632,0.698,0.034508 -0.634,0.698,0.052806 -0.636,0.698,0.08321 -0.638,0.698,0.12557 -0.64,0.698,0.17913 -0.642,0.698,0.23903 -0.644,0.698,0.30274 -0.646,0.698,0.36603 -0.648,0.698,0.424 -0.65,0.698,0.4765 -0.652,0.698,0.51752 -0.654,0.698,0.55591 -0.656,0.698,0.58787 -0.658,0.698,0.61614 -0.66,0.698,0.64136 -0.662,0.698,0.66768 -0.664,0.698,0.69319 -0.666,0.698,0.71631 -0.668,0.698,0.74171 -0.67,0.698,0.76588 -0.672,0.698,0.78951 -0.674,0.698,0.81306 -0.676,0.698,0.8321 -0.678,0.698,0.85122 -0.68,0.698,0.86685 -0.682,0.698,0.87954 -0.684,0.698,0.89091 -0.686,0.698,0.89951 -0.688,0.698,0.90723 -0.69,0.698,0.91383 -0.692,0.698,0.91984 -0.694,0.698,0.92501 -0.696,0.698,0.92906 -0.698,0.698,0.93318 -0.7,0.698,0.93717 -0.702,0.698,0.94159 -0.704,0.698,0.94432 -0.706,0.698,0.94828 -0.708,0.698,0.9527 -0.71,0.698,0.95601 -0.712,0.698,0.95899 -0.714,0.698,0.96147 -0.716,0.698,0.96329 -0.718,0.698,0.9656 -0.72,0.698,0.96724 -0.722,0.698,0.96834 -0.724,0.698,0.96913 -0.726,0.698,0.96936 -0.728,0.698,0.97021 -0.73,0.698,0.9712 -0.63,0.7,0.066253 -0.632,0.7,0.03553 -0.634,0.7,0.054847 -0.636,0.7,0.089026 -0.638,0.7,0.13253 -0.64,0.7,0.18524 -0.642,0.7,0.24684 -0.644,0.7,0.31281 -0.646,0.7,0.37848 -0.648,0.7,0.43047 -0.65,0.7,0.47943 -0.652,0.7,0.52191 -0.654,0.7,0.55844 -0.656,0.7,0.58954 -0.658,0.7,0.6166 -0.66,0.7,0.643 -0.662,0.7,0.66805 -0.664,0.7,0.69428 -0.666,0.7,0.72148 -0.668,0.7,0.74492 -0.67,0.7,0.7702 -0.672,0.7,0.79381 -0.674,0.7,0.81757 -0.676,0.7,0.83824 -0.678,0.7,0.85705 -0.68,0.7,0.87326 -0.682,0.7,0.88745 -0.684,0.7,0.89927 -0.686,0.7,0.90929 -0.688,0.7,0.91709 -0.69,0.7,0.92373 -0.692,0.7,0.92994 -0.694,0.7,0.93598 -0.696,0.7,0.94049 -0.698,0.7,0.94446 -0.7,0.7,0.9486 -0.702,0.7,0.95283 -0.704,0.7,0.95709 -0.706,0.7,0.96032 -0.708,0.7,0.96279 -0.71,0.7,0.96543 -0.712,0.7,0.96847 -0.714,0.7,0.9707 -0.716,0.7,0.97318 -0.718,0.7,0.97547 -0.72,0.7,0.97709 -0.722,0.7,0.9786 -0.724,0.7,0.97988 -0.726,0.7,0.98087 -0.728,0.7,0.98158 -0.73,0.7,0.98248 -0.63,0.702,0.072946 -0.632,0.702,0.037208 -0.634,0.702,0.055461 -0.636,0.702,0.089198 -0.638,0.702,0.13341 -0.64,0.702,0.18613 -0.642,0.702,0.25834 -0.644,0.702,0.31477 -0.646,0.702,0.37862 -0.648,0.702,0.43501 -0.65,0.702,0.49156 -0.652,0.702,0.52765 -0.654,0.702,0.56421 -0.656,0.702,0.59426 -0.658,0.702,0.62202 -0.66,0.702,0.64872 -0.662,0.702,0.6739 -0.664,0.702,0.69912 -0.666,0.702,0.72549 -0.668,0.702,0.75014 -0.67,0.702,0.77608 -0.672,0.702,0.79981 -0.674,0.702,0.82151 -0.676,0.702,0.84288 -0.678,0.702,0.86212 -0.68,0.702,0.87807 -0.682,0.702,0.89249 -0.684,0.702,0.90413 -0.686,0.702,0.91413 -0.688,0.702,0.92252 -0.69,0.702,0.92944 -0.692,0.702,0.93612 -0.694,0.702,0.94185 -0.696,0.702,0.94744 -0.698,0.702,0.95166 -0.7,0.702,0.9562 -0.702,0.702,0.96052 -0.704,0.702,0.96426 -0.706,0.702,0.96816 -0.708,0.702,0.97149 -0.71,0.702,0.97474 -0.712,0.702,0.97775 -0.714,0.702,0.98097 -0.716,0.702,0.98293 -0.718,0.702,0.9849 -0.72,0.702,0.98764 -0.722,0.702,0.98928 -0.724,0.702,0.99033 -0.726,0.702,0.99085 -0.728,0.702,0.99133 -0.73,0.702,0.99141 -0.63,0.704,0.073118 -0.632,0.704,0.037336 -0.634,0.704,0.056401 -0.636,0.704,0.088793 -0.638,0.704,0.13211 -0.64,0.704,0.19294 -0.642,0.704,0.25398 -0.644,0.704,0.31563 -0.646,0.704,0.38109 -0.648,0.704,0.44097 -0.65,0.704,0.4885 -0.652,0.704,0.52788 -0.654,0.704,0.56386 -0.656,0.704,0.59527 -0.658,0.704,0.62371 -0.66,0.704,0.65001 -0.662,0.704,0.67477 -0.664,0.704,0.69997 -0.666,0.704,0.72606 -0.668,0.704,0.75384 -0.67,0.704,0.77808 -0.672,0.704,0.80274 -0.674,0.704,0.82648 -0.676,0.704,0.8475 -0.678,0.704,0.86689 -0.68,0.704,0.88376 -0.682,0.704,0.89784 -0.684,0.704,0.91061 -0.686,0.704,0.91979 -0.688,0.704,0.92813 -0.69,0.704,0.93529 -0.692,0.704,0.9424 -0.694,0.704,0.94823 -0.696,0.704,0.95281 -0.698,0.704,0.95733 -0.7,0.704,0.9619 -0.702,0.704,0.96706 -0.704,0.704,0.97096 -0.706,0.704,0.97469 -0.708,0.704,0.97842 -0.71,0.704,0.9818 -0.712,0.704,0.98536 -0.714,0.704,0.9879 -0.716,0.704,0.99086 -0.718,0.704,0.99344 -0.72,0.704,0.99577 -0.722,0.704,0.99656 -0.724,0.704,0.99817 -0.726,0.704,0.99838 -0.728,0.704,0.99951 -0.73,0.704,1.0003 -0.63,0.706,0.076694 -0.632,0.706,0.039346 -0.634,0.706,0.058602 -0.636,0.706,0.094277 -0.638,0.706,0.13732 -0.64,0.706,0.19221 -0.642,0.706,0.25507 -0.644,0.706,0.31882 -0.646,0.706,0.38117 -0.648,0.706,0.43925 -0.65,0.706,0.48892 -0.652,0.706,0.53314 -0.654,0.706,0.5676 -0.656,0.706,0.59902 -0.658,0.706,0.62802 -0.66,0.706,0.65447 -0.662,0.706,0.68085 -0.664,0.706,0.70669 -0.666,0.706,0.73374 -0.668,0.706,0.75958 -0.67,0.706,0.78208 -0.672,0.706,0.80797 -0.674,0.706,0.82977 -0.676,0.706,0.85042 -0.678,0.706,0.87002 -0.68,0.706,0.88725 -0.682,0.706,0.90262 -0.684,0.706,0.91405 -0.686,0.706,0.92412 -0.688,0.706,0.93336 -0.69,0.706,0.9412 -0.692,0.706,0.94838 -0.694,0.706,0.95392 -0.696,0.706,0.95868 -0.698,0.706,0.96351 -0.7,0.706,0.96885 -0.702,0.706,0.974 -0.704,0.706,0.97837 -0.706,0.706,0.98267 -0.708,0.706,0.98728 -0.71,0.706,0.9912 -0.712,0.706,0.99474 -0.714,0.706,0.9978 -0.716,0.706,1.0003 -0.718,0.706,1.003 -0.72,0.706,1.0046 -0.722,0.706,1.0066 -0.724,0.706,1.0072 -0.726,0.706,1.0082 -0.728,0.706,1.0091 -0.73,0.706,1.0104 -0.63,0.708,0.078932 -0.632,0.708,0.039678 -0.634,0.708,0.058948 -0.636,0.708,0.09663 -0.638,0.708,0.14149 -0.64,0.708,0.20031 -0.642,0.708,0.26192 -0.644,0.708,0.32957 -0.646,0.708,0.39024 -0.648,0.708,0.45097 -0.65,0.708,0.49553 -0.652,0.708,0.53743 -0.654,0.708,0.57177 -0.656,0.708,0.60274 -0.658,0.708,0.63124 -0.66,0.708,0.65876 -0.662,0.708,0.68477 -0.664,0.708,0.71029 -0.666,0.708,0.73648 -0.668,0.708,0.7614 -0.67,0.708,0.78662 -0.672,0.708,0.81029 -0.674,0.708,0.83566 -0.676,0.708,0.85743 -0.678,0.708,0.87603 -0.68,0.708,0.89311 -0.682,0.708,0.90784 -0.684,0.708,0.91996 -0.686,0.708,0.93051 -0.688,0.708,0.93888 -0.69,0.708,0.94707 -0.692,0.708,0.95395 -0.694,0.708,0.96012 -0.696,0.708,0.9663 -0.698,0.708,0.9714 -0.7,0.708,0.97789 -0.702,0.708,0.98293 -0.704,0.708,0.98802 -0.706,0.708,0.99198 -0.708,0.708,0.9957 -0.71,0.708,1.0001 -0.712,0.708,1.0039 -0.714,0.708,1.0069 -0.716,0.708,1.0106 -0.718,0.708,1.0128 -0.72,0.708,1.0145 -0.722,0.708,1.0156 -0.724,0.708,1.0169 -0.726,0.708,1.0182 -0.728,0.708,1.0186 -0.73,0.708,1.0195 -0.63,0.71,0.079011 -0.632,0.71,0.041596 -0.634,0.71,0.062829 -0.636,0.71,0.098014 -0.638,0.71,0.14639 -0.64,0.71,0.20426 -0.642,0.71,0.26709 -0.644,0.71,0.33297 -0.646,0.71,0.39635 -0.648,0.71,0.4527 -0.65,0.71,0.50036 -0.652,0.71,0.53981 -0.654,0.71,0.57524 -0.656,0.71,0.60576 -0.658,0.71,0.63487 -0.66,0.71,0.66317 -0.662,0.71,0.68764 -0.664,0.71,0.71446 -0.666,0.71,0.74076 -0.668,0.71,0.76501 -0.67,0.71,0.79153 -0.672,0.71,0.81698 -0.674,0.71,0.83992 -0.676,0.71,0.86071 -0.678,0.71,0.87961 -0.68,0.71,0.89601 -0.682,0.71,0.91048 -0.684,0.71,0.92372 -0.686,0.71,0.93519 -0.688,0.71,0.94189 -0.69,0.71,0.94911 -0.692,0.71,0.95635 -0.694,0.71,0.96228 -0.696,0.71,0.96909 -0.698,0.71,0.97593 -0.7,0.71,0.98247 -0.702,0.71,0.9873 -0.704,0.71,0.99254 -0.706,0.71,0.99855 -0.708,0.71,1.0035 -0.71,0.71,1.0082 -0.712,0.71,1.0124 -0.714,0.71,1.0162 -0.716,0.71,1.0201 -0.718,0.71,1.0226 -0.72,0.71,1.0255 -0.722,0.71,1.0276 -0.724,0.71,1.0291 -0.726,0.71,1.0301 -0.728,0.71,1.0309 -0.73,0.71,1.0316 -0.63,0.712,0.082592 -0.632,0.712,0.044188 -0.634,0.712,0.06406 -0.636,0.712,0.10422 -0.638,0.712,0.14794 -0.64,0.712,0.20609 -0.642,0.712,0.27122 -0.644,0.712,0.33768 -0.646,0.712,0.39829 -0.648,0.712,0.45529 -0.65,0.712,0.50659 -0.652,0.712,0.54536 -0.654,0.712,0.58091 -0.656,0.712,0.61194 -0.658,0.712,0.63997 -0.66,0.712,0.66619 -0.662,0.712,0.69174 -0.664,0.712,0.71761 -0.666,0.712,0.74434 -0.668,0.712,0.77086 -0.67,0.712,0.79435 -0.672,0.712,0.81731 -0.674,0.712,0.84052 -0.676,0.712,0.86272 -0.678,0.712,0.88162 -0.68,0.712,0.89885 -0.682,0.712,0.91228 -0.684,0.712,0.92518 -0.686,0.712,0.93594 -0.688,0.712,0.94535 -0.69,0.712,0.95352 -0.692,0.712,0.96181 -0.694,0.712,0.96873 -0.696,0.712,0.97508 -0.698,0.712,0.98216 -0.7,0.712,0.98983 -0.702,0.712,0.99608 -0.704,0.712,1.0022 -0.706,0.712,1.0084 -0.708,0.712,1.0146 -0.71,0.712,1.0202 -0.712,0.712,1.0252 -0.714,0.712,1.029 -0.716,0.712,1.0328 -0.718,0.712,1.0348 -0.72,0.712,1.0379 -0.722,0.712,1.0414 -0.724,0.712,1.0422 -0.726,0.712,1.0434 -0.728,0.712,1.045 -0.73,0.712,1.0465 -0.63,0.714,0.086116 -0.632,0.714,0.047025 -0.634,0.714,0.068597 -0.636,0.714,0.1062 -0.638,0.714,0.15556 -0.64,0.714,0.21572 -0.642,0.714,0.27859 -0.644,0.714,0.34451 -0.646,0.714,0.40914 -0.648,0.714,0.46531 -0.65,0.714,0.51255 -0.652,0.714,0.55249 -0.654,0.714,0.58782 -0.656,0.714,0.61704 -0.658,0.714,0.64524 -0.66,0.714,0.67165 -0.662,0.714,0.69635 -0.664,0.714,0.72055 -0.666,0.714,0.74649 -0.668,0.714,0.77207 -0.67,0.714,0.79806 -0.672,0.714,0.82235 -0.674,0.714,0.84503 -0.676,0.714,0.86661 -0.678,0.714,0.8857 -0.68,0.714,0.90234 -0.682,0.714,0.91613 -0.684,0.714,0.92912 -0.686,0.714,0.93978 -0.688,0.714,0.94924 -0.69,0.714,0.95789 -0.692,0.714,0.96629 -0.694,0.714,0.97444 -0.696,0.714,0.98236 -0.698,0.714,0.98978 -0.7,0.714,0.99731 -0.702,0.714,1.0046 -0.704,0.714,1.0124 -0.706,0.714,1.0196 -0.708,0.714,1.0266 -0.71,0.714,1.0336 -0.712,0.714,1.0381 -0.714,0.714,1.0429 -0.716,0.714,1.0482 -0.718,0.714,1.0522 -0.72,0.714,1.0554 -0.722,0.714,1.0579 -0.724,0.714,1.0602 -0.726,0.714,1.0619 -0.728,0.714,1.0634 -0.73,0.714,1.0651 -0.63,0.716,0.083901 -0.632,0.716,0.045275 -0.634,0.716,0.066761 -0.636,0.716,0.10382 -0.638,0.716,0.15274 -0.64,0.716,0.21609 -0.642,0.716,0.27589 -0.644,0.716,0.34269 -0.646,0.716,0.4047 -0.648,0.716,0.46229 -0.65,0.716,0.51047 -0.652,0.716,0.55128 -0.654,0.716,0.58569 -0.656,0.716,0.61851 -0.658,0.716,0.64462 -0.66,0.716,0.67057 -0.662,0.716,0.69704 -0.664,0.716,0.72284 -0.666,0.716,0.74901 -0.668,0.716,0.77851 -0.67,0.716,0.80321 -0.672,0.716,0.82783 -0.674,0.716,0.84898 -0.676,0.716,0.86985 -0.678,0.716,0.8894 -0.68,0.716,0.90618 -0.682,0.716,0.92138 -0.684,0.716,0.934 -0.686,0.716,0.94411 -0.688,0.716,0.95308 -0.69,0.716,0.96238 -0.692,0.716,0.97103 -0.694,0.716,0.97971 -0.696,0.716,0.98876 -0.698,0.716,0.99754 -0.7,0.716,1.007 -0.702,0.716,1.0161 -0.704,0.716,1.0249 -0.706,0.716,1.0337 -0.708,0.716,1.0419 -0.71,0.716,1.0485 -0.712,0.716,1.0559 -0.714,0.716,1.0625 -0.716,0.716,1.0669 -0.718,0.716,1.0715 -0.72,0.716,1.076 -0.722,0.716,1.0791 -0.724,0.716,1.0821 -0.726,0.716,1.0851 -0.728,0.716,1.0854 -0.73,0.716,1.0865 -0.63,0.718,0.083345 -0.632,0.718,0.044958 -0.634,0.718,0.067167 -0.636,0.718,0.10393 -0.638,0.718,0.15692 -0.64,0.718,0.21743 -0.642,0.718,0.2849 -0.644,0.718,0.35291 -0.646,0.718,0.41351 -0.648,0.718,0.46621 -0.65,0.718,0.5135 -0.652,0.718,0.55371 -0.654,0.718,0.58858 -0.656,0.718,0.62002 -0.658,0.718,0.64729 -0.66,0.718,0.67337 -0.662,0.718,0.70382 -0.664,0.718,0.72797 -0.666,0.718,0.75411 -0.668,0.718,0.77997 -0.67,0.718,0.80525 -0.672,0.718,0.82835 -0.674,0.718,0.85065 -0.676,0.718,0.87214 -0.678,0.718,0.89235 -0.68,0.718,0.91101 -0.682,0.718,0.92536 -0.684,0.718,0.93699 -0.686,0.718,0.94774 -0.688,0.718,0.95775 -0.69,0.718,0.96705 -0.692,0.718,0.97631 -0.694,0.718,0.98574 -0.696,0.718,0.99511 -0.698,0.718,1.0049 -0.7,0.718,1.016 -0.702,0.718,1.0273 -0.704,0.718,1.038 -0.706,0.718,1.0477 -0.708,0.718,1.0582 -0.71,0.718,1.0681 -0.712,0.718,1.0751 -0.714,0.718,1.0827 -0.716,0.718,1.0901 -0.718,0.718,1.0956 -0.72,0.718,1.0997 -0.722,0.718,1.1036 -0.724,0.718,1.1079 -0.726,0.718,1.1112 -0.728,0.718,1.1129 -0.73,0.718,1.1141 -0.63,0.72,0.089707 -0.632,0.72,0.050858 -0.634,0.72,0.071119 -0.636,0.72,0.10927 -0.638,0.72,0.16062 -0.64,0.72,0.23132 -0.642,0.72,0.28663 -0.644,0.72,0.35299 -0.646,0.72,0.41782 -0.648,0.72,0.4722 -0.65,0.72,0.51938 -0.652,0.72,0.55932 -0.654,0.72,0.59438 -0.656,0.72,0.62487 -0.658,0.72,0.65259 -0.66,0.72,0.67905 -0.662,0.72,0.70658 -0.664,0.72,0.73125 -0.666,0.72,0.75722 -0.668,0.72,0.78321 -0.67,0.72,0.80871 -0.672,0.72,0.83529 -0.674,0.72,0.85582 -0.676,0.72,0.87779 -0.678,0.72,0.89563 -0.68,0.72,0.9119 -0.682,0.72,0.92579 -0.684,0.72,0.93875 -0.686,0.72,0.95023 -0.688,0.72,0.9604 -0.69,0.72,0.97006 -0.692,0.72,0.97853 -0.694,0.72,0.98754 -0.696,0.72,0.99747 -0.698,0.72,1.0086 -0.7,0.72,1.021 -0.702,0.72,1.0342 -0.704,0.72,1.0478 -0.706,0.72,1.0619 -0.708,0.72,1.0738 -0.71,0.72,1.0842 -0.712,0.72,1.0946 -0.714,0.72,1.1052 -0.716,0.72,1.1144 -0.718,0.72,1.1218 -0.72,0.72,1.1288 -0.722,0.72,1.1333 -0.724,0.72,1.1367 -0.726,0.72,1.1395 -0.728,0.72,1.1438 -0.73,0.72,1.1474 -0.63,0.722,0.076725 -0.632,0.722,0.043671 -0.634,0.722,0.06565 -0.636,0.722,0.10292 -0.638,0.722,0.15107 -0.64,0.722,0.20983 -0.642,0.722,0.28036 -0.644,0.722,0.3452 -0.646,0.722,0.40503 -0.648,0.722,0.46375 -0.65,0.722,0.51206 -0.652,0.722,0.55218 -0.654,0.722,0.58842 -0.656,0.722,0.61949 -0.658,0.722,0.64836 -0.66,0.722,0.67462 -0.662,0.722,0.70053 -0.664,0.722,0.7272 -0.666,0.722,0.75444 -0.668,0.722,0.7818 -0.67,0.722,0.80748 -0.672,0.722,0.83237 -0.674,0.722,0.85613 -0.676,0.722,0.87826 -0.678,0.722,0.8967 -0.68,0.722,0.91382 -0.682,0.722,0.92771 -0.684,0.722,0.94106 -0.686,0.722,0.95227 -0.688,0.722,0.96311 -0.69,0.722,0.97289 -0.692,0.722,0.98296 -0.694,0.722,0.99365 -0.696,0.722,1.0047 -0.698,0.722,1.0159 -0.7,0.722,1.0292 -0.702,0.722,1.0433 -0.704,0.722,1.0585 -0.706,0.722,1.0742 -0.708,0.722,1.0913 -0.71,0.722,1.1054 -0.712,0.722,1.1178 -0.714,0.722,1.1307 -0.716,0.722,1.1398 -0.718,0.722,1.1488 -0.72,0.722,1.1562 -0.722,0.722,1.163 -0.724,0.722,1.1674 -0.726,0.722,1.172 -0.728,0.722,1.1776 -0.73,0.722,1.1816 -0.63,0.724,0.084147 -0.632,0.724,0.046127 -0.634,0.724,0.065873 -0.636,0.724,0.10529 -0.638,0.724,0.15739 -0.64,0.724,0.21428 -0.642,0.724,0.28025 -0.644,0.724,0.3468 -0.646,0.724,0.41143 -0.648,0.724,0.46584 -0.65,0.724,0.51643 -0.652,0.724,0.55611 -0.654,0.724,0.59257 -0.656,0.724,0.62462 -0.658,0.724,0.65482 -0.66,0.724,0.67899 -0.662,0.724,0.70562 -0.664,0.724,0.73321 -0.666,0.724,0.76032 -0.668,0.724,0.78583 -0.67,0.724,0.81118 -0.672,0.724,0.83617 -0.674,0.724,0.85974 -0.676,0.724,0.87975 -0.678,0.724,0.89944 -0.68,0.724,0.91611 -0.682,0.724,0.93088 -0.684,0.724,0.94441 -0.686,0.724,0.95608 -0.688,0.724,0.96616 -0.69,0.724,0.97676 -0.692,0.724,0.98709 -0.694,0.724,0.99818 -0.696,0.724,1.0107 -0.698,0.724,1.0241 -0.7,0.724,1.0387 -0.702,0.724,1.0555 -0.704,0.724,1.074 -0.706,0.724,1.0914 -0.708,0.724,1.1097 -0.71,0.724,1.1243 -0.712,0.724,1.1423 -0.714,0.724,1.1566 -0.716,0.724,1.1675 -0.718,0.724,1.1776 -0.72,0.724,1.1866 -0.722,0.724,1.1934 -0.724,0.724,1.2007 -0.726,0.724,1.2072 -0.728,0.724,1.2127 -0.73,0.724,1.2185 -0.63,0.726,0.085416 -0.632,0.726,0.046943 -0.634,0.726,0.069861 -0.636,0.726,0.11012 -0.638,0.726,0.16265 -0.64,0.726,0.22264 -0.642,0.726,0.29811 -0.644,0.726,0.35491 -0.646,0.726,0.41576 -0.648,0.726,0.47177 -0.65,0.726,0.51979 -0.652,0.726,0.56312 -0.654,0.726,0.59709 -0.656,0.726,0.62754 -0.658,0.726,0.65684 -0.66,0.726,0.68415 -0.662,0.726,0.71042 -0.664,0.726,0.7383 -0.666,0.726,0.76524 -0.668,0.726,0.79004 -0.67,0.726,0.81552 -0.672,0.726,0.84008 -0.674,0.726,0.86448 -0.676,0.726,0.88448 -0.678,0.726,0.90392 -0.68,0.726,0.92112 -0.682,0.726,0.93512 -0.684,0.726,0.94793 -0.686,0.726,0.95978 -0.688,0.726,0.96975 -0.69,0.726,0.98 -0.692,0.726,0.99072 -0.694,0.726,1.0025 -0.696,0.726,1.0154 -0.698,0.726,1.0296 -0.7,0.726,1.0467 -0.702,0.726,1.066 -0.704,0.726,1.0857 -0.706,0.726,1.1057 -0.708,0.726,1.1257 -0.71,0.726,1.1464 -0.712,0.726,1.1653 -0.714,0.726,1.1828 -0.716,0.726,1.1987 -0.718,0.726,1.213 -0.72,0.726,1.2256 -0.722,0.726,1.235 -0.724,0.726,1.2437 -0.726,0.726,1.2504 -0.728,0.726,1.2569 -0.73,0.726,1.2632 -0.63,0.728,0.085567 -0.632,0.728,0.046151 -0.634,0.728,0.068859 -0.636,0.728,0.1076 -0.638,0.728,0.15732 -0.64,0.728,0.21771 -0.642,0.728,0.28481 -0.644,0.728,0.3554 -0.646,0.728,0.41707 -0.648,0.728,0.47192 -0.65,0.728,0.52046 -0.652,0.728,0.56168 -0.654,0.728,0.59761 -0.656,0.728,0.62717 -0.658,0.728,0.65785 -0.66,0.728,0.6855 -0.662,0.728,0.71198 -0.664,0.728,0.73937 -0.666,0.728,0.76619 -0.668,0.728,0.79153 -0.67,0.728,0.81669 -0.672,0.728,0.84132 -0.674,0.728,0.86498 -0.676,0.728,0.88665 -0.678,0.728,0.9061 -0.68,0.728,0.92364 -0.682,0.728,0.93819 -0.684,0.728,0.95153 -0.686,0.728,0.96242 -0.688,0.728,0.97363 -0.69,0.728,0.98454 -0.692,0.728,0.9957 -0.694,0.728,1.0081 -0.696,0.728,1.0212 -0.698,0.728,1.0364 -0.7,0.728,1.0538 -0.702,0.728,1.0729 -0.704,0.728,1.0955 -0.706,0.728,1.1175 -0.708,0.728,1.1406 -0.71,0.728,1.1642 -0.712,0.728,1.1878 -0.714,0.728,1.209 -0.716,0.728,1.2294 -0.718,0.728,1.2458 -0.72,0.728,1.2585 -0.722,0.728,1.2715 -0.724,0.728,1.2825 -0.726,0.728,1.291 -0.728,0.728,1.2976 -0.73,0.728,1.304 -0.63,0.73,0.08942 -0.632,0.73,0.049986 -0.634,0.73,0.076874 -0.636,0.73,0.11174 -0.638,0.73,0.16191 -0.64,0.73,0.22533 -0.642,0.73,0.29268 -0.644,0.73,0.36374 -0.646,0.73,0.4235 -0.648,0.73,0.48202 -0.65,0.73,0.52926 -0.652,0.73,0.57016 -0.654,0.73,0.60652 -0.656,0.73,0.63769 -0.658,0.73,0.66469 -0.66,0.73,0.69087 -0.662,0.73,0.71907 -0.664,0.73,0.74408 -0.666,0.73,0.77067 -0.668,0.73,0.79753 -0.67,0.73,0.82201 -0.672,0.73,0.84738 -0.674,0.73,0.8718 -0.676,0.73,0.89088 -0.678,0.73,0.91032 -0.68,0.73,0.92637 -0.682,0.73,0.94049 -0.684,0.73,0.95376 -0.686,0.73,0.96599 -0.688,0.73,0.97652 -0.69,0.73,0.98838 -0.692,0.73,0.99969 -0.694,0.73,1.0115 -0.696,0.73,1.0249 -0.698,0.73,1.0413 -0.7,0.73,1.0606 -0.702,0.73,1.0814 -0.704,0.73,1.1052 -0.706,0.73,1.1322 -0.708,0.73,1.1603 -0.71,0.73,1.1859 -0.712,0.73,1.2105 -0.714,0.73,1.2359 -0.716,0.73,1.258 -0.718,0.73,1.2779 -0.72,0.73,1.2935 -0.722,0.73,1.3077 -0.724,0.73,1.3201 -0.726,0.73,1.3311 -0.728,0.73,1.3404 -0.73,0.73,1.3486 From 4948f982efe2999c0df3ec6ba2370a5a42826e17 Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:29:11 -0400 Subject: [PATCH 02/15] Delete .gitignore --- .gitignore | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 0fc4bc7..0000000 --- a/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -*.pyc -*.py~ -*.swp -fileslist.list - -# Aptana Studio 3 project files. -.project -.pydevproject - -# Code coverage results from nose. -.coverage - -# Build objects. -/SpanishAcquisition.egg-info/ -/build/ -/dist/ From b4aca069739d0e8dd86e89477a65401e312f4b85 Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:29:32 -0400 Subject: [PATCH 03/15] Delete docs directory --- docs/.gitignore | 1 - docs/Makefile | 89 ------- docs/conf.py | 197 --------------- docs/devel/general_info.rst | 94 ------- docs/devel/index.rst | 12 - docs/devel/packages/devices/awg5014b.rst | 12 - docs/devel/packages/devices/dpo7104.rst | 26 -- docs/devel/packages/devices/index.rst | 117 --------- docs/devel/packages/devices/model4g.rst | 47 ---- docs/devel/packages/gui.rst | 117 --------- docs/devel/packages/index.rst | 11 - docs/devel/packages/interface.rst | 89 ------- docs/devel/packages/iteration.rst | 32 --- docs/devel/pulse_program_execution.rst | 46 ---- docs/index.rst | 10 - docs/installation.rst | 75 ------ docs/user/examples/acquisition.rst | 22 -- docs/user/examples/acquisition_acquiring.png | Bin 150697 -> 0 bytes docs/user/examples/data_explorer.rst | 19 -- docs/user/examples/data_explorer_menu.png | Bin 60195 -> 0 bytes docs/user/examples/index.rst | 13 - docs/user/examples/virtual_setup.rst | 10 - docs/user/examples/virtual_setup_menu.png | Bin 73869 -> 0 bytes docs/user/general_concepts/devices.rst | 30 --- docs/user/general_concepts/index.rst | 12 - docs/user/general_concepts/resources.rst | 16 -- docs/user/general_concepts/variables.rst | 144 ----------- docs/user/gui/action/data_capture.rst | 92 ------- docs/user/gui/action/data_capture_panel.png | Bin 16230 -> 0 bytes docs/user/gui/action/data_capture_panel.xcf | Bin 38009 -> 0 bytes docs/user/gui/action/data_capture_sweep.png | Bin 30996 -> 0 bytes docs/user/gui/action/data_capture_sweep.xcf | Bin 84101 -> 0 bytes docs/user/gui/action/index.rst | 9 - docs/user/gui/action/smooth_reset.rst | 19 -- docs/user/gui/action/smooth_reset_panel.png | Bin 6561 -> 0 bytes docs/user/gui/action/smooth_reset_panel.xcf | Bin 19393 -> 0 bytes docs/user/gui/config/device_config.rst | 87 ------- .../gui/config/device_config_connection.png | Bin 32256 -> 0 bytes .../gui/config/device_config_connection.xcf | Bin 96006 -> 0 bytes docs/user/gui/config/device_config_list.png | Bin 20130 -> 0 bytes docs/user/gui/config/device_config_list.xcf | Bin 57584 -> 0 bytes .../gui/config/device_config_resources.png | Bin 32672 -> 0 bytes .../gui/config/device_config_resources.xcf | Bin 78700 -> 0 bytes docs/user/gui/config/index.rst | 11 - docs/user/gui/config/measurement_config.rst | 113 --------- .../gui/config/measurement_config_list.png | Bin 140457 -> 0 bytes .../measurement_config_list_settings.png | Bin 7521 -> 0 bytes .../gui/config/measurement_config_scalar.png | Bin 47477 -> 0 bytes .../gui/config/measurement_config_scalar.xcf | Bin 149586 -> 0 bytes .../measurement_config_scalar_settings.png | Bin 22210 -> 0 bytes .../gui/config/measurement_config_scaling.png | Bin 8762 -> 0 bytes docs/user/gui/config/pulse_program_config.rst | 67 ----- .../pulse_program_config_acquisition.png | Bin 12959 -> 0 bytes .../config/pulse_program_config_delays.png | Bin 12278 -> 0 bytes .../config/pulse_program_config_outputs.png | Bin 14006 -> 0 bytes docs/user/gui/config/variable_config.rst | 150 ------------ .../variable_config_condition_editor.png | Bin 15794 -> 0 bytes .../variable_config_condition_editor.xcf | Bin 68934 -> 0 bytes ...iable_config_condition_variable_editor.png | Bin 11478 -> 0 bytes ...iable_config_condition_variable_editor.xcf | Bin 31776 -> 0 bytes .../gui/config/variable_config_editor.png | Bin 20814 -> 0 bytes .../gui/config/variable_config_editor.xcf | Bin 53627 -> 0 bytes docs/user/gui/config/variable_config_list.png | Bin 38587 -> 0 bytes docs/user/gui/config/variable_config_list.xcf | Bin 103445 -> 0 bytes docs/user/gui/display/index.rst | 9 - docs/user/gui/display/plots/colormapped.rst | 22 -- .../display/plots/colormapped_hilighted.png | Bin 13587 -> 0 bytes .../gui/display/plots/colormapped_normal.png | Bin 14075 -> 0 bytes docs/user/gui/display/plots/index.rst | 12 - docs/user/gui/display/plots/surface.rst | 27 --- .../user/gui/display/plots/surface_normal.png | Bin 56563 -> 0 bytes .../gui/display/plots/surface_waveform.png | Bin 146758 -> 0 bytes .../gui/display/plots/two_dimensional.png | Bin 12994 -> 0 bytes .../gui/display/plots/two_dimensional.rst | 23 -- docs/user/gui/display/table.png | Bin 25327 -> 0 bytes docs/user/gui/display/table.rst | 146 ----------- docs/user/gui/display/table_filter_editor.png | Bin 7994 -> 0 bytes docs/user/gui/display/table_filter_list.png | Bin 6592 -> 0 bytes docs/user/gui/index.rst | 14 -- docs/user/index.rst | 13 - docs/user/pulse_programs.rst | 229 ------------------ docs/user/pulse_programs_acquisition.png | Bin 17189 -> 0 bytes docs/user/pulse_programs_multiple_01.png | Bin 19802 -> 0 bytes docs/user/pulse_programs_multiple_02.png | Bin 15910 -> 0 bytes docs/user/pulse_programs_single.png | Bin 20921 -> 0 bytes 85 files changed, 2284 deletions(-) delete mode 100644 docs/.gitignore delete mode 100644 docs/Makefile delete mode 100644 docs/conf.py delete mode 100644 docs/devel/general_info.rst delete mode 100644 docs/devel/index.rst delete mode 100644 docs/devel/packages/devices/awg5014b.rst delete mode 100644 docs/devel/packages/devices/dpo7104.rst delete mode 100644 docs/devel/packages/devices/index.rst delete mode 100644 docs/devel/packages/devices/model4g.rst delete mode 100644 docs/devel/packages/gui.rst delete mode 100644 docs/devel/packages/index.rst delete mode 100644 docs/devel/packages/interface.rst delete mode 100644 docs/devel/packages/iteration.rst delete mode 100644 docs/devel/pulse_program_execution.rst delete mode 100644 docs/index.rst delete mode 100644 docs/installation.rst delete mode 100644 docs/user/examples/acquisition.rst delete mode 100644 docs/user/examples/acquisition_acquiring.png delete mode 100644 docs/user/examples/data_explorer.rst delete mode 100644 docs/user/examples/data_explorer_menu.png delete mode 100644 docs/user/examples/index.rst delete mode 100644 docs/user/examples/virtual_setup.rst delete mode 100644 docs/user/examples/virtual_setup_menu.png delete mode 100644 docs/user/general_concepts/devices.rst delete mode 100644 docs/user/general_concepts/index.rst delete mode 100644 docs/user/general_concepts/resources.rst delete mode 100644 docs/user/general_concepts/variables.rst delete mode 100644 docs/user/gui/action/data_capture.rst delete mode 100644 docs/user/gui/action/data_capture_panel.png delete mode 100644 docs/user/gui/action/data_capture_panel.xcf delete mode 100644 docs/user/gui/action/data_capture_sweep.png delete mode 100644 docs/user/gui/action/data_capture_sweep.xcf delete mode 100644 docs/user/gui/action/index.rst delete mode 100644 docs/user/gui/action/smooth_reset.rst delete mode 100644 docs/user/gui/action/smooth_reset_panel.png delete mode 100644 docs/user/gui/action/smooth_reset_panel.xcf delete mode 100644 docs/user/gui/config/device_config.rst delete mode 100644 docs/user/gui/config/device_config_connection.png delete mode 100644 docs/user/gui/config/device_config_connection.xcf delete mode 100644 docs/user/gui/config/device_config_list.png delete mode 100644 docs/user/gui/config/device_config_list.xcf delete mode 100644 docs/user/gui/config/device_config_resources.png delete mode 100644 docs/user/gui/config/device_config_resources.xcf delete mode 100644 docs/user/gui/config/index.rst delete mode 100644 docs/user/gui/config/measurement_config.rst delete mode 100644 docs/user/gui/config/measurement_config_list.png delete mode 100644 docs/user/gui/config/measurement_config_list_settings.png delete mode 100644 docs/user/gui/config/measurement_config_scalar.png delete mode 100644 docs/user/gui/config/measurement_config_scalar.xcf delete mode 100644 docs/user/gui/config/measurement_config_scalar_settings.png delete mode 100644 docs/user/gui/config/measurement_config_scaling.png delete mode 100644 docs/user/gui/config/pulse_program_config.rst delete mode 100644 docs/user/gui/config/pulse_program_config_acquisition.png delete mode 100644 docs/user/gui/config/pulse_program_config_delays.png delete mode 100644 docs/user/gui/config/pulse_program_config_outputs.png delete mode 100644 docs/user/gui/config/variable_config.rst delete mode 100644 docs/user/gui/config/variable_config_condition_editor.png delete mode 100644 docs/user/gui/config/variable_config_condition_editor.xcf delete mode 100644 docs/user/gui/config/variable_config_condition_variable_editor.png delete mode 100644 docs/user/gui/config/variable_config_condition_variable_editor.xcf delete mode 100644 docs/user/gui/config/variable_config_editor.png delete mode 100644 docs/user/gui/config/variable_config_editor.xcf delete mode 100644 docs/user/gui/config/variable_config_list.png delete mode 100644 docs/user/gui/config/variable_config_list.xcf delete mode 100644 docs/user/gui/display/index.rst delete mode 100644 docs/user/gui/display/plots/colormapped.rst delete mode 100644 docs/user/gui/display/plots/colormapped_hilighted.png delete mode 100644 docs/user/gui/display/plots/colormapped_normal.png delete mode 100644 docs/user/gui/display/plots/index.rst delete mode 100644 docs/user/gui/display/plots/surface.rst delete mode 100644 docs/user/gui/display/plots/surface_normal.png delete mode 100644 docs/user/gui/display/plots/surface_waveform.png delete mode 100644 docs/user/gui/display/plots/two_dimensional.png delete mode 100644 docs/user/gui/display/plots/two_dimensional.rst delete mode 100644 docs/user/gui/display/table.png delete mode 100644 docs/user/gui/display/table.rst delete mode 100644 docs/user/gui/display/table_filter_editor.png delete mode 100644 docs/user/gui/display/table_filter_list.png delete mode 100644 docs/user/gui/index.rst delete mode 100644 docs/user/index.rst delete mode 100644 docs/user/pulse_programs.rst delete mode 100644 docs/user/pulse_programs_acquisition.png delete mode 100644 docs/user/pulse_programs_multiple_01.png delete mode 100644 docs/user/pulse_programs_multiple_02.png delete mode 100644 docs/user/pulse_programs_single.png diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index ba65b13..0000000 --- a/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 9245862..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SpanishAcquisition.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SpanishAcquisition.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 2ff201c..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Spanish Acquisition documentation build configuration file, created by -# sphinx-quickstart on Mon Jul 11 12:50:19 2011. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath('..')) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Spanish Acquisition IQC' -copyright = u'2011, Dmitri Iouchtchenko' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '2.0' -# The full version, including alpha/beta/rc tags. -release = '2.0.3' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'haiku' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -html_use_modindex = False - -# If false, no index is generated. -html_use_index = False - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'SpanishAcquisitiondoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'SpanishAcquisitionIQC.tex', u'Spanish Acquisition IQC Documentation', - u'Dmitri Iouchtchenko', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -latex_use_parts = True - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True - -latex_show_urls = True -latex_show_pagerefs = True diff --git a/docs/devel/general_info.rst b/docs/devel/general_info.rst deleted file mode 100644 index b4c9722..0000000 --- a/docs/devel/general_info.rst +++ /dev/null @@ -1,94 +0,0 @@ -################### -General information -################### - -The developer is assumed to have read through the :ref:`users_guide` before proceeding. - -Testing -******* - -All the non-GUI modules in this package have associated tests, found in the ``tests/`` subdirectory. These are split into unit tests (found directly in the subdirectory) and server tests (found in ``tests/server/``); server tests differ from unit tests by the addition of an external dependency, such as a hardware device. - -.. note:: - Test failures can signify a missing :ref:`dependency `. While users can get by with only a subset, it is recommended that developers install *all* dependencies. - -Unit tests -========== - -The unit tests can all be run with:: - - ./runtests - -Server tests -============ - -Currently, only device tests exist in the form of server tests. Before running these, the devices should be connected to the computer and :ref:`configured `. - -Server tests can be found with:: - - find ./spacq/ -path '*/tests/server/test_*.py' - -and run with, for example:: - - ./runtests --no-skip ./spacq/devices/tests/server/test_abstract_device.py - -Documentation -************* - -Style -===== - -An attempt has been made to ensure that the reStructuredText source for this documentation is self-consistent. The following stylistic choices have been made: - -* The headings *for each document* are as follows:: - - ### - One - ### - - Two - *** - - Three - ===== - - Four - ---- - - Headings nested deeper than this should not be used. - -* A single blank line separates elements within a document. Multiple blank lines are not used. - -Building -======== - -The documentation can be built using:: - - make -C docs html - -This places the result at ``docs/_build/html/index.html``. - -Updating -======== - -It is crucial that all modifications which cause any changes to the user interface, whether they are changes in the GUI or changes in behaviour, are documented in the :ref:`users_guide` as they occur. All changes (especially internal ones not mentioned in the :ref:`users_guide`) should be documented in the :ref:`developers_guide`. - -Example applications -******************** - -The example applications are provided to demonstrate the functionality that this package provides in the form of directly usable and useful applications. As this functionality changes, the example programs should follow suit. - -Creating a release -****************** - -When a new version is ready for release, the version number must be updated in the following places: - -* ``docs/conf.py``: ``version``, ``release`` -* ``spacq/__init__.py``: ``VERSION`` -* ``setup.py``: ``version`` - -The ``CHANGELOG.rst`` file must be updated with all the important changes from the previous version. - -To create a source distribution, run:: - - python setup.py sdist diff --git a/docs/devel/index.rst b/docs/devel/index.rst deleted file mode 100644 index ad4ba9e..0000000 --- a/docs/devel/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -.. _developers_guide: - -################# -Developer's Guide -################# - -.. toctree:: - :maxdepth: 2 - - general_info - packages/index - pulse_program_execution diff --git a/docs/devel/packages/devices/awg5014b.rst b/docs/devel/packages/devices/awg5014b.rst deleted file mode 100644 index 162829c..0000000 --- a/docs/devel/packages/devices/awg5014b.rst +++ /dev/null @@ -1,12 +0,0 @@ -################## -Tektronix AWG5014B -################## - -The implementation for the Tektronix AWG5014B arbitrary waveform generator is :class:`spacq.devices.tektronix.awg5014b.AWG5014B`. - -The following examples assume ``awg = AWG5014B(...)``. - -Run modes -********* - -The currently-supported run modes are "continuous" (``awg.run_mode = 'continuous'``) and "triggered" (``awg.run_mode = 'triggered'``). In "continuous" mode, the device will output all the waveforms on the enabled channels until stopped. In "triggered" mode, the device will output all the waveforms on the enabled channels once each time it is triggered (``awg.trigger()``). diff --git a/docs/devel/packages/devices/dpo7104.rst b/docs/devel/packages/devices/dpo7104.rst deleted file mode 100644 index 348bc47..0000000 --- a/docs/devel/packages/devices/dpo7104.rst +++ /dev/null @@ -1,26 +0,0 @@ -################# -Tektronix DPO7104 -################# - -The implementation for the Tektronix DPO7104 oscilloscope is :class:`spacq.devices.tektronix.dpo7104.DPO7104`. - -The following examples assume ``dpo = DPO7104(...)``. - -.. _device_specific_dpo7104_fastframe: - -"FastFrame" mode -**************** - -In order to allow fast triggering for on-board averaging, the device can be placed into "FastFrame" mode (``dpo.fastframe = True``), which enables memory segmentation. The acquisition memory is split up into the requested number of frames (``dpo.fastframe_count = 17``); this has the negative side effect of decreasing the maximum allowed resolution, since all frames share the same acquisition memory. - -The final frame must be enabled in either average (``dpo.fastframe_sum = 'average'``) or envelope (``dpo.fastframe_sum = 'envelope'``) mode. - -.. note:: - ``dpo.fastframe_count`` includes the final frame. - -Waveform acquisition -******************** - -The property :attr:`spacq.devices.tektronix.dpo7104.Channel.waveform` downloads a single frame from the device. If "FastFrame" mode is enabled (regardless of the final frame setting), only the last frame is downloaded. - -Extremely large waveforms are downloaded in chunks (the size of which is specified by ``dpo.max_receive_samples``) and are assembled into a single waveform locally. diff --git a/docs/devel/packages/devices/index.rst b/docs/devel/packages/devices/index.rst deleted file mode 100644 index 2b5e11b..0000000 --- a/docs/devel/packages/devices/index.rst +++ /dev/null @@ -1,117 +0,0 @@ -####### -Devices -####### - -Interfaces for various hardware devices. This package allows the remaining components to talk to devices through a layer of abstraction. - -Class hierarchy -*************** - -All device classes inherit from :class:`spacq.devices.abstract_device.AbstractDevice`, which provides all the functionality necessary to make connections to physical devices. Subdevices inherit from :class:`spacq.devices.abstract_device.AbstractSubdevice`. Each device class has its own test class, as well as a mock implementation. - -:class:`spacq.devices.config.DeviceConfig` and :func:`spacq.devices.config.device_tree` provide a way to find existing devices and connect to them. - -.. _devices_testing: - -Device implementation notes -*************************** - -.. toctree:: - :maxdepth: 1 - - awg5014b - dpo7104 - model4g - -Testing -******* - -All the device interfaces can be tested against real hardware as long as the hardware is present and configured. - -Configuration of external resources should be done by copying and editing the example file:: - - cp test-config.py ~/.spacq-test-config.py - -File structure -************** - -Each manufacturer has its own directory in ``spacq/devices/`` (eg. ``agilent`` for Agilent, ``tektronix`` for Tektronix, etc), and each of these directories may contain any number of devices. There should be at least four files [#four_files]_ per device: - -#. The device interface (eg. ``dm34410a.py``). -#. Tests for the device interface (eg. ``test_dm34410a.py``). -#. A mock implementation of the device (eg. ``mock_dm34410a.py``). -#. A wrapper to run the device tests against the mock (eg. ``test_mock_dm34410a.py``). - -.. rubric:: Footnotes - -.. [#four_files] Most devices will have four files, but, for example, the IQC voltage source (``iqc/voltage_source.py``) includes a non-server test file (``iqc/tests/test_voltage_source.py``) as a fifth file, and a :ref:`graphical configuration panel ` (``iqc/gui/voltage_source.py``) as a sixth file. - -Adding a manufacturer -===================== - -To add a manufacturer: - -#. Copy the sample manufacturer directory (``spacq/devices/sample/``) to a new directory in ``spacq/devices/`` corresponding to the manufacturer. The name of this directory will be the *package name*; it should include only lowercase letters, but starting with the second character may also include digits and underscores. -#. In the ``spacq/devices//__init__.py`` file, replace the name with the name of the manufacturer as you would like it to appear in the user interface. -#. Add the new package name to both lines of ``spacq/devices/__init__.py``. - -For example, to add Oxford Instruments as a manufacturer:: - - cp -r spacq/devices/sample spacq/devices/oxford - vim spacq/devices/oxford/__init__.py # Change the name to "Oxford Instruments". - vim spacq/devices/__init__.py # Add "oxford". - -Adding a device -=============== - -The sample manufacturer comes with a sample device in the form of files ending in ``abc1234.py``. If the manufacturer is newly added, you should modify this sample device; otherwise, copy the four sample device files over from ``spacq/devices/sample/``. - -#. Rename all the ``abc1234.py`` files to something suitable for the device. The chosen name (excluding the ``.py`` extension) will be the *module name*; it should include only lowercase letters, but can also include digits and underscores starting with the second character. - - .. note:: - Due to the constraint that the initial character of the module name may not be a digit, some model names cannot be used directly either in the module name or in the class name. For example, in the case of the Agilent 34410A, the module name is ``dm34410a`` and the class name is ``DM34410A`` where the letters "DM" are arbitrarily chosen and stand for "digital multimeter". - -#. Add the configuration details for the physical device to your :ref:`test configuration `. -#. Edit the mock tests (``spacq/devices//mock/tests/test_mock_.py``) to refer to the correct tests. -#. Edit the server [#server_tests]_ tests (``spacq/devices//tests/server/test_.py``) to use *all* the functionality the interface will include. -#. Edit the device interface (``spacq/devices//.py``) until all the server tests pass. -#. Edit the mock implementation (``spacq/devices//.py``) until all the mock tests pass. -#. Add the new model module and mock model module to the :data:`models` and :data:`mock_models` lists, respectively, in the manufacturer directory ``__init__.py``, as well as to the import lines. - - .. warning:: - Ensure that both lists have the same length. ``None`` is an acceptable value in either list if that implementation is not available. - -.. rubric:: Footnotes - -.. [#server_tests] They are referred to as "server" tests because they have an external dependency (the hardware device) which acts roughly as a server to which the tests connect. - -.. _devices_graphical_configuration: - -Synchronization -*************** - -To allow for consistent state while performing device commands, each device contains a re-entrant lock. Every read and write operation acquires this lock; thus, multiple reads and writes are mutually excluded. In order to provide a similar mechanism for user-defined methods, the :class:`spacq.tool.box.Synchronized` decorator can be used. This decorator will acquire the device lock, ensuring that other concurrently-executing threads cannot do the same, and that the atomicity of the decorated method is guaranteed for a given device instance. - -Graphical configuration -*********************** - -In the case that a device requires a graphical configuration panel, one can be added in the form of a non-modal wxPython dialog (inherited from :class:`~spacq.gui.tool.box.Dialog`). The dialog should reside in ``spacq/devices//gui/.py``, and its constructor must take the following arguments, in order: - -#. The parent window. -#. The global store. -#. The device name as used in the global store. - -The latter two values allow the dialog to find a reference to the device object itself. - -In order to announce that a GUI configuration panel is available, the device class (child of :class:`~spacq.devices.abstract_device.AbstractDevice`) must have a ``_gui_setup`` property which follows the following template:: - - @property - def _gui_setup(self): - try: - from .gui.model import ModelSettingsDialog - - return ModelSettingsDialog - except ImportError as e: - log.debug('Could not load GUI setup for device "{0}": {1}'.format(self.name, str(e))) - - return None diff --git a/docs/devel/packages/devices/model4g.rst b/docs/devel/packages/devices/model4g.rst deleted file mode 100644 index 856e7ea..0000000 --- a/docs/devel/packages/devices/model4g.rst +++ /dev/null @@ -1,47 +0,0 @@ -###################### -Cryomagnetics Model 4G -###################### - -The implementation for the Cryomagnetics Model 4G power supply is :class:`spacq.devices.cryomagnetics.model4g.Model4G`. - -It is assumed the reader has read the manual for the Model 4G. - -.. _device_specific_model4g_virtual_resources: - -Virtual resources -***************** - -Along with all the resources that are mostly direct representations of controls on the power supply's interface, the device class has higher level virtual resources. These are resources that are constructed from the more direct resources. Following are descriptions of what these new functions do: - -* **virt_both_units**: Switches units on both channels simultaneously. Will read 'unequal' for its output if units on channels are not equal. Otherwise, reads the current units. -* **virt_both_heaters**: Switches both persistent heater switches simultaneously. Useful for avoiding eddy currents. -* **virt_iout_sweep_to**: Set a target for the power supply current to sweep to, and start the sweep. -* **virt_imag_sweep_to**: Set a target for the magnet current to sweep to, and start the sweep. -* **virt_sync_currents**: Sweeps the power supply current to the magnet current. Output will give whether or not currents are synced. -* **virt_iout**: The same as **virt_iout_sweep_to**, but after starting the sweep, Spanish Acquisition will halt until the sweep is complete. In effect, this is a dynamic version of the wait time that output variables can be assigned. The output gives the power supply current. -* **virt_imag**: Same as **virt_imag**, but for magnet current. Note that this is also affected by **virt_energysave_mode**. The output gives the magnet current. -* **virt_energysave_mode**: Supplies a variety of modes to save energy when using **virt_imag**, described in :ref:`energy saving modes `. -* **virt_heater_wait_mode**: If set to 1, Spanish Acquisition will sleep for a second after a persistent switch heater is toggled. -* **virt_sweep_sleep**: The application halting functionality within **virt_imag** and **virt_iout** will double-check if the sweep is complete. **virt_sweep_sleep** defines a sleep delay between the first and second check. This is useful for dealing with the overshoot that may occur in reaching the target with the sweep. It is possible that the halting functionality will see the sweep as complete when the present current equals the target current just before overshooting. - -Note that these virtual resources are not internally independent with respect to the lower level resources. For example, **virt_iout_sweep_to** and **virt_imag_sweep_to** both will change the power supply current. This is because, as the manual should make clear, in order to change the magnet current the power supply current is changed while the persistent switch heater is on. - -Usage with condition variables -****************************** - -If using the resources **virt_iout_sweep_to** or **virt_imag_sweep_to**, :ref:`condition variables ` can be used to acquire measurements while the power supply is sweeping. To do this, make use of the behaviour that occurs when a condition variable is in an order below all the output variables. - - -.. _device_specific_model4g_energy_saving_modes: - -Energy saving modes -******************* - -The virtual resource **virt_energysave_mode** has 5 modes, which affect the behaviour of **virt_imag**: - -* **0**: Does nothing. This is the default mode. -* **1**: Both switch heaters will be flipped on, the target to sweep to set, then upon reaching the target, both switch heaters will be turned off. -* **2**: Same as **1**, but only affects the switch heater of the channel being acted upon. Note that this will induce eddy currents. -* **3**: Similar to **1**, but after setting the heater switches off, the power supply current is sweeped to zero. Also, before flipping the switch heaters on, the currents are synced. -* **4**: Similar to **3**, but only uses the relevant switch heater (the same as how **2** differs from **1**). - diff --git a/docs/devel/packages/gui.rst b/docs/devel/packages/gui.rst deleted file mode 100644 index 7203d53..0000000 --- a/docs/devel/packages/gui.rst +++ /dev/null @@ -1,117 +0,0 @@ -### -GUI -### - -Tools for providing applications with a graphical user interface. - -Class hierarchy -*************** - -The entire GUI framework is built on wxPython, so all widget classes ultimately inherit from a wxPython class, such as :class:`wx.Frame`, :class:`wx.Panel`, or :class:`wx.Dialog`. - -The global store provides :class:`spacq.gui.global_store.GlobalStore`, a container for the :mod:`spacq`-related global state for an entire application. It includes separate namespaces for devices, resources, and variables, as well as a single slot for a pulse program. - -The GUI toolbox provides several GUI primitives which appear frequently, such as a :class:`spacq.gui.tool.box.MessageDialog` and functions for saving and loading Python-pickled and CSV files with a graphical dialog. - -File structure -************** - -The files are laid out in the same fashion as shown in the :ref:`user_gui` section of the :ref:`users_guide`: they are first sorted based on the fundamental type of widget described in the file. Within that, some widgets may be classified futher to restrict them to their own namespace. For example, the plots are found in ``spacq/gui/display/plot/`` rather than just in ``spacq/gui/display/``. - -An attempt has been made to keep as much logic as possible out of the GUI code. For example, :class:`spacq.gui.action.data_capture.DataCaptureDialog` only handles the progress widget, user interaction, and error messages; however, it inherits from :class:`spacq.iteration.sweep.SweepController`, which handles all the sweep logic. The former is not easily testable, but the latter is tested in :class:`spacq.iteration.tests.test_sweep.SweepControllerTest`. - -Conventions -*********** - -Dialogs -======= - -It is recommended to use :class:`spacq.gui.tool.box.Dialog` rather than :class:`wx.Dialog` directly, in order to simplify disposal. An explanation for the behaviour of :class:`wx.Dialog` exists in the `wxWidgets documentation`_ under "Window deletion overview". - -.. _`wxWidgets documentation`: http://docs.wxwidgets.org/2.8/wx_windowdeletionoverview.html - -All dialogs should be *modeless* in order to allow the user to interact with several parts of an application simultaneously. It is often necessary to leave a dialog open and switch contexts to another part of the program; that is impossible to achieve with modal dialogs. - -Messaging -========= - -Due to GUI programming's asynchronous, event-driven nature, it may be difficult to ensure that different parts of a GUI application can communicate with each other in a timely manner. Two methods are utilized for messaging: callbacks and a pub-sub framework. - -Callbacks ---------- - -For uni-directional, ad-hoc communication, callbacks are a simple and obvious choice. Thus, they are used extensively in the GUI code. - -For example, let us examine the common case that a dialog is launched and an action is required when the user clicks the OK button. The dialog needs to provide the usual setup for the OK button event handling:: - - def __init__(self, parent, ..., *args, **kwargs): - ... - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - ... - -However, the :obj:`OnOk` method cannot contain any logic of its own, since the dialog does not know in which context it was called. All that needs to happen in the event handler is a call to the callback:: - - def OnOk(self, evt=None): - if self.ok_callback(self): - self.Destroy() - -where the :obj:`ok_callback` attribute is either set by :obj:`__init__` or afterwards by the caller. This structure allows the dialog to know whether to exit (depending on the result of the callback), but does not require it to know what happens when the button is pressed. - -The event handler logic comes from whoever creates the dialog. For example:: - - def OnAction(self, evt=None): - var = ... - - def ok_callback(dlg): - try: - values = dlg.GetValue() - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return False - - var.a, var.b = values - - return True - - dlg = SomeDialog(self, ok_callback) - dlg.SetValue(var.a, var.b) - dlg.Show() - -.. tip:: - As in the above example, most callbacks make use of the lexical closures that Python provides for nested functions, reducing the number of arguments that need to be passed between GUI objects. - -Pub-sub -------- - -A publish-subscribe framework is used for events which must be broadcast to multiple listeners. - -For example, the :ref:`data_capture` panel and dialog send out ``data_capture.start``, ``data_capture.data``, and ``data_capture.stop`` messages to the global publisher to indicate to anybody who may be listening (there may be zero or more listeners) that certain resources are being acquired. The :ref:`measurement_config` frames listen to whichever resource they are configured, and act accordingly when messages are received. - -Subscriptions are made with a call to :obj:`subscribe`:: - - pub.subscribe(self.msg_data_capture_start, 'data_capture.start') - -Messages are sent with a call to :obj:`sendMessage`:: - - pub.sendMessage('data_capture.start', name=name) - -.. warning:: - Since the methods associated with the subsciptions for the given topic are run in the same thread as the call to :obj:`sendMessage`, it is necessary to ensure :ref:`thread safety ` when the subscriber may perform GUI actions. - -The handler must therefore have parameters which match the message being sent:: - - def msg_data_capture_start(self, name): - ... - -.. _devel_gui_threads: - -Thread safety -************* - -When performing an action which affects the GUI in another thread, it is crucial to use :obj:`wx.CallAfter`. Peforming the action directly, as in:: - - self.display_label.SetValue('value') - -will cause the GUI event loop to break non-deterministically; depending on the frequency of such calls, the app may freeze or crash within a short time, or may not do so at all. To avoid this, the above example would be rewritten as:: - - wx.CallAfter(self.display_label.SetValue, 'value') diff --git a/docs/devel/packages/index.rst b/docs/devel/packages/index.rst deleted file mode 100644 index 971871e..0000000 --- a/docs/devel/packages/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -######## -Packages -######## - -.. toctree:: - :maxdepth: 2 - - devices/index - gui - interface - iteration diff --git a/docs/devel/packages/interface.rst b/docs/devel/packages/interface.rst deleted file mode 100644 index fe01129..0000000 --- a/docs/devel/packages/interface.rst +++ /dev/null @@ -1,89 +0,0 @@ -######### -Interface -######### - -The interface package contains code for both user-interface and code-interface purposes. - -.. _interface_pulse_programs: - -Pulse programs -************** - -The code for dealing with pulse programs is composed of: - -* a parser (:class:`spacq.interface.pulse.parser.Parser`), -* an AST (:class:`spacq.interface.pulse.tree.ASTNode`), -* an environment (:class:`spacq.interface.pulse.tree.Environment`), and -* a presentation wrapper (:class:`spacq.interface.pulse.program.Program`). - -The details of pulse program handling are hidden behind the :class:`~spacq.interface.pulse.program.Program` interface, which handles every step of a pulse program's life cycle from parsing to execution. - -When presented with the textual representation of a pulse program, the parser does its best to tranform it into an abstract syntax tree; if it cannot do so, it raises a :exc:`spacq.interface.pulse.paser.PulseSyntaxError`. - -Once an AST is obtained, an empty :class:`~spacq.interface.pulse.tree.Environment` is created. The nodes of the AST are then visited with the :class:`~spacq.interface.pulse.tree.Environment` over several stages. These stages are: - -#. **declarations**: - - * Populate the environment with the variable declarations. - * Verify that the declarations make sense. - -#. **values**: - - * Populate the environment with the value assignments. - * Verify that the assigned values match the declared variables. - -#. **commands**: - - * Verify that all the commands make sense given the collected declarations and values. - -#. **waveforms**: - - * Based on all the collected information, generate the output waveforms. - -.. warning:: - The order of the stages must be preserved, since each stage makes the assumption that previous stages have been executed. - -Resources -********* - -Resource -======== - -:class:`spacq.interface.resources.Resource` is a generic resource with a getter and a setter, each of which can perform arbitrary code in order to get or set a value. - -If a resource has units, all operations dealing with the value of the resource should use matching units. In particular, setting the value of a resource with a value whose units do not match those of the resource will raise a :exc:`TypeError`. The display units of a resource, however, have no impact on its values; the display units only affect the value displayed alongside the resource. - -If a converter is supplied for a resource, the converter is used to transform user input (textual strings) into the valid type for the resource. Typical converters include :obj:`float` and :obj:`spacq.devices.tools.str_to_bool`. If a resource has units, the user input is automatically converted into a :class:`spacq.interface.units.Quantity`; supplying a converter overrides this behaviour. - -A resource may be wrapped with arbitrarily many wrappers. Wrapping and unwrapping are both non-destructive: the original resource is always unmodified, and a new :class:`~spacq.interface.resources.Resource` instance is created. For both getting and setting values, the getter and setter filters are applied in the same order they were added, excluding those which have been removed. - -Acquisition Thread -================== - -:class:`spacq.interface.resources.AcquisitionThread` is a threaded wrapper around a resource that allows the value of the resource to be fetched at regular intervals. This is particularly useful for live plots which show historical data. - -In order to pause the acquisition, :attr:`running_lock` should be acquired from another thread (if no running lock is passed to :obj:`__init__`, pausing is disallowed); to resume, :attr:`running_lock` should be released. In order to stop the thread, :attr:`done` should be set to ``True``. - -Units -***** - -SIValues -======== - -:class:`spacq.interface.units.SIValues` is a container for all SI prefixes (from 10\ :sup:`-24` to 10\ :sup:`24`), all SI base units, and a selection of SI derived units. - -Quantity -======== - -:class:`spacq.interface.units.Quantity` is a wrapper around the Python package "quantities". It is used internally (to communicate quantities between objects) and externally (allowing the user to enter arbitrary quantities). - -.. note:: - Rather than exposing the :class:`quantities.Quantity` interface, :class:`spacq.interface.units.Quantity` defines its own interface and uses a subset of the :class:`quantities.Quantity` interface internally. Thus, :class:`spacq.interface.units.Quantity` is *not* a drop-in substitude for :class:`quantities.Quantity`. - -Waveform generation -******************* - -:class:`spacq.interface.waveform.Generator` provides a mechanism for generating :class:`spacq.interface.waveform.Waveform` objects. Each :class:`~spacq.interface.waveform.Generator` will generate a single waveform as its methods are called; after the waveform is complete, it can be obtained via the :attr:`waveform` attribute. - -.. note:: - All values should be normalized to the interval [-1.0, 1.0]. diff --git a/docs/devel/packages/iteration.rst b/docs/devel/packages/iteration.rst deleted file mode 100644 index fbb283a..0000000 --- a/docs/devel/packages/iteration.rst +++ /dev/null @@ -1,32 +0,0 @@ -######### -Iteration -######### - -Tools for iterating; the core of the logic behind sweeping. - -File structure -************** - -The package consists of only two modules: :mod:`spacq.iteration.sweep` which contains :class:`~spacq.iteration.sweep.SweepController`, and :mod:`spacq.iteration.variables` which defines input, condition and output variables. Together, these modules can be used to provide iteration over a set of variables. - -Sweeping -******** - -:class:`spacq.iteration.sweep.SweepController` goes through a process of several stages, as crudely drawn in its docstring:: - - conditional_dwell - v ^ - init -> next -> transition -> write -> dwell -> pulse -> read -> condition -> ramp_down -> end - ^ ^ |_____________^ | | - | |____________________________________________________________| | - |__________________________________________________________________________________| - -The ordering is approximately linear, but with some loops and skips: - -* If no pulse program is defined, the ``pulse`` stage is skipped. -* If at least one of the changed order's condition variables evaluate to false, ``condition`` heads to ``conditional_dwell``. Otherwise, if more items remain to be iterated over, ``condition`` heads to ``next``. -* If the sweep is continuous, ``ramp_down`` restarts it instead of finishing it. - -Those steps which deal with accessing resources (``transition``, ``write``, ``read``, ``ramp_down``) do so in parallel, using as many concurrent :class:`threading.Thread` objects as necessary. - -The sweeping process can be interrupted at any time for many reasons; some of these include: user error, device error, and the user pressing the "Cancel" button. In the case that it is interrupted, the sweep simply proceeds to either the ``ramp_down`` or the ``end`` stage, depending on whether the interruption is fatal. In the case of a fatal interruption, the ``ramp_down`` stage cannot be expected to succeed (for example, if writing to a resource failed), so it is skipped. diff --git a/docs/devel/pulse_program_execution.rst b/docs/devel/pulse_program_execution.rst deleted file mode 100644 index 2bd5d4a..0000000 --- a/docs/devel/pulse_program_execution.rst +++ /dev/null @@ -1,46 +0,0 @@ -####################### -Pulse program execution -####################### - -This section describes the execution of pulse programs on the AWG and oscilloscope. - -.. seealso:: :ref:`interface_pulse_programs` - -Device initialization -********************* - -When a sweep begins, the method :meth:`spacq.iteration.sweep.SweepController.init` is run, which performs several steps, one of which is device initialization. At this point, if a pulse program has been configured, the AWG is disabled and placed into "triggered" mode, and its sampling rate is set to that of the pulse program. - -Device utilization -****************** - -If a pulse program is configured, the method :meth:`spacq.iteration.sweep.SweepController.pulse` is called by :meth:`~spacq.iteration.sweep.SweepController.dwell`. If no output channels have been configured for the AWG (ie. no waveform outputs have been mapped to AWG channels), this stage does nothing. Otherwise, the following sequence of events occurs: - -#. The waveforms are generated using the latest values written to the pulse program resources. -#. The AWG is configured: - - #. It is disabled, and its channels are cleared of waveforms. - #. Each output channel has its waveform loaded. - #. All used channels are enabled. - #. The AWG itself is enabled. - -#. The oscilloscope is configured: - - #. It is disabled. - #. If averaging has been requested, it is placed into :ref:`"FastFrame" mode ` with an average summary frame. Otherwise, "FastFrame" mode is disabled. - #. It is put into single sequence mode. - -#. Execution pauses until the AWG and oscilloscope have both completed carrying out the above steps. -#. The oscilloscope is enabled and a delay of 1 s occurs to allow the oscilloscope trigger to ready. - - .. note:: - The oscilloscope cannot be enabled as part of the above configuration process because the setup procedure of the AWG may trigger the acquisition of an invalid waveform. - -#. The trigger loop is executed the specified number of times ("times to average"): - - #. The AWG is triggered. - #. Execution pauses until the trigger event has executed. - #. The "post-trigger delay" is waited. - -#. Execution pauses until the oscilloscope has finished capturing. This includes performing the averaging if in "FastFrame" mode. -#. The number of acquired frames is verified. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 1a47294..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,10 +0,0 @@ -################### -Spanish Acquisition -################### - -.. toctree:: - :maxdepth: 1 - - installation - user/index - devel/index diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index e41ea03..0000000 --- a/docs/installation.rst +++ /dev/null @@ -1,75 +0,0 @@ -.. _installation: - -############ -Installation -############ - -Installation procedure -********************** - -1. Get the source by running:: - - git clone https://github.com/ghwatson/SpanishAcquisitionIQC.git - -2. From the SpanishAcquisitionIQC directory, run:: - - sudo python setup.py install - -3. Copy the example apps to another directory (eg. ~/spacq_apps) - -4. While in the SpanishAcquisitionIQC directory, build the offline docs with:: - - make -C docs html - -5. Create desktop shortcuts ("launchers"): - - * Application: - - * Acquisition -> ~/spacq-apps/acquisition.py - * Data Explorer -> ~/spacq-apps/data_explorer.py - - * Location: - - * Spanish Acquisition Documentation -> http://ghwatson.github.com/SpanishAcquisitionIQC/docs/ - * Spanish Acquisition Documentation (Offline) -> file://path/to/SpanishAcquisition/docs/_build/html/index.html - -Dependencies -************ - -These are all the dependencies in use by the package. - -If only using a subset of the package, it suffices to use what the subset requires. For example, all the GUI-related dependencies are not necessary if only the device-related code is to be used, and vice versa. However, everything is written in (and so requires) Python 2: - -* `Python `_ ``>=2.6 && <3`` - -Device drivers -============== - -* `NI-VISA `_ for Ethernet (and GPIB on Windows) device support. -* `Linux GPIB `_ (with Python bindings) for GPIB device support. - -Python packages -=============== - -* `Chaco `_ -* `Enable `_ -* `matplotlib `_ -* `numpy `_ -* `ObjectListView `_ -* `pyparsing `_ -* `PyPubSub `_ ``>= 3.1`` -* `PyVISA `_ -* `quantities `_ -* `scipy `_ -* `wxPython `_ - -Documentation -------------- - -* `Sphinx `_ ``>= 1.0`` - -Testing -------- - -* `nose `_ -* `nose-testconfig `_ diff --git a/docs/user/examples/acquisition.rst b/docs/user/examples/acquisition.rst deleted file mode 100644 index ad66579..0000000 --- a/docs/user/examples/acquisition.rst +++ /dev/null @@ -1,22 +0,0 @@ -########### -Acquisition -########### - -The Acquisition application allows the user to: set up multi-dimensional :ref:`sweeps ` of :ref:`resources ` on physical :ref:`devices `, acquire the resulting values, and run :ref:`pulse programs `. - -.. figure:: acquisition_acquiring.* - :alt: Acquisition application during an acquisition. - -The Acquisition application is composed of several interacting components: - -* In the main window: - - * Top: :ref:`variable_config` - * Bottom-left: :ref:`data_capture` - * Bottom-right: :ref:`smooth_reset` - -* Accessible through the menus: - - * ``Configuration -> Devices...``: :ref:`device_config` - * ``Configuration -> Measurements``: :ref:`measurement_config` - * ``Configuration -> Pulse program...``: :ref:`pulse_program_configuration` diff --git a/docs/user/examples/acquisition_acquiring.png b/docs/user/examples/acquisition_acquiring.png deleted file mode 100644 index f4d2578b4061230b9a933d4e67502fe63b0ccd71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150697 zcmcG$bzGHgw>7$uZloJg6r{ULQd&YlIwT~eyQEt{q+3b^q`SMjyE~;}!MS*z=iPh1 z``i0`|D5yFrLb7*UiTGq&N0Ur7s2o3CDBocQ6LZqy3|`SB?ttL83KV-L_z@1;3dhL zfd63Zlq5wUMT4X};2Q)38A&n7!{eXyhTItN4D#o-K^k>s+8K{ZD6sZJSY%0FA;fzDU))7mM-@;`grV8|0Hj@Z;{6T*>FkC zdKbcxks;xSC4xjL7S3j{{#HEmnIG1wzRD`W^gz3~urNh%*k3h;Uw|1ltH36r1Q!S*rV$dN@NRIsl)@H8qHGrx_YsOAh^~Hox1Tfn`qq}r zKhM9U{Oh^DF6)r{3mKiGH<{q^RalWZ2Xjtunb5$OnD9G3kC%OXS|rF!L3y$l!>}<= zDZ8uD<;`=tc2SF-(QI&099&$#xZEvRbIG$EQ_riFCj0&As8j*h@hj&-?W&0CYVJwTbz7+CBdgOZ2;zV&$# zh5s%seg5iIKzX_S{=vceNBqoy55JdvS*M2LSsJ9z7aA8*79VGo+}P+9v(71#LFaU3 zvS&XU{5xFutP%A)H7)TsZ{9fcnPXOUa#LHz=AoQ;ySTWF6qqnUR101y^M*`A`dt6z1fQKN|kbV4Krf%$)XpO zl;nJTd^eYSlF*xT7{dFrF?Ne#2+g7P>4utaRCIJ}woIHXGFq&(zxfNbOa)0C5)#qd z3oS88L+u-pZppX)G(l8OdESGssMC78!r%w2P-0+ujyNG$uE<(AaR+2;%9)^cAy8 zhXB1WOxS$4v3I6O_I&-*=v-@LMQu_6*RU+f$OT*Ug5#5&&zeuN3>@&bQA51cP1&bs zW-`Ziq|Y3=z3%y6zwU`oNO(#}7$jjBknR7n&hcoqd!-G8LghTKbWBnlqPM^QrCev=pai=Uiqt@i}e z`{mhVi$?#o;jiHWYz>3no?u0j^l`ar;WmAT3&5J_8Q@`7F~hsKxG1+6!8X|%O*7MH z$N&BzuAqQ!adCj#Zj)ZK(u(6HqU)7YF35EsKYqlg=0!oda=$*sp3BL})@LW~E6ifo zu(VkfDV|DNtgB(7qoeyp&j6FBJ-5?oL)g>XOY5Y&vqJ=#o}P}6e##ZYpPeszjldNU zs;;4tuvo|869vK|XCNskG_-4BA*IC^0h(>nKd{!|a`#W7lYYsp;`NHws^7=PT&az{ zUJOZ*VV)PU^I+VJ!{6UOhh)QiD!TWVOtun>>GCs_o81~ufrRO4b&P;Ooclhea_c1+ zO-+(9^vmTpzA|U~H1#7g8TGu<%5y!zAc-g`Mcdff!7?AvQBw!b&uh^$FvuxrE8@bN zoA04iaj~(*oSoV4?}t$L($mt?a=cbdIHgsN#9q_gm8y3PKWe;<2n$1-t+G}qP8E<@ z(6zO-Jvrg)GgR@%!c(3dj?vs|e@xiq`EUW!*|kx>yhXAUGhelf{*q7OrJ|zxZT;ZM zs9V#2vi3k%=I-wPMCb^b5P(jsr~gT6HQX=IU)%;0NkKt@l$2CTPLA?9E4CAt8VVXh zkq#8LP&G6+5Yfiw#As5H7*hdLUR=`dx))D5I?GuX1MIO!5xeC`V#o`m-WYGc!VpX>~Dc*-Np_phamsDahG}PXnOby-z z2DDnkl}g6vjr>roS!9=4ZpZM|i{d{5`jivl! z?ah4kZqhG+81~7{w_4Wqg4q6e&NADkZv>59)mysXx=|<(XHV9;W@^j{B84spwXLXV z%u0_qEm!Aj>~bXMQ7GG?zd-;=gpt1X3dI&dQap&AgEeevy`+*B2|~=&u5)yDb-vKi3BP{{| z%0JPvE00b1_m_`Z{DSJ=5X1lHKYBrlwYriyG$g6}&buVl3>+ZuNht02$Y$mg~1(v>y@<(sPiR(CB8HfE)Yfwc!C@PkWFAJ z6|tF&4A%#4jQV4`7zX z2A(;I{I}VzHt%z#Uzkt*lNzhphdGB!3wLql6{xLoWQ9mV$UluImil%o+86oiOLkQ-QhX3ou0 zfEk{dilj~>mAz{D99^VqGLxIOyt2}=&$VQ*HJAc$?b^!W;LrCr)y_Sr0YiDP`a*1IAr=?l&L01^jz~;ab z4j!I(;Kjl9ZUNKvkXPfY^-wId0FnFaPLeQ^%O}D$2Xi2Zw+2%UTm9nbphP)cxn&NM zs*DshVbWQxb!l8Mc2Ahg*kSb^x^8)DcWK~j;Nw3euH1fM((ZJEdB(*Nckrjm&T5DU z<=mgUmiN@Ydu*EH_idkNFI|%DYySr{DCS91N0!|b%X$nYHYJ{0KqX*mw_QJ`Ox>R7Q<>_H%F5HMVl){_e#OfA zWqT|)fyYiYd$!zs%4)JmyDg&|3Dh4#0b{h98XnS+u;tV2Q1|<56LpObv2&ArGXQ9r zi>TQhH+u%~pW}|@DsUD4IX(N>_no4~_8Oan^Ve1=Q`34N#l5D%E&3i87ItZLnTdQ> z#qV_82N+X^@?>tCFJEKn5}=z&S`DrhA_si#SIi_$&`RmFx!MQKKshp!9MZH_KLMO- z$4%n7rA3V931zilb)q-NRW1G8Jifs~3hjMMedW*_o4wcF40@wOG0tn_B>Xk&QtaQe zw>bLfZkrq)43`mM6Ko6+&}X^y_Vx>%EG{?N;_|0zQ?;B$#(%H-H+ebTLF;6gL1YFI zZ*Lx5BhHwE2#>5*5Ym=lJKAKoSnusha~GKoK%btSwcYF|dj9&wIe-Nks;Y!&2$}M!RD66S0R7fCgt{?_rSOWM?Tq!Oabbe;93$T)v=rYG@S7P8 zLDYZl!u>NdCufJ(Kxjn7=JCo4`uk&1QnFAt24^?7wI5@T(~OLP(YY(}8cZqrmfWVru#5H6 zs2Dajc08*CvA({({o#BtsJf8a+e;e7MG&du)WQ=8#^_rIj6Xa zT2)xMQ&Cex?Lsi2Wnd8_|HTadBN;6kZ~qkN+e` z2>RQRnT(E(_Vo9&FC^>M*utZtqAHI*7L+fVu7)2!8h@J^taZ?SDfbqOpDA~-i(S)*x;R*D2*jgPRjobbU3B<-YHKj%R>!lubG1ZS>UUcHAc{P%B<78^{5YJ(UFG$9x`0kx|ZZ7qb zJCtI0r`N*Vh{Jp`47})A>}YYkSd9WvQnJ>;ja1Ypqt}L^*dr?N`AmZw=2};idcG^3 z_)0=?s{Y=uLVB|*q>6+@W0(P?culQhLCK+us;U@U7WlCAKf_PYq`rQ-U7>87VX;O` zv%mEN8HU%1tW6D#$w4>vwOc`b7=MJj9h`Tz_V@2^8^e}3br^Xm&3-?!Sfh@QEY=rl zaxJ`l2e$Ix8|88=EkX zLmsoKA_nT;SQGK3kJv_A$UpC%m5r6K@`Nar_yjk?f%N4wK%Qm%qNF10bZ_~p??Lx| z$w_|>V8Wk;4trB}JEJda9S?d9wd=ksTEW7@ySiRoroZvMc|WVBsQ47@F%V~`r!q|X zmX^@B9ku0foee?epBS~Oy90isJG;0%ePp_}MO20X*Kv6J;yv9stY})Af~TjaKizv= zcuhXOkdTmdWq>3p$!-2(!D_6uHQxRftN6%HOn2UJk&qj>bS`LUI27|eH6k$3twL63 zKCEYevi-8{@sqT)N%W13#ARjCK7HB&^iq!$)6W!mR6!a$C?GXJR3FSO>_B%j4Uo|q zQk8S%OTCsBZTo&snAHj{hA(>DL`S7Enk0gBbQ0v+(-cHxIA?pq!iF32&AJSH=s}B1f_$7D~vt0(s}P4reP+2^h7gSXiF^2V<3eNySZoW4E?B zTR2_|g+h5eZrB0uQ(adxR%H!A#2{T)IeXndP;s`uqhNQm`F?RzzRTWRHq~SE11TBV zG{7iZLtW8nX_1A6WGTGY!00h}3Rd3o78QUMVp4MSQxhS$Mqp0g54 zqa4Zh^5vFOo2l&_uXlKbEN_F&r|y3_0a~m%O@L2@7OyB=D_D?B&cXr>aIs%QrTCSC zqmp_;PNS(X<({;evSWK5L+myiLjA>eA{G0)yY&F~28W2qb~)fRTBmTWErChK+aDMz zw7xxJ2ySC}cei&q{n`CVWY6U~&*?(lEt^pITJkiRt32SIo*T=HqC@cY`TaZmqmj|-@^X-}CH2MOg42`@13f*J zg&|==Lc+E^Z}Q%FqhZ6~puAXGK*4+W-DIrGwiH)!m6s>XhMo3fj#bvW+HShSFH=R7 z)YYB4);l)(;*inMJ|523kn-9GwYHw7G5)rk<4h%H^<&ho#R7{*U@(LO0BdL*V*=|S zX5xrxyB>3Q?DL;p9Y0l94wofVZF4{~c=8!~r}R1x506&d=%%TfAOIycGhKB-9euv5CEo53@~V`GbTu+%-&WDwF?ef zOd#ZqHwRqPvb~z_?T;24#(B%R$Q{y>VyFqeI{(0VKEdMxbGiJQ3T&n@VvdkMTSIJ4 zhgy&6GjhpGbCXkCTwFHo1fjU`6a_p&cA%4m8YTN<3e3de!Zutb@0Wh9YA9g?rUNdL ztgrl&vfKvW?G4Lky=PsK50U--vOrFdiKEXD58VWPXK2~hAj?`fS3H+B`ThMpy+--h zzx&9%9Rm4}>y(`ch<3a;-|`3lTMxOj)6@Tn^g}SnyO}M&PQbl)2l%bUW zyQGAYk1y%hFIg1y4zG`8rKPRCrB|4C73Kt&pTW?;?$x(hlfX|`Y?HVw*B%_~P^f0+ z;$mwsVJ4J~OklDR^~fUd(OtuJ>}TAS8Z&x^lJ*6 zj;>&JXfFROH*DUbuVEXW(JxN|IqdH44x8YD@2K8TS|^Nzt4PE_Ofv12pkS(gTj0V; zD28`jF12Rm9iU-Rsk1VXP>7|+a^z&tkRL&SX4PF#cJ@ns5-wKTaT>6Z+TD&CWv*I| zY0)WXD$Kc(xI6*@@doil5R%d+Ac&yg3sEiDNP9@``5_gfUgsE|{ARV0Wh!xPZzs?o z-vC;IzJK)z5#Apj%ym2Lxdv2??rZZqXhL2!3u0%X6#CdPHsxWIxzrTU0+7L?m6x+a zeiatx8@)$;#6_UvDFIKNbHBsgV2UyNX_b_Iy6H5YW-SJ%`) zji3jX$_0JJb+mKe-Uk*IhVSHf9jCX<&qn<`?o6i49ZKGuE?HAoS7&w}e)g--a=Pxw zWOWUvnn)sv#Ac|kPrP`_(8@|)!uUqJ>wI%7At^~lULHeLRaHx-R@H0z<)&d1a?1f7=wW)=HMtv1eyz?JX@)K)QMU{DqB^U3RnH^6rEXkU*VX zo%QH%{v|KPnEYsBastI%THD+m95IwUs`WUuIs`+Cj*4pe zIZ=?WTP(=3B}4Jk^ZImSwYsjZ51#72a=p2b+&+4F@?t?S*u+8JK!w&PDk|y?pqSbM z&|lEghgB`QqO!5E3EpgHNgs?W=E8=UkT}{oG#J(Z9@gOc6c5nt=_96Z6%=xqiI7P>t*m%(Zc(8$z~5cACv$G4_Sb^eWNinG!!*61C(OBhY>q*PjA0)nv%C7 zxiY<`fC9%lyKHJc5>m@;u zVViqms7p#pG62tOe=wRo@5<-2+g@QFtksm-Ut-wxnjIHvi5&KC>~`_|dao~#iHLYs zi7pP76fE}PSJ&1+8p*AU!C}xShie9n4xNpSn51MGC}xT5R$)m6bX$go5A~EIri7gI zL6&os0L&Uavf5*{9Ecd?^?MgU5HNE^=>fv-&c)T*{(MD&Sj~T9zE6%{iT_-k6zeF& zaZ^=`mDZczH)46r>JEDnZ{ENFF)T+ht2Z3e;_yIjq1NHraji-=;nX#s>k|OrvdP@R zM_kr9hLRMx;`)QhHB^h7Y{x)zlq*I@fJH_|&PgT9mP0ZI5!J#F^U~1Htsyz*;sUUIvDj&sdKcsyRxaB zfBSo0CoZj+$+NNXHxKnP`#T*+}uhkDZ zb}=7Fc`dgO2J@k7CBMs;cUIa;%F5Q}YY0_;X|+__tPST)(1d)6i=z(7{2CwM4YaVG z)h)HmBBsW>Xiwi*OEQfnr< zjWufX<;bNC)>kccf}jR%H3kLGY24y7k-L<$Ygc(H@ssnT0@H~ayo25Hm~_yB)Z3=o zSL>~~VZWgKnfH$l5hYbuu;1JQ`y)47xmd@e)UNdEplS(bd;DYwZVIQtb>udf`iDsc z7N!rafd9HNw&7f*W$dAc&6qa~>~u9tSft=h5TJyqxVQ+oxVQok;76wW05814e-RS@Z$^N_Jo5_+$mrUZiE=KdKZ5HoZg9WTe~=bV z6a1xKl6?JVi{-H6WPiE_7SRpy4s-`Ls~;0AwHsXVtOpW&i;F30TwJ($b_fZ%-+^*{ z|B4yqzX<^!402bfF>@#B?hQr(}|z4`TYYf zhrn{tGSmUwAf4Xcf=2kg{oT(#Zfx~mZ6!_5+xk$ydZo10xfXB5N{Cj^W5@evNmE$D z^=4`I3NX|CNtc{39yi_8ug&WF!_Xuwh~*wKpf*3ZsK9Z9KAs8M9jMmKIxw1XyhglEFI?>ocG#ijadH+%UC7qhot;qt%KI$dr7X7{$6^oUEr ztBrrkrQa^4t1f(LBh*J1%Z*u89_}BZBK$dWnAs1TwNAf)qSq&jyTk7i**_^U%$c(j zvk;=4w!DX3xE~3h*)sWavoe)Gl|xR_Y59yvG{6S=?+ch=Na?A(@I^phA6{vrdiClF z76Cz*brV!VZ`m7rLrGcL#idoOo|vm;V8M5*6Cox!*+8S|k(`&C(Qb!s^u63A&x@x`5&((Bgr`!Bqhi*6P)bEY)y29?$7UU;Sey| zr;1UV?r+R%4tDnUx=M!xvz*c(yA#&U%|5@A$;w;(W~) z6aREwS=4#|#L9keF-18y6DFR=9N%Op#jumadUsq51pA^4TrZ|@#f<&2mpYWfY*I-Gu)1BKzkybTMg z2BV%$SI1rHD#2Loe7P29LuIhPC4Oo8#*QRDrPezpui(w61UGf z>_L#U%K|7x1N1T~XcP}?N?LI7@NMs=6$g_eB(}D@0XUQ0Yv31{Dcz7P(rRoEK;OGw z?PHP?rIk*$o*A~>5e@a> z;s;1MzJ`1<_nYqC*nZDHaz#1~8X0S_kDd9ys|Se74NgnhQfr;`&JP|DIv`3y6mrx^*KFn546)nC9U7{Z`gt_N;rf2oPW`~Rf; z8|WrF0XPG-J^!;Cq%)Et5)|Rd$w?qgiC9@NKE(4A71r=HxSsz29aS+ zc64?jXyR0<(qd_alIDdSHTi51sjcX0_bXQD=4D&!0M(tLUG_S#{IGr51q{;qN0TLnIC6ua>{7U8QS=e;D z(S*rQ4CUNA_Ah(QD>Tk-?og&o%*}7ND?3AJDF$pc#v_LQ{N~k2{{Y8^`z+PJ)BoZ2hfq z(>0J#J&)rY7#SHKT{}c4sQCC^4T=r68z?x`@~gL?22YP5iYvymnG}7}LHACl6h#7b zL47^H;QbZO7vHok`?Gu!uG3B2c~%w^31f)_OF)^$Ixy|hHE#bbdd^b6)TKiHM`Kf+ zPcyDo-m)m?clvO7a$16}u{Hx@)sKzk?}VpleoUAqYkuMNo@WjgS{Cn;@)N4vo)e?W zWmi6~Tf1*uQp$i2-HJOwLqcdi!OLfvoh-^pfRE_(?t~Hz(_;B%q$|ylprBN{@31N= zQUhq~MP--=8q~6iwgMYNeiFBgk$9;mqTgbg2|2K>kBk9vu*0;M$aR_HQRgU|B$(o2 z-4qUphu5!PlM34UHNwCEXr4ubqN9^a6Cz?@@H~mrIwv%Z0>Z+=0Bi^*B$>k2Tc+VHnx(nW}mr&(L(4AsROS8lM0k2bED(%`5$Gi8#)R3nat6oEl_Lwwsz%)$ zt?3UKdgKN+dQ0foT{5K-{z`bOEu&j|JW;qEQ%%L0@3&OT3&!NXJU2`J9}EI_C{~(p z4OZWKLeIGcTm^grx3YNRl3Lz1U&q_sVUtI6%KFv?BGSfc0N^kPTFsrshFh|!Vx5MN zs?GkEsj0~Mdj8~Odo(oCw@coTL=N-L{|U=ftBHf&kj-S^IVUH6)<{rX904UIot(qa z|4hzXe>lhwsVlVpKcG&%(k&BHfL9lbIC;p&%R>P#nI~C8Ngh{Lr%uTW9{6;Kbo}I( z&rVc!8l=v6Tq~OzQoe=0)`Lq4j7V4bBDM8L?2d0A5eB_rdTO$9+ zk9kY~N8%TPLkRE6nLPVebE&er!ZyY`h`2956~|$h=g&rJ@iy;i2?{kc2p?{jQ@DlN zOXrU<1Q6nIBDo}vE)nw{AE;ZtTCw1Fu1)k`7~lRuN47briUUT*ADJ!I*!VeCUz6@} zKUzE{z5qdJ9RMB7uFIgONfKjTre4XrJW|49;i{%IRA*)HE}(`?M0lhqz(hh#Zvo4s z5Fb>Vh@Rw(g$D1c>UmED3ySk((OE8u)l#D`@F>t;mT8Z+X#SnnB!%DT!_Zc^LkT$2 zG_rEif2WVm5ru|Ox!Cx2_9wSy?s6sM-@*MOy#Ot}f*Kh(d!8Gcu>qe_iOFBd(FyyZ z$r)5%QwTY(0uaZXG#&1L$=jkEjE&(zC@Pd{rj?v7tzgFtCWU0EGG7Qig) z9z854!g49R{_O=`pJ{igJhktft@hP{uJ>~xJg7zQD`r9BA{9Q-{e5co0Bq9Q_A8up zR7IR_U@3Ec8*e^va(*USqEd1{jo^4!o+TZFgG|}pa0WP(mq?M!0K-aTwE*M~AXte( zhcu&3{aXOe0S#%i`Dz6Qs)Mfmb?;ltfZ;I@4SS9B=8N4MI;Jr`)2fFX}nzqL;` zl^xj|c!YCgQ=|c_20AlBLc(shqlQpG0YSo(*o_Vn-vO%tBq)r(eR^3>UNY@TdRZ}r zf*?=)vF{3%cGF;~f>29ao9~QuW5-|$AJFW&+7?pY7c^$*w*_oeo;#*^Kp(TI?(^r> zo={%e*w)jbxUu>gE(nl*j3&4Ws=t4GY4VnHBkZn(0$;P@t_XFc$s6*$&v*ZHYyNclOK229Ph;vpOlVsB$U>B*we<^Q z1vGJgacBO`7Vk?0y$$ud?M~k`vh}OPwRNg3JuAzIAsUL>hS32Plim*8#Ha+zU(%>a;b! zvGKT#J2aUs9$U{O5CI51Ct6Yx7vDQ zC^=$QarY;UErIq5(;Ekz`|J5C0?ylCnV2=Mbf`I;i<)aGjn>V7LX3Jj#5YKMeqz^@Cyx%lWw&(3c5K?Hm zhb3e)W~Y`xoAb_Xq8_qzj!6DSIw{zs%4kao zghRi)UIMWOL5c=J-A4!9s?Y7&n9<$s(gP(O-6JQ}%4yL&Ir;s6<1@7O;Dbk|4KfuAeyq{-QL`|fBya2|HQIQVowb3u1>0J zYM%gSKGISB?O#~cTc^QQsme-QJ~iyQfI!3i#XurY%m+3T)oZ)`JFgTQQx(!UU<@d| z^*D zK3gv$K)sN}GqYA71q=x8loLw=K~AGLFk{Wm5I5O)-mz~{*DU*g1%~FweD16c5440I zQaDm?-|F>_fAaJ99|6=qpmhHNxneG0dn+lA9UmX-0j1ad;(9C0YsYA|{L=vN$)5Z< ze|ci|{Nlp#B{m}Pl|N@OsUEzr7Wx*dal5PU%i>x~Ch z1q7s(rM#6ZqXr3sqLA3rYa=QmH8#*Tu4iwJlshRyN{fv2wdaW0WZ1?kFT(fx1U8d; zupzMtz2l3&%s&8|k4P5u4`)whYN6J?$lE8t%Lp(tfB*m0e4_rX)^hkOXxtV^KR>^3Go04e z*1YznL*t*uWDh`!+;p|It+6cpwZCtHE8f!Aha;}|R#x`o*yh0d2y^k-Dz{5=V5Pi% zm&!FLm-0w`Xqs5RhHRviX&BO1i+Ka+q>P1n3qZ)X`a))4h{N>RMY$)5s)X9Bkz&|4jtL-PqP3& zw|^$h)y>UF_e3}#reCebt&+6-Z7%UwTYFVqTT^Cy!24g^B{nBE>{g@S1{)jMt>)vs z8ZJ{m2?1emZL7h}rCyUO0D~+btys6lQnSJgIJk+^B2vKNj!wVenC^id7@D%e*oj{s z*nr`v8K=RG?ep?8Ku5l*vWi@KuhnO+yPolO(t7%MIF)P2{REBX_`}eUbJO*$JIgUD zBU!CV^0Mh_*b%pGBjFXiF6KjG+TsoJCF5a^C7!tcRqsmjynFQ5W-Qf6jqrjr^Vm7t zvc^71#*yGricVVEP{8?K{8_I# ztKWKn5e1X}a{8O;8NP3|o4@6_ciWl1u$M zhyD(rY0P7%)xa%&vvpQZmxuSV39SAA_4WqRn8C4C5Q+Pb zmgVy=LhDZsw-|h|UNu24NPrh~YdAx6``lGaTN@b#Wi`cVv1aQwk=KD1Sn*@hmwaSb zUqANiP1^eUD`YVQaZI|en3=xL)958af&9U!0) z3Wwq|fRii09rMXz`nOMKILQNpB?YGip^lEuDdpmhNrIp7;{;(!OGqGqQwOVe*MIs` z_~MFy;ipaxSbWAz>RSqm>s!G?1PB~JYW(3)ph|yxhNHo5jnN#PEweM+5*QG!^=UEl zC9ts2Dk=aXana>cI{*$n8}Npy+Y5ntdCHLgqFIBpL*3)~V-VASILL{oL@q(bivu#_ z*Tt7h)Xbls0Xx{$`IIg|>Khx#I45TFaDcl^B_WZuwGDBa6hbZw=*8cyhH)H*8db46yeExu`-jw$zft-;H zeK*_EvZLC;0CbYo&CO0fWE|(6>^M^}PIMAZdc{{XG)O?4%FN6h-mw=G7YDv8c;U6q z@R>R%rt{s2waX>1TwuBY_FZt~QYCnat`4}a=_<@73|hXx%ck-tqOCn<2_Wc=aY#YW1-#zH{s+Kd zhXGTCG7sfpU}mOBE&KkLrZ^$mSo?N-j&pt{k+k9_>K;@`xJ27xgWL6XR%AS%6C=3H zW7`J&g=s5ofsf{Sz?gd7oghX;bVSlvjb!?Ny3I{>?L7nf;A9vf%NGH+3!u%i0FScS z#7}tPhkK~YQNtzV3VON31J20-GM|$;905$Lj@to;j>q}?zw?#Id^kW_tE;PoAXI@n zxc+=n6Hs#}Ba#&Ff09e4V0Z&V5x}d9CG-CSIhig-v^85HSBxj#($f>%Za#Im%bjAz zm!Q+tMGYBVXp#(O{hQnbY}mXSpp<|0qn*8mC){A^$V5IVR)y~+;hM-JZ43d z+L=BB;7zjMn+ykT4~a-}IgpiT-z(TzTklR5_EnKI8TEdfl4+0%4GlG1ZI^Oya$zIm zyGf@(d4wq}EG1$yGI5Mcr`Z$2A|hY%O}Yj$XsTF&P`vz|sgfvx$f$CnqXQnaAov%!z&@aFZLOQR4_s9)CmjUWJGp6~0^tB(G3X=#-|wn0s{|5Hk#3Vb zpb>YRWVN)k!tcXg{&f&32r;@H63?tJlD+pXUj=agx?yfM{P^GT`32m$9UUE$`3TxV zV(AFEtVoF~i2vQN%cgyICp~`N5vMy^tSdy!o;TDg9tylTfSx0uIox^bMp-F#%~_~f zX1>J;9OEPZ;IzsAv>KZ%&RMT5FAGacqXb5hUdvcxQQphnxC>B6XI|Z0zAxXM4{>x@ zX9>^T1jj0#^9UwzP*1L`s>DU0^QyW&SI8>ie(I?VSW#dCMI&Mb`aelbOpNH;ZUS69 zypc*v5};m!9JfO8;OzfWMz9soFdzl5fCF^Eg-073(it9_MtchE+{Zvo9RAW6R;P4{P=Nv(nS$RLx7L}D7wFp2+JET1)S7ldX<(ku$ZeX z9Bp8-B4*ZukZ=V&21yA}yw*22AJZ7{781L;WPsBO+%``X&JN|EuIDzN?SE)ib+O=n z+sEfO?l;vTuTk?z z@@=RL1@oRKU)v6;*^^zXM~@;oiB}C zg)X?%d`z->@ES;br{lU~M0_XYwnerjRs($)l65Ve(Vor@@g9vbLlh4|nuag=a)0(* zufemy@u_%2NKkA+<`YN!^~^jq+;<^gkZ4BXao_#4=j_Rm%elLcPwA0$bu6osx~SJ> z(y@q6px$!XOz24-O5RGwr(XM}EZ-8&wHTzP<01}q2l*cf?o0Jb0Ev=o$^o+xn~Ah9 zBBn@Nv{K>$+#4*oH$OUlm9nCd^V6(wl2?(8EhdBnQjlSq7(JH}_WU^k+Si3j{vpFV zL(G+gtye7vwi9^=Tgh{%0OR2TaZd~}y4#K4ipwh535Su$6InWG%tI(@38~&Y!FsU$ z@w1zC1(v^2`i3K>xE<@nSst2@%qlub(x2Tf7^g2*c@5#n7>^MfA{B zJp8)7&?H1-``{!QDSI429g7?tl`tJyrP`mX!enZ?V=ZdD*-hRR<^AgYIAkoba3yt> zFv>Ws0Py&JD%c|IjSvAoD-V|HLr3@1i~Skjz2YWvPcI0tytf7FhCkF@rtwS!+CPKw zeY~Y0VsTu5VqFX+Jk{cxU<`~m<4?yJlL8hEUDwL#jy17-Kxu8b zH>HVVWw&^+mGPmGs80=~=NubY8BYicDITfFNg=Z*JP9DOYwNqCgEeK zoATx!ItX+{w#I7H&tKU^l2NcYF`k0!YNJfyhtoJZwdIVx{SIB9~CUFXE z+^2xI*B`fj)Z(xw96YjvVcec0Xo9|yMCIqSBK3S&!{oo~e!=~xzwEdf7PFIdPt5WM zYd`{MFqS+5xt5YOvR(d2JaGNCKUH$)Up16%GT=4VVu1~;B2yJ|I&jjTWGG|aDVK_$ z_h4;wU8nQAqg=S})6odHEOw!=#&E_t(uR^p9CC+Pr`{nv?EInV_pMDD}NfKD~AK7=|)RBX=2Xy1*fG#&&5hPlK!*@yoPw@qJorT5`!AD^%3js?q&hx@%5A;9(d zE0)1Nrhg$B(cGV4garp2Pszmv{BsmM?ElhTIN)IxIT<2{44qj0b+Z{Q3TEt`0y({i-<_ z2!S>`(jJ$^Ten|06;Y;W=9vm>xP z+Dxzc2mxzb+uf@?LL*~R5t#=7$SU&I=6Nt=QSoJ zHy-8r=9;OL0}`20eju%)G?ABXw-^P8(r*Y{9F2*l&|eClA! zp4Vnoj3-&1S}=(V)4JiD6N8i+k~M-9*XK{^iwFym7dTg`znCp>w%b(+Yk*<^<28Du z&^@w)FuWeIv@TI^wrTk`M@LVuBE*&?x6I9WP$EAr zk;$UcZJ+j2^vx%q8?H9y&+PW;tDoWhzCz5S-c!r7bJxIJK_b(sku+hG=q+NoSy{ab z{Z~vWC)-7q;dt|dYloRmE5Aqz3O_;kJAae2*tR3_3wrXuQt+hQEd$%Q^&_kPgaVy@ zg+`X(66lH!Yxk_&Pp-GP_kOXpFr%fq+kQz@gM}24bE2tXk^~4p^M0*-@2hDIMXa=h z7LG=7z4#&3zjINIGn?3-YTqN~hSsLi7ogJlyYlv17>=7lo>yo+($o?oE<2-lp$|HY z8sv?_J3N-&%h)zkj#pvTd5479Vkt6&*iwWuC=CBv1!@?>btHG*%cko4+PzG{JpfPT zk7jT%1<$bLlfR}+u60EkwutaZgYEW3pRn9=4gsxg8BiGUm)Bdn_u#kLG=pPPEr0lb zsnSVH6SwLy=cPFvO`fA4)`+~Kge_uO;#UTdy7=UUrk z*z>u-&n}nwU}F{*ktv2IKaL+R6niN9To+SpJTF3tCRxn3;S&|9{>3hfKu{T+?a$$H zrZ8=60!|hKKd7T&^Fnw)t(5I3JN#(8KLY|G=5_AYaajoU#nTCR3g+PZvNG3aMmLrZ zZ+022{m8Ozx-gr(!A=8Fk7&%omwEkd!qHiw8U|mLt3Kx>Eb^KFF&U*tlQL zd*Us6KR&A#SXYT4c6Xm$%CLIcNyTdAoiq*`Dh>DSHhj-A7SHLZT{0pBlkR+XO|{Yd z`yc=|+&4{iIkgAkxwnSAxoPtqdxWQeEg7z9Y>dz6ijD#!^nn|Iq^VymBqAS)!h+rr6dksW zKB6Fh@<5-}nT~=B{mEd`{OTFQ;Vp(uGDGfugFfg{`4Z_p&k5)2lL3eRcBYqf<%FT^ zt)iX(=M2!*0RJWmU$NNo2krBJEBh}bgT%zN4A8)92@5}gm47c$)VCP;|NTi3jrD~r zI;`U5qK?dAw(rXjX^o`7^3G>L93%Q4=qUC7_am*mCUCgxJCql6x0te~P1kkcELQ=Kpr>+ek}x<(z@!8%i8=ARUU4ik$lgiZzz4Zbya z!#}jtIWOKg5r(1^b1pBFK7V<1)+G05g`a_C`6_))qsA5I!EQ*f9oC=j&{7{|Yqo{Z z>&6Zib#Ljp6z1+azI2>Vmjo)v9ltX@n@}q>erXz$ih&D_U52IxZ-3<>aKwk^-|-ZE z{5vuqjPSum7}3W7A1#V&RQuV^CPi1@icvt5_j!1$W_(DJ3pUqV>~$npT5Mrj%B3T4IZ4I_oGGdX{>GXWj567OpU;OE*|eFL zeJ!Kq)`As`u!dOIJi}tPLd;CZ!cI}|#H-E6QJ);qq=-T#`HJ|AU7cpHx=iYf)2yE! z3Wn1t8VvN&wTCSikwpM(7pvkM-Yf;MuY5M$R3{VykgTiW2C6Ko|E&Zke@dz4dNM9% zNq-^q<5U-aD9)xdIC7bnPY@KMK*0* zqu@YQBNeUmH#h{s|5l9 z;Khy-RKo;50Tu8Hk(6vF3;7gAt_|;(DK0hf%?!QRQ*1&ZDU%gBXBP7dV}dBk-~R}} z^U@P4Jx9*1f*(FD(m-cf6>hrAp$?(3fP~Ol&>9AfnyFB5&?3C}Z%I#}{e5fU55}>g zonkuTJZAAeKdSW5VPO1)7L;)4_oe?^$Jb~G_%MoS3UT)Ixe5-W9rD#MV(({F<0xx( zq&`%`g%bSt1!J3@F2F6Kwn~x@BeRiKMqtNJ<;23QE_bZ#|NmbkOZv$tohBT5M&%RK zDKyR1OACZL|F;1@;DLuFN)lVxPu?$}B_XILEKk*8QX4O4t_~@R5z5v18x+Xk$=#o- zffFA``fzjuB^|%cQ$DZ_XRPw5X#e{(MTw^i;*L=I(zSUi6i4}a&_wG&;hkY*2Kk4O z?2xx)nGE|yI)2&kgpSDnZZ|yn>E9m-tF40XwX5#V3R3JwwEiGdLPZ$RD|OGQPc{qWL%jf+Db;eP?9Pzc1ASPIG_MkT_&*G5#wCDzUd9~9I~{Kz0IE!gcL z7@XKRQdd`NZ+{czi>fLVR5SedqbkGz*^&?RsTjw4EO#U1op@)8A?4pZEV#(jZ<3%A z^L#KB^|xG?@AZiAT0y?%DB8I*DfOlEbNT8M|`3)N}%vEmz-yYbQ9*kyM6xeD!?{M-*n<; z#^f$S?s|*e(@c{5BeJWg`*>~CsN)p5ta;4W4;ldn2l;o&akf$KJYl?w4Cu%i``-m+ zx4xC)Lm&@Vz;v`HO0IHMRF`wIuC${FueBzIbS|}ddwaa6r~fm2b5?2e3*S=rSGY!> zFiwXs-Tuz$_42XnvVFs{bJNP{H99f?V-c7^|6N`KDpAG>-9PfpOe#tk#bEH*;>y@l%uQcUa6;kvFw5tkR-Bujq&~31KtQ)8 z%4Zh(hepXl8iStR7B|!bQFks;tm+FKN~f`+`s4G|#nb4%m zG~?PlhNGKdzuzvk5t#}fS;<`e-*O?hd|EFSP}at=Z^>GEJ-I#HXsmn#n z&d=m%md~M=yo%efb18Ix%DH+#J%hB~4uuS+XgY2S$HEj6$>%nPIzRlMcg!w(3aA$N zV)f|s{8pv$pZAAK$irx%WvSD~ahXzG_iG@iBp+Dqvc*nA0~}9v&BVf8p_t;SpZH*? zuQ5^~Q~|kKv#NxX|7UlG$cg}%I+00HpyEEl3~yHtm*oX0&&lUfZW zfJ}^)&3?$5wV(sPQd*&l+9)W^hQRrPP}IxqdrSQdEQ8bPcZ2FLF z_~OZ;3n6SV5Wyefd!Wi~5PVO%b>hI}gY|h_k>DU!Ls9sy;JL}e>H1NhNJ13zxeeP8 zjkd*b>UUsbq%WQ>m|TeV%k}XdQq~yOJ5dNt;yF)q_V$9dWZ9fZ6>=Xm+F4wwE&L|MIB? z^qa*#3S56!PE_~XT6o`HVNhk}I3`u;pySH-2Y(7yWj66|0?W3Uu7dr{PUC$evW^B% zF476xYiGgiBZ{hC%luN2oVkBs;AI#>gIYCJtltbYWfQoGAw1XO?Zbj^;iFx~_%_ z->s2~HCjtV*P8xX?1$h%d`98tZAGpk)k;^R4`V4_n|}9olx}L&W_INxc}iK_?HhwB zeY#a&tf*7^HW&>Tcru-Kv7XXD0-n90Ow?Mt9g(PF$@hZroe*;A2HE6+Tr~-)8sRX? zo<)=6)vss=vlTBN){9N$F}&|qd^^zjspo1(jagE}Q1+`Z*GUJeb{aO$KPf6IW+4*l zmgD)pg#ZkZO{)|F?kI?a0YD=5jUqm!iREAD!xF_o7sCMm-l4C6!>okjFq{V;p+E~$ zH;2-;gqAWG1W5SLvUv#?X|H+$;^GX?Ybc?3UHaWvysmo`)@l!@W3Nsozai*<7i)W* zP{#e*j_^|)icGCK2=fItV&H@sO7Z|~-CSf?7^3O~I*c!X0tI4?V4q#Y4Srysk>xb~ z;$wH*GYEPjgX}?0{W_#$O|w;k0FcY@=Ck3`3a!#c%Cyi_NhWsFuk>?qdfq3Y7)C#) z^;i#86HQ#8g;&g2vE5>n52o`I zPdCK-Dl;?tAz=LxH2D<+8z~!t2RT~yCxYmA!Rh@P&iK?H6o$ystjlue<^5|O{@L>r zRst%saaS`3Q}k;Z3o{Cigms#J%mjT_`0^>F=w2+G(yU*^VZX_MFPxk8r^(;7@I5$< z*EbGxx!rf3&=lm$?66MQ;Zt}x|4CftokmixHd5wfq-@&kf&zHdU~NxnVk)-Tdppk`HE3{B1jqgOXhdLi^%0;!HG;JDbfyz zK{5*@YX#9E50`CMZtMh{vZY!uU0c0fHqUP^lP^90V4!ArA9T$(+?YkA@uJkPCqe&8 zF%6!y2<%$%0Wa{I4t*a0b-7R-Hc3W9NZO@naKM&VRvlJvF zn}2>=Q^6Y2_$HD|xpj8!VNSCZ(GyDn^kM>zCAvrNo9(QC+XW}QF-Zc>|GcKEVD~AE^FrSf_KU`r5w81Tj7njfa zfRO}fOVdK%pOrG3R_Jt^nt_7g;Phd|lj7t-&TWi;M;2*5x?eVUa6 z7vv|2)$c3B8UM6DQ~95s_THD2H6pYkVc`jPi>9iOG3O$j*JB;K=j8avoraZ}U>ZN-?cp*$Qf%!d}pD8Q12h~p(@t5kw8j{hayWL(mH}WW3Z@O*p7;{&4(>_`BW}NCf}D;EdX4A011lGrUld-HhydSOPfqL zhquS;>LvQDwwb`pnq;)A##?IzbgL+g-ssfVW_hTKX4BXgWIXW4dSHOx z!Z@b0^UCOUCD=>bNp;UE<^FcA(z%o|-Ep5D=pUX^`_pZtTN6@`o;Gzf+g<`0@=iRk zLJAp0mdQkBX=B<>vQ$!jQ@eo+Hp6{eIYk#!E4CBUY|5f2>lwc>ekG5UeedsQbv+`x zQ7jb68~*GI8XoZ>PYu}%n6XKvT1LYMZP^#0mtN#T{@I~m0on(Ph5LkJ`R2-WU6S-h zk@Ri3F#eB)c*6n$=Y-4(U>%P5${mCqDoH`=5YU^pbKyx-wfEt1tXOWnb(Kw}HQi<` zKn|P!9^8{D^l7DYJ7M{lmCM#g=yaoS;g|D)w$IquQEk6%xn73re4VusRtVxs8v{K) z-&qW-JU>prVA3*NGv)7|1s@^*fBk2G))k^azVoohPKIOC(rt|(Q{hqLi z*9y_W9U0y`EN|W{S&6lIT?E_`3lKd{%cc4``OCWlEs6Xs~(I zxAgapn!PUS6RyEgh!D?_)6f2pHRJxSKt_?`l|g~hY~mUhK*CSu*1El-xq7xV-|4Tm z(OkxAb45B|y-SDbc1dZx9$H0K{6uhZYz&A=}_y`NcF8Q$sj}ON8CkDVxS`NkfThJ@C8byKx zysQIwm_MfmzwikGeAY8{rtAkiWHWuU#io`3qtHu>pyM^t-mBy2{JX!2Ll1-!z8kZL z^Kf$3kY+q)uBdQOk?g$ldMB!{k_)ETf{F-~Mi59(Pfx|%{-nJciMuQ=E`3VCoQ`Mj z=0*zlyuA$9T0V!kZ`{ScG6M&2eF!bQiq+t|cSMh;d9I*EUw6G*4rew0`Ks!7OOwK` zTp_s`;J2suowgH1BH9vzhu1&SFS!}H&v*75XFvwFWMrE zu!IKZj}G(FdWAgY=Bd`PZ)iW|1G)m3Wfh|~6veC~`BAl+u}p)Gc6uAYTQ8!-_M7-U2WO9GYg}xv~IodpS|7 zUv_Jq1>B36KCN8Hz`8K_0FaW$waVXj-cGY8>*A?yCiRX*gVb~LS^gmzUMEF|bNiUhx*$?8YBapEvq9SLahrY3 z{v*2XZ3vc`qC!lXgZggF*Fh823BZE^oDU4=7Gn}r*i(QuK~i&A$QksG zEd~hrGZ$7>(pOBQfDUKk&u=granA#S8B4JIj(EDV_ll^Ytk0oX4Nj7OVX?&e`)8;2~* zzt|nNq?Vn8Cdn`(7$7gVUHoq@KnFC1sA8=Mc2}Ze)hJygc3DF6Q#Bv8Hc^0VEr0@8 zyVM+s9JITSZQD!i`1W<)vAuc?YT&-~7ykwFZqna;V&xTJ;9~JQ#aQtHfzhjaOs@Q9 zo&(1h14$7ZQ~*!_#{)lNb~d?2>F?@WOvJv`p1W(tAJNgJiU+faGs=%+G9vX?hTMOI zB;yP_NwY+%ZI}#)h)A{SlJq_PL`j(ScCwVeyQ<8c{J+Qsoj^0(KW>^e<}d`i$xvk&nCM?@Z@+fX}p}m`K=Dzs+D{Z%{?Ez<^<&9+$0+fkFyAzq}`zGhW9O8{Zc5!(8%6+RT=G20*O(WaO1-0fQVXN4zcZ3L7)S@(- z&3jc7s;R51cMi)G44Ihnbf-FJ8gxOwwkD7Zz?5kZ z*}It1OF@Tcbv=FOFf<(M7;WB6Py1E_odTXI;hFza0c%p#U~_%%lBvm_S;1P@`vrVU z7MteXqAJNeVs5E8pntAh-o{e{IIXT%S)sL_-(fk`e_vjpf3)}A;Q4NxOwoo_np++t z5jJknEn`?aLpjW7!*`$jjHr8TEzP8p9=y@*sG0^^E;;XJ0?r8jS`@;dN8Khh=g&OO zvf7>$YL1fRKcd#-W@DqXgW?!pClx@w)k(rBhnD6@HAy+hx z{Th<=vziMqxFBMRDyp1n)5>>_+l?eprpCb#2{0b+o>x_96)!#NdBw6IF(R=>%%^ui z&UUB_M>4PaXjjGJKF+Gd9^Hb;Qy)*ClWt`kd};~p-A;87Z9Z4qkq@AeLuTRCtRD$D zSRg*X;8Vu*h7NfTB}O+~Sn3mJ&5aQjmg;)QkCpDp$yj~9ZPXit@=2npVk9)3Kb+OO zRjf{%4)-}~z~;iZD?@JGxVz-zz z^}H+|NWFiFIVXAMaU4nKJ$>8lTxv$$60WbfclUL7Gp(L*S01Cc$i(z2eJ07_9R~}& z6*k1dNlfdPnEX~ct@9zoyr1Dsm4u7cPS|EDvY?51#plMpn|j;^A&(+a7bbjfJ{A`c zh6z2i6~EXV7Tk`*cax(t*0ChlG|S_5mQDA*TqJx!6}!b}WEf(0v+sR38spOYm!1i^ zqs(B40fhI58rv+&Eft&k-POGJ*F!}-hW$#hCnxrRBnQEbiztdd*%eR}V4m{XZR^F? zy})Rq83=m!1Z1&%jBaa|Hh00j!23K1-6+Ud{N@ri=6eJ&c zUXhA32BD!HzIG`?X!$f_c{WWfCH1mvy{CD370glVZ*5L_0{ z<*eW$HL7oxQ(F#3zdkF7l6HBk<02QS_kRAX-Ry0)LtQ^^44&3S0|H8uO52Pfd_v7{ zNX6!C+^zy}k^mXpc?nu^5&|SMNi#m{9AC424*l+{M#aaByH8Wh1-a4`qtx>%toR94 z`9acblozcZTX4dzTPdNC@U$k@>DwMj7TfAI{N~bJ`Fn|7MC%4lKM~T?0NB)27Y!wxl@E#>&3~G&SnHp=$4%3Xv`Ym#H zwUQ9e{>BAaWcSY@NLqWI9~-D@bL!penp@&%vOg|Wj8Er9YUB!pk^>}aAxZ$p>s;}f zgW=&-bUMFSiS~9+AReNk5f&)#=>cn?EzNm<*_r40xdoaOXJu&w?e?2@}rVv-f7j}Q&V7P`A(z>?sijbXuL ztk8!dWPcQOYoIATM1u20Vn#pu6E8H00po*3gGqYHtRt$E8`9yA#X?~|F5Wp^S=H5G zXS5oEl7rD$YZNLZI>ljRrPUXVk8Rdq0tXzA4@{P?y?zXUbY64KCMY9i#1+O!kq)(p z_^~*W_Mic*ovcc{UNXN*vQbXK`m*QnvTNO5l|cv?>Y#~vK7*Zxz@Xmmd37Z6EQtxJ z?(}?$(fQ!l-uBJl8=|kTp*XGdSI?pg9k-0_s5W3J#p1ngBgG7|u)GBQ<1i66(-{~z zQaE2s97+a_zi_7ZzUOEd*H9qaP8}B&WyUKQ-ZbS5B>;k}IusRpNL2OJc772PVZ$cL z{kQ2#k@R0;Vk>N1w^kUtzuhYAawL>1#XHAxPr_+3stvDf-wzQRg|$Cz(4x-Ahv(X# z=oGXWB@EDKdK;adn9CP9pnB>86P+g3$-gwmmK9P_#_o$#Z!yx+7AX121!{grwy*&s z&OfYaN33QP&E|I(+IPT`Ke26q->ALaNHLR;MCCX-264iopqNEhq}jlW{g9VIc4wee!whGc>c;;0(c{`HBG# zA(7uG!+Qp|B~~mim`IU6#HZ{{Qf>aM+J;zH#^N>fbF?_fTlC|999(J!C-Bv?njo`! z3)n7swv&2~aG4U?yg9mDvJa3Ih zK1=|Dk1qBoYM1vz{KNR4q7tE0PNr{R$kMuzdGDxFJ?yR6KFo2trO2hhFlznjaX$>? z2aPk)J@ffO&+>0BNxdtI6cn|JLTH6$D=AuiUW(07QcL!%AbazBwEl4@74o;em57>H zI;|-i9-xrGU$Eo1gZ|7l>bG0=-QfD9LUOv={KieAtbv+Q3CvL};KQ5O_)cA7`(?25 zl9-)3U^IstKk^HXk_F+vhd6e-03q=vay=H)zqViA`|ew7%}%3_{>5lb9FSsW+2W^I zU~dw%C`PGkeq***Rms;8LRSFbJ-YWs&9A=Q0pnc!gfK3 zs{||H;&Bo2bg!0tCo-F#eo?nq$eArH6p9=wks(lJx8!Bn?XvVD9v(m2M37h4wL_G~ zfyo|#ac%d~)YS9xHVte%-qGS@3LDkWp0`SRRbV8xm(|>cSTJfkv5T$Q>;z1)>X6Q8 zJzF?Xr8nnOK5E&X4x;M70)i7p5u4$3QM5Mw{S*g%&Xf7!o8KIYw(}PvgoY2H9^M6+ zqWUB9($0#c^nWqa-H*?k*WUn*{1RFujuJ4uKz4;F4DjcrWGZ*8R21M{w>`qO-bgz~ z3Eofm1R@pd_UJG2iKsNF1g3oCTn%UGv=)49&fM0k_Q5EAy{&0VC2vz0*={x_+T9&` z6C@s8yOW-38GJPI5{+hbQo z3*Qh%s&X~B1&~|se@oIhF?HB|dMW$+!6lNGC3$n?o0JefGNkJ#4WJ7# zWvMB&nb835>DV=%bXWvjnqP_1Z;szodCh!vKT?1Vi38y;)ZLh`yKaYC_Cxyf+%Urv zmzZfj6H31l5{x#^wHf9EbQXB>&KY(3@Bn)LRZ$GQ8Ag5D0bOloJim@idMy>z`jL~{ zL&lpkj7oFmddlr6;xA5=K;%NnkjF)@wJ|1#@j05Nm5)t4@_;lNzwCFl$iZCgI9=g< zxataD>FP=^poL~o|AB{MRlAk@@_p?$f#0L@UFX^^#N`{(fKWs#YkuW*t(t=|-8_Tz zw$}9A!l;vo@cI+;6z6laSF}t{hWoQeeB6$-ch~a`eG~>jCh^EjVtHdQM5Np;&>lrB zF)>eroZ}t$ffZl>)t3oGTGZfYoGB(1un?Xkt@~;^E>6cX=GW~<~}mn?d#P3im9F`;&jU+ z|3sNx{kmC*zK{Feqy|Ejw_w`l7h!Cp;IFkG^&pU=PhGwChl^)kUzr;y-M)=vcvorE zNH3L*7s{J`F*^Vy#K$(B?u|i#1yiD4u&b?rO$*3m|6~>*dlK~;rxgIx%DMk0%{$>s z68_O|=IrM!>kMk(ppSp;C7Hhfk=w=kVdZ(IxA*cFQy&S^Nt&RKIa8|vB@Y;{|II60V*RB;WPzbeb zmwo1Y7Yy}Yr%ENWoJ^i-pgyfJ;d zFkf1uit^5@N~GRM!ga1EJkoUyMY+@S>znc$ZRMmI^vQgwcxb+>OsZV#I%^qE%}Mio zg!;{Ng|Yob*RBX5f6S@Xv^VnQ&YtIqipg7?FZz|(n-u;EssZ6e)LSKVP&xFT$JZVf>EWP+o8G^uc5v2q z%Y8wUVdcAE<2z{~)Z}ae18tw+4}KerY7nfK5-eQnJIW=9S=OQ^d18dI6crYBw7uuJ zGYRj$-XFTyl3NfG?Am-1hG64zM%B3)-g56S94HfFwWy6z(=vnXxQjbyz`GW4bld0A z_%kF*g0yBQEg|jmZ$YzVWgA@QuYV-vX8K@xGxWSm zhPL&G<@Sk-z6ngsuY@%ePbFpcA|lZ9$qf948l9V!8jjU59g~ui{WsmPadDsA=y3io zDV+Bf2K4tdC~nT!)Ok8q*m!Ko9=v=ZM=Qa6WlhYrA(bB7!F0;2!TlNDUKj+N36{PV zr%Dj}KS9DDzKQW|vMi3FMHyuW1Z@n8&3n3B(YX#JVdAn}?9`$KDq*20xzX|iRA{lc z8XfLaYu!^ltNvLq1Jg|{7zpL~Tq9ZH0)+d`bAdi7Hj4p3tX}PF^+eXHjcBtLf?6(Qbgr_^ zLU`-L45RvCQ|_M9V&rXSAl?t%ABwu^4^FuSlAS(KQT;~cOKsIEN}IZ84S9lXMp=|N z;J^qZz|OFFHhPhqZBt~+lVV^|YZN;Tf}x%~$0~hHlwssBLJAa%e;kzLXsuZBC)$eb z`SakB4cC6Wa|P4LkJ}zSU$~@zZX&ut;`K|#rq)K#jk#mFUZ_{wI>g~#Q@at?a>m2E z>A?DP`Fc~nYo)85_wWv`?mbM*&7~btxZuMUvJ`Z!Ro1n3a`_wU*@zlT-fVXlqp|B) zwEwqp>_&_x+a^0PWG6R7Ec<^+y~98L$K=WR>TphdZ0*fl-)|6$zeE_?sl+ou_( z3^YoU=x5&88Nx>keAN|~3K#Pmz`D5c$gfkyK{BaG~R(XU+}c` zJHK)m^j5h!AT&9Pg;RPpKcG~M}eUJ4hW$yjrG#o{Pc zWq*eeYAK2^SW!0C(n*D}W@gOCiz8tK(+OYO&WFh4M&HrZ;RjSKFPOGQb=U z8-CdKX0@`nq@X}@zFaO%6c`p$KNS@uyV)N!+~jHjr1+S+J0;(? z-%FZe@n`l&6{9RQ79*~VN|i#8L3UgqJ&r=c7WY0I@I~FN0d4u{zAE?1Yi6vV8BYro zrr?;wP%u@1hv}G{_+R`sts87|dA6B>mg|t})kNdI|8$zu6|0NxrGSQQag=xpXsgWYFIT&%nwV;4u6RbW+LCuFO z(S?&=?2qEfWedxv*CRmpe$u~>JAz3Kr^*D`>{d^==E?w0DH%m9qm#hv%Ib{S>ycaJx31$d<7g~>*vL?0OuGTY zAl>L9KUEnAk-WNd5R?OC94^daQ$Go}0NpydtwQgWzh`s+7E^7J5g(xJg2 z;8SXMmoR3BAagjezsK4?Y2LHFz4rpLI#(JnKfBXgEiotadu|e6n;di_=ugt)=7@8% z8cQ3R?)jswZd!sV*IpiiPDNH=*wv@!g)WEP(U;+2jIOuVoOwQq8nkT&-=*wQU)=AC z$}j;w_!{`5M_b&XaG4V*s7XGB$eS&p2@)yCW6-}mkZwh1>WyYUy1iG7B>)uSE2wBb zOxZp)CgAI}r&@9zCP4!{*DZP2HupA-+(_;#+$Lu>*CQdB`FV_$6l#{Tdh&#en#^~E z7|u;9bdHh)vE)kCF)#Q&_onmXU~Fdi;4*#tY$%p}*!t+@DLS$>dRz`QyKPe^b9MU> z7mG%#YJ)XU$8eTvJ6w3`6z<%Jd( z5OP^RR{=sg31D|^q$a*c`x7ZitscKpKK&T)fD`umFvudjD!ThofTq`1rg zD|7`(2RXZeD2RlF>#$*KHYrW~rQUkpy__!3=cSa-5^48hOJ%%+sDMT|jU(hOE52Xv zn(vI&kHwZC>HJb?Eb1c!;$Nu5N)EPisrstOQHYGd7gkHPARN_mihOk z_d??Yi}#xa6r!3y}1qYf)yE%!j}G2QUeqjkD>$SeUgND6U)LtSZ% zg|f_*5l%PE9o`3Ay72VWC;FDPYO!R}DxMyfVaW^hWl3SR)Ym(h?pbScrgr7p88ti> zhFCb+?MG8tXq2j@6<{3RA;UmZTA)j1{dMN0@^i{y0!^?z0HM9g*#00~HZBw%`77b% zS~mhWon;E=^_wA3K3L$o1AQF*(UqGy;c_Q?-n$dMzl{S)EH3 zJ3bxv{23+0qUqOF(A2y`pXYW!CXkR=I{ZGIM1P4!g^dSU&WYC8o9!{v4Ypl$!^9V7 zzAw}XE_W(9Ga_BExja}Y0Nk75-KOA*(#sZihaG`R;+Bc%oT-83^Qpmtf)NP|voz{r zRi)nTM0LiiwnuhKPt5uH%8;&LWe41;QY6ywR!`+}zwefKs;P2`8i^*=LmuDSG0K(T zhuOg~ms$Osp9KEr1rQ&g6$=Na#Mu7UtW0*R{z$r1_LH7`Jnsu=Q2b5#2KL77{AA#G zswp86cBG}rXRUYLvnU?BG%u89wma;b(uoBvRlZw#H0F~@KV;8;(|7LUbAfwojRwrE z3A)pzEK&WKJRKJhJso$|?meiV#GYW#*2INM)SIiSNl6HcZ0#opECWFM2<}qMZ6=ns z|B=q0LmbcV9v3bD-9+i@5=|YLp8?f;xoAh*w|aYtw{l7I&^ns)&h>h|;=9XnPxgV~ zc`xbc{gm20pDhF|W76LTj$!z(CB3Q!?WijlK{{cz5SY3w|ETBr`o~JwcI0)votlpQ zuw?%Bm`vhf;-mMa4mYRZiByH!1T9k{a3YD{T^XG?&3iwlHMYc4!yZj2|4B6mlX^T6 z7BwhOO(`7GD1iwv0}C~{S#La#JXKErY>SM!MTJx~%L)r^ACn4Fe>wz*P`Al-&uuQJ z1oc}Vez@|d0}Ww8ztbB{SnQLbxmA*!Y9z6L1$^MkCF*d8kdJdCLv84xcQw&0b*X*hwkC~3+Nr=f^= zd!gFC64O!zi==UPp8Rm?2ki-ekEiGARL)NV2PR?*3JH4gHJD+8UX%3)`dEC3p#Z|~ zFai{UE4wq=@Nkw|qdCr(2Clc`^3TCu?MR3kCw2({B)h?LH^3t;>akR>)qV(tx?C*A zYbJ4R5A%ISj1X-=7UN1n&&>n@(*MLj6KyPUemcESoR9f9<7YuZC1jcTA4bu3la0T> z#K}VKH%~sKz5B2_TUP30D3`bMq%+m@uXKUoId9GBjwaPtf`vK6sFPyRC$U)L5)}sD zNVfkplAu&Gj&-!rwXD*TY}}{sqq_f}sHiHziSxVEXr#UpHlA8cFt_awrUP`anDju_ z_9VkzOpnaaHK65mJrvuF0L^u%S>e+aMVy)^9GcGbUYIqnp~!bLXTMT|(V`|uBpBcE zpoA3ZeMEEMyOf|gHhju?=l2E)td2w6_RZdHUVK3)YmVGVafy^VHT~fX<$pMwchmBI z9$hHQAw;iVZ{ktOV&8Z_;&qCk^<5++<*5@ojpyA|MtoS{p%8eO4k<3HT5GxdwaleI zO8yL$7*UTGG=9WUA4CLKjamMYMD2~vf57@?ub?D~D1Lna1z~f#W-7>b zQWflYtYKiRY`MBJ^6rNYic@)m!m0k50AP_Av}FvigU3lV^D`kcK(H0a^3s42Zzx`3^H zqn#iE4|%glYq;7yJ;`12drSJYX$F&76q^5%_nYiy?KV9!GL|>jCo+PI-hv@WEXaqa z(?)7(>_k$VL*xfd(OtKP15r6Z?`5tr!|)oklbx4D+5dVE3J_Pm!aj#iwjE%DML`jTS#F17ERC^=ICyzl(MDDKfw^k@MBV&d_V zFMJJ1h?J@BW$-CX4`WnP(9A6pQ8)~%EHqyQ_9>$_5Sjd>-x{uWz8fd_VbDoM!q5U zOK4`iaL`^NxbSej*?mnf$0OA_lEnvn*1RrN@YEXTqgA;9Rj=A?P`kq>MyScFXN2M5 zUJwbUH#c_n;0^R1?UpmSc@u9h3G&c%M-h(-V#)KxUUk>s9$c-aEuc2vSfGmp1z1Kq z@6oCt;*SDfjk2&i^WDA6UTxx7Qy=CkeV-#55$keM@g73mWEfGCbLD4FCa-R_aNKMY z5|hnCFmd7aKYx&A21_f1xs3>4@PQ?QM5k#9XfEgEfKXLQV|xE*hoX=YbvU00KS4_n zEdW^7fPs{m+cfVt_Y_R%Y$7EBAaTJ9l-gjCTSSFBUhmgk-OQg36CU54jvnIS4Ol)0 z$)V8EF-`}08on2vmIbW{FAj+Xo6Sbs;-Vh|19|V_=j8`!87d-v0uo+JMD=1(%hiNj zieuMps%t%RZRYDonv8@;^Xns#YduDrX14P+*OkC+;!DS{(Nufs!J0G{kJEQ&?y(I@ zV=wZ1!gBbKxTzvjv_!MHIalVi;a+dNf)ePByZslVfNF|ID^=M=9SbKbb|N z!X;A#?_zpLy;)W9m9HNIk&H*bdAifBW2V^)p3$2{lVFNR<0>eStt6@&X4p{22PlMe ztaKqi+%OcVQA3@{NOrEmJ}buA0=Z1^y{lE$D_}X+lhE~cxaK$yyb-RIdBLD4 z&;9JD$$GlOJeU4~HjpN-rT7$XIW1)h)p}5}8Wf#=m&!0bij_SXTtQzIFnn(W7ZiQh zQ->C(l#YTHLZ2A(n8Y#s5rGih+xD3D2_~4c6ABW9ic?`IX2DQ8#3j;QTrSS?g4;u* zwZ0b|)`zk+qYS7T)WSeNV=`tt3 zUjlbGSm8vQmYew%v7O?3=pe)IWbmM)8bBSOmdNFQ@4pBAitEyF3yo~}ylNT6fB`{P ztv#QvG;{yT5f4P@(ZlEdZ*#@HD&8iw*lPcI2j+_gBY3IAD<4?)j8Gk6X*vci(=)T zq~J|g1sHK$Bs`+tm)MAfhQcNAf!gS-2{35@jDe5=_{KbZW!0Ay7}m2Psy5y~(J{ht z`#5zSBK5{e$?#?6X)Cp$fEd@p8{&P>pjB{m?el#ojKnfLaOk-o83fEX0p_yvjH-l~ zr^3!ViRszR<@0oRRM$VTdkm903xHa_DbdSF|7*Y%PXaB1!dWqmzsNPy!@uIPV)GUu z4xfTm2j>B>r@P4TaBY_1Wk4u-u0d z$KL?IL<{H9RW@U2_4ZH`RD0+z^?!O=k5OKDNksqfS~8Il5MLiGFCLr5CcO%f8T01a~Bh$TNT??21saM6}04fL8_oem4KfpgP6Kq}K@(tjG815R1>U$A*T ze9B6&y_rI`69(GmqT(G4xYOhoOr^b8zRzgq8dt$&kx8`=uskboMfA8N1{T|k+1c9C z=74WSxhkP&QZ5Kmpzwa{Lc5te(J}dq!6PIArFKsWl z?EHl6?5ddp15e!5^_Yr0!)e5u@15aY7R>|5M7^i03qj{&H>Ye`zr-}<%De*;Fzzs~ zd)-yo%Sg}ZjNHEF0ml=&q~X9of$_2uK-IAY$F>1gkpMh;Fjcn8Z8I$xO!~qEKHnVy zU7NMiw4ux=%6=2yS~>^~7N*dY)weNjmaIl4E;y)6$0x&KAXBPSB{QMq@Ys<>Ji zjGO>>lXaOKo$6P@ZHKFh=DTLq;yV&xqNq}-SLpmwtcA((9_Fd=1M|{4J39m9;n!+f zOi=vs^jBwb{Dlc02nAcuxNp?}0R4;n4uKnZc5+C&*?qC;0dac#A#PW7vn0 z1#@-kqPlL*kwN%B{z>68OtXDr1S;p35hTPd$eSI{-DJ~=NHWw)DV>P8qq@p;gdl1q zn(=p`NBj4pd>%4rR=i6v;_S7^T@d7FaL5{iOZl}n!9OHgf9$>DK?}VR0m3Se(+&)z z9!%}2aM+Xs6YjxcueO@O1fIt!U3aGRPdjvAz<+f8c3#RznGP>FauxpnBkC)ovTDA! zZ@Rl~x*G}U?o_%{L`eZbx=W3j#D-+#Sl`N0p$(wTGi?78+8 zV8I3M&n7!-*!b)g;DMO+1^ajS_QwZd5*{mm;N}DcXt_La(FMW15j&Gb`5+}#G&{i# z4)_$>1Bzx3aCnPu^SK2k0MY+mQ~M=>ug1+f4I{bN(rBGJ~KyAq&8J_gTY%_nQl1Xe>(8L$H zsI$=U=Q*{t_+UTH%^zla&4>xnhXdp8N1wCte7$Pj?JB{?=f%m(QhoiJ8nW7Y`3sC$ zCdYu=0w;P9@GAu0%u}=<#2>^Y`f02hnm-=vUjc%Du93 zD$3vg^u|&L{4UzsYF_NFk!W_EcsOcF(52=Cm#nF^KGJjdtxP@SK>u|6QzJ26YDxd) zp8}eyu16P3STr$X-???iQ_S3(2*bj{Rs}L2iMWR)XyXY99ut_*3E8@_X@Wk5YffoA z0XYDM1|cj~vlZkNUi)0$S8E}2Ah&?RV-pAHtulmMP>DE9WI*aWuw3>BxzJR?!lWXj z`Ie(u+wnJO3D~uN9@%)$^kxYU?&TVuiH=|R=-*VpG=RzSIA>E1}GnE70riTKhn5Q z{-nR3NC*g|fv1ye96$>n=q=HdIQk_~Y0`1}o3|1-vp7gS-Qf>#+$L`1_Hx+ zY;i$$M09ByL8DG>sgvgf%rph>d;dnd2yQ{M7xQyM>3~}84ww-CD`p^V?#aRtyyJ51 zzV{$E9Q~{QH^dZ>ZZ=bH2o~P~G@o$G&2Ig`aP#B4itmV`o@c;a7Zzd);t|2?4vcUi z{;8=@+vO(h>UIX@&mE?mT1_fodwy7av*mO~1jKRh>-+cb#k!RgBwDyJUYi3r$0A4g zSn#3#PxkruSsA2;_nR|3gtcG&5O-wezQ(5_B{lRzF zO1yA1qklxb{0;4?={1U8mZaS;)zDXYx*Q1cBcekzGy`sru#O%s4`{cP3NE?nIV-3f z)pc|}ZEtVK7KDFH=m1?4Xqha&&6i0OwQtX=h8!&VP$Ki&v#tIhMl37@93v5f`c20> zfry!M4|H;y{#xs#2FE|>a1y5@)RSY8(S)XatT73_T>z&s*XkM1*Xg~S(}MxBn&;1- z#2OcAUcL)4;x;TZTPM@HDRGJTQTEHo0ddI|{`0ol*ibCdp4Wr8uaMDoXjtAhvCx#j zAeu4-NUFa89)X;;bDY4Fk^-dE3=9o*Wr=!?0f%3R1n>cj1rcx04C{eC*;?a9v@pnS z2m~p1z#IylTnL|w>m%7R@ZHr*?hQsGfXR+x1doHaFLJ!hak}Rkw5v& z0zU9l;{i5q@_D9;yeeC`K}1c>b2h{J9|sE{gtn^vu+vk$` z<5Xf|VmSe2-p@Je?{|v6zS=~96nr@qaFPoeP9dHJx_mFUXSRIhXq}y_SB@pB7Nf4FgSY~;?jix^^QbkjtKI^PEbn3h zY4Rvs4W0p$4Ww`Lk=-F!`7JF=*L)x!1q<|Mun@~c8Sr6HBg48LEFeV51>GJtV?i8% z=lD~Yv`{7{aEq69b>;iaXBV!R!0;OUS)f1Mo~x!E8X5xEL%-YFXfkjNJejKlGhno* zsAh1wlk(Xf?|k0~2gY`*UBPGy%F4aq&&Gggi>E%$&G0?Q*M$l=P!V#NM1nMdo0iC5 z(o8n2z+Qx4v-j#`0|qk8Vm2s#yU!g-DNX@G8mZ^~Kyr7KSGtm&PJdB0?5Kz|4Uy+% z#r!>6xciNvQXNAU;G@1>0~3HZGi;`7T@KkPA6t$aPQAeKp$1Dk&;y)IRMw@~KkH>G zW!^-@$K!(159&ZRFS}9oYq+5F_cQtS#xRzn3mu&30@3{wg%9!$MlAzie6`t?sIb{2?E8` zO-!Oe8}@qSGcROyQs_eqcmtq3R@fj|C+Nv;aptT3!04!MY@b2-%z-eZ7ko`2VD(<6 za>9VnO*znIJpBaF8ju1z&kw)g^pah~ih7tNufGj1KKI1REiT8VErG0Nc^x{+Sw@la z1AB{y?@r{NQEKj4+z`O+^1dLhvc?;0av8Z~=E4aLNBx>;quhPe&X`k+?|vv~`jbq9 zi-DP0s5EPjsjkA^e2pEUSXB&KWe~#qFg7U`KV&g zC@YrACJz!pw0Lsd-J}!~#{ZVb{_v9P3)nGWMSpqx%j-zH>6go_anV&4sEXhyFRn;W zPmdtxws5x_O>vRQ0CwL`?eBg0x8cy<^IPE8Ki=Zu)cftJfdcX9Wlm{VUj#rXGdO8p zLt~^qwz1{s6i0#M%x%SoA3`N0v~VrL3q}9=8*EbL*4HanEpNPo1FzeQgL{K^A#b9T zX?!o^6&<8+J|S>m2sA9^VsWcLUpR03A7>4Cn8<<>Dq&`Kef=>kQ_MKRd^oWO zWR-|~a4O_`P7x~Z5hzi6}=zbG3@wQ~u^Y#Y~G&wldr+bpubvhP1= z_!}A?E+r@T$z~Bt{5f^bn^yNV`7;`@SqyXSe(m2%JD*%SYh7P??$CoA9jMpEz9fi& z<@7wW0zndMzf)fI0fvi`a8}?t>iSfPrta5xvfd;~ePT{e9-MctzT<}Slq7l`QgeRooviiFC5Bn7&A%dU^d`;VXB0kr_^%^8a6`Y0#Uz$jX&=-%vOcynJg#=A6W zk^uGKu(s|shnl@#fSvuKD31jP_d|e6rV0|U*8moabq4n}_xt*w^kB$Eu8vn`Q@rxC zbED;eFb$whWIvp>f|Rc1zO@j_rOSLd6A0ZDs~CmaMxsHz){S9qCz8L6$5mi3+6!Q6 zrePmyLa#nTbU(THK1HZ+GCc#~FVQ4gd>~C=*0avYXwa`>BVv+;f=gf5n!_q6%X;;; z#BKK%h70rH_JOaZnrE@#eL@4sAV5wiQ^k!hG@#p*4Tp33RdGwsSM0ctw1Y953~me+ zGFJaAWBoq&Q#Z{1it5i?iJwslF!Y?TsoN3eTP(EfU9zTH)0F8Wh~MVXfx#Mr(0#Sf zGz-R`kx8>2WZc54HzDsYmOr4yq?o{JXuxl7!I5nNHjRa&UfU8(mL-e#X`8RXXP8#6 zzjIZc6?$#&QZPzNhFpG=5HR2cB`^k9j3n+QCIUL>lxn`bFwkqo1R?Dq<$c?EGP$Z4a~!u}eU*bK+reTpWl@jJ@0xl$jBtElJF3?1iw~^)lY9I6SUyV0 zM@_u$gl&!(ZCP(Ae)S4WBA1jJmG1XMU&`WB%gF9%S$91v@%3ZX zCTWOmnnxDvVY)-$Dq9gh8YpcmT{;aXKggR-B|2>k;H>UdTEpzH{~s3sB}}cs-fO;v z${Pm?m}I)f+FlisN~T%?jSl5(>m2IX(Mr@0wWE$?fT*_bDhw{St*FjT3+%`pCmSG& z09Ok*vYL2?WoXF9#9y!*snwTFqdEvc3FP7=9jgh1$d4axhbBL)Ng*A!zlylOomPy@ zAR|m*{MPtag%usq?0u`};T}*L+W-AK`i_O#Lj(`?11@rC7o{IIM#QK%qfGESla0+m zT#paiu>|LPtlcuMhqctA%Q;jkZC8Q4jerf)c$62 zXH7e-(}t;8=R)<3Hud0UE2!a3_e~2X0)ngz1`ic-3pcqI_Eos~#D|qx^$MhGK9N_~ zLQ>i@DB8^rH>PiXOhTKWw!qDI;7Jd z#uJ~&@HG#K!%qA(ai8;PfpgK&2>oMoJf;n3Zh?+tJoM_?k`yWjUWoIBx{dL+R-n=q z6gh^VMfC2fGyQFUgx0=4`p!|&{>ukwIDT;iV2Muw`?=cEEx-#00!wLU=h|Ukc2}Xm2L? z{e(y1tzfK1Deu}6Kw5CkPnO*d`QGtJlzaJ{^KPuy&l7b=J{Kuu%D6S z!+(lNxGJ?Dz16g!Rk#QX%>O5SrUHRAs~Q!LcilNvv5xWhc8l?>b8l&0n*pArY29;e z22Q}?kB7k^iM{XbgMGZE42j3xX#huTI{%{MwD!lZOe{1$fE(O)WIc(DGS)606Gb|m zufm|>%*eUEF^xp@rk_C@n@p$ol+rJ8LQ2S4ZAHfyc{3=bQ2@pU7Rl^4-ls2^ppJq^ z=EvM~HP|2|C&Wln0j_Pn9o38Y#aN zHY=_mpfiw$hB6R{dkPB+pFB*Qoq52H!P0Q_)HA*u(_TtVO4wGX2?^0iVyg@Q4UckL<^&WhNT5-d>rl zE?Y!CTV!z$bz^J==IPz=n&&{7?C6{4U3lw+cU`;a0VYcsXO2hRT~ep*dL=Tn+~poltLg9e z8xMt4k~$n@U!ogbU?IZ>l~vA6c7odS^w4ucrhXV*C^TH}>*G2;X@x(~uhPWml-+zc ze-SSQ1igLf?_pC@G%W07 z-Zcacy!2oXuI#;R+4N1+R=+Qk+GDe5&`zm0RCpzJP3tC@gJ=c{uDs+dnq3tjhY&!J zkUcvrUy{!hI=l!iVSy;_0fp7T$TOX<`Lk#OE=p<7KkVYq>u&U!3G$gYH~boUv!g*D zP-8=oztB)||F!|?Qluc~-xC^HO)q=i836Z$XNg`yj@m1|)3ix(p0*{F+Y*TR!m!_& zTaCkljV!E|;)->Qx&NsBsqRZb8+V1=rSj!qqbbbQ zjP|doy*H78z^nP_Me>|!L&`>1ut3Yj=Q3oA-#$rM?5n**^kx!~{*BIIy=^|8DPkJ- zUfmcIf!kIv7TiI_Ol~9CX|qF{+Q%}?_Y4j zi_j{<1>r(@6FXv6>k%aVRf?pIj51zf^>l}XFC_H+mlbsUgg(7@8eAk&n7BbPBlqW z2pVCJaLQ~6ZH~*kQJ?G#wj4`U3WIVu1Ox^v9x`xJ1HHSN7TrOk%R5YKYqB%;US&)& zQ&4Es)H<}5YL_pk3&J{!{&w*O!vniSail*##&xi%^{**=3~wK z0`BR)b}`E)>zR(%j^t5;?Uskl6T*Rl$^>dHnLNWH_#NBujU6b+p0#!_CRjHMI)#s{j&Jrz?*7JL zT&_@t6wtYFDZQehhai&jvmD^o$%M7SW_beQ?~OImXC55<-!dZ>zk9^QOhCgHltG4g zf~y)J7uAD|-e<01``5;JqYrjs(#~p*ld${K&hhzdhLhUKlGElX&@NEq%avd`jAofT zti#W5RlO#wXcIc#U{j?g=Jc0W3io&1aA(t_>adNzLyq+Ex}<8G$5_n?>ZbTyDoPju z%o#;D+rUyBo4&65OTCPB9&#gnUqbxgKb{lKh0~-{?s(dxSira#YsxbLqXXYD3u18YQWLVj((!wdGY(&`nvMiAy7c=>=&8*%udjcBjM6m(1=i%f5&tc-ZK30S|TB7tlgRyI*`7h zw);!XuULF4o8%7`q&Z01)|qFhT%^Mao4_Sl>w&3@-xVJopU~t&fUls~V^sjxrP>;g zIbop~=Kzm4R4U8oW=dpHw;WdKMZn3%{USRz-qlI}*qqEc2UCEf>_AMZ1b$Mj9veum z1?k~q1Neb5II1g)aQ5G2b-6k%SQHQj<+xnduN;roqRw}(kA{P=J`DT-*|{n<&}`H`gyIP4vFT(8$##wJ&NA}LM9KNz{noxbgykWT#8J#c%tETSjl0<` zErU-55XuTPbwoyR!HTCiUX7+Na&0UZE@o7G7C5{jo}9pn-A~;so9L&V39=~p_jdWe zhHv0o@q7^xgcNpgT09}-0!b$5_!}oawC=;ZA0Gx}5BuYNu=TM$> zctil0z5xSC32gsutDqQHGW@!gh1er#vBL_85%CM z;~~+I7vZRMZ7E+vfbovC{+no1DFahf~Df)BLt~;6e}~0xKK=_Dj(tM%XL{Q`d$zwK2UH!`|6%rZ7OBO zSJ)$+ibIqIh+kT<(0?}(;8s)ls3;SN*u2}^;-0IEkOg3(nhQpNjeZA;#R<3H(Mbfs5l3EYfPg4iWOGnVJ7HS8dR8S3~&B z@HAB*uAAJADc^c`8qa3LR31C<*0Nsj^*6!5Uln_lIAl}7-cNNM>%8dCt+b^2w56x= z5A(`FzHAYcvqOxt12pdw*>eVt(ih^lcme9hwqlNfGHSq53a~nRASET6-Y5)#^sB^* zBv61OPD&5yVgPw6@J7H*Oi2Ne7adkSQkAn_K4ahO@4K>74}zsHlT7Yr?X`dKUQ7_(L#ZVwJi_=Pg3QcmM*PQ{XoK0!LZJ4MRRXxZrA z00Wo3JG(``^%a*{6!%A;5JO&EF6|;drerk9JIi_l({GDG3ttw*TTPHMO-N7g|0_WX zCn14>I@Bf1b9HEq7Y3`<{R5lH6nraG9EAqU>ISGHmt$ zi5)Lh7ZBEHxms|bU@Qp+s{ggApx^+Y>@+Ob&92$k=hWle2F%V*2L-Bpyrwa)6Pd|A z^8t4FFk*;w=>rUeTnO0|x_W%;4yRrL$U8<}b{P>I9ft1($C~P7*hKDY z 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
  • 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 Igu(6q1ASYI5dZ)H diff --git a/docs/user/examples/data_explorer.rst b/docs/user/examples/data_explorer.rst deleted file mode 100644 index fdf1521..0000000 --- a/docs/user/examples/data_explorer.rst +++ /dev/null @@ -1,19 +0,0 @@ -############# -Data Explorer -############# - -The Data Explorer is an application that allows the user to view arbitrary CSV files, with the option to display some values in graphical form. - -.. figure:: data_explorer_menu.* - :alt: Data explorer with plot menu. - -The data explorer is composed of a :ref:`tabular_display` panel, along with a menu to load and plot data. It supports :ref:`data filters `, which can be accessed via ``File -> Filters...``. - -Data Explorer also has an option to take the derivative of data via ``Math -> Derivative...``. - -Plotting options -**************** - -Combinations of scalar columns can be plotted in two or three dimensions, with the "Curve...", "Colormapped...", and "Surface..." menu options. These correspond to the :ref:`two_dimensional_plot`, :ref:`colormapped_plot`, and :ref:`surface_plot`. - -List columns can be plotted in three dimensions with the "Waveforms..." menu option, which corresponds to the :ref:`surface_plot` in :ref:`surface_plot_waveform`. diff --git a/docs/user/examples/data_explorer_menu.png b/docs/user/examples/data_explorer_menu.png deleted file mode 100644 index 43e25aac3ec1cf39d11b7c062e51b20f3eea2227..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60195 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 diff --git a/docs/user/examples/index.rst b/docs/user/examples/index.rst deleted file mode 100644 index 554cf06..0000000 --- a/docs/user/examples/index.rst +++ /dev/null @@ -1,13 +0,0 @@ -#################### -Example applications -#################### - -Spanish Acquisition comes bundled with example applications, showing its use and capabilities. - -The examples are found in ``examples/``. To run an example if Spanish Acquisition is :ref:`installed `, simply run the example Python file; otherwise, use ``python -m examples.``. - -.. toctree:: - :maxdepth: 2 - - acquisition - data_explorer diff --git a/docs/user/examples/virtual_setup.rst b/docs/user/examples/virtual_setup.rst deleted file mode 100644 index 0f72eb4..0000000 --- a/docs/user/examples/virtual_setup.rst +++ /dev/null @@ -1,10 +0,0 @@ -############# -Virtual Setup -############# - -The Virtual Setup is an application that allows the user to prepare output variables as functions of linear swept virtual variables. This serves as a utility to for the ‘From File’ option of the ‘Output variable editor dialog’ in Acquisition. - -.. figure:: virtual_setup_menu.* - :alt: Example of virtual variable linear sweep and dependent functions. - -In the top panel there is fields to enter the virtual variable name, initial value, final value, steps, and order in the same syntax as the ‘Variable configuration’ portion of the ‘Acquisition’ program. In the bottom panel dependent variables can be setup. They are defined as entered functions of the linear virtual variables. Upon completion, the ‘Evaluation / Write to CSV’ button will generate a CSV file composed of the values of both the virtual variables and the dependent variables following the same variable sweep format of ‘Acquisition’ by the order of the virtual variables. The dependent variables can then be utilized in Acquisition by adding them through the ‘From File’ option and setting them all to the same order number in order to be incremented together. diff --git a/docs/user/examples/virtual_setup_menu.png b/docs/user/examples/virtual_setup_menu.png deleted file mode 100644 index 1b1979f2fe4a0d011bee206eb847f87b65868b2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73869 zcmc$_WmFtb@HU!+1cE0Je1YJ>-7R=ArW*?%A0>Gd0yUUDegkQynBDC4vZt4fp2F8${6`f^u)(Kq0+(^DgWo^lMAdXRe6X zzqfXBBK&X4#sCMeC-05CRFsf>Vs)cx>!4r~2G)$Yw36#GBFx2s0@&Trnl$@OmaSJkind{YCD-Qw}2) zO-+#3|6Ga7Xm4vf)&Ax0ZTx}ZqWCcXZWAmZw*DuK-(Kw9|Gp7ncO+^0rV|E#pUi9N z2Dh1(ngD^lBO}Mo z4D|GqAY%jYuS%Ed)9V9?_Kml9_Np*%JJsSn;ES&+`(Q z{RtXxT|Pbyo4r&s-xmKnKI@efd5LO;F%^W%;UXpVd0zkhfj0c#F=0dMkBtAE(%Buv z=Tmpw2nKa@E-!z?L8IGNjF*wo6f=}3aR4&Ul*~-1m8<7hRUJBW;IYjYc62txba)@s zXxp=~+o~*iHg4RZ`Nvpcnek4)tS{Db6*f~{e?>N{hu6#jFI+1Bs^wZa;a_~TNwqJK z9cm8cJdY<^xuQ*|Z?eDPEu@8-7jUogyl0YI#l02(P1b_HLM^r8Jl@|H5;GK6g8p~p zV6;qsK<36}GI_Pd3=4ClLSl%wD5}2^HD*$=L3T789733y{jj}#Npz7ARG(Se*yFup z-j`@SuSUrOd%c(H&`}A9%sMQm)2t9!3gW+ohN)hPj5;rTZ~83+o2gley_N%{Z%fz zo`cz$4WG{^-_2XVuJtsYs9XQ74~>b55+7gneZ2=0iMo2q*1`O~D0+Nt(+)>ovnF|b zu0GIeh5Ea(b;=Y-`g|VJLiTOHQd;U8QDA?%JmT+pMo7-5t9i@C=rukes3yi2GMG>1 zzaiQiX}`&qo0y)+SFx5lL8_Y99oUZeUR#GA<6Z%k8P9v$lxTl+YA|`SW$jKPvC(Cx z|EHR|w@;Sr(Rm5NN#`#f6Pdw@LRBa5(#(v4Q(ou%yh^>4hX=P8jtgzh^4K<%Sv}>6 zZs*~RlGV`Rhhz2)mb_`KiQD5VVzz^L{1b3i8JuhAN>d3e@_yNS0KTH=oV0#KFuBy@ z2K40|_er%^M5S)$y|Qc5QvM%TvneuZKUyD&`J`#sU@(aCG|XtMQ}_;MCC>jwCyU#} zM2?RiRalt1rnc_jo<%{?v?#f$N$vGxVriMLILe8-cp~P8UP>zTRPWlEE@dUp!MXVG zBh{U966)S%h3*M;Wi}vY{E@dK8P&69`4CU`@Nk~MslIA$C%S$(4)9*w0``DR$ zcy#;0JC%uv=S7(hXdobHW@ZK;y*TT?yNkX;2cp2Uq|Igg`@jm<6Rg$z8O_0S0PjXd zv|uT?V(n=dBuK8d7reIBgEZZ#8_Io-UV6%L&V6*kUMo^}df4}7Y)T08pKGP3E*5Oe z@E~*d$~5N{mRN*IoK-vajSqd$rc`#PUOuCD>R%dx5)t53Q&&^`r0W|MRVgNniHUjm z%U|DkZd~HwM9iyox7^r1Wyu>QfNLryV7dlf$m_swMd|ZJ3H(Uzk}_A~KN6_m^~g^286ibI--qS6J=Ft(A~c zf@9wPp%#B>=|d{&zX6+usQ?~3{NP}{Q)eud9fthxxm-5m-ug-GEb|Fo{!PfOk> zZ1U$_r$lD*{WGih$|2yN%DxRXKi0qg)!uhM_C3QVh6AaMi#>r1Hs1s<{tuA>PAJ;|D+VzVX)PJ<<(WiWVP^ITz*r; z$*)X|l+)5N$!aSK8H!@fI?Tp?*_O`)lT(3mi=SB`F8>^z|0?qJ)-OQg-(L6|$V4Ix zw%dYn&%MUYas%M;XQ6kTJT4hd&k(FOwM%kjZJwavSq?@#w~$*W^rM9T5|dPE&5wW@ z^AF?}cV}4XZI`ZP?aw_|Y%jk}Ibj@znTx)3AKunQYhevtCG62dR$k~JSuAk2@PSes zwZ;xa*NQ~|o-f@IDH2J~kU8hyJE{^4$zvOf zJv5O;mjCJ9GH&H8y1AmafGP?MQTBW@Z*=0k4ocsuo>kNk} zesFGNeKF;^Ck~4G%p!bUrwmR=?J5NJ-`gDvp?QxaOyR=dzT3LYdb{@cutt^-)2M&( z{atOZntE1C>_t9lu(;hI4~pn^rwXAsz`CZcW3(wpa&co9Yh-6SV8h7rS5O#!I?wJM z8-~0ZSS$D6=-7OuUv74ac-~pi%hW^A4@H1xc#b!Ac9^tPD$`g<*J?;x5_a8B;FN`@ zrj}%4s+-71+*wrnZYqoD_aQ$qxfrE31oyv@f7y3dMX!HNJB~s`bavI(z4-|%pnIX> zNrHn7Lq@^XH*KSl$?TQ{tv4#!Leg=^P=n>2gyjFzMfmQkoP+xW;~;NxOysTm_7Q_X zd5WcsmucFxLhZYt45G^``1FCd)rS;;?ny7rm&h7kx9zHmmSP_6QRrg~j%gIXB zf4;UC(HTh~u!w|bf&b(BG0>p59lcUt8s~|}EsfK?L+&Wxzj(3FJhlS0{x4vB!a#%t z{CAjk_@SO6F2;*EAzH2i55>R!1Ey#J^uHJHil({#2@kI<<9||q?dcCloKB~?ig@Ex z|HYo4Sy{Sn>lOO_NdLD;+^Kgk2mceN{@387ndmKGPNX1M?I1w>IpDGoY@DGyI|x|B z7|h2lQYaj(yIFRWG@RKMt}@{x9(0MDIja8G%LWPoEFspHFNi|h1_rjojXyvRVp~m=YAKnIUsL9UKHrY>S9Y$@h~k#xDw*3rscEik{T2BT;e3lbv$i{i zXjC#6*-9lPQk6$9#jaO<1mOC{e#wNI*mH|P$-OpGGjC!h4pCXK+PoVby(~}&;S!tLx^}s%jlX48y+>y}=JR-x_i{eg_wEpz(}nfgC6=UYcju*)lrZmZ`;GYD z$Pq|GXJLAJw%B2cNC>?(gb*HE7=(_7w=IGq4-u%otzEtdPWE-;^?A0sS~ zc?2e(mGX{wyQ2}C;Fq&`Ux~@vQbW^?;D&}RVsH~lp*Eb3sXJdi56hHH#VLBVQ$^oE zHkuO{F`vOGZ5s7qz@@ewP$TO!85uGW7VJMYu0Ht$;HR(L+#i*vom-a%t?`cg!_lhw zFZXGSVDevyaJ5S_gP(v{8yfvhxYone6%Ld&@gmdUL~(P`W@39kF2D61^kB91W zAv*Z?;eeuFpOA$$St4y>epOf%1x#_fw^zM==+N;GV3Du9RA1Ff6IEIXfYkY5`BpTji+&9`+rsI-j@mOxuk8z; zq)Xo!02yG}7ulW7O9A&bCtJu2s}$aePeK5xfv#a!d~-r!+ZArS8O7vDg}pIFMyT(g zha$DxAE%WL0!wqut6f7RHWrBE#yUE00~oU!dqcA3#taq>Xt^%ImkG)`;A%$9wes*~ zZuQ^KM<<27CVcJ7wWye80BCc;)`(Rug+6iny{9AmiIX=-jtO+RRGU-<6-(YEp>mH zIuP2>o0cvuJv*tlxA`6(vmF*kprp(_)A&E1tClrcyz$XkD){S+J$XX602pfPyW92` zA@SjZaxP3PEL|g`d^gS3OGzFc3n~B0*%mtxcVh_Nrg=9E*1EC3h$!IY;cdDHdYcDX zvKSPqJ4}F%>3js`3s>asDE|3n7v>GxBBPPnSH5)6@uJyq#c;pPuqKhUl~UMwsvp;_ zC9Y++ux)oO1t4%`Alt)lhNbvcS?Pyg5PTew)$aAP+Z}`~4EH^fjFU3~&Mp3KYqGkE zwbi#U^$dL>o3G4#^_TPCpeVMKDs8=3l6om*ia^;#+0i|d$c#=MpOHYzTj_$inA_IHQ;<#&AJ9C{6i{Lk6?bhb>ME!g@*M9wkF8li#tC zO&*MtT2INbuv^wAPVCCa@P;+*=$cF=u>vy*IP8(C-^(Zl8WcT56iH&6CGG3lHvWkU zJ$opyeZ2Li-um~bvhA?qGav*P8ynlRdhDP3hXkM_V}=3PFV=EIBEZb=mKGQ7mGPt` zp`nYt&9lun^%kQ?JAFB9LnJZTXOa{Bcx7;y!a8{3G2^$ZAaoLmZ|K$aO)v4r8#wVc z(@UVNnQQx2+TxcP$U@Wl>GzU&875_!-SyQfZ_ct`zDk$YN{uU?Tfka@cMt&^0U@oZ zO2Xgdde8;-`$yJgh;izM@8Dox2KCqHRv%h%AGfX==qY@#QMW|mk$Qt+p6d_ANZZWCpvXp@M=&WX zQK*zcbGe3gPQ*3p9Bv+DvCSN>`HXO8-8D=afSWQ*?`UnjZbI9l_@(N|`l}Pq4Vfh- zBvi^I}uZ5v=2X~u-c}> zSddPuuZdMyU!Q1nbQBkiAsS8m-xouAYSCS7O4BBh^VXD#nwp-O8EA#}oUg+2>;w~_ zr=WiqHfX@rh_crIJdn9=W?L`vY}>n`eul$Q^(6MoQ}SZ&Y;;_NUE{EyXk5C7()ja> zYdLzo{&1ReukOB#Wdk@oKd|@l5&|DoQk;_y3KAZEQ_BC+@z~yty!QP&j56S>PlJQo z@81h1!lg}dzg(ItaOr-&^-2{q@gP?W5_jIp^RoyV%k9bZQxdSFXnh%Zc{UT3rPx-w zyG-!X3`?LG{N}~1{ggNuC8mw%=}KG|6ykCuRBI^#7CmaaWEF>to)syD(;4F}H`8Xd zLeqY_{VFShlXT%E-RAP)^o5ZaxYm~qxs}xc=EHwlET=&*n2+;Xx58f@qhp@M%TFUT z;<56mj6^03v%&I0E-^wo+m&Cd{B6yGwo6JD3gQe(NpDGMhFtZ~TSD1s`SJ34;ch)v z1x=RGl-6)Rp#?}2*gs-gHaQgDOV!%sb|pD=-^L;~{fe@)+!=~#H~BmF+eA%EVa|cU zj(hA#1Xr+?U9_*$WOwE96=rZuUQSt-yOLs`jdM4g*1Z!Pl=knSaj>P~(FL;eZTa*; ziP4d?dxk%&)TtIiN+Q4Pvgb{T+oT>ua;-^YXfxq##3zNm?ZQ+`BWp>SS*fVdjh@ec*OXNYWAI~B9)pyS9YOZ08^%2A>?aBW#^>$^}cQB|e z9w(JdMFy|VO9xp(ri2NQ!oDE;R+P(A9?O3%H;2nv;CnblF*G|IpAQ|Zha9P;DrIU; zgy^5Iz>c7E_XhvhV`~MYNoE#R005{8T$yI*>m2lN%ZjIReSx8nEUmb{=*yLCuFVfZ zNum37e~*fO>{lH}{v|hhWvZ03-BZnM1-;g?(gD-E)AQs=EqO{QkzdJNJYd}Dn>cru zEM+0?r|*f$U=IlIB9t}h#3#yMV-JZpk%3!j8Cyl> zCeZTc7Lt+`bqv`<(^%?GPFzFCGD*tcrKT%wYqouT!dxB{;ej)yGhAZkM@s?vpyC{o zoS(nql~$_RitVZgq@+m&TAyc~77OD8xd#Z;X_N1rGmq5%<$yGzO*}|j{SnjGx)U7S zsj%|XV!>7L3(cje!+6g`-R{5&t3i^7eOM2GUdOY_+J`6C#jR;EdA&i2Wl;ky0wExe z$`?|3wd8@ed6UhqTRURbfJ6(jM}c>+2l`@?7f5o|(?P545sALeC>P1(rM9|Fbzvs6 zNRZ43pT7Bq@H#LG*gaZw-(ikUFPArcw-|S2Swid4(H3qxklp#`{=NWUu>32*TpBEq zf=`P?EeHpI$;|#^PIN)Wjj#sXpVgVJtN&CeIxl6Z+oZ-S26Brea)~?SZf3N}{Tzp7 z3P9rhA$N|AumkLKRgd~*U3?T04@1kQQX<~Lq<<0$J0k$W{{dfCsCmpdZzsu8qtws|QLA?fkGsLK_uMgb5{`2dBsi zoG5l=@g#t7o?u+{ih}7)C<+?7-lMu{Ln9$~ii2?MHk-W3O4XIcY<6WCY;6Y=i{{O~ zY>|cQ?|x$q#W5SCsg*ZG$n;7ib2PfnJzkd7(?VJ>TSygSlmN6~F-!>Ds@i+|1d#)4 z4Qq0b4UF~hk`~Mw`30!BVXSvflu^vKe6@d4JCL~K;<0sn1_7vNIX5hnU zVHUq3sQpRU{i((0;T4`p0XCxy%Dxq8t;KO%^*MkXkDN_l-;_s?5^*ME59wW84i6UL z4ve_b7^mLz0BUcE|1KqI$ot;~SlWl9W8~$F4XE}k*W;XVuCo8bI5ZADMfU!MSAw2A z;dd~TWAi8)1-7>y%68WcZ*uW#{)Q>GsP{l?wk0-DPs%*7^!Y6mVNe;@r-5b-jb~*%}x4M-=dD=pY=0OkL4> zNt)l8Z`D1g&%DxWm6erkg8$sT$8Vsb_NL<5u)l`#!;0kMbYbnkp7}OZV2vc7PTq8h zxN*AM*2Fupl%dzu)yc`r=QQ8{2jVqYn>c+ol@5O%?bw))^Qq>8$NQhfdc3}Ow-r)< zu_nO;_MG}9Loh~t0%3GM(^*-2T{a5t zg~K2r^KvxJKDG1R$1Q8uztlN9GmFTOPWf>r^m74TXGx^eQJyuSSPZfy~pIUR^ zw60b3cd$}!EEg82l>r8 zH8rup!EnN1*t4JxK6s6q+l`$a5{EN@nTg3)GO~auB9TK&WmNu;la2b`K2eJ<&u))* zHci7v!otG$3o9`($wS2uuU+dB45_-^`NWcorwPk3Q^;)SzeC&Vx`{`Sp~`$O^I&yG45qjApmCk21|MGiG{BfWAX2b*BSaHJzV0F^*1I(-k8X z)3FB0c;hFX?B8hNIVPI_1|c>9E!a(Q7J=At#8wM@!lqEyjx=nk2mTRp_1pnfq(m0d zx9PPVS@y_lwA9cr822}~Z0wOmqJ+;f?C9X)Z}NrhA^8zylr%J?oSf=&i4&H( zpD^lkbBU9alRqLMfhG5LcQ^O<0|NqzW0cHzXTx$&6euZQ&HZxv9%=D@@nHg7Ut8PT z)x|%>{$p`X_@Pz&C#mlz)Lj|THiC2zq+y8PJ zd4t%X=xeseB)qx+M*y-xjMDlzwKVs5^*HzV$rco>t<0rbaSziDn41VSD${if_#sA6rosG8dS>y5fF?I{ZCKOg5=Z}q*`cXr zSx!}z{~;WjeQcbJmaM3xKBGgNVQfOYjI5@}(>bBKNhv5-j9OVvPS{ix&0!Q+W7cmu zOJ`YJo}ZoTD;FmwHdIs$%#;-p2aR4UD854Fno@aY?-dkgUz)N?u)9E#wZ+GBPxojF zhw;;JR@pq1DUX}NVhGfgbTm1o#o_+D;G3@<%b)G$WcreDWrky;HM4AwhD^Tw&}lz6 zS^kx8`v6k5UP6SjR)FN)``%^)b1G|-?hn$67Xi(14ycJs9l<36oS$kNGZYma9M z1T{Fmly_fuoaqM{&n`vk~wvdt2Pw zMWUJr)>M!*KVss?o@wcqh=kqcA*gzZ-H7Up^)zbw$HyKCBTd#M813KwJEDtXi>8l& zI{&r>9%kz))yZul;VMDsV(I`l+I;KM6ryJ8oz%#g(}!bQlj%DGh*MXFH|(`uuufG+uD*WoYq?$J-`qsM z*p69r`;98ST(COK>t*C5#1Qgwi}g_N*%46A)x%xCHkLZKW;mwpX=KdL_rP*kMJ{{9 zbKS%5h)e!;@60y7%`w3>W6^DPit71!68a|Ya8(ATMtzFwBaVeI6j~2S@rRUI*2omZ zr+1Y{TYf?}Z;?E0@aMD~8rw#AZP%7*RZ`?7<;QGS-Si2pxL?@Hd2BdYU(Pk_NM1(7 zDXb>@1Z`)UU}l=;G+erx#tR+bXKqW)7ci_%4sag#Xn?D66x=;8EW}aHhdXhbCsa?6 zwFYtJKG z`w;deUY*fQjIPcMx4YPxFdL3tZT^SK8aCL=^Rkv zYC%9-{Y84uLdml#Fu$S%i@}g|n#}+6v0-`hT-#IGwtG0<{76-Od?f3kuFfg?>CvxK!B{ zGly7LS%xUY4ntqlZh%dWUrLmS;H_clg8=~us%*A3!JPD_pgaKzFTb~E=K+=F+3eZ* z$PmGz@7>UA8Z?&}t5mZ0S*3n*2it;xo5h14b>;=r&S)L#cC1*gK|5Ohi36V)mZz_C`suFYmk8?z^le%I8tqP|L<<{*tYP6io>Zip=i7qQ& z%CI4lD`P4b(_3s`ydqcJaulGLl8ED?r=!^bHi`3v3Kz3Ypk1lEtBj5YQ&ddeDddyS zGX3p=_k_V0vkzm}My~;Q+m88FX8x{ud|vKgnyXD5r984|9VvQX49@B>}Lb|&4p)ZQ}aWS_Q%;(QKlF2a5G z;kP+ImVU`XP2@6VBJu#y(Q|nS^eB9w>SL12;xP}tabcWhN6FwHca*GTLfyN>dNvyGn193BxZw^kK&o@r!01TMFxJ*G;ea0HJ?&gCSwJ=n6Z2~ z_`OdPUBkjBgaSiIIFFAe`Af;aER`4u$oXS)(!R<(cp!Y7gmv)CIKftuWA}~()(VD5 zt?OKhg>fdG_pfC1V!-`mx%N-7{4bV^Ls=d{xwHETpD}8}%h*hEs7#e>@I0<2w+=3b zY3GQy^_*IpdLNG*PG_?wDzQ5+~G>*o+))SYQwrHz$@ilpVs0C_1C-sH) z{O}xH0%vkz$|54-BbqH|=f~m|IiHon9p}|lvT>-+dv>T4x)+9^$=1HMz)_k|QRFv( zLYMC?#J%HM|DL6!Pi?a)azIzfP6FU#_0@D`+|(SNZD zfP_1gRaz(Ngu0Jq{w<%0C>Y-}kT6H#g!Os>cS<;zwPMA5G!3TwEz4$PB;i>ZJJl7jG>1>)PQWhN%hg$E{_rYNv=9 zktSkdrt)($p7tqxP^_6TVWco6~9` z|C?Ge>?F-s{I>FR@5F*_d+0iYdN)8QMD=cR?e21hZiSk&o%-XN=dSYuakTSS|ED%0 zYsI(G>Wc5^)mhIwTi*GUA6Zyv2?fD{gUjoHnuV%o)BxVsH%;%24g4dFD8&P&M!BI zE45z8A!|?4TCT?2^oQ^d+ztmduZXB`RCfc%_Y@iC!d@&9ZE=gVFghOAsNj9wewuDa zR@0+fwKyBVd|i43&yslCq7}fZVa_lHqdpx&TSHnytG5y7x~wR=?(y3FX416|cL1yj z`&u<;v%&7Cj`l+Gxwi4ehTQyb!$!2GYBG85%1Bc z4%+1B-lX2&7$*jQ37yrhVdF`jNtLLobxAurcj(G23eH$n|Jk4UR%QMOf6(YCSaly$ z*cC_L3K~sBe(*;ykp$RE~?&(OjK7(<%4TZ z^VahGd#;wV0d0}KPM_mhB`(=sT@?Mgi=8H0>=b{SqZh5MN|AoL)uPCjcbXQimdr)F ziep;Y*uUtjZb@<<#t~3k18-z15)%_G$v_dIuTYN-J?C&*&hP?`XKc{iP5)rmdwG3v zD#VT~<`l)=Hi=}8;J4vBGX@q0&s}jADowFJW*Nr6IrJ<{`ENk!?nz-Xw9&RnF@G`M zuA&`_g`M-?n2F}ou=pUPNSCSoIwsXcXS{Jswd;f9)b(J?_xtfr+mc}kJnt53C)Hxc z?jaaep51cs)apZ1C#J*+Gg~K0-RM9Z)ESnRa0hfAZ*MGgqdqouj7q}|in`)!O9>Tf9?gcS zE-H%X>sRZgrP*%?ny#*k_P)nSz z=e2bB4JwCqz_XH%Ve;q%{0MFtIqxK&|YkkrDUo0nloYnDs`r*85t%c>zAkXp*@9}QhnR_ zUPXdhh9e%RkN7)?-a?}$FwT5-O~#`xZzGOLt>k+#Rg|Wc$51xtNTt<=@?7$?0U=}P zDruoLw@}6t+iq7p3ucpj(8ES19LYS65-5_6VHsyRf9aIh&gUf?K6Vfn%^lY z1_p-a)X-tL-6DGvO-RiQO7sO)o5gz~j75hI4Im#XZN>{@X!Nawyx}7HvBBQd@0RO@ zcPp+XO1Ok;19+59>LWuD6-UF!s*SYai47n17>CIj?DZl|V<{kvo_C^g^=2?`NW1!< zSTKzl={Q9SYHQk?U$IX9z%q76#phCJq($y7uu?vPOqeJRDpAd8mC^1iH`=Uw4CvX< zdir=#)wK#vY!bFh1>hbq>JVpnz|a%UBy@b2rlR7)qMLv>R%Z-F1XbE9Og|~Q^g&}{ za&8_Ti0b^rLH9SBZ|2y_9J0QF>++f)wp0ppW~Ed=1{_y9R`h0;eTF9!Sz!o2^}IU2 z&daDn6j$7R1H{?IY-s$WY#g&i3C&ri~w-KQkI76nw)skU8kfr>t-|E zib1v#Q^ILckepKDlfl{c-02&B)x$?^5C~^hGro}rR)qA*xzRR_msq)*;pR&oS5}tx z&QD=8KIpi23p2Q6(n~@ssU$9Kg2^I(@+Hu7(M9TZ3!K|9^#}{SM}!W)huFv7OTpm1 zNYw%nX6hy<<(n0udgo_8AoqWTmW1t?9r=h#1TQhWHojUFj-6^<;nYLv{Kli(pRuZ( z_vyi{IAT{+CX!6ck|hz_gjEJ>N^eu@d;9mt))Si(ot4j>&xck;V8za))*L9u!&Z() z*G)VYzavt=N#zfQ!on;noGz2RRG+M@$VNgX>6F%j{BBrhqK)aE?u&#&eVF5~!)l89 z(tUC$-wO0ISZvEcHzzr=n@pstuFLRSYJB1JnwQMh#U!#y99Y-E01%rstfykL#U>;i zrO##VJf-KJKuO~!9Z00TbYvxWbE5u`py_Amz=6Sm0US>M7-Ptv>UghQ;Ev?=OyVY{ zL1&}zfIxP#9r<=)N2{r$bF?P@{*a?sjmp}30z6893!`R{ilsC6U9pd#i;>Fa?1rGQ z9a(A@^o2TNvoL9Kt?)?fU<8@a^t=D&A~Y1#wohvwZ61zS7lx2?5w+b zX?M&+%_&~O|IL?#Vqf3){~?~7mJ&=~B9m-?o?TB02GrAE&Gsj41O2NX4QPH)DwoY5 zf{Gn%!GeN<6U)n`?>4nHeYo@y5fPhoRYd;apc^W+1@h*Q9PM^b6;)Fx!3-gi=7k1= zs5n`v$CLKCt)kWRvUp{ZXzbRMf>r;1{D9WsX>xkFjl+Udy(&j|t*QE!8>4rsU?ho# z*(B~;uaGrye7kGkHs7RT2jr981E+~N_WAz6z7{S9hTuZkw_gS+?=}m0PoYRJU%`^Z z-Lmzfm(IYWKAYYz@McKI>!a$d(|f&1@9)_+6jFpN%^0+)>^2S}EDa4Jhq2bs7tt2i zo(7_bnquCsAYQy~qL9l;TP_BHxa&Otz6~lhyH^=r(Sy_Bvl|)t8&Be6WOK*E^ zmSx6o+H#M&w9QS1;1u;dCSujzDkbxDcw@su^Fh(q?_M~l*OYNZC%+JDJIT;?u_h=^31 z9YL13)TW5bmHPct^;X(Xawj!BaB5Q=D~<&b-Rvg-(9Mbe^=wUiekJeyN72d8fWTm# zwN3?;VJg@}t{`$tp0EV3TxCH6P4FQV$ zK%$~d|JZk|`I-wDF~w9vwhHNREHw_O)eM2dwOySn5m7|$70h(RZZD)$!Bam#mN|nH zQP0BUJ-PAG0I$G#%-U7~SU~`DPj5FmrQP2W%+}t@u}RzBSO1QcFaK(CP+U^B`C6Jj zw-TTK0jk>{ack8Lva@Jjeq#N5br=is)(6W?z-t8c_A^FfgvY~;&6x>zEzOq8Rq-1Q zpdwLIn{V`pGg7h3;-iOZ_!TZwplJ^D`p#5B30?!jaI(D|^68efC3To(ccW1SMp>B; zCdy%8gah9By~A|aJ6Hrb-HLi{BjNaUy3}*!MxC{INh6K7y?sv&7g5b0nZ~ldYPGjM z!N5ig=ajY`EOQOWzVc8I!W;_NEPIaP=!QajrsC+563X=S^ri!O)10&A*Bbf9yR8TW zJT8?Cx2nfS_Z2iBE%xH6qyf#&{xuAFL)@-3c|DNB}$YOdbl$ImUtyb{5_@7X4w z`qy7syM{z8gjVug;K9;SUW89WuBiv0&T4IJB#F_1C^RS}1)L%Dz=?mf6`7b=*z9z9 zTUAlv+pLD3a&3#DCJE|DFv~;>AaMNyN%Brh`vosz#S(u3Wo310~@dkZq+% zzTmK6to@Z#Vi7E@<3-=|cU%{jl8XNu-`Abj&k_P4W^T5pUa!<(0u#_;v)ZOlRfDbO zDb3L@vz`r|&1C%ZPguY<`~Pf1n2y~0AC7e_@&AX-3IE4-LHvL5AS|xOv*Wr(yNtf6 zDHM5md5=0aWjJ{FqLPwnY1l~*B_$;`PR^r(v9RQ1?BI})YkB@h9y~m}y+^i~gai?N z{TxrRz|-niW~{T5kJrIcK}ktYNhv=ACHo;XG}Qh<jv1tQ1?Hm`113_@ShP z-q+W642h3?}YZG}F@F(QVX*!7tNZ70M@x5L1dhv+(fF$6L zpxg2D?Y#y;lN6_}p1%Gxk+*=E8AbiFZ3vG@V41kIwDD^!Lt)bht?3|OV@ni`Aw973 z-EW#LQI^-zT7A9xm7cZR9f>M2$X>FwwM}HT(t4HqTAO2hcee3Iu4U)14^1qx((jRy zl9K*xp#5VG8hZMu-Vlu2*SYR>&iGddmG(DT`a{i6lKcH?J@F)6o`PvGHfOfvYi$oUvXyJajN2CsOEwnW3u5Fl}3c5dH7Nm=7%?) z{PGwvzUMK95#m^3YD_YQ`k-fQ=8G`l@=Q!db|n0YI~i~%1P2EXjE>HRy)!Z~8AxV6 zKjuvhFfL4KlZr=aH)4)jVnvnL9Qsu*Ux94h2OfJDb3^DUi#49*5%pL@fuEtvqY?|z zUny20j5$1Gt8AGq2@I9U?jZVN6=!NK*UKxvSS`1y`fN(IGb%jqyrjR%BajNCsHoT^ zb+c$9vRAydN&M8{k9~lc`Fw0mi!8oNDsa3IF{*M1rCB9AIn2>#ut0IStM_eR_(8Rty z)adNqBaf(fb7p3)DaiNhs6gu<+5d8$b>H?v#&|(Mdj%nEQDrJfLdt;P9q>E~q7awI| zrUFL`y9d-x^fgQA85xe9=TtKKC%1_Sk&JRct7gmmrW(ZLm9Xs;v^1b95Bw!XKiKWK z*l5%2S!FBDG@umMcQXmV;37+p5TpeL$+WT(5G+aZ)WdC{5Bpn{JvTQYv)0Q2F*qdB zPnHNZb!&izinO{0%WT+$>IdoA0mE{u02Xsc!o+vVP>Sk6o>(EXWga9ULEEn^F?wjk z24wkia)}bQ?(a7O!lS3QyH)nySHX)7a42c%!r6rD+c-J)6wwD9`+8U`R0(Bt1&Iz8 z2!5bLM7sy}lmL(C>Y}8yglOD&R(>qar!@TbV|Bi6`GWwY`5xoRUlK^)95E+Hyi|guI~_W&bj(SombfVAwkd2tqRUa{t^&T_)ie zwDBXnc)aNXE+SzMVi6`O34KuMc75$oBKEllSFuw_lbM<19!PAm#8y}*``r&t6#*d) z^W01Nd|%w^tPkg9JE&Pvl@lGMpiVYZ(Zojdz$zt3HHQPwn7J^%-E4mkS1HV1T8%q^ zo^U=vmZ;SxGjL>^3Pn$0C46D@23K@@RxvsuMp9Rap;0wpp-c5Y75HI*}MsbXxHSoQFTWTR!R_zJA=wR1E|9?RxOPcx4sfF_Ld1!#EQWeq+q zG37|x4AwNhlTDC(>;BcU9+*VW(a)Dd_1D!SNh){mvJWs}HFnWNIo{FZgj z`T6`-U|YC-Oq%!9XzR0pJ8AOg>rFeVwbq1DPS$G1{78*}^X21OABQ;I`Gsc20j`XT z+}2XZ#Lij)F6$raLl@&(+iiP1n|m2vyL_tOyl8 zMCKo@E_xRls^ZpWgbkyk z0=c}~kc1M8)^$bvD6BFmXmy!4!n82oBN-RVle-LOY;JgsZM90b@9g2vRoN)B_@kDS zHdu1Q??N_=Yr|p(Zk=~`#JI|_d?OOL$8J`gk+H`!XzIVx_XYp-^z`*_)X#g7_@`@V za!4gebyS*!=*-EEK;=X02N6^ih&yh5GsI}nDIkz5H8b49=JBZrN`dqxWxOqPSEqPKAn8_R?0kc{8})b+9vU1@lxp@G3_^&HW4^>A$n1)mEI$vaxDC!*v;URYf<$bbB0rE_aNF8Cx>;4`^X+af z@+gd@*YW5}heZPCiT^{f))gkIi)XiwNHP6h!n!L8MpjuO_5+zRj)Iny)dTL;gSK?I z*k2E2XzgOx3|~Nv)z&KK#DPXmA}qM!>gm{+jBPE-06Vq050kKV;|>Ink2l+|$?MT< zcukpyKy@^Yg^IB5omj%tcP=ew+?lP;^ms6_I;UeAZRc)0p~ntS{1;8VTD(_THXqy0 z&cnj(LqZoKY6aTNx92e*fnRaUisQdZWiD*qwN?132)>3|B5>}gZl5{a#x3-imaT+b z-Q>o7mO^KeP18P&9?fkiiemJtsdJnYZxZ1))nRG~a6Xz;! z!DWI0nQE7Y_0?4=VX{pw$n zih5tqu3TI<>m_b3GM|bkXzsUdPO3Ocf;Kw*f9O>O)arSsg7j`Uv6r7rS2XJ5Xwh4D zA*N45N4*(a7pKzpz`Li@t5B`h4P=#`hX(t{c0p0tPK*UxCL4;0NfHL>@ZVk95sJ|` zTE-tgUA+~@sBCLL4iF@YWNx!0p$Wn^+0v(M5+*C>;OJeMCT6lE)Lyz&e2ny6*@Dc%9=$4+9SS2Su-#JwWh~*)$GJNa^cWxVf!J; z*K6+M1evy;$r6))T-E0#8)7MkR&@8YBa@QydiwjFD!`{pZEz^^oi7*h``2eYUvnzE z@e~R9&|oYad@v6&>ZCt8uTj-|y8Cd#x+L+*@f9)@K2>Dp8W?3%xgcJ?5ImRaGN$oO zdjg}_ik;k!SPUzFEGAu6yPg=o+aQA6+%J9`&4X736%!$AdIWxkLAn=N5KcZrXF++m z@0#(`s6R9>volGczkg0H;|=e#!k`gR9>Dyww7Ct!%fB``%$26AWfQ@>kry(xRvNqvDZm_*$M{9}R;y zyS!^rf`W)N_&1Msd~0iYSE2yTf6v*)iesgNqEz(4=;LzeRx;n_!q4_&Zqj*zbeZH> zVxH+xW?f1-MT1vpcbk0GEaBoRL5-?WVwi3Wp#di{1&UU?O_w>}B7K&6*gIO@93Ge% zh8;+8q;|b@!8-yq30ZxACGUT#5b0L1+A!c#EYzr2tn3yHJs+a~pvY9lkSvy3TWR1W z^tDFB)%A7_Zwl2$1};23r2uIu<9XmA1Jy?M&}mvg80=iyhK9a~7uLU-M_JoH>X6(N zD~hG;^xb);zIZn=t1J3M#AI`%?RDm&(7KN0akf8?vax+MWSe8AqRWMb-Qrh-M zck3tLzY+z92)YM=YsTvY+}CU%nLZJbqEXhmjC^CD%qTGrw=ApiqNXp|MEY}aU;@&2h?T}B2nf&2p^Md>! z>|8bN{mp+b1ff`{u+J*0d{S{1=x<+9*U+*(@RrqnwPbtYO!dR;*6Gp55r)#wgTZ80*e*fJURFPTOkm2Fr0;RC?+u=Y z7Bw|Vs_2F?+PtT-vPz1l{65(5PLUMhkh9R+a@J$n#J9`X19uYk(kq`4hn9^{d$01; zlt|~#F~`JWFAwoe6IK5lA7@j@a7CTlB#h~9*(Q}dKZ-6%M=wRxr%xQe{04gtySKbU zQvi8Bhn-^fDQ{c7Q;0V$7;q>Yq!XkHEAs)rJIbJUyH}ZJb=?s;PNxWSrr2|wE~|K# zII%$;<`{`pg0>%$u7qcz>NM4V>XaCu>p(R%G8z4-WS#Y6(xlSRgVTU9Z! zC5-J6{-WxnE>3V)XprW@S;6aFTxElMQM#Ai%a_I>!MJo82dIQxX`j4BUU;bMZ|r=E z$)Zxt>;5&}RleIc+ho6+KBTxmBC)cYnisnjuc}yVC4J305{)zMIx6yWUsILYK6)rI3&aa+YIpk~_FloPR*^h{ezReA9z;6Ro;U3^~V$_;7XF%g~nj zHeA{Q!qguoBB>}(P~Y$zv>g(tqSvF>8k}}drWnV>Ff{aTe~oJ5xo|~g<=T?7-J9n4 zsz%HeuOBxDdU^&MdjZXfsowJR%H&+dMR_d3>AwbEEuY5Au~-!eX*D&exINv!R-*5{ z0?x=@UES%}_WpVLv`t;!Pro4yMXaxOU$`f{E!o#WzFgCq`CjSXDbu2 zAXSiz%7WMtl^|od=c)ZT+dEc^W;)xpB|NQG^LwYyZ}##{P)bfCvp&MI(#lyIvxQ%%_GeA0QrjkLxyT%b1 z>)oByE7B8zOGA`5akQN@;bZ87pau$S5C$AF&(?^WZ8K6Fz8)F=o4>!29!3%9iFsO=ZZ1!VFyv^j3+3a zsmgBhuIAesx0_?8@16*6C~QPM@#9pyn$P-cZ#fo&%X$3uM2in!aGN*I!Owp5EM3yc zsI_)O9Bg(U`Pt5IslM5T)lHc_Yg@y`aA*v&Tn=XUj1g(&%pV~_EDc1eQNFc*%yheH z*}Wa@>A)LkOt4%+<&&8A2?GaOp&=1gb$<@4$x^}J*ED>+bt;+>|Whb6qS#vc+6?PWeI?e35^q4dI zi)-SVU5Qc<^M>AuguSD#l8>L2P+P$)Y8r%|SMzo8V2p}gU$ma~70<37=u z3u9}v=iwJ`7?`!|V%FC5>Eu#!N5_BtBv<<47LQxH={I(T8{{;fn>(whf!$JM>~q8Z z0`pQ;)D;#w*7{yNVY76irJfZv0(>P*K6kZ?LZI>Rh>KAc{_y*4Gub z!rVPFiHC=TnpQk~dgeQ){*q!VGW@u3k_7g~>O<%vR&1=EIPr|S`W?qn5xchBR{!|! zBw%l2U}H1wi6BO#If#+|{6=bJ1O$RH{v~{sQWyYuCf$;pNsqb=B_j+NHO*<#u+v&=`t}L(pBetFH<< zcG0&s@`qN1F&35VQ|7M*Wi5!$^)H$g4duOmU~i22btrBzG1q{)w&?T5K-1@?PSl>% zLQLGn&ae^SJgBQrU&k-9HdFFB0q7S_?|auE?Ae|`!Th9%hrlUfjdhMCH5i^sGkEx& zNZzMuC46*D zw6M3WERyCB2riM!jiXS=NV4m|l#|;^n$wwZCaw02v&K(auXA@qm%svmM zB+XtGm9@P^^}i73j@}_8152oK{ep3A>nCD%3=wN%2s7#k6wcq+M3%g#^Ec!4-`^%i z!)JAx_d-#=_5!S_j)ON6Dj=!3N5%T=IDCsq1}O70Fvv#kN~9kjg9XaP#ZaGhM#L7wonh__B5GI5iBU^A*N4q$(PuE@W#T?Z>}9I9mM6n>rG7 z;oZHiNTWv9HUAd)7x<-=F9bbO+YX2Dr_8~>v#YYN0Z#c}-q`<*+!DbOmOb2D+uG_{ zYxjrqe|B63NJ7VJhwCqt!NML_wh!=GqwnvcEAf|=w6%xMw+B6nqs1g8Ujl62^zN&1 zr-iBvGIDZ{+-NaH#n*ofqkl+04GoQ>mF7kX1fS&ESZ4yO&cJw%1VCmw>Y16nSJQEd z_dZyXl$6weK)?|UR1F}H(so$wY7_(l5!pSOd>%~ced7!GR=uKdmJ6K9GuaLNfE_}@ z!VGsu(*Nu*Ixrwz8y(-aPOYS(@>NMG?hh@>32>RP_an4c5(>aP{=*B zyR@WjGMo~anwlzd{bz81m1%NeAuKX7QbY|n03#zc7ZnN%;5Gmo*$*-1ann2X-Ew8DL6r z^Yil$rSg^mmhrRAUQW*&Tdz_aom&0&=Q^oi`scwzL$b%s*Sf&u&`C^6>f7iJ_jqUf zkA~vU^YA~3No*EpDq$Jsn&BN^$RB;Z;g_wlL_k8C0wjk{%c0qMn?t|B)GRFvOG@(_ zG#|8P!4~FsX;ILLiK&mx)^1)!mcKtu?~e=$IxV}Cq-ZJwUoOu=S<55!cl~zlRe@DC zlt)odNi4EuWqN8xVK#S$|3Uh*W9kd6W^p=+Y@V~@NhYa61yl;)stB>Hj_#X1F9u%o zSW9PD3oC_OfKz4?bUf{P;Y&h>FKEP<28OFzOrleiCR;wDDu!s@2!IX}W(!bPcHr=Z z3)vzw^OgQxmmwkD70Tn}GN!VJeq7k0NLDJZbYA_vjWNdgyEo%b_*1j6jT}>gN(Udh zZN!pZ;b;M?oye#*Z=^W_A#j`jw8<9k@V?l3hV5O~yo31*tZmf!3THe~*3kSfa#uA~ zqQ)ta4-A@QSC>Hcd=gxFdTr6hn-tI!VQb}Qh>_V^G6KLkU zhJ2Ebd}nJDc`f-m;oJDXX5yd@#Qko_>MNx)9>*FcAQn2R^|zK+JA3Ga*$&+M9&_A! zFwlud;3&3njOwJf3PcRf_<~h3$7%v;=`uuBk8-25-^=WNPsuq~HHOWUs#{}b(^u^N z@xk+HJ=pVRy@1h*dc!O<380N7*{b(FU!n zsPb2Folp_H8-1Ku3{Pd1`G@U?Kyj$3u+`Y_4;9^AUR-KVX9U(tPUjA~BJ_=jG$M#D zbt$(|W3HF8o^uyiH&mAOyxGGV5R`Tkqn0X^pGv&L+bbCOz}_bEgZ-IYkUC+4;+Sp& zPI~1jMbGKs@$NKzsd0!uzAEA#OQYU!;shoCx~fNF@r(x|**S(mqi1?l63NbKp(h(s zmDjrnR?`zrlgO;kjLo53Tc&*KkfZUmwX(?0%8Jbo(p*;BuU0N|D(AB6<=NAGd|ZM? zpxL!o)+g`ei)(6UgJ5=?&+mZwAxi$fAK3oF4q|Cp39Cq9ec z6&1+|=-dl98$xE5=8CP80v6R#u8h-#tKX_FcL)hzFu(4#Gm&Y8p z(WFg1RDiQ%?Hd>9oq%K7e=AzAQyH!z%DRyWYH3$v18t;0=suWFyrb|m78x|Oe-Bdc zS}Gu@5avl}7UZco9>kuny_2J;J7%PCMVOmBZ91$xIAYH`8GkQJ8Z@dh*_+Aa+7~cc zy2Ne?Eu${`)XE|!L%vnA*4dk}+e%q#6g{*5>7||c0QP-b?da;uFr&bxXv zG3_74cCYHv1Y4Fy7j#^3?9EspnNLxe_VXVvEkciU8q%S9rVh8OPaprz!AwB#e@E0Eb-HS_=>|HgQ? z2+jz;9Zp3J#G$V|;!GK|P8xO?6)up$*2WhHrzU%s;xubpu|z`Sw_bP{SekU~gL+5| zf0+2^*u4Mb)r+&C6;UycJ0XurrtF3ZRr!wUQFI;wVkpCu4r73F7qoX{(F04pY357} zf5*(V8g9Hp`hD>j{VSI7j-@a;nE@ z>B+-(=U3fjPdGwC66`PpX)+H7j%dM^cz9M8uIfk>k!39-K7pco8c5fU;KRB^9~U&=K0?HbriZ?&(IPASHPLhWu?%mqcP4h_-;btK{aqMa){Xyjm$Qw zW5S&g@;fUxCi}?o^=HotUbWuz1zE)_nDKPg;Z09<>>%vCm^m*Pi_yIaXD+tnpMi%9|Mx}^Frs9=q+I_yK z7*^rJ7q38EurPjnZ$^2Ja9xq6p1>$46AG$Oi+(#tBKyt)XZPa?Sk>9f?wquJ?=SXg zf%CZq15<-ghr6W_=**;Y?5B<^?qZ#mFyleBD)&Q*njU&>T*VS^C0doz!iM)zi809-sLJPrgixWH%j&~i;<@W)V-~qN>EI}?JVt6K7Xa` zCC~Tqa{Wt`DjE8H;GP@LNQrD6B&-SivV-?Xe=z@jhf!F02994&JpazLR9CUyDDR2& zdWfcOBLqrC^K&m9a;w17Y80H1fE&O5F|Ple_|>(+J$VrTMy|M*H1%Db_gHSjjmFd+ z$kHeIs%5oo=ntW|e@#~mvG(D6EDOHQb{L)xe3Rc@DaaU4JuSj#&*#^9X8t`fVKS!5 z-{!IEmsN1(d&R+;83IL$#sF+x^&wwXBNB40_u(t;BRU>@F$(f-=}W3qBcS-L*zM55uPEoA$cMOzGj{>7D zaN1z%)!lI|>$UhyY@EDw=MRx+J1YlY5*SHE8Fwnj&H3{JlWusa^^_MxQ!BmYr8KGHbC<|y9j z^I2_&5^0C|4feldL?2?4{k@3cbC5X}x8!Tb?T0?>SR_}m_}z6wskZ4_Tr*4Q10{=P zOH=#Nr#5<9B3`-aAdS}>L(23vMuUV;TRW|u{f|g`=dHVxTk~oy`sUeGC_z0*8cBwR z#kvq!TWO)io?I&&FaP@+-R+s{-)TkLzTh3EccqYp)=y44R= zD%QVLA2*5a`mm4CR(+bV*pjbpjwxS>+t+KS@$wLnMDDgwCR9kOu@P3fJ_<*uI5^V( zB9r*A{fO!Y>uUOKS-+h;8a->o;)UpvEqyg+vY`K6=u>=cT6cpRy80!&SZ{_lWW83X zQ>(#k!DiiwqSYGbvBB+g8gc3vq~7G%ZxarZ5p8-Ta=~)xq~4O()q-=m=->tsM5Qza zzUHtLf$+dY%x@nWYcG~NX^5vKj(Z7A)uJgUT&$xu7eR!!d;ON$K!< z80?xEk*Y0u`R&b?t)6Ycz6ZvKdOxFb=ND6AD%QDwiFTWZg?i(<_QUtMI0uzKBM`)h8$MBntANyw{#?~idr(+dMuAP#8!sO)@*d}Y`?Xpcn*d>j= zrLhM4priy|J9cYp|Ktxw?9YyCl2QfM2{;0nPU_OAbdT#Gk{)wlDp?}YztncW<;ziu zJ-yWvn)Yp*rVse7Fl#@G*||r^mt@deV_@dzYvt3kY|9 zNp($4Lkq^7oHR>xLe_VDeCpT5XjBcB@Plc3S!;9w_JikkK8jvj3w#i%U-KZ@ylE zjN)b={=dkHrIl65sBj|*DX9~-T7sqe|5N?`e?-nx22Zx0E*bTW)GKWxSg#F(6&IUr;1?NWp9kCsj105H@b7ZGniP| zz51Ujl#y`)ite5r?f(Kx_{ZT)$<}s%+;Z2+?(S~yT*bo6^Ids&cL9&<<7p7u*`2l* ze40-{08qn=u-K~CT9X?a8~>5?f=Fi^=gJNKl)zL}zW%7#2L%CpBIxMo%Oa|G*V-}v zfqxMHJ3ij7=l>9-^?z!4^FQDHKV!0?g@uJ8T4_vo*Cz#Fu%syMmuojSw-4X@a2}@W zW7E^+9386xNMqIX{^o3Mety4Tq@|{Y#~HTp)z~->xM9cWFJHdp;!*}A;JJBuu`w~f z|G*tXLqkBd?cm_xH8MXvHzx^@5c7_Vc{GjDrIULqo$PZc74dYiq<(6<}i78O@9?DPaNfaQ+|ytPBijfW$Um zHaUBgpSLdFuM;4nALw<;=#YH{pP;;Zz_JJkpKT#`k%Wc zG>el@<9B~2WB6T@7e8a<)-55m1PvEvge~BM?460H|E#4T7RLD4^q(L*J4`uP^5;vA z;W1U|nRdx+$Gb74FJDa|DT})Pf~sO)bs5nbw&DA>0)=0ph}%{(a32jX!_Vbl2Zjuq=1tm9d$4RCs@-@|Llr zzVkt_`G{sc%6Z)zi7cdeD?S`bS~}2Z+_e}#hP&7eZw`hq7G>r0`<_zj%_h1;T1ecd zM)P#Xt!Nm0PV33T<@|FTuizG| z1=iAOrsu&rOls$4!4w-=aTAcHvk_`)PM(Vqk5v8`)edv#i*(DGB(t?hcJ1J}OZqi5JM;t^El39q$vH+v9K&wA~JA$f2 z@AJH8c>vD^^loUH0Ntwvr&&%VlvvQ`)$h@^ZzxgwqvhjB#2js?lOb;gEJy`fbUNMm zvS?U$6PTvGYkiWWqvod74Zj)sRYaz&&PeyKfaX&yzowsXFA4E{ye7Tb*0+c%%wzIG zT+=J(JwEuKUE6oed=b?OS=|swc}7F;a8a#ykSUi(8?@a+lxgDnShIr?=Hk_t|D15) zdFc*f{IN*B23*sHyWx_opfsOIRn+vXi2Q@|g8q`_fq5U}_;Mxv7d3cT8x?o6-yY32 z(fp!O!Ov#ad3`G@-@@2r#H{RtrR%sZMa2>4&v86D`|Z8AX;Y4OyUYshQN++~3fBf- z#Em)vWnoULtm{_aa5r#vjj8Se5qs%W4H&B~H6!6W-)T`d%?hGu5{ixm z1U9$UWh6Tk1`bFCMToa?LQ)gypNTV&X`fQ)4K2#$M^bwAV6~UYao1q67>ao^U>lHL{3{sQ5xJjF}Q6!IggdTnEh(cu66vL(6sLZ5s1wNQB6U0zKeZ(+1b1$fVv6s zTlq^R7&NV|B6*gTR~%WEJMBT8BLQ*WH_h8IV+nXJeGW6TvTdIx#)yb0e}&auTZDVj z!68upH`SM5cL}YfQnpWVCLgY$v#wwBW*zt0h8{1JKa=|!mlit&N$TpG8L=QIqqtHwjozuIupd8bm<_joz8gq` z*^+NX*m;wL%IK~y@-ZD)9)4f}MBUl~@$vgz6#Z1K7|g=Vc9)FXrya<}>Nm(2ty-Ny z%lmIA8&Oq4|QIF>>oh)dkfXK0pMfWUUma(FRuxA8}E^koMo~fk^?5 z156YAX)#~D-C$>nnIG7{`G(?M;lqjIRTN4`39^~oCDz7fiVmkHzv-39z#FC{jf{DW zLuFhaDP`uMmT2hSx?M7(EyJ}oBU?3ECL9+HmTpI_qUF>TfXLha)l73+l~28YGUs$t za;?MNGTUinSjPoONT7P^)1!rm+rihB+wVy0goMeiOn+X&0>_vJIK7*baEa{d~_usx4N!Q3~R%avzj)J<%O$;3cSn zsX2jD2F{tUPC{tUAgv#+pGu7uFtIOAMMbyT+M``BZ*A0_PWSq2-xWFsl+secUXIH( z)7ZXW08N3O)@u6W>b3Lw8!q2gJyk>?SzMikKM!1=no*r_g*g+HER0-%D2%F=XasAH z^ZO>A%_f&!tlX>oSS{UQd7#u2-Wi#Q948xYl-xMT{w!S_0lswVRFb=j63qY%ly8e5 zg=LI1R=-8DcJdj8_9AAgNj|q4MAo6Ij_C`>3$})Rhm}fF)2J*w(ulS`y%P%-Y45eU z57p{H)?aPO&Jbi3nc?%;rw`nFO%or4%Tq}}zbbU@jn6l?M&OQl?MdFBFN_eV2U6;0?l z?K?m6Ik1aZ)Lc!DSDbb7>vQ3W!)cg{DIbC^I~QkO4-QzQO#9Fc69=$8_}-p0bqZ#B z4;DbO`-GBY4OC!k*Oes6{ueK--R`N58;qHetl+oJ@_*?*o91)x#r1fRqV=2kLqc_r zMVPSm={K2w`a2bvnrrosNiFvEDcub#f|>eg=vjR0)za(N_ zS4FEZAe>|-`iHoG7mrg&NU(rHR#kDUBIo>ZzZ|gQnLWIz>=U(+mMdhbG0Ed1)9DS4 z_8D9_bv#!-?Ca{#g@z;W?3?6KaK?JcnV8t_?_~9Fz=lIsic<1Q+?9EO8%ZyJ4r73w zYI?se2H@yS2`*$LP%v6#^f}co_fV=d$R(C;g`s__E1d6L&D9*~q5b=9ym_&)Cir*z z5XRz;^uak+#nqzI7t>iqQ|sXLIHW69#_ZD4ENJ0vfX97AJ7Y+eQZJ`HSX8(9x2dVz z0ai_^*5FSkK}pbjS9Io(k<}MW6sl)8C^L57S3ErLfj>UcSZXAR4)N>pbcVRWhz(CO zX5oQxufdzsMe;5>a6{vj1OeM}-=nru(&VZf{TCbkO^f!MJ2CJ|Q zh<>w;E&=RSW4(;sx~?CGq)H|B^5C}-1gC(58AN5$%ON75`y^s$6Di8`$%yz!T};XR zxnRN(y+}@g^n(ETB#D&uWhy~HoCbk{X1eE-k;pwOp_mKE55@JZilSq0x7|cTMZiIE zv*)cbo%81LT1%qt?)2`{K%5JPny)V!eWc$kfKAM9lTkBrBp@*$&uLN`Ux(2i$@CS_ z>c6H!65sIDf5t?}2E<~R-XjIuZSvVXqd){VF_lnZR?^a0K#H0&Sx+KOLUfN>>S$5E7HD0 z@BNT9!*T^2U-+BK8|?L8Qx)PfS5#kr|7{U4pPZ_Hv={!>Qkdlq3MZ)47bBtTMEP(& zU~sdqhPzQa=L+7dE)D5|Ym#f`D!#$&?D?>jg2rge-q|-07qkA%RUp&ki=dHZF}d*m z>MA*@5{+l?%EypI)Sf$na@&4s9+LnL=Q;g+j0Dp7FV{r>)nw~Dnj-2W8ayfOdKtKH zR_jO6%3a8lz7YT&@qw5+{S0y0Gyx!60In#PV~0|e`9GP0pu zskqBObo&1$JOK(WEEttKU|0RqJ~5N&%k0+H7Q4-g&_4|8V*TQJ4HR0q+5ev__;dm; zi~m6ZzwCBWa%}dG`r(SYenWpfH2(UXHQJ5uiC$fL@32i@537Gx>i> z0nW7Czpk-+d;V3s%(oX1oVlOepbz7X=Z}^(-Ww~_cH35dbltFb_9rgeUgmlZ^3bDO z`jB{MD%!qE;q@z+B57E-c6rzVq}^@{JyubZQ&}VmiHqeAlE=I0+{CuurDQ$q{nvXTX*lpZ z3#)aw7)sXu;Qm5Q7G1dI__W3L2N@@3e+!IVp9!rl_V|NjpO?C{^# z?L}%Bz3vR-K}J?)W_$%=ufxJKd7AR7svM#*dK=s>Oc3zHU|(M$`)`iXJOPSQ1#C1voBro)b5Z?TqbvnO&Aq@!LfJ{N}1 z^qz%fdN(9Iy`tg+r|C%7$cTKVgF$FyBrP@d=RZdQ03MTn;2!kg7g)Rf9f9aht4^)n z510HKhJZ7<8_4jg+fL9rJv+4KVT>;R~?d@?*b3d&?+RqgKX24cd4 zHL6VZuY!RQfAi+e-tMD_uCA`|>xiS(R=wKHbUwRm5ee-fAYccw$Qwxs^k~>I0k9XK zsGk^xi2#xa#N~cYrU36dTRzZ#GPZYh7lFYcZWlXt7sI@VBpkQ5w`BbGFL-%tSDlg- znPz8auhUN6aV z!+pV?@LtkfBYORM0(R-)%>LOio?y>e8GDq={1AOysrtc40)8qRSL2fq2hOH9SF05$ z3O7wjOHYT>!Z85P{hq-BamgetY$DHjzB0$H_a_bzvFHw_Em10P4%|==-kPMPa#xga zI#r*zDqHZb`PU|eIRdUgfTkss^P$q@0SJ%2@XHyu#cGQk# zVF3^Y@df*EFSsnvWyLL+(&s)lPbTp^^XXndvjo>t6ta3Sc#LswEJzsr#Ozi8E%y8Qw z2JkllZFw1CsprAEc?pOjnj^%$Qp@8SlE_%z=^b0%yFtAm#rPjQ3h=$h~TdS6N_ray+XN;CF&)=p-aD~cj!iu$= zlN;~HYtM&q&la7!*f9-h(ul0PKnPnDo{FS2;+2GU-6j1_T zm;{swb79Z9_W)AUI$e5f(fK{I!P5p(CAU5Qo$mO1CnDbSa-DTz;BWynqMR4!ppTq6 zbeCb%GQEJFdA?m6u3pWW@f`Xw{K%B;B{|Fp%2c;-+ChJ81cfwe3OLPYl$HsT@qyV2 z@&b4A>2y$Ob?Ea5;O$PC8V{KZcC~x?Y}*Ugg%jLDHzU~03yq1*9;9%5n0v5yRB9&I@tgNg*w~%~l0Gnm2H<4nyN5iHfw<7sU!Un29 zgx8*>BC`f%@vO09JKWJ3Au4lyyz8b)nL5$dCaOl7EOh!tno*mB5b(G<(qn;?tfq1$ zoS62tmOEf*!Ni#?{ z1J1BlE4WaLw!d#8XXSVui9Vi1V;vrmyDXhSj6K{b7pr{5&RA8O@80>O2qpI;9IsO$ z8hCnWPW8WNnZF+^q0i$cMi1a_Wap7DHKe+e-mSaXUUS-1-7k9(8Cc|^dr_tP4T*1D z%NyOBiT$veviGHW;eNB8nJiI?AOouZP~ZU(n2VoUOyWmgbLk~yMjTKMCG+6e=vV{Z z4v)|!4irH=JwK!=dmiVTh;-?vN(=7B6pm&4jn88W!bj2vR%tyVT{LV>?JjI91AlQ_ z&17Ke8l955(1RjSP?tl$njUE%*@<3{YQ8a5kZ>wzDlCATt(AK{K2tcu}9B+cM?*~LP%xjjOI45!kF@s z$(5YR_S0#>j<$B9(fxT+B>uI|d)pZ5|A(%8A3sI7D3d9sgF;NGL zC;8TEvX--XCwEta*FHTDM)$ArIRaB;m``MEL0|#A`VI&c>cWn3El6?b}s5*BRPL!;_C2TI8Op8xa(9Pg$429 zu~e}BVx&>IA(gZ@aBLQnG29_!wK@8J$?J#a8K=w)%-F|5#OW4Q{R!sTa(nL}0<7%> zWy>H#W4z&biMd)!N>VjuW7uaaT@Otc_V8k9Sj(a3c}~JPY*w#k^v+J$l7^!Ycug;1 z^aYG)l@5BZlc5OSStfNuYrMW2j{^z4{WRQnIqqD{mK$u+r zopnqH^VQ_x;Xx>}y)B|LDe8E2IKf!7Fl7TMLfzTa)YOiSjs%K>O;f7jPefW=}u0$g-4A=kmcPWda$$Mhs2x6*xX$bLqU7yhbgL# z>R=mjLLDO@_zGgVaMqO5;F>RWST-4l?Rz+GVQf{4MCp+e9n<7SJ_tC=+d-mdlh>k^w*4qGd3T4`QPe0Ey>QC1sAqqo=u@AI#q#YctLbgN*j6pim|{aTKIQQ2 zawCaeo{vMbAB(EIQQbbg_9ogoARy88N6%aBT2=!aZzN;;uq(fO;c?|fskBH(1M;cM zloT@s)H)OyhvhtH{(QLiLOVDIF*`jq3E~lE(p)rylohOWyb;4Qih$2&UsqOE9+W>i z(d+Pm_H6Q@c>%>@f^#*j;Q~gW9zDSqFhY7lGIw4e7j9W9KI%YEEsj9w@)w1$b8w`H zr1TBC5YM5_{eC#o8qh(k&bp!F@~j^2s)Abr`;mVFg-i%)$9MC37Q zbhgwOHag0FC+*?vRhKf9f^tyxssg*wmvm*JD}P#M{&H38!(3yk)nMy)CuJ_tV$uN` zJyyf^o=8u4pVNJNbHH=Bh$be6lMg!$O{X5~E&P?$fe%YVEY8A-W>xpZ2<{1vO8XYr zseMvhG9)BqEK=yg^j7Ujx#GKP=yhc9GhM`Rg4|;y0^$ zdU^&sl|!VM3S1_mUTCIUwbClPEZQ0&S8jQcbPZUKtnsiuZ3XY|vJLXKj zIQDV1A93ulIJ_dDm3Y7UK=rh!BhkPkqJOLKO0jA_^(Z_wHP*QxxUS9Iv_d?BsC{p| zsMMKLhp3hTd2OXPqP!w?4!;zWAF>ft{YCMDL-OLRP39{;;HM$Fe|Ql5^Y8&dPnWcC zlNm-cqRzg;JFwZnTL!sqOcng^uoc;~9PT-FAOOp^914Y%|5?JWMt2AY+G{v`1EyEc z_PEhM03BpX8@%6$9lN2d*u^?ANtFh<#U&(YGliUkFdKSnU4}`o$MAu~5M_V)% zN70r}VMMQc&yi~AAL<83LitUAwz>h%JH!3`g`587Mj!As);;0JpKENiM-I3lAww1& zCg)FflizK$r&Ht6u?eGq{IBI?Cad=!VIn60iM*V&zOedued#bJplF`B1?3p!E-`{! zo#y_jM~-`sBvm%VYgivrp1dEPj+T-2l_efgPi=TIA{!e&zL2r4eUAS-?>TI`RC=-T zP{$(=27Nl1F16<_Qj?Ij3qx_i#Bp(PS=|!7sLYZacpexNa8kf4;k=Sc-&kd{d%cqC z7RyIL8Xobhv(K`^KRFoHlU&+vEk|Xp!i#0bT3RB@;4R$>H~yyfP!=1T)VJuLzkcOn zoa~+e&1yyl2bEZT2aQzKoX$S~yr-C&nsQu>P8IUs-QPz8)?urWWX+;;g~~glCiu2y z@uJIcYs_=W43ah0)yX7$gJAwR@u8znCmQ`mIrX{Z1d#q5z!`Nv5i~OJ_hH83X z5-B`aa*O!ct9g4n=*~)y{y$?D8Z>N>OBg z=+wHMMwq$#ld0HUa&qZ3h z%t5q57sR)j_V*ms*6!Qhu2&@%cyk)boo0KHKz@fA##PPVbey>T5;-x7%g-Z2COp3H zn*A@<-YTq(Hf|p*rG-ieP+Ht6?i6>5l~TMADDLhM+`U*S?(XjH9^Bo7yF2Xk{l5Pm z?ZIAqZO$^8Nis9fJaYeTQE-WK4r^2PcwaXg*D=|Hx9sD;c2~c4!otjH>yZ!HGudIi z-2fu*=jYm=FFvQ@FHP*`gS671{+X7qEaEY|UU2Zl;o-K8Q@?2GwCKJOWWz6#(%!%e z3)I$SHY~#c?Cx=$gG=^`T))~uE)}<~9Z9tm#gr=Ew+EJ^WAhDSK}r8`OwRb6rzJ9f zru+D)E~Ra;4cZQzt8@EF?2SJ)-!VC1uc*0cT~&BTlR8Kn-kRHp+o`&jk!$%z*p|cb zmFVi$T*jlWC3#r+-6)5>w?eC2-1&)F$VFv9WwsGVAT%;4h&e>WZA-4^`gcgkLB%j3 z5A+95?^Gs-$CK{idraO;yPBwof#{2A%6)Bv$=?w^>Y{%}UwtHMg9(Sd!Dj|l<|rcs zEu4;bF07r6fHQuSQzW@@Lxf^X8YPPH$x=+c~He2^0-z8H{%`mL({ z$ne8qZ?@%#n23{%Q4&BaG!li~qIp$Yt=7H*7M9{pI{8iV!~r@4+;%%SkZXW;6oqWk z|0Vlu(ChQax4wYZ$A5v*%F1znXQ*jtzVFJCk0`4dTU%Sh6fx@E&VJxunxE}9oDEJ@ z8Bavb1fUWraW5ykRSQl_&h7k_2|xNgat5f$njO#jDXX~%;5ck^R*{h664tq$$OSfo zL2TcE08`ui(!Vmav6Sk1&42--C=^ z;;ht#0gH6sPqa!@kXG(~+V-~8*eiC2(1tATm#=r0@7;WE@bGNqOsm$#8DPkP6 zFzc*k9mL)%wJ9-Gcl9#1cxxFAsx zn-(q~9a@^u-&R-HUWgUBKT9l9pBpZP;*3DZ>|oe~E#|KdOl)MI#@ym~?vdn<7@F+! z-YE)Owu8_=V;BCE4f0QHy7Wfpvgf#s>Ya!*i~6UexkXpl%SuVH(X2P?^7Hjg`r;wO zOD5(PTV!cC)86`YS^C{`6z9c&$EIn#$5m5N>EzOIi4U;4gE1>1)t=qZgjZHXke|a( zuP>ZbH1iwn?L(ryI{)DcJ+-WgYgQ>*Vm@kVD(`r+&f!%@!$y9jwPymrQ{t@Jxi#I##*wB=%;)_aUN z$7NqtELZ<%Sd|nhU$~3EQzr`H3DNlr4Yb?D>dlzuM^3^BzVyqX7qN*aHvy$P<|fo>AiK$2;)zz%fi3li z$WnIU9c3hM3QU#n=`6ODs#nG7GQIGY8$-@vuK*0&Uip#APhi=68DmqJdr-78<+@bt z0Ih}Kyu^HD()mG!^opA%T#IrK1sI$@l(!oppa@)miA{Q^R04mz0_RqtF$jy*-j(WD~5!H1?CMj}XpwVvfa8x^pWV&Dm-@k2j7oQOc3}md zJv4d5@9xOp=2AAE(X{!tKgp56OFxjn+1c4J#HKvUNC*<#kk8>rEjxm7#8*8G(f2c> zDlC9q!eo^~Z1(?szp>+aDm; z&%J=d+2TRT~cyvd_2QV z)GawBh4A3;P+LnYZD?10Z*NZ)ka@a^&b$N|kIKr*Mw*v|Kx8@q-W#rtF1g=sV(A$e z&>-{dEzB=0M6&`lHb+OtBv>PUL*Y|bcegg6J(bOOv_AVqyua;|_Zkn7%Qy>NaI?}-uK2=~0pYun-i(0V&+7)f)88$5* z+#{;Gn$6qWyQ;JV1hk$Y5dO=TFPQ+LInd@6&x*EzDhe}DRv@?fBVYoMGJ)dBt*~J% z95oY=m-6!QRdYeYK=@(!T;9Dz9!XxEY+6GJY_Q^}WG^4H$v&~x2f2oyES@m}?0{wO z#FiQtu>C;}jEXw5C9g?dy6gcAC%G>J4i3!VTU#`gN(|AwWJwVbUTtk{7F=9pt-3ai zM2@Qn|2==0^zvc3XV~O;>u2Y&weyb=sZE)~= zHFb4HF6Oq}qtEv|c8aKstd9|CK07-78@iColdq8=Ovk^z4Ur({|N54O#@1Je$Qc$C zBcKNjSbx9VFw~x{_{)F8NBWO5_8i^bFzRP0TQjgJw`5Pt@Vkr*B&*vR7+UpNcHX=EPyXLzF0J|Sd70`@-+Q|&3N&eZG zaBT8WHdg)L$oIRy1r4xaR=C6EzlBGt4^sjI#a*-R0jj`*=XQ#V^e!c|m%sra*|@0> z&n)#SBZ2vpD8i1pJ?RT|WGOUGWv3nb7;kqaQY4hiw7fnr^+`*gjl)y;Tuw$jo)mEW zb`Ne{qJC%2-VO2G%m|~dk(fI>?V{rk8IKMNy0y>Q%Z6cI-EODSX10H$rly9esQsIO zuv}uJ)b|P0fGmGI-__N$@Lyid+^l``gCgx6HWYYNq`)?Imz!OpHwt-&VH?G~N0?Yx z4k|f@qk&N&e40}`7(5xLD9vf+E z02|ishkiyUh~azq&4A74M4my6zUk>}$HObSA5%q~HPGb?L0Vd)6!Hi<)8?J! znZ|wk2cwip>d$(A;dx(@FBK_ne{s7k6Dn$&zL!mO`EY-ZGCE7KZ}#aohRo{fZ}q@p zBwT%-Q0L~7y|e>XKGW$Uh0Du^{SpPS+I^E{!c3{3ecoi>V6?)}(4taL4VO>jHR7b~ zY^Z*r^S61B{XGYbzax`ekJt-bf7j>Ep$^H1(`(AAG@_!6#ex!-mb%jkvo0O|!$?pQ zH}f#CJr0%BVqov`i>Inpq!5Cw*La6n%YrjjMaA~nRLf_$8h7rfq~~|KCsjPB?fV>p z{QL(mS4j3q8gRoDHH7{kBf33GKC^Dtjo84$37PRt2g3u4cEc%p-z@Gh_TCHpM$L5( z+q16Ky6LQ4WoqwG{56$KAX0MFbb!!=}j%dp7*SB1;c=JV^zSh@BlPfVz}CSrsKvtio%RmpH*O z7VU9x#ekgoSFRk_>Z~ibRQ}6M2xQL3!po%+>L|T?hlg%^2>!8Op;wkhMms*8<-ZY~ zcb7*_#=Lo$^kE;(6GHBnRl`wE6>V7FBAK|DKF9XaFD(laFd+}!4=Szq^@X1RO z{AolLPq=R%okv87b${vSjCO0c49tK1C~gHy2n%^%bA1{L&l|IhhKc-Ee%ay-j9a zG1_Z|=ZFQ$;oS5BOuQAfkPpui0RMuQ0_DW7F%lywNo_R^;@wmM{RpWdZDT`~jM2*= zc4w{;C+t}EWuHa0@m9RmF1lR_SFUj6QnLKr6~i|cSa8wS7xcJhsqXdAiFew1-O_U= zl~`syVXeo8+upVODknSOG+9}rMR~cVuX8qWw!TSN`P}}q^j7T)izBOp9?6N*(^Hu4 z!De>;d4WXNSfnDAd3krBgx4QR%-P6hhX@o`r$iYmtFkjRdM5$%Z3V~8eMzZ`Cm;yj zZop?1ey9Z0)?wxp+aB}w!{vlw|vdVZH6mMr#uA+h_#m*JYdf%)` zW=Y8bhJE1jI=$84$5EVqJRVEwp)P{ZhNuc3QJvxqdT;WssY2&6M zw(2xtbn3o7q>P$dyTMs|bwx!1@_Iz!ou>r-RG6W-d=WpTxV{1SR=bZi6+O3CA34E5 zr2ge5uQ{o5o>Wi75RcXBHyR!*`P#ibmW}MJl{FKXwj2Mx+_dS@b}+KGW$LDIk%SVEobMXbbh>g>SWrRZnQd9 zUn~2r;?tF7r}xu@QlWH8vAJ<_QoUhYiQG;2qmyq(fBy%tA+kt}QkjuOKtt9$ee-os z4d{oHR>TRSJLD&*2bJ0Ya7$x*wRqv+#b+4u&1$j7T6OCW;j($`9Qs|(az3O0)R&LP zEqJ#2Bj;R?+KQ0@8>-T2f^?b! z^6KiLaJb$kD3UkWBVPjSrlO%yqQ0j%trnIMvcXw*N_=%>^mV6DMrVrTMEwIRNhY)F@=uYA9CY>_hyINWUViAm2~7V+C9m|r z#--xSvj5gNZ`90WdX1t1ws7|GJB}5awls7UH*@8F(-3?EMQ|J`Pp^X_79*>^o}S%4 zefpAv;?-Ag5=2|#9ruv;*CK(5j!UyIq>MjRJRA%8_PvjB%B7_h5*Pvg*oN^lRQIz4 zB&I2e5FI^z@y<5CA0UJ@RuQH-(qgtgAjTY;h@Ma~Za^&&XL0P0=TI9efO%BkBF?O0$~!hVPbXFS0Twz%)KRevHgE*!?PPYOY+^f^S=^g%bsk_ z_z1%Rw}KR3N)dNFb+aW9_vv|j_xFbydixHg}`i?$|ZXco@F4Uv`jm!8?rZ?ao$7>q1B}5QD#v4Ws z)3`l0z=7a}cuTOi^EYgKz9cQVNWY{^yi>OjdQytnuZ$cR(Ognx$=q1FFO%1f_CSV) zJhKIfqrVbs;L zS>hqaL7AiYFMk+1gq0K*D-seMZ*Aq+{oebWs&)sXE&b>AGYbHKI1(vAf08exA+oY* zcC66@P2+aZuxSpT;uS#mnS<2B zF)~{3_6lwH3Y(hAK7SVBfgs0I?za?OFRe8tXDW&@m0yaqm08De8oUw%PfSD5Pr%^! zVt;O!nb>z)-k)CdCM|LOg0jS9($v@WP5fz%sP*a7J(#<+?+qh>+1g7V*D(_1k%v2- z)zR|&(lp)%PT8IEVrrVH@>=M5l}Qc~pgnuSjP0@X+1E*>!+!*S7dQr=KD_$u{J22Z zb~`e2q@w(h^ps@y)5m~2@WKj>+co|+nFb@ z@}>Pi41KQjY7(Dicsk1qHBzSC9vXAak2g(4xrKw~^Q$0JJ z4Y%rFLwyY?p=(*n>M1qT2LY= zzmFPGc)!zk5^D{ONvDL)eFGbBA)<2dD4&7&?JFx=PtN+AECIJuAo-bl)6ywNl3(Sa zxOQ3oh*7}B{ePBp$J45o-DKgs-hcpBpYXQSl|J?F^3;RK((b|C_Wph$z6{Wo3h{vg zf)b(SrZoiI?0oe&;bBw?tktT1KYq+X=xDZX@A-K8u$gJB#%*(3=APv#@#rweOr}Lj;I7FXOJU7B#YPsRU z1&FGa12nknQxMImtt1JJc)*=Y9@h+VH;pRc-V28coU|bV_{RVy{jV(ES|P&Bgv7+? zkrjYI`Btm3!hT=r;n31UA*{Uh+AIMutJ;K)o=@Em2w9Xiy;jW*IT00E+B>LxooxK{ zaO-$AXV{oD4P1Ezh<(|Yep-~X7*wYbbI61~>7E5JS`1$-+Ky1S3qvNPWki4H-0s#A_~N~TLSbOKQ4o+U0UeM1KY*uQi*8X99u*bUw_W79DPTmu$1W`B4;p)t z5P>9saN*f@TrA_+6i)Sj@*4ZlHUAs%^_((}NG+=Y@D!N#)keWKSOL%zmEoX(LBpsZ zpX;6r84|NxN8&T@pD5g3u@TGM%s zTBF_Y!T6j2Nxkt9hBEVmuct)4F{*_w*$p4Q%@|PokO{YnHAJ0oChse>t_b&0g8+_U z)%BB*{--ZC*!#o$;EFNE`}b<#vld&fA6wBB$JViAQ2>x5`42Jb3B`-%*98v4pZ`gy z%h=m5IA@t*TlSkayE5Eo83T3L*yGOc8k^G&13;B}eI}}P0pTtXH-*>llklLOW7-R_ zX=Pr6o23j_iBm(r&LGWML}ZTiwWV|X_40AGqF3h;3&qU_+^~l7+q9oHWU3f%wkJTq ziSxdvXUrb27Jf>*oIX|(7DlvWtMBPsc?nRE4+{Yo{kcfPvz>Y3)NbNZ(8aXe3j9;^ zQOlHX3B#>c`;lYa>L!%N%5n87|B$ie{L}wJk=){jt7*R_7{(0#10>PyrKXBl{@t&C zuSvFO$rG)^)c@Xys4Lv67jQX zi_Q_~Hjuvm2egvzO#-ss&*WB)Pq*7?V@6ex|ADrF?wO^fE9jg}m(z(xmX z9=zA0qEzEK&T(H?MzMSNjAe8DU4LeocNY@rb#P50mRKk;jY&5cB{)wiLP%Qk(0ed@ zMj^XvlSAr<=7N%3XYcpMvJgUkprm`dGOEf(YkK~Qm^0CPxAC6frbeAED|@|Y(_Tmc z`J+V-<+G%)AMB-jIs?;;wEWLrVv}bf*ZQiDh`EPWi}110o~UzJQ^X1Bve z>N3(%itlomW~#}GGtBI(sb*ebNMht&NV}B&yEJ(ac?-qy3De1zCZ<%rYkZ4DCIk#= z$q`qF7szK-UoJ!y%Q?&4xKdoS5#>!1hnt*9@J?RupX|B}l$$A#!nj#PY1)=$Ryxo0 z6Rpe0&N~8$MH!AU%9}K6J{!l*3>AosmY>n{e&%10m>;^RZs8f3HkMh8Gq%(rgb9V!9m=3vSy&?Mu~_Lnj?Dhm3W6R}J01{v zC_}{7X2=_@d8^T)nSAp77%3i{2uTMgBJh(#Bg?sDYbi)`UKm-t<|Q@Iy1ChBnpb44 z6P;trI0zc9h;h$2P!q|Ui1?@;29J8lS%|7*2pXLQIsUHblxA#n#FEbXlrDc|ok!iB zlJWMY6=KV~g6K!8kg7*qX1T>)y|xpmxg2jP(V$uI!2I0(<{JT zOBD#~H);D;`btGb%E*jW^iS*THf%$=+6YV9;A7@HlRWd6OmePbYto?rKMGNa{`S)f z5nIQf^7Uq1Y$RJg4#x&(-G`8;GECFa!rwj1ozYaqh)7_w?i{JjGNT|-ZKqFX1uqn6 zk$smQP=8!;cx5UJ{OelO)oG&N-3h-O(jXML#00>IBxjn%If)eGSX8$^(1UMY^BSRl z5K1zHsLnzTR=38&GU{yLi9~A}&dyH^O<$_%*y9kod6oI5M)!3Fqwju?70kdz^Ly7YIXd=AEnhzO38p z=NYkx*(M@|9cFkvK0$xxo5<15Z%J+H8&Q4lXqlJ2xMuUS7WIDP_`!--<~5~*UJ*MmQe@+<_U_L#FeP%yWoMhgo&`aipcG`E9ARl%g(+yp8pQlmz{ zMl}=yJ1||8m4gMl#ty2UQ0b|{LdB(_q%32eNhY?Os&nm2{g_u*&PGMU9jD|xR&=?q zvNMK_686W`AS5ySFBu)ks<3*4u4&#(~U>lmAUC6*CVtibJx%qY zj@MqPYv-}3*5`xF*t+-uO^CtFj~)z;Pk!-nYpR~J1adce(OfC9hR4M}NJ`m~NKTQP zYU?qBoDolZQq|Bedx`@IE;T^}{FobR38V9}K}mxjCQawl`saMqt(|Tv%&iQ8VcUU%0d(|Y%F!gp1ur;=x}!S?~S`zl_YQqyfgsiJc-)>Q?SW>jhcD?y?o z2rF(Vufjf_4-ve~oi-tM;7W00McL{c)Z%NyyobCT39@&6yx<7rjr!f8(4M~dyl=HF ziMkO%NvZ3}4k0P+>wDfC@a!bg8Xhn$8!-A{u)_QH864WX;o#=2TP>BxRZV3p2N!!- zNuad0RmQ1cq_B#cI<4nBgVBZH!jVirX0W<(EGM6k;Xj?@Qpgbb3X!`1H!&y_2C^hH zHBqBmI*rVyiDZ@hzhitV;Kg4xOY?Uhb-U>e5YW-r`%J1o-$1BZ%OBw4f8VC4npHVO zos3(i5M>^#FTmY+P+8{^7j9$+yKTT;KA)5@Vm?d?`W4p6q<6Hh{5hsxMWyE8q7jX# z)fofncR!L%KEZRNh|tNN;XB5aS9)DNy5Xjr_(Mf&zX$>_5@*&zXC|0dhq5z-=%<_4 zg`2Ls>|O=R!%(!oGfi~6_g5rNuMK$^YOO8^q|)3UVN6KV(mt2I&rOYs^>A78T}V|A zOyyaD=s!P)8g7C8JbKkyBNj4qyh&BcW+upSdv`Q~fYiRxBQ0$@qG}vmLIcq{YE7zf(ZFEE&560CN zuz0mtOur_N9xTR(tnB1R#;cYiMC#Pp4ZB!0q=xH}kC1r#|g7F&Fj8{xrBitE~j_Hz~C81@Ear1zz29 z+bue*lxM9#R$fV2P{UZwjm8X;B~Va_X!AlPPxr6)J>AopYODOMP3cYWRR83jirxSk z3vA3VcZQg<$wDuD&)p{VanU(km6{=B^o#@9?4s`Dn5jUcGCFEZt`sN}6o@K${6^mK zp2uZ&+7N-BW(VL(H2!!xv8mxb-wSelz9!)I4@Eu~Q9*1QCqO8HT9t7KH)eDFpqsK{ z2p98#;jk|!5A5EAB+*x;%BhpFI;}UGm>xTp(wL0>l+QP-3_&cY82t8MPwS2!GHlcKN}ieAMKm zkhyF~A75Kqxz~wJwYP*M`QCShav?ZMl+&<*5o|zcYfZgxv{2l>@YCRrL2Xm$*1f)t zY}iAHnwh$sFM9Z*&l^TtWxT4if+i3b&S{=0wYY&R-QN!x=#eU+MZd(=1TZ8IIE)XWy7*!vmsOv9;cP#L23V9{6Uno-Ay0*WzcXI6T=Um<(I}nL! z2#NK&2I&4-HhaGygveCUT7468MV6lTS|xtBPo6(hBx&$9o!OLZa9Zq4f4#?v3OB|#M zJeW%{KD68acmL;%S#kA=$sf~&85yj;Emq@}_d?gKetyiz&=EBe9Zg2no=3?MLk}X(MSIB#l;JMHTiP~sV9S+&4Qgvn>KpBOS0dvO33-Eoaw#uz zwCJ-$nvDs`oYSS^y+~%(fun(N4v-`tZSXbjzCe=$JLKvQLMSHjl(hu~zM$vDX|FPA z)^W#mDl3HNG_Z69H6gH!3?Z64a%0bwbS8w3^>OT>a&U0m@Fdxzh!h7*Be8|Bs53pZ zlbqDB`UXXyfY_p$2JdksXHvT&$mIG=6@*tvKwev5Z2n2m?X7{;rAJZXhjc7z4U~I5ehuCWgSkze!i_TS!<3`mF{<;A*=bH({L{sOJtRt)_|4 zDVj77mj~qYwOBS1C%|WaMmKlSA^vQt4E=sgrs#Syu!tgORF6<}s~ttTMuA7u6)!0u ztR0?6LEJSDZeLUSHs2!r3X6w_KQm5m^Be_dhx#RfL!KfVQ_<}w(=K+b*&jW`9&Vh~ z1id2z(Dk~w*2Nywtu2H%pAh0}6}X`EOWrp>&|L$yjzrWxy<=T-T%U@^*0Xfqnkf4R z+T<Um*3`{Vnmg>eE0-i7N;?n!d zK+~Q#CKYDb|CvHpsNT}_{2byWlUXD@Om;1i=-{j(5i|9aO(z_$`EUi%58z<#p&V4ol)i$%liwL)QBXRhvF!{ z$L760(~04ga#Yf(Y@oJIn{wm8L+E-d7J|QnL>teTGccksEc&nQLuT@a$L43SemLFu zv^*J0END$i9~0xb8xB$8dE4-lC-Wa3oNY7StBw*UM^oaJChK>%o>^ui6#R&J{yk$z zS8Ll)lkVJ;F#^Nz*vaRHC4V_KmZq1y|7HDaF6=DVQgz#TSot=empr-F405d4%OXv$ z+W3P{n;S%alSB`Qsf`jB{$L)`SkB%i2Ofr67570 z0{~LE{vree14Cbr;6Eg7FL@Xb;9j=6&UO+>*c5r(T5C33Gm<8tpAZEFEzGN z@Zr(kB^E*rQUCohk>;6C1d9LRi-C7Y|1TJn|2Jlse2riwVz+6>zu{X#fQgC8#Ktz& z@^4x}z)kc50AF`#=E`-E!w7hC0B)o=;QOgHnkF<40e-a^5n#O)n%!R=<`ool00g#b zuF2S9wR-utxO#t)SAg(`!zh8C_I8Bj<>h$@R&G`nfFr2PL+r@&r%W!6S2XW`2j)5K zHhT%%;+VYOI5{`a+ObbRxvubwhsxK|1c?roKi;^TR-9ElUj#5YTcO>}Q8T)PH-NGz zwzXtJ-sUVZzXJClF-=PEVI+K)jZaORj4kH?dxkf%4cEzK(crqELNc0$&~^X8c75af zaW>ukZGeZt>)I9f#(#``fYA*C@*}>l$|@-MbOxYyPE23`Ysz^p1`Rp6F(Q*cTbr9C ze0&7M!^89V4Ikj!O8`9+Fq$+6qstxJh`tP1UsMx06rlPwhJkjZi-K&6m{yG$s>sD@ z_2K0Gxo4#h*VE`P5n&+wl#I{?@n*ssj|m!wWmXm0AcJF5QzQ)U;Zs)9en-T{w3^e) z%#b1Qwa9c-HYNrdlR_Jie8L$j*5CynUFJpLqp7)>_utyRiwA(hET7V_a+Gs?xh6l@Z>{M7d`J|T_7 z%C5?9$xjJb=wH1sd22V2VxHIYg& zLfrGi&$^(ZBV1-rug>c1!ZsoH&-C>4)wXI)fd9efiogd>Jwrp<1yP8!wRO3TeFQ+* zy;Mr}>eM$U%kHLJfAnbfl8GS@{9(a{%Su{V|FK$y=j1k*$BMTZWKU3eWl?NQDhLz& z9!7NJagO17a!MH|p#BQwO6Qlt+ky#p?KMfu4WDa)4$6%3let`?^>e@DjRnn2v}E<* z`C+4s(tK{Or9=m<+fr*PEXD;M8=k7DOzv&smS-dSu$tY*Kt^&s+(8slxi}^8XQCf zYuBzlJ}>)^d4cQVGDP@9A?<>Ozgpe-2!ZGXy%3EZuZur9!tZ{# zLIiMh1H#%KNaz7po@ehf%Hpjjn~7h`A(;0f#t8N zc}tn&sM=P~ML5>TzFLXOK_Xm~`6&gPeH*;U`UUi80$vqZLEw+sk4Vt{Etb1m_q>^i zMzi(h4-X03RwgO=hi4~auJuAZ*l|yR9G>@2_82`Pj%^La==4)ueVk?3nnx!h&*hm`=O1l}>vaj< zR)^{kgq;v@TzoE@L!X#Ozt3U! z&cb~iEC=OjxvN(xIz~&t%P14&F&e?qkF2qaDmMLEoP)`BIBay@^huMk3B@Zk0R493 zSCN_rem!hRfWOYTU6Yxw?)7DmgmOa*uJB@v?nVR!JmzJI_6GB5%eIxF(Oa-b)eFs1 z?l2#6ijy~Bkq4BhQQoNw&dmhX=-Y{k|ah)7t?z8aHZe2Nr(}zW^ zn;Em-lF-7FUCcvp=Xf4M8x73+#pJM8snm6paJ|O6dZDcy+Bc9d zEL~1bDxxd-q*hR%TlTC)_*c~ZkH>2zMqZ?iC+(B-pPh9TXsTdHhm#<->rkPrtbX7D zH=i$etj_*OP#G@g8-%q^wDlr?MJYPcbNtn9_3(<$w}EpI@WyE;6BX-6?G!;<7R`OYKZbfLrdv(|3H3lEz4qkl6 za5e&^N53De;FEdJ6z8oJ?=Nw`%+}dfFa9kig=%7w`cIF16=l|AZ=(tiR=J$%NWERr zOk#q4C{fbNjl6_x5yFxcr+-crM4?;_cKC~`{+Y<~KlFTyO)-%0yTb2^soL15;f@pP zUXOb^Ji(+lD+oF?M5O&<`*WH{YaXR1@cxSG%%r;}4AlGQ$^DJJsi0U1dWc-eoj1m8 zm5#tgM{bbVpdiS`EFdQz{r=s7h$sA2WCKX=BX-6V3--)Vis#86v-}t;tTD6$-BxAliDIXA8 z95%w2F$);Dc#BOxt*xT;R$Nnj_THsUDW3<6l8j84D-oY`IoGy7u~fe7uvJrMS7zYk z+#hAOY~uxc)GSF~_lKH2{wcr51gnc3IsI3k=g?IMJ@qrypWLqA;^6>orRGPylmt@D z>({RVS1CUrRcmokk1=WGA8o?RN9`#44?>99sD_DC1CzbIy^)EDHxNa}9staC#*A2D zmgAep$OVky)3?zOGydEW!7wjw&0u-cKKWncO?aZsTOTOb2yY%tb4D(_=AF>`P3nzENsSt za`G%0sGl=rivXXk*U-n(PG6NuW?K`Fy1*s{OW!wW?`$i|uCthtR-jm0d{i+WGgiJp zuinK-8L_bQ=Goe3%wy+`FQ^G@`|OHJ7{`b06}FXO?M6sbj`Utqd6HeIloZdHdMt1e zEm%Gp;inCrl&YL+Qm8IoIB9PEr_jS3t33|f4GWBYj}v!Z$7j~Mk6CFaaDM!-v6v~2 z$(TTdc#z-R zKbJz-^`Aw{w@C3--Cpc=Q-2$#6Or~uFiY~+o_D+6kf{vN4y+xz6}Xn1`h^NV?meB< z$-FWmZ*Qq4W6ChC|z0NZ6{sI{O8%mv1+X7^uE#+k!uwU+Hdi_}$27 z`2}B*a988|_t3E7#$_y>i-GpGK`SNF6;o3g<%*%-S8%W~%s@Q0f`{;TSk(kc;h%}K zMOja(D>XhkF;?8HS+Y-J3Am#e6%Pm-NP=oS?%7%*7j7OY%=nT}F)+$QS=?>Fo|TDT zcsD(1{rx?StODXV+8grFUf))j(bagIN7oz}WEYj^zmv)+>T#lbK=eV|B7wmQzg>Z? z*jc=zXKRr{xeDBJb=3kRDlBsYzkSFir&0h|KRaSYQ3H?bs&s0YK7R|4+5}ODOdR6w zq&j`Igl7vxacph#ii=1fwnCr!`$P_U-+*Z8J+0mGGYT){lm!Soy+K;~Kk2SN%CXgO#ROewtB>>lrYoUKBL@svD3v2+^oE#TlxzY zmRtIDu(-Nz3Oe`KJ9x7A0v74b_~k0b4nE>^v1JH0ICavCZqq(eaNOy%JQ?ChUCoSr z3R$SQCa%r;P=A23OqoRRrtbofSrZVsp0slr!}}uC0VPWRvAzV?ci7%o#*nJy_Xk4@wBv~La|IMY{5lXgwT)w&=8+WO3~@u!EL!m%%B`_}tMvGQM)4~lg-dT|PH)ayJxcu!jY2stskMk9!zc_T4@`s?hy5h1ps(D8k9Io?7Qph3wxiNt-7u zsf)v++oWC0?nUn6bN$xP!LDU?GBfCS^XzzLy*HXNTI`8Bn$p^8;#brCn8nAySja(c zIs+n?oayP{7gO|P%_(bd&%nT^KeSk!f~ZO)h+4;6ep|;Lg5TZTdgJf@(y(qtQ3+t% zhzwVBVP|dYr&mhuXB!n!iCVaR{LqW)t@1H4fdO__k~r(LE{P1X1cPsV7NrjYv?ndVB$ zd=#m-OE?f_E*hK(RvXYjF%K#L&8CUBwoQCOB>05(Ji*?dYqN5`YsE;jpn-9NRKjBDc+xbHAHDs-5)>UBux3H1gO=jiJ&3LS#t_}S{iKq!F@-gWo7RSptA zL-uUz+6x_N{GPG@i@UcDs^eSVJqhj-+$~6OXX6eDPOyzz@Ze5xcLD?nF2QvZ+#Q0u zySrP^>3q-moqMb9ovN98Yo?~A|DdSe-FvNGy}H+4>wTZkWAa!>icD z1sg~jWjK=>y`p2f@WDpwf-J1M%{G7D?I&p+*(oBri@!%&6#6FQ#t(j0zm0&v0x9is z3tWAN8FTXX%vY8nUN(B1;?{1l%;;PVH;+vS?y>Hqr>)hvag#G5szOTcD{d;$5mv$% zCgE7jd7^N57YUYX4rGTi&aoa;G%b|@#pQq#haUsBSHaB|Q97yVT9r}LxF1$oR|^^F zqMGcG1DLNRS2(3zY*8=c>x}l=B<5+3q;0X{BYSSO!G?y)GAji9#pHtN%tn*CF{zte zW%wRgs_-36+%PoZ=E=1I<@5p_mFQoSY^AI;xX@3R!fhVkI#f>*n%H9B^yZ@y{zfYb zpbm+}2&nWyS0qvD*;rYE$hC;X31CogRFIQwDUi`SBgr)@O`BI z-PZ?SS63%X9wz4?1+bmpZCE(NXv-gC;cZ+jefy2ZM`g$$A!{ojEjewx^gz&avKA)M zJZEy#M>-F2qKg=Q==|j6R<0ZrzugLcy1ihoQ-y%{g`o?;0 zbB|l6$HAW5^;BLgvJ{_*X;CH35%Y1bqN}e62WPjRGOrIzQpC;np6@4`^yZbVewI+8;v(x163 zLl?75PiCG114}Z+#bGIa-pq*+Y}WK%bz={=37fT6SI?Hht;w*7WI|+y@>DyZ3N!0LIi|{2uLQkbAYw^%&IebCDPWx?ggngYl+1Qyi8oP|+@fBI%E|jlw6lr-o9+*9KuW za$GGeYeQq((-5P|jF{}qF*_wp^OyynpT|Wyuqt9~K79BsW9@`3pYFYW$V&ht)WU9I z&-Uc?C1*}KbTB|%jbVu;K3t%sg;bL7D*>^9@w%Z~3UN{P+H6z-9>hDtqrjDow+zQl zoDg{sHTxD>CM=CeqriNIv6fOT8r|OQMrHt{G#}2ZWcnHA*X`RHZak{iDyd>(JfFbz z6Rar8mx}YNUoxfhm2V^jHw;aYyIn6DBbzj+FX3Ydpp+ts_On#$MBs<2SCcGovt&w3 z^!^Ag%4ZnO*}Wyhyy?|~wZ9qJt#ZAV{7@tU9Y~^_s2X;7$Z)Y5;cb_2)`}00>KfcQ zU`@{?X(U(bZlGpQD1=Roa3|s0EN9(m0A4qQMWHjeM?(zCG9gub+9Tb*O@l^o(`*>> zp`Er)Cj$+PwL%~&gjgTpv3%eCR{8N6)A4ZCH_CZ9^*gJ`X4!;`fpl8rLizeJezf~r3 zyt{+b(`x+yzz~Y+k5EZij+{vLIV{TGT>5nWI^*qf+D07yA@DO#0(8T-_ccbMSsIou zv9}1jGbJ3tE-#f=%+Ct1li7$)S78wOeoGn{_7fFbr8UPKzG)(V-g^D?yRRrO&t%Li zM&gCY>sFQUuO3qc@Aj?VT-f z>WHl(h%5s#gJYKV?mGg@)SK4*53TbpG=#)T{OiB#?EqcL1A)GJEc-}wyni*xw2rIrc;1eg~QSNsOP;|Teil9GUF$l}!TXjEd zukTRZ)(cmeyeC1RIy~Xe+Omgx1Yu6(TNSfNowB}mhBT183kQD^Hu%{_OZIg(M6|pz z1Xn=j4yn3R_{)5ioFa$C{*4!W_tFrd{_Lh*Es-Y{HQQhr^6rnufpS+V)qyX|Z_&X& zT!vrLT=QXWS?yl2F;?8EZPOnq%v!6pU{NfKO3Eb?T`N>e7*teA15G}zZJDyao>UL7 zux^eh@lx{r`Vd5~S(V3wr9$4>kyAK<-ySo{y9sGu3(zTm)te0LExP06vIs3Lg)e;- zBEQq;Mb48R>1k>Aj(vFjN}dpcKyiMZ<6Jx!U(fIyH%&+XIO!4j?MXuEkI|&6bj+kg zb7l)osZ*1N6U`#v#ok|eEc&CLN$_cggGuL_CdtR0m9}Al0S25tMXqfIhU;`1Ot`mL z;9z=dxlnCzK6LrInNV-1A-&vOlSw5hIfo7i@S`)^Ce1shmIz004@)_nne<#dX*Jjg zy%={_9O%fJ_7nlHU~j6GSPv)o+^kZ<>2@K_SWier2>2NQ0GtnVj-@A{lL@+ywg{yr z;xZi$j%$32J)Tm0ox|P9DJmc_trBUA0Afk5IQ&pvudWmHY6wE$TvRC zFtnTZq2Ly;hBuMo*DRjv>NC4D5V79h!(i=Z;1bOfbkmz)+8uf?rY^@wG%{jjH4Zzp z2(6}tDrPtU1h%PE*#ZL76UA6wz^o!vxov0F09hm##cg$T;(_XO&87HjRcE2C91=MW zJUKD0JOS6WY#|_PyNeinfn#A|VdD|Ls(8?Ab~OTk;Dcdi00}`he&;jw+?48S@M~Ii zHOJ1H+{v;RFf0xs(CzMIe}YB+=n*`i6f5K9B^V=9NL4>VG-~wBzR-rK!%LIXQU^;z zv+zNPFt*{DaNw3S#>gaKd@UPqq}X9zPdgAQdKhWW2Zku6b^a^ zO4p7E=qvS1?UIPlTdiX;`1vJ8+y9NL5B9*-FeJx&n64uthxCecH+I+^~ z-@Wj8ox)D*5QxK+Z5$TI77Ttq)~!ZfsuffIQqJoYD&G@0C1j+L5H^q~;6TMgM7Jp! z@w+oT-Ei33kzX4APv=|JiTAjl0TxUvsf;>R)8Hs?$=e$tz337y_j5xqIDKhKk1X5w;egHyAbbEK zzXZoGrzJ`fva%8JYt%C8p9c26SxEXV`)Fsu;Ho`P)qa2T$NFOIZi($aBrZje57Tw= z-~pb&w4MKf&tkk}A}Pt^QskmiH^hsoUHXTNfR<~BMY6=Q>pQ-ja)n{)Ea~?J2S?#M zo5!JJ;XA)AjXg9stpa;g5`lN{R*@_a1YcP2#^X;Q$-MzE=ly`f%JDErQS<8e^snFW zZEsM&7goKFChSX@=o<_>Jvb-6r5LcRR`Ffr3XCZ{a^tzY*{(+8A0q4$HE7>LddP@f z(}YGnab{Q8Ju2=I%otLQ2lwHk5`~35y!{)v)eLV@SJ2nnXZT$xjsOSe!dK3D^VG2+ zh&t`Jf;imf0^9Y-=Rd~zj_UBwUv`cjO?9j|5U!-5^H^w@o8%47G-d0HLXq|h6gLh~^R%8$6(~&YDjVEhjft253q`Wz2n$*OCib>km;D=t|JBXSOj{F3 zvn>|plcNXof&sR#xTTsQ$+U+RlXqt;do_0GW;^B_#y}>6sf&n~n_a>?*xyH~~}0 zjqx0bzk3l;QGw8Z(5?F2M_Jh}h)Mrn-V!DWNt9S1yrlt_p76hzN$n5*-GJvpc4cL( zpC8m;7!a@!KHu!d3Yav)u(Gn+oc`ISaNV*cStgC-vKh5IHnbGMQAFIbfI#iX zQ<;#UAe&JatPT3a&Qu;W0y#Okd^$)4_zdvlAjD(Q4FjAR^bFU(`4{1)L9Cr{-q!H7 zJNVUZu?>4~c*6c55HBAQA}0g|1qIQg@f9F+|FME0=%i z`;nT;VV}*I%W1z}iFaejf=IxNT`%;^l_^Xo6edGX5Di5QlVY()OLe%yWOVY@-*S`S z?w+r|#W&0un>MJaOHqIqOU2D(T~r7R?dtpDd*6S5LEx*{gzfZljpjPXodZ}v0r?CX zK~MLl?lL4>W|>aIY40ZG?wwUhIBK1+v0QlJ(We9940~2yd*lF=Ve;dceAZ@Z?4EJZ z4=kn{;D{SrbbI6$q}XtiWw$+&D<7-_%Vl0l#M8yb@UW((T5Qm?In~L%t=IDiRTx$dO*iRRQJ#sSbs`26x#Be2n-1_{ z1?TCwnfN*f%->oSkuVuq8BzHlf3UK!=!m118Lu=D0qf{kJ2fi-Cc7L>;e~~T?lTOZ zu-;O+ZJqmfgP4wVj2UuEXY8vv{5@2KIkxg3E}C4*Eof;GU%q9f#zb5b{8XQ&V9p^b zsIy8*i8w=(q|nw3sS!d@&MlUfHKUQ0O*1<9OnIo$52i1y3IA5hf)*N^m|dX&SK9=q zU`jaeosH9%%aRFcSuytL<5B*R(siBNmP-1F*KUfB{{E3RV7H7m(~M7A7Z*C$G>?| zB)LN}c@xeIa6O(uh1_aai0)2y zy@+;TcwXei(5qr@(E==)QD)VIHI_^kSDEkGSIOi9F;;riHhd2GF-xK$H{(Y)k@i=1RGtdr2B!}vE= zaUvzsW;ZkJ&^tOx-W=d6d|0g`q(eXM3%?{f){jTs%Xwq(SQkPxll#V%Bof~%0D6gs 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. They may be organized in an hierarchical fashion through :ref:`subdevices `. - -.. tip:: - A device resource will typically correspond to a setting on the device, but it is not required to do so. Other types of resources include measurement readings, and virtual (implementation-specific) settings. - -.. _general_concepts_subdevices: - -Subdevices -********** - -A device which has several versions of the same resources will typically have these resources organized into a tree of subdevices. - -For example, an oscilloscope may have multiple input channels, with each having the same set of settings (eg. vertical scaling and offset). These channels may then be viewed as subdevices, and each will have its own values for all its settings. - -.. seealso:: :ref:`device_config_resources` - -.. _general_concepts_mock_devices: - -Mock -**** - -Mock devices are software simulations of hardware devices. They are typically created at the same time as the device interface and support the same functionality. Most of the time, they are used for testing or as a virtual device. diff --git a/docs/user/general_concepts/index.rst b/docs/user/general_concepts/index.rst deleted file mode 100644 index 215a9e3..0000000 --- a/docs/user/general_concepts/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -################ -General concepts -################ - -Everything tangible (:ref:`devices `, :ref:`variables`) in this package is tied together via :ref:`resources `. - -.. toctree:: - :maxdepth: 2 - - devices - resources - variables diff --git a/docs/user/general_concepts/resources.rst b/docs/user/general_concepts/resources.rst deleted file mode 100644 index b5e3e0a..0000000 --- a/docs/user/general_concepts/resources.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. _general_concepts_resources: - -######### -Resources -######### - -Resources provide a way of interacting with :ref:`devices ` and :ref:`pulse programs ` in a generic way. They can be read-only (RO), write-only (WO), or read-write (RW). RO and RW resources are considered *readable*; WO and RW resources are considered *writable*. - -In the typical case, a device (eg. a multimeter) will provide several resources; there may be some RW resources (eg. integration time, auto-zero setting), and some RO ones (eg. measurement reading). WO resources are possible (and are commonly found as pulse program resources), but are atypical among devices. - -These provided resources may be given arbitrary labels, and these labels are used elsewhere to identify the resources. For example, given two RW resources with labels "reading" and "gate", one can set up a variable which writes to the resource labelled "gate" and a measurement which reads from the resource labelled "reading". - -.. note:: - Resources linked to variables must be of the correct type: :ref:`output variables ` shall only be used with writable resources; :ref:`input variables ` shall only be used with readable resources. - -Resources may also have associated units, in which case the value read from or written to that resource must be a quantity with a matching dimensionality. That is, a resource which requires acceleration in meters per second squared (specified as ``m.s-2``) will accept millimeters per second squared (``mm.s-2``) and joules per newton per second squared (``J.N-1.s-2``), but not joules per newton (``J.N-1``) or joules per second squared (``J.s-2``). diff --git a/docs/user/general_concepts/variables.rst b/docs/user/general_concepts/variables.rst deleted file mode 100644 index 8e0804c..0000000 --- a/docs/user/general_concepts/variables.rst +++ /dev/null @@ -1,144 +0,0 @@ -.. _general_concepts_variables: - -######### -Variables -######### - -Variables are used to describe sweeping acquisition experiments. - -.. _general_concepts_output_variables: - -Output variables -**************** - -Output variables provide a way to sweep over a range of values on a :ref:`resource `. An output variable will have a customizable range of values over which it iterates. - -.. tip:: - The unqualified term "variable" typically refers to output variables. - -.. _general_concepts_output_variables_type: - -Type -==== - -An output variable has an associated type. It must be one of: - - Float - Each produced value is a floating point value, with an integral portion and a decimal portion. - - For example, "-5.5". - - Integer - Each produced value is an integer value. If any of the generated values contain a decimal portion, it is truncated. - - For example, "5". - - Quantity - Each produced value is a floating point value with a corresponding unit. - - For example, "12.3 GHz". - -Constant value -============== - -Each variable is assigned a constant value. By default, this value is ignored; however, there are several options which make use of this value. - -For example, if the variable is set to be a "constant variable", then its value is never iterated; instead, its value is set to the constant value at the beginning of a sweep and then left there for the entire duration. - -.. note:: - The constant value of a variable always incorporates the :ref:`type and units ` of the variable. - -.. seealso:: :ref:`general_concepts_output_variables_smooth` - -Order -===== - -Each output variable has an order to which it belongs, defined by an integer value (either negative, zero, or positive). This order is used to determine the looping sequence for variable iteration and has no bearing on the values of the variable. - -Variables which share the same order value are stepped together. Variables which have a greater order value are stepped more slowly (ie. they are on an outer loop of the iteration) relative to those which have a lesser order value. - -.. warning:: - In a single order, whichever variable has fewest values dictates how many values the other variables will have. Any excess values for the longer variables are silently truncated. - -For example, if variables ``A``, ``B``, ``C``, and ``D`` have orders of -5, 1, 1, and 10, respectively, then: - -* ``D`` will iterate most slowly -* ``B`` and ``C`` will iterate in lockstep, between ``D`` and ``A`` -* ``A`` will iterate most quickly - -Note that since constant variables by definition do not iterate, they are all put into a separate virtual order, and so are ignored from the point of view of the ordering discussion. - -.. _general_concepts_output_variables_smooth: - -Smooth setting -============== - -During a sweep, it is sometimes beneficial to avoid abruptly setting variables to values, since this can correspond to large jumps in current or potential difference in configured devices. To reduce the negative effect of changing values, the variables can optionally be "smoothly set" at various times: - - Smooth setting **from constant** value - At the start of a sweep, the variable is set to the constant value, and then (over the desired number of steps) swept towards its inital value at the start of the sweep. - - Smooth setting **to constant** value - At the end of a sweep (even if the sweep is prematurely aborted), the variable is smoothly swept from its final value to its constant value. - - Smooth **transition** between loop iterations - At the end of a single iteration of an order, if that order was not the slowest-stepping outer loop order, the variable is smoothly swept back to its initial value so that it can be stepped over again. - -.. note:: - Each smooth setting step is always 100 ms in duration. - -.. seealso:: :ref:`variable_config` - -.. _general_concepts_input_variables: - -Input variables -*************** - -Typically referred to as "measurements", input variables provide a way of gathering data from :ref:`resources `. - -There exist two types of measurements: scalar and list. Scalar measurements correspond to the acquisition of single values over time (eg. an amplitude or a frequency); list measurements correspond to the acquisition of a list of values over time (eg. a waveform captured by an oscilloscope). Naturally, if the measurements are done several times, scalar measurements produce one-dimensional data, while list measurements produce two-dimensional data. - -.. seealso:: :ref:`measurement_config` - -.. _general_concepts_condition_variables: - -Condition variables -******************* - -Condition variables provide a way to halt a sweep until user-defined conditions are satisfied. - -.. _general_concepts_condition_variables_order: - -Order -===== - -Each condition variable can be assigned an order, which serves a similar purpose as an output variable's :ref:`order `. Whenever the output variables in a particular order have iterated through their values, condition variables with this same order are repeatedly checked to see if they are all true (in effect, 'AND'ing). The sweep will not progress in any fashion until this is so. Every time a condition variable is checked, a new set of measurements are obtained, as defined by the input variables. - -.. warning:: - For every check of an order's condition variables, variables in lower orders are not re-iterated. The sweep is halted until the conditions are true. - -If a condition variable is assigned an order that has no output variables present, then it is treated as if it had the same order as the nearest order below it containing an output variable. -There is one important exception: if a condition variable is assigned an order with no output variables present, and there are no lesser orders that contain any output variables, then the condition variable will be checked after every single iteration of a sweep. This is useful for taking measurements while some resource is changing in response to the last iteration's assigned values. - -.. tip:: - If a device's interface has the ability to perform sweeps, then condition variables can be used to take measurements while this sweep is occurring by using the exception discussed above. - -.. _general_concepts_condition_variables_conditions: - -Conditions -========== - -A condition variable can have multiple conditions defined within it. The boolean value associated with a condition variable is achieved by taking the sum ('OR'ing) of the conditions' boolean values. -A condition is defined by the boolean expression resulting from a left argument, an operator and a right argument. The operator is one of'<','>','==', or '!='. The arguments each have a type, which includes the output variable's :ref:`types `, with the addition of: - - String - Simply a string. - - For example, "on". - - Resource - The name of a resource. - - For example, "sweep_target", which might be present in a power supply with sweeping capabilities. - -.. seealso:: :ref:`variable_config` diff --git a/docs/user/gui/action/data_capture.rst b/docs/user/gui/action/data_capture.rst deleted file mode 100644 index 1eea98e..0000000 --- a/docs/user/gui/action/data_capture.rst +++ /dev/null @@ -1,92 +0,0 @@ -.. _data_capture: - -############ -Data capture -############ - -Data capture panel -****************** - -The data capture panel controls the verification of the setup, as well as the export of the acquired data. - -.. figure:: data_capture_panel.* - :alt: Data capture panel. - - .. - - 1. Begin the sweep as defined in the rest of the application. If there are any setup errors, pressing this button will generate messages about the errors rather than beginning the sweep. - 2. If the "Continuous" checkbox is enabled, the sweep will restart from the beginning as soon as it gets to the end. - 3. If the "Export" checkbox is enabled, the sweep values (of both input and output variables) will be exported; otherwise, they will be discarded. - 4. The location of the directory to which the values should be exported. - 5. The location of the file to which the last set of values was exported. - -.. _data_capture_dialog: - -Data capture dialog -******************* - -The data capture dialog controls the sweep itself. - -.. figure:: data_capture_sweep.* - :alt: Data capture dialog in continuous mode. - - .. - - 1. The overall progress of the sweep. - 2. The currently executing stage of the sweep. - 3. The last values set to the output variables. - 4. The last values obtained from the measurements. - 5. The total elapsed time for the sweep. In non-continuous mode, this is accompanied by a rough estimate for the remaining time. - 6. Checkbox controlling the termination of a sweep in continuous mode. At the end of a sweep in continuous mode, if this checkbox is enabled, the sweep terminates; otherwise, it restarts. - 7. Prematurely terminate a sweep. - - .. tip:: - To avoid leaving the system in an inconsistent state, pressing the "Cancel" button first allows whichever stage is currently running to finish running gracefully. Then, if any variables were configured to be set smoothly from their final values to their constant values, they are set smoothly from wherever the sweep was ended. Thus, it is safe to cancel the sweep at any time. - -The sweep consists of the following stages: - - Initializing - Internal setup. - - Getting next values - The next set of values to write to any resources is determined based on the variable configuration. - - Smooth setting - A smooth transition occurs for all resources requiring one. - - Writing to devices - All variables with changed values have those values written to their resources. - - Waiting for resources to settle - A delay occurs for the variable with the longest wait time. - - Running pulse program - If one is configured, a pulse program is run. - - Taking measurements - All measurements are read from their resources. Any resources defined in the condition variables are also internally checked. - - Testing conditions - All condition variables in the changing order are checked. - - Waiting for conditions to settle - If any condition variable was false, a delay occurs for the condition variable with the longest wait time. Measurements are then taken again. - - Smooth setting - A smooth transition occurs from the last value to the variables' constant values, as required. - - Finishing - Internal cleanup. - -Export format -************* - -The export is done to a regular comma-separated values (CSV) file. The first row contains the column headings (potentially with units) as gathered from the variable and measurement names. The first column is always titled "Time (s)" and contains the approximate time of acquisition for each row, relative to the first row of values. - -For example, an exported file may begin thusly:: - - Time (s),field (mT),port out (V),Pulses (V),port in (V) - 0,1.0,-5.0,"[(0.0, -0.078469520103761514), ...]",-0.0100000004 - 0.25141787529,1.0,-4.375,"[(0.0, -0.11684596017395288), ...]",-0.0100000008 - -Note that the "Pulses" column contains list data and most of it has been elided for clarity. diff --git a/docs/user/gui/action/data_capture_panel.png b/docs/user/gui/action/data_capture_panel.png deleted file mode 100644 index c2cc34f423d5dda4c2d15bd04ca0cc67bb49daa5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16230 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 diff --git a/docs/user/gui/action/data_capture_sweep.png b/docs/user/gui/action/data_capture_sweep.png deleted file mode 100644 index 26a2275052a93ae317939d7ad75c6c66532cca46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30996 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%0DqmXJbB5M(~g*xqgX?bN1YWP)( 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 ALjV8( diff --git a/docs/user/gui/action/index.rst b/docs/user/gui/action/index.rst deleted file mode 100644 index 4d07891..0000000 --- a/docs/user/gui/action/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -###### -Action -###### - -.. toctree:: - :maxdepth: 2 - - data_capture - smooth_reset diff --git a/docs/user/gui/action/smooth_reset.rst b/docs/user/gui/action/smooth_reset.rst deleted file mode 100644 index cfbb734..0000000 --- a/docs/user/gui/action/smooth_reset.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. _smooth_reset: - -############ -Smooth reset -############ - -The smooth reset panel provides a convenient way to reset all resources tied to enabled and *constant* variables. Each step is always 100 ms. - -.. figure:: smooth_reset_panel.* - :alt: Smooth reset panel. - - .. - - 1. Sweep from constant values to zero. - 2. Sweep from zero to constant values. - 3. The number of steps to use. - -.. warning:: - For both directions, the smooth reset panel makes the assumption that the **from** value is the current value of the resource. For example, if the voltage source port behind the resource is sitting at 5 V, and the constant value of the variable is 5 V, pressing the "To zero" button is safe. If this is not the case, a jump will occur at the beginning of the reset. diff --git a/docs/user/gui/action/smooth_reset_panel.png b/docs/user/gui/action/smooth_reset_panel.png deleted file mode 100644 index d64db1af911091934bde5237d129bb86a932d43b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6561 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 T4Qrt^00000NkvXXu0mjfbRppp diff --git a/docs/user/gui/action/smooth_reset_panel.xcf b/docs/user/gui/action/smooth_reset_panel.xcf deleted file mode 100644 index 300b66e02afab1c01edc7b173f4217745a3887c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19393 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 z zm4Xi|jDO!$SzOtRf=*e^JagJuYz)VVhOK8plS2`x_9r@GP?g4i9Hj{f_1_y?8q$XR zfhY6FukeO)qFxBK1JlQcw-Kg|-!`+~UmcH02XdH&FM1~wIyuG{8Xa4a%%chUY*{U# zkTdH>pG>V;)=)^wI@c$YEvxlQYJw(BJRAz0ir*wn$SLxBOL%`Mv~ixz@x!Wkeba=3 zyWz8oo(Z8)U|n8r{85UYss2sN6Nc!?>)3KG3Kg)NefF{tniMvmu(%u-w>?ogDNKt% zvAW%Up;-AWL4re;X^OUrADFwXsQG;Pc+3h1ON-!#OC$VibgO4LiH3rq!uq+yKki@2$ zq+UovMkMSpJK6LF23}(IZ@@r+B~zkR2vCD~X#8r1-h(srI69RBRbKx)mbDH%2s{D~ zU-?12Jec;#oIFXpcPW7^s~>aGR+Vj50$RBP8nz)a3s0g~8p_I+j7GRrg5=$Yk*_M? z3kx!d_&&TJ7zle!?XAVFy;6{&H+=lus^V3>DCm^w)cVt%#hu|evAyOr)@k*98dhwr zB$^+ATGbiD)o)Z>OBP*wTuT+D@4OaEPN2qJa+a!q1B=tf*Dp1fC-uofcOARZdOn(Fm*L8=#rv2sKUmJA`wz_)=%z;Q0NWMnYyk;z?>zwJ^Y zUIdoRTA6K9B44=!8q^S(&Lv?kjd}IIhCSI)8VXYhlE58C(({n2C-24g;r+lsd~9rM zDQ@YNf>dw&k!P0|FYiS`r%WfFK7}orTDb;^ZCg%ZH4jI%tRk8pfm%*1**}JgYssRk zifhTDnc`Wp_&iaT?6yqdmh84n;g&2uQ=}z}=80>`VwvJtvS_C0nd;xPtZIv%;J8e-K6Fg`00( zx1kbck&4$p*jrnQmmjFAomesuoP~(UOe8bUM1*ZdEu828&kR1VpDCK~q+5T=^||#T zP73NcNCM>f1(E_Jc{whR6L7i@qB0z%*K`l}6Y3FrdS=2)`e*Z2eH?qr62;n(_O89bW>osBWsk_V}darvAZ*+Q#8gjOWMpCQVimSH)5xsZBJ?sv|f6pDMDow72Db1 z3s*7W9zpWE!&MQV_Nf{Ss@?bzeM2rB;WWS7>XX$9mPg>g`}iL^GV>^d;2CK=kz4l6jTX`FK z4PKqqWZ5?F^2ZR!vhCR-#1O)lZFMX;a@oc(c&YEYG+MMy*k-&-xISXFPIuhpr=dt0SQ! z9+9lhj+kai)ogo8yZBjc?DV77q}D+5g+Da9!WBSKu^kP*a1|rV))lUbICH3KFsSy; zzv6HlqjK3mSJS~3uMEQS2<&^e$!7-;7Dxdv^-$%NbG}8gY@s4mSE%swMT9l4pU3&( z37YIK#W078%hwm|xo(*=B36^c^ zZ(vnwOtB2o2D?#X4AjH2{ScOCJcjO0ZKwEV)(4%s&Uj?bpfd!U3<-vKM6x;!G0l=T zux%-A;wFH_zd#N!(WfT?LexoN`#RB&PB;FOSia4L9YA~qwjrVZuiBY*I zV7qzXmRAO0c?9+zy6Ni%5Eck5Td!TIS9Eqr= diff --git a/docs/user/gui/config/device_config.rst b/docs/user/gui/config/device_config.rst deleted file mode 100644 index d2ee623..0000000 --- a/docs/user/gui/config/device_config.rst +++ /dev/null @@ -1,87 +0,0 @@ -.. _device_config: - -#################### -Device configuration -#################### - -.. _device_config_list: - -Device list -*********** - -The device list shows all the :ref:`devices ` configured in an application. - -.. figure:: device_config_list.* - :alt: Device list. - - .. - - 1. A unique label to identify the device. - 2. The connection status. Double-clicking in this column brings up the device configuration dialog for the device. - 3. Graphical configuration. If a device has this option, "Setup..." appears in this column; double-clicking it opens up the device-specific graphical configuration. - 4. The status of any long-running tasks. - 5. Clicking "Add" creates a blank device. Clicking "Remove" permanently removes all selected devices. - -Device configuration dialog -*************************** - -The device configuration dialog is used to configure an individual :ref:`device ` for use with an application. - -.. _device_config_connection: - -Device connection -================= - -Connection and model setup. - -.. figure:: device_config_connection.* - :alt: Device connection configuration. - - .. - - 1. Address configuration. - - * Ethernet: The IPv4 address (eg. ``1.2.3.4``). - * GPIB: The board number, PAD, and SAD. - * USB: The full USB resource (eg. ``USB::0x1234::0x5678::01234567::RAW``). - - 2. Implementation configuration. - - * The manufacturer and model must be selected. - * When "Mock" is selected, a :ref:`software mock implementation ` is used instead of connecting to a real device. - - 3. Connection control. - - * Connect to or disconnect from the device. - - 4. Saving settings. - - * All the settings controlled in this dialog (including resource *labels*, but with the exception of resource *values*) can be saved to and loaded from the disk. - -.. note:: - Disconnecting from a device does not actually take place until the dialog is confirmed (by pressing "OK"). Thus, pressing "Disconnect" and then "Cancel" will retain the connection to the device. - -.. _device_config_resources: - -Device resources -================ - -Resource labels and values setup. - -.. figure:: device_config_resources.* - :alt: Device resource configuration. - - .. - - 1. The internal name of the resource. - 2. R/W flags indicating the RO/WO/RW status of the resource. - 3. The units associated with the resource. - 4. The unique label used to identify the resource. The label can be changed by double-clicking the appropriate field. - - In this case, the channel 1 waveform has been given the label **osc1**. - 5. The value of the resource. - - * If the resource is readable, the latest received value is displayed. The exception to this is slow resources, which always have "[N/A]" displayed to avoid slowdowns caused by fetching the value. - * If the resource is writable, the value can be changed by double-clicking the appropriate field. - - 6. A subdevice with its own resources. diff --git a/docs/user/gui/config/device_config_connection.png b/docs/user/gui/config/device_config_connection.png deleted file mode 100644 index 91f6f7de55cd776e85faaceeb98b638806f71310..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32256 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 K{Um)q@P7cjt^K|L diff --git a/docs/user/gui/config/device_config_connection.xcf b/docs/user/gui/config/device_config_connection.xcf deleted file mode 100644 index 5388383e1a68ef5fff5137ce673658bef2515797..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96006 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 Xep7J1H=1&PHwt*S;kI7)ukQZ=6L!~8 diff --git a/docs/user/gui/config/device_config_list.png b/docs/user/gui/config/device_config_list.png deleted file mode 100644 index 151b5c4680a66eb718cced6036bb388461962cea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20130 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�?4d#uWw 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 diff --git a/docs/user/gui/config/device_config_resources.png b/docs/user/gui/config/device_config_resources.png deleted file mode 100644 index 12de5cf5ca630e9152f33446f4bf25bb88396a5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32672 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 KC;5kCKm8xBY_Ns^ diff --git a/docs/user/gui/config/device_config_resources.xcf b/docs/user/gui/config/device_config_resources.xcf deleted file mode 100644 index 2ac4ecfbba29bbf2e48991ea4ce2d76d842f1818..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78700 zcmeHQ2YejG)!(~Q@5MzfxXVovF36yYoP-dPK)z5zfB?oAFfpdsfQ=zC9fC0!2w)o- zVPvWcVAH#edsA~!aZ$Z4-R<6;bh_Q0@4cDbz0;{!f{^6Pr~Ccprf+%g?f<=*d9!=# z{5$R%vt;fqV{VzhU>?VDhVM8|_aG$jmk2?pgTG`55x+{{I1KO`1R(^%SrCROX>2zb z+K+@d;Z`_o#{4_x+&VAg)_dmP3`HE&>$u5_7B5{eZ_J`Q7tFtTO4Q_AZk>DQf;;aS zGnqyi_uP8ZoH5hSo=$(=BxWd8Q=scj-E>v!$Ml@Qu1gl(HD~Vpg}08Gp`Hs0mv&dE zlx5tzaQ@;k^B2y&ZQdg6f9EZ?ESk4iyFl#SwTP8;?uv9!e!ACzgjaD~*asXJ8P9Ri z3ppJtbNN*Ze;l{7?LR6sg9AdN@EzW9rU zpr+vp=&=?NL!bC%A#2Z|`SM^47FG2_l1 z2Nb&H&V`V?e9po}YA$2Z{0HWZnUSS3m9?RzjB0nyUovk268yIL3l}55mQwQ1fy|r* z^XDv5@(boHy!Bp?v@zr6EzG!=9{#2|i{@qAJpZ0~a~IFQbK#f;i|>J>1dE!+KYv1% z8m^!pteQ@>kaa!rAzqDU5F zG*f;XBY`AC3+b>4GSV?0XW*pII@-Rl{lQnqa_42x#9&t9~MYM<9 zcid}Pe-@XI*Q(wO728=@@-vF9egqsJ#ko7NMessV#rhsZm2)bhI!hJK{|ZsPdFjGA zcg&xw2y0LB7lycjKb2)5e_@F1_*2TlGB6V;e>1o+A`e2bAcwCrTqlRq{(s(q|1}LDQgSl16mKFgL)x?a z@S}5YeMhbx44JkIKX3B29eFaL8_CMV>-rfbOLs9GABoVNWT7(bt7K*VKa{M9xj-QN z1xQxJ{j5kWi+B?Afs&QU`SiQVHsGr7P66Uju4A-t7fXD4&;W@L&U>AB4hJa%*FMPl z9H>H?+)xiEf~1}ke@89>$&qMAs@pz6Kfz1BnE;xMgcI>4j=Lh{3LiOe#dJ6eHUb%h ze+pcvEj4|64qn{PDPCwT^(0=Xllv-O=lnkuuc+v z)qefP2A}<-ERdTvQu^pk4>b9H4U#7R3UTQy*FhASue|sVY7eC7D)J;?oNub*cr+lJ zZ)`*Jc#KD$v`)o1rK)uX#>tgJ7PJ!&lC_|3^;|eX+gpWEH~|a~W?;$BDNh%`@y9{v zPM*-d>Z?4>_z(5J31-K^jy4*ttzo4#y=yn?X-Nco{S~#3`paZUji}2!PuWPrPMx2_YRq9)yb_TnAwR zga;w4fbbfGjS#+qZ~#Ir1RDeq9-bHoBO#64I?zI&33fikE{0~@3OGgt$c`6S#3t(eNm1(?Qiq-}Tq z(B>j+GaHTvY1S7GHr5of%KB$O7S)dxD4$s;C_@l zo6h;@o#~(Q(c8-85>{dER7P}#yHi-5!r#fPPT}xm%Hah1-?uas5{370EEE$wGVl=2 zgOa7NAFcuGejfgZRr~p`_w&2^=Qo}52zidY(vztWkjKb77VAhNJlin6)kCbdV=?x} zC1(_ObS6H3-#XsHpLBK(@&6~cm3F;>LfLeEYPeO^+8uzqUQa!g!rB=GyQ0swK8lv#o z+Oe57(6p|8F{RW$)1vx+rgg*Dc9y$98T`4^p>9pa>{rMuMmP*S zGMFir%ZhS3<#Ky4w)2=-1$~%FPK~V2xhS|kJg;h0i(o->(*jItYGvQg&FI;p4td1S z+pyV4%U~e#)2*{!Q6EXa9tos7>Xgv)friU*%#cq92r#UmgNttNM7yu<2Odz3W-df% zaCV^|RKc;KCJzTO^t?Z}Tn-%HFE<0U7@)H~bv(dktGxx8z-gD=@3vb4(9{=DM1_35Ee->{ZJU;%qn`l+f57iTK zg#&)trSm zmWb7xPP%j5(zZ%to>AJ+*qqlamo()!$t8{XjdDpteuG?6pIViHRqqDKDMSosdgv z!)wLrVGC;wwWT$++Y6!-{!~$0J8oh2#K|A~;{$(8i22R_+S&t`CPYp7{>IS3qpqnf zsR^$Uw{l(OtTIAL@4>H5`>DJ%3bp zR4zFZegsxqKR*(3L|UDAM0dpUDYx^;*q4tS*=9JR#%uJ$^}~I!U#v;QW|N+SW~^hy z&^wM8jxj=XyZz2|a8Z*{pI9vhvq%%yYLQ=iC; z$UV6%Ievx9)$r%U*gw>{5?ri@U=V8Z9)4!BLx}oF5H?f`y9U202&owr#$Szl)V4XK zTFAeu$vWpZLV}=mXv=%JW!y{HAw+QBF&CP@A_%Vz6Lf-m)TaIVMnS04H4E`d2j0LN z^Oo~(3^PZ4$n)zmho2SqC!SAz57HUDj`yX$wMBoH{wyi3l8@)vIcx@-G3OzhZRP_} zn{0=}KC#;VdWFq4;VqkOZJJGI>zJ^9dw8R5kd3vr8mz{g2d&nRVna7tcZ642D@R;m zwNBVzwXR9C>a3m#Z&#mXstE$fV z&EBeQyQ?O@P^GJqiz~w`#n-s~l_8b2oAi4sE7yjXRgQV8vT}0-JjN^rsHv>{LAPg% zerKhjQkt|9pW@<*@CtDw_i05)h4e~dg|5Q#F}EZ5F@BXoPR!@8CStQm&q6cSv24hK z3PVLh`TP3a6&0_Amj-v-9DV@mMK~iw(qz`LJfi&IbK#4=JGAMt_sdeR*uQe90dj6AEBg*1=d-d`heiLctQaw-7%_(ZAp&bB0+VYm$$Gyk{f`sty z1#%K&)T$4-@x;ybxnvLw1d;&1juo|G0YV8q0`AA$LUOc7t-lYozs`P zVKalA3TM2Nbx}bCAZMfXP}nCH>t6#wq+`Mx+rt~IgRHDI00=;i#quFQjs+mcQZXVJ zKeGN@WvO}-@q=EH*C+lAcwX7gPLIhUKw6H3hH)oD=dnNV!5qL5=wFe_aE zX=q`#S(R zV;5FbPppbkBdi#EjVsCUV7~Kw+K0< z$B&O)kd->=BY(X2f8t|)z5Dp_JqS79Tpv1U)YZp}j)fl+Kj1z;7IN&!(aBeo?wo$* zv14Or9y)gSIsW+Ei}#gYRdDRsIltL`?4zB>CO>~ncT6rS4=)$ja(l}|%4^@(?lLTyEf~0lpD$cIq)eiDhn?Y-{U?m3n`OcPAJorS+;WD z2S3KI=!fZt`C>kOB>|gFS^>@EqSEkE@z9XJmKsVM4!^74Sz5X}{9x&r7fMUF=u6f3 z{qVg|FK*Othe(?AOsTFEI!2TpTp51PHwWIo^u5EWm+$><`lVV2wPWeb|M|Z3`&Ff- z(|+@P>BetM&s#{l9*#V`uLvM#%A4;cs*c9MtSou$30@*98$&SU)gAx^Bb)L zAP7K?ZQKh^K#&lpd%;bBAOCPVUmgbd;XZ5QUVXjOc}mxa_(8AKVQ?683mwHN#;6Y* zj-t$AVC6U*sqa8K-Jx>;6^(Q+v4W3%PvPtN z(8QW+#Rws%YotLAR;iFP%r1RA@Au<=pJEqxT(fHPxXqAQ_|*D0#=SA5#P`!n*d zyyLwob<*t}?c>{1PD^)mw1>BQ!QU!u`iJd<+NF#aP7OXK-QCd^-qxPKRA^ohX>3bw zlV1Gh~=TkOl47Tn?d^;_ZUoW_qO!sYJx{^p*2!ryDQw~$y<10`^=kNALvKvN4esTxL(hW z%KpN&B^e$hTY1`wqrro>(4*2L*wLN$fXh`H`u*Mrt1Haqus$%vwaZ|1xwfS}bA*oD z+yUdZ&?C|l*hzv;I@i%_FLdSnrNLGIp6l?J4%fZ$EVwca6!sj z3eWJ-vXE~Z{xT?f^qs<#7Q93TVY!NY>ka_L2s zTKm+e%YqI6&@$;CZO-vdkla_OWFMAZrTN3snszKdB)vxS2SKtie?Tgx@&3;7u;NrF zNVKzn6nH^iL81!+<=I(aEwDg2mFI$HNTED86l^H4^-H3y(&4vPk6WE$^&Ne)w5fS) zbBa~mbg-d*T>X$D-@1)!PM#irdWg&CtXjSG!|@-2EU!k9u9sJ%NY_cLsYsueR#TBa zC9UxxIG>c(P?0_%t)U`a3o!_#qkxlMoxAgoM)q|^9 zaRb1wyMo){ZiErX|J1wP52eHoecbIneql`FoT{N!(%LhMblS7WgN@J7S*K^_B^Q(zpwy?4z`2j|Cf_p~qOqGl=xxVeHx2_5@j3i$Vq>&GUyszuO&Q z=EHc0dFc@T2SXFje*`i_%}#fK*$G0l*5eF!N_k7bMmc)Tubny9opzpj$9dp0yK}`b zrz7n~^U18KPn8CnouQ@dY-bSZr%Hp(PTCQ~(LUv=!@-7S=wT39$9PA8=(79)>9uw& z->=l~Q}X+yB3iyziTAL0SW&72MA`u&-3|h)2y}q}X}-W&;3xoihBN?l0l+h)P@wAz z)+2QH9i%7!Ui`!St@b4Q|h;ZlgxmdZ4e*cg{yZZedBU{ImG2kec$E!AQ^0Mi5e1ZnjsP9k_+i!9j;+txLg~R zw)+f?F1<@+#)F8)12lXD8uB-|T)$o9YPo=aqHHgTI7&+yCu-q#w_bQ9l zu;?`wtz%JfH&JqNniC}D$LLd(l-1&z_YPWZUXGvKLjqQ=Ge)hjC2r%{qmUdQy5rT|O`zk5yur2(s&v^cQ#!`Ndp`O?CBGYvu z>N?;iHsr5&Ixo80Y5mm@I)|roTF<-ev~%}~ApI8RWUc!wDR^uU5gIw<*I155O0I{f z2wOVjPEm4Engb-|$EYcgl)~`u>;)yD;sBGPy)y9**8XC(6fM(vquXWDK3MzHwMVzf zq;0VFr)!J$$iy>P`-{btK{KSHFst`SPiDZ1QrklAn8)*FsMQmK#X3*4TP863tJznP zmzxh0#qyB+I-0Fi&{4a!rCNLA^&AX%K`x$fjD3)~};Hsy7$6vE*(JxSb2pH+nHkrVRQhUyQHrw9N zFKv;S53zYt9*Z;EUc5B(L0cG>=xotenZP`-lyfgE8%bCH${L9UAy&^7Lw;?w9!d)N z%o>IzI%~8=CUg<$URW&>_lA6CiNKN&i?|8~j8HT()@TXCqFDSJto`XM(Po*L2Wx*i zbF@h&roq~u&J=BwiE*&@r!z)3%cOa*_7|(AXqm1#x=AKYgS9_hQ*@(D8V75CIyPw4 z#Sw1-OnJ=sgv1kR#WWbGw%x;>B4m50i^QN1+6sz62m$Vbgs_X$#cJ|LCGpHcs7V&H z=eM|{m|%ezf+T?$9(B9F2(`FFf<$YHqv7_*MoqMK>grvtX_H@ZxgN;sMzn--?t3O$ zfKPnln?eJKp&;bm-$}O4EQA5V6*VL>?h948T+dGAZ+nD4tMtQ2D<7&zms2J%TL@xU z{oRb4oKY<6-p^G{{nmNSs(WTT0hkC_R2&wBL zX0e*UQPJy0>Y^KD(lA*2)3K4}nZ?jrPy}KKSb8R`i`7t%;$Ii&Q>1P%3Kjj+K+6j* zL1EK2yP_CoNl#<|NVVO;9dWt72sOJxf>7)J*81Bc8#K}SL02aTAclhQKvobOfpGDB z8TUOOiQ=K>1!v-VzCP~7t9d^6ekL=q45_fC^Ka3OtfhemEacz%b>#@&9QJV+;X*O| zToo=5L+5P|JI^}&VWh&0SFS?TnioQ7r4;#~q*YjpNVH-!t10hDADu z0$m`67R&C?&o$vnc{I*wdEwG?mUSy!Acp2hCR?7%hy2=XK9C5~Mny}JC7f(t471AO z?vPJR5eR1?CJ@8FVbRFgCQ}#|#cD7|0q@LM*G1~Oh*_)#a}@A&ja{TJx?U#rgS9^$ z8y%im3^kRUfjT%J|KhzFh#XmNH6U0)r}C31%)L+_17b{5257rY2CQsSgxP zP2cNs%_w%=9yWCB&8`PwGUG9qqIGd9*5L9`7Y{)gz7_^gO^twlsaa-Wd5$2=cwG=O zz>{NVY=vOlD=7LGr@IV-hq`$vy^OTiYl5&l%HNAlZ78Z3`-BS}ckukk)ws-3$L*lr z8wU(LKYbm~XRL?Pj&<3Q*`M)>PR5x81Mi^=ui(XTALv&5SKLwR!Li$5vrSuTv!$;C zoh&xqH`u1=X9@JP-gcb1dswYc4F1@7xS)VJa_ll#t<%<6tp(ubv3r}*`c#TlQ`UDk z>Kl(Uw-1ZO{Ltu-#H*^9FUL-U#WHQR#q!?B*vr3~78^7DLyIE)at^!}!rM$8KP>48 zukn@)6z_O*28w9BDFgLyyfFiHZ9e|RH)kN=#W!W3z{M*nmD+Ba+N6S_$Vh%qf)d|Z z48q;s^<{2%Pof~AC`7wJR9wjjN-#tR&PTXlMJ#~nC@Om>`%qNo3RF~rn9i2wf0U^3 z{P4BFWMc#1TIy>&zbjH>J%2cIgOOJt4f!6zvN? z^-$VT1fue(Gny$%oUJlAnW$K;Gw%cA%KjyHgu3sjTu)yIR+-pv-(Ww}@3rc!$JsKy z#q#*zZOw6}3l2MN@5oIFir6Bm<^H5|-voqGu_i$&&0*7P7tDu$ar?~BOwRE4s;h0|JaHPKhr5&$jMP_~Kh-S)6 z%3T5)&O_H$EtZ*RdfUI?DyiF+`lzQDfmtrr-#ggP^m{FO3y2CXubRz|4c^)WqSBe^ z=fkq<&X1bRPo}6$mvZi^*Vi9s%d94ok&4POws!i1!DKr7Rg>u*6qU1MW2Svzq9(k- zBxfV4G}47uQ+ih%=?o#@ABs4l$Vdk2r+7s*bsiDIq$XXRfWr9Ls_Ifk;O%$4WeKJ*V_fj$z>Vs`{pgW% zVrT5uT#uCd?3n;?pWrpnn(&e4OGIz8<4-W<@D|9kZ^~NgGSm@Qfk-yR$>W_qX|R~;Ud3UqJyyVKc{@R8=(KyR}H zofzIi2ZqUOr~^YsTsc0UQye^U^a+c_n6L@m6hbVL|31ebG~PeF(X99*;C)UpThR3Y z2$1oWN%cCw$7p_cGxtGLBI*MGao}=R&&#ttNKl^_!+Kxx7EYxM;O|uZE!n?J1=#44&MD3l>gmcr7>}oITLRr)GXb~v1GUR zq}&eG&)PlBLj4@xt$q&V6V=ZOvy^)ZSMLUH4Gi{K$PT^G@m4IXXfS@jRA$T2SNU_< zV9$3CXis)G2B)u7chKp`M6DL$5YNBUq212vA)DDfz!1J6ag-?&nGQGk-6uJR)3-7cx{bI0H)j?yd@+Pnrnp8(` z92D%HHYI22U`-p{*qtWgYN&%p9{nHNX@*SIN{om|H(|s}8r^)ZP0=P;6GMG8-Q^K3 zxn9sbV*_ZQdd7|s`as1;r@}LCLf_W_5^3scG~b;1l;77Ct?i_*oX*dNI5k(-(axr@ zYQJwQ(kW%D)%w<8D{8O_*1F3qeqUB3(S-{wjlTdADT-{2McxFHO3V}nb%yrxE=omB zrQO(_I_pZsE~*X2!eH}Gcl%uYh!-`w`Gk#8Mp!RGr8Zsg@RwZgclHaQf$HoxhOes) z{hC+3`vO}i-hE?u>5-`bCnj{~3m}oE^F}*?ihlP!G(?lWbT~d6;?TVJMmw6qs{HPH zNT-yKES3#}EvN=3Sn4h{``z=9M895W1}d6WZ8*j(Z-hA!pdxd}LsN3+*;BHY4A%7G zjosm@G?EYX||-2>Be$4ubt__G^k{- zSu%y-G$lDq$Kg72ZXie7WrP^TxLVUx=PLG3CX*{Mk*|F`rRGP(>k|5 z9Cw276}0^j;=SiKQlYmKJuw~*_YhLjxovgSW&zGlFDi+6-ac={==kf%XM>1=NS}^` zn&usyJCL{XqVl%WgC`Q#>9CzLS$r+2FtorKY=^_!2-yG~hHoci8@qZvY4n z3!czG+Oa$K4I>qd#bi>(anHPbLR8LA%5{+^Ag{)=ufeV%q~qh5duc}z8EzonNkxPl zih`PE5CSr(gpjf*Ek2@e#crW^FT3ES>=H0NgjB=LlVb)#6iFazUWq@`kH%i`>47#9 zK}6)cLF=SSLvW|^2oH7w;bmM`>os8rItj_2qqJAJuGXu<4D8oYaDI2{NB$5jX8M0vBUx7CE#V^RLLF!-WQAs zSSCO6!Kr>*6Rk=eyWw9DgpFhV=Y#+ht8N2+HRO(x98XPlru~tts z%3*!C#OQb7SYmx=CLy=LGyn206u~CXWr6H_zf1D6+o-n~>}33eugK+(sp4x6ts-R3 z?euvaM?7b5JU#k}rE=14kRvBj=#pi`9lKYlZn*olbvNxj?@*km5optBxnwfn7^B0s z)N410$q!vZGzxi>zxnl(YJDpo5^p5`6Zbq(DWoHw1-}k$U=79-N6u72Uf|$CR44>b z?tbn96%x-tlz^?3{ep7r*B>ay_I-wOcJRJKIkxW?l(P=~fO2+46{-xL5z4W>hTgqr zfI--x+em|QY|#yrV{s2qj#WPa%CY;8gmUcqBcL3+^&iTySHGbgJM{<3mG*sxa%|r% zKYJ@g2*-}Waj#I09eRUutnUTNS#>W^&KmRx<;v-MgmR_6M<~bo-e}VIy56Cj-9|4^ z&Mve^C|B;IM<`e7dxUaU-wTvugI=K=8=Qd}0b5r41?AYUKTwYC`wZpm;C+X3Y~L>^ zXC3+h_rLb+1k zBa~x(Z#3z9UGGrNZlf0{XBXNdlq>hqBa|!kJwiFF?*+=SL9bAb4SJ_W2$5i0_BqNn z?M9eDxic(f{B3k5rS7+}N%Pz=EMlYYFw!(>#kmpZn;ssN@B$%fZFO$!v{iJEH_pD$ zJ>GUm$CUsnp8p3p<)Jy7n$LfD!s-q)#0XhQauJ z9U-p|2jFTN{fSzAI211Ljbw6!UN1d&2b}xULD0-y!`ajkA9jba-`nWd@0+xR)^|WX zowRYmq|Ima0Ml`UI$r!?JpEUAdNyJV?B6tLi&}j+%16k?WKvFhU+|>TTh6$p&6+z` z8&3xIZke=2t(K?01e*@<-jVF&^U-nF(BquCC?;vHtEb*Xtv;S}&g|vMuurdu$PJ@I zM$0dfLLCg2;aGX34UhPGR^(`ME=zBH@3GNjI%#7Ab*R-7>2+GdM&??{Oyb3XxR#$z z^rJgq=&co;OHZM8Z)GEEs3l_Cno(`-u({vGM$=gKBD^?KhKtm-5_v1&J6>H36}Hqq z6f{3|ZrnL#Y+6s9V^G8`l{BZG6qk?gpd?e~7gTA2N)S*n@hhxU=N431ffIiKG;d2Q z!CE@C;EO*+B|!p7NF}_#FU~7~K2!M&$DMdudgLrGJ*k3UDjT5^E_dc(cDX8esaygA z_)K}Rmjh6X4__j~Qz^~6A7{g4ADpsQxx0CB3FF^XAFN3a1`LL#^ttdV&keR!pBZV=7HF#g zx)WNePXIn3!3~EKvkL8aodFL+H!W2#Z;@`#3iCrKK#GxR`q9Tw>pVPzrBS7 zaB?CMH^;eQ1gS9)N_;8BN(to~bXt-R1uDBH6RB25^NdSVi2O_S2*{ktM5bd>jVVyj z4hG9K%V|DJvsyOt8dp@*Q(7%A><-U|JFWYY6cBhd+>R-7PW zj%}X}O8F+-4mEAsgb=ulpa)xpA$jbd$y8!h=YJGOHIj2T_cci)t;#f};*UlwV2r>^ z#|CZ!d=dXMIPnDW(FtT&S)!B2nWTd`U4rQXGslpnK9y+6(5!5Sg*Z&YmLOKpq7-Bj zf%-41GsH7VJM&M}W``F6=l{#km%tJDawZM;lQ!jHe3Hx}9`*U#1Q{QF=5k3Jd%noq z1gva)o=%5hQ{^G5jKGZg=qv;C@Lq5+w6%FjHrPPRU~c#!JP1u? zYN;YEwe<|CLR}jXho)SQh%$5sTBHTsUO0`KNiGP;JTN#Q^W+PGnGP)@6+DT933{;J z)8f?eHv=$)kO7hnkjyV~KbMpZr|VN=iBEAeuM1dxA(I}s;M`fA7o&!eYQ?`iaQ$V{ zOgAa%yf8JAxT!y@x`b(lwUD0Q1n0 z{TGH>=}J~EX@NB>)f-=#SI8hOutKF4DpL&8i9;#T)~covk5WVzL%?YUBjoYJvBJLE%%kHgdT zbBlZk5jp79-%avJczoSJ|F+5l>+nSc75#;mt{dYtb%9mgs0%FaL0w?gPf!=w{YR<` z?D`|r1$OJdy1-ujRu|Z*Kk7nh-)DYedw90{7onQK9>H<1>H<6TrY^9)7j?m^dr=px zL67P}Iem}nLaFajU0{8`3Ds?f-qi)Wjb79RyU-rhg>oM~stcvQM|HvKdr=qIpjUN) z4Z6K?rBGED*iKUyETgImmJ3oBtT0$zu+lE-f))Qrb>Tl|f7OL{jjUr|)dlO;Z*{@C z^;=!AZv9mktXDtP1$F|qcinuasSB*?MqOZW59$J|euBEd?mtppVAmg^F0fnw)dlwI zx4OVi{ZSW6`#!4+Y~L+Edn-gWfgOY6UeyJ5=uKTQRrjJUSc4wbg>w2H)rC^u zqq@NQ-e}VIy57|VyNzDd1-sB5)rE2&J*o?(zDISz>U&Wa*q~Q+fem`2hN>>Gou)2W zMpYLq7o;v&VX(SjrCrnoEB=w{!hg*EstfHJ`=>5gw|=V&)~(;_f_3Y!x?sKfsV=Zn zF09r*LA?+1h#NMzk%g%tLl=2rfJt+={$T%Z#4^}g>)*c%QnB=xSfcIUg_6AcV2`i5 ze;3}kwk*{FO#(LXihrBf0KP-G4Hp#6qAAVLK41&)UnkX}~03Evpn@SO%AzL5FW&<-9Eekt#G?v(4`tV^Lwt0v{X z-z6J{-R>j{2cAQUCj&d_;UyLSr90^do$iL@}7XL?{!vM$i!qDD6 zlRK~tQQ21bvu#5Rd6x{ZfbHr1ckKinV1c%+`rpZd$X|7{*$pOCaBEvO@zI@ZF!M(D ztwBt8sll6fSqL@}p2E4(vKncAbQr2iQ8mTe@rLzimg+0B`?$cZ(r!1FepJrU!N+pk>+1 z2e|%o>jrir{GZ-wF|ZTi|MX6a9(E!Cn4k-?>IlHn18&A(+A~u0+Yx|u>$f9-O89@B zn=zhI;eHmIV;WSlXaSqV7O11wfX z;c(dsUs-d9_TGGrS=d?`m_uuYr`WnPBQmu2=7W_xJ1zng_P-6PkDJ&?}lN&CiJDO1r+IIcwfmG{@GR8PN&N zyHXg#I(nyrLG=y)BZ-Jt4litlO-iAp6&FyUf%dU1aD8Pj{l^yQ_U%+Z zagMFDAzmu`=C<}ZX%HiV1G-VfM{g3Z0mM&xKLHgV_4@&Eq4b{N*61m0>ZqHJ=+u=} z?saXLR;c?`mnGv2A}Ak?S3f#VpAv-@@J%k|v+ybva4@V4fG5Bg+2Pag;l!aVA8SM1 z@yhqzdG)5Dmv+zN*@xWq#G&0bRO4MwAfSDSG3}EKW)7lGcf-y62kvgn5=j15rA%`Ch>EWX=@F_-cp$qu3 zNbsj&0vmMqff%)3{q9K6XJW8A5{~cf_G1kAxskiovv>JU0s9z<`bm;5ACBo<-OcA? zSc5`+ES&wH`w_agmKl3Ncfz@8+>)PNL zuYNT}4;vjieWB&2AJXtJ@PFV+BS+(FTj*DB{z)`VR#>Gc-j2T}#$NBjw|cNG8}PWo zTl$Y*0wwH}&}M0RgC%EPr#A80cN*b428SoXTXIlP;0plo(37uW_5q6N0XyMIxb{uQ zz+UiG1S3~Ph=VKBx&`7+IN{arKhA?`D+BH(`2^0zM6<1$Ya&GAcF?|&$@)NyieTT1 z#1D$w8yY?z4>MeHo4>1(tM;Gv?go7zo<-8{OTvo-1MhC|dxU%&aCaKyMGZ#Bet$nXj?Li+UPck7Lw_3qNKV=v5&)s*xFdi_BkpcsT z@xPh`1BUT{Vcca4;28|+w_J43;I?WDpUeAdHKX%I&vMps<%r| z)#cRrY?U9rUX)V=S7a>=nN14#g2n<$F`9E0G!)RQr8!4IUBSPirGB$rvYL!>#&o-A z-e0^bZdIDyXDwe_E62%ccG2`!l^l-=;|o3|u}YTWB`oPCA&K!Kt?DM>i}$grE{S$m zyo*+~3-JO?wDIvgOE}}5G~sTIZ)J&=_!gRQ*>mwCT()={=3lf}cS< ztcTYgGMZvd>2{xa&xV&)#jeV*i`K@|Cu2_*a^KklXs+LElg!OcaZTwq(YU8*W!%a% zo6lVOY7NrBCN`HG!&|qBP0x2IiItQFP|{6;-a1xwlfYZ2Rb3L423SRDfC)+iOn4me z4%&;-0889R15CJVxfls}>yUWfWQn&x!nm%&6mLqib(jyndZ5`DYfQKKjJwvaS{b`C z!zP;RPt?ZN0u5{dlBVBmm5fb|agFI#v3bwh6>%%ltUlABm#dKmQ1k|+fmLi==|vhq zVg;o^_X&FISk+AeZ=F_kNzhxzDthZQf$YZwy>*(vTgQaU9&e`!!J3PC9&a5IE1Qx1 zRND;rUb!N6MTS*0)zwzVRs#(JpG5s~d#+Juk}URB zIjejxuHR*rb7Wt~v7&u)o-CbOkN+JnN%@kz%Ks6?!S6; zrE82!UL`~eqGR~q1ij#XD5X`%Tk5=iM5!=ZkXP|Be9M~=&o(wa7g5afc}scc%_;l& zQM|m$8SC_Zm>U_Hz1gXAN_k80|MtPoQBHYPYksS|swKK5lAF{bud?Uc^UPA&+ol|o&shH2L8RYH#rx_`=AM*JH`;;Dou0Slw%jINvt+WZ%vtGse%%fe zrSs9Xdyv8>UdMm$^GM>A{y(Ji${y*wvPU|v?1|2_q8mEX@^0w7vL`yP?2*nZd!+M9 zN36s9K~7|3)+UF}A>}T?|M`O*qa5-|r1MIZ&MT3`E0M%2E%_FCr76!O9ok^bG5VUz z*X%?3ty;JHRBdjpbfVrC{7f&m<~I78nvK>KIV*fCi?%l-oxPQ-cTqYQ;eR_N@d~6f z`$Iaf=#kDVdZhD;p6E;~x}h^I?}pARkes`?3J_aQIk!W|yYK2zmBJW7UcpE6V(ajS zcs=iaFr@{=)^Yua!~AGoUg3;!qC7V=f;>B&AhnLUDf>`pSG2~qdbc2*H@50prQG}R zfBxXsQLXZdmi!iZg+1C1@@$t^Ac4lqzB#Aa*HHTEUZmf1ukAWf zom(x{)?0&d<58+ZvyD2YL$g=utRkw=Y}wa{8WgC03Kb~CCsA$YNZrMfm?e5=Zh#h& zm?*Yy|4l;Zq(T>9g<6;(wrz=cypBI`{ULuTps46Q>u)}Tuf4@jWY*b?uj6z*4#j}Qw zio4q7N`2quI%IHtknA$Jq!09?^rJj+M_eD+H{I!|1g5IwEZ zS@)Mg(W4hSowS3yHK!H1gE}^X8{2|ump#W$V_S|5VlXvwtiDrdY2=u_&CJlqLK;v* zBWsnO85&t#42?&rp^@FBV}?fd$_@*)GeA)4(aZn=Ie}IN#I#7c~l3XLd)x!h$tFBY)J;(khUm@BFuOlnxp9m zM$d!1fwwx3gnjAc7y1!mkD(rvhqyb!%!lya>Fcn)Gr?I_67g8AZP)cX_e7j>R*eV% zi1RKx?c9CBnRDG~=c#uPAfSi4HLg3J6<}4 zIn+>zasbnB3yewj{PB z+qJCAo@h_D`Lnh}Te4Nn0`ezXlgwh-+mvFc0TRs#rVg}un}KM`pflbCTtNF5xWIHm z{HjE12@heKA-;=dSZ+%IlUO#{hENre(3HR|W7{C6DiT&AAHhVn*pigNDe&>c>-CVO zBUIp{_xTf;Lj^+lxU!aJL5~0*d&p2es&$l){*1~;e~HFNt;o;EPDPB5{vwr+YKg)} zEz9_*W)(g@0Ws#3It=7dDr`8IyuFbjm2@O5&=M zL6Mj%wn5=9qeD}di09Ju!Y0q**C6eK@@ij*&%+FM-?Qg|(eC~_$*o%IqkLXya>2%v zU}BpUBfTl~sLyx9Z9dgHALYZt-#yuc0-! z6{D8i7L0Iy47`(6tIbxXGq!h{qFipxX4tB;X0Pz#e1_GEoTP9{pGCk? z%1J=bM&#s*>YxZY356ObA+2!|(r4l%G&v(Dq0N~%2~7mS@~HlRpjxyWM)9vr6a;F^ z!ZRtHCE&Hg(`9ysy`h=0wxn&pSkCikzmDTup}}rjePKwcYPRZ}^qGVyJ9s9U#ai^L z)2W0cC-wkNX1>>|+O4`)@f&V-tG?B)TC7+8p|w>BNv(Jd$U=zlVT1@-2obUnB6F@8 zBbVltGPzH=8(IJ&X(lL63msroD|rh z@DJu^9+;nj|B{$4N;{Lq7_cpIL?gj`>d@Xwe2G5k{D)v}ggn(Pn#j}9?OY6 z`?+La4A`WS(oF9&b)0>Z(3~~b=Tk7ZBW;1FE+i&@2dBBU@GQ-5J#? z2qj5E3|P9TQl-3mEqcpwXWAUgsjTaH6@#6VSMavHBn@>b`qCK>IS)Qid)!`eL3HBv zMh$_T2hN+Gw=_uuT?)Rd+^un7VmZ2@KpkvyZN%z|=0C z-hioHJ1(Q-bg^L{rlxCd6J12fT`A%U3NRJsHGrL9l)^vQqQKNn{{c*;^BJ9(+FePX zm<}%(yW28V?Qu;Ns@-Y z6o+AQ0+VEVz(#Pc6Twy22wLu3ojznKKn{1VJ0B*KEp zVzb_S+>v&p`DE7hP8FCPlUF#cdGO!`z_F7~Bru*@%YFqsZPH-5b>F$jQw@nJBEvia za1&+`;1PxS1K=ji9soDN28Oj4uzd53KDyKebJldB3+Ak?xXzW+jd2^tjN5d3oHAXI zW{*?n38vZ;*bD(p;N-wmdm^0(=q9G4d?*i&=};fagJU|BhjKcGhnNoDA*LgCi0LpL zVmdxYFgl56r{i&m=|CJVi;cji+T+xrHzbu2cbYw3F$3*EW?&pM1JiAB%FsH^hWAXz z)KhJ6&p4W<2^>gIwZT2Z=&_qvRLX;6pHdziyOeT4iFqZaqgyyW9nwM!&uT4wR_R%- zN-H{Oh09{2)v2~Pb(jiCWptWmi&qRLdyv5t#|)-)Yn(C+O|!y1v+?ItE8a7VJZS<4 zol~uF&oI*JCKi?Q;Mk{>2gh`X2*-g3gC36#3L#HNf)LZ;AH*Qb)l`;gD~r;a4)EZz z*vM|GHBKGUK~fpXrCH+@%fcRLS%CjZA^KMQ3%x1+4epCArV>7z?{Ky>RejnBv&2ne z^`=^IzY*X1sM?M`IB-q>YHvEbvDlgI>~OR+9pBoJ-Joj}tKO>u2OM$zmg6?`!hvgY z)D@wl-@h!fHWfLt9UZNha2{lDqA>2Lr5 diff --git a/docs/user/gui/config/index.rst b/docs/user/gui/config/index.rst deleted file mode 100644 index bbb7b16..0000000 --- a/docs/user/gui/config/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -############# -Configuration -############# - -.. toctree:: - :maxdepth: 2 - - device_config - measurement_config - pulse_program_config - variable_config diff --git a/docs/user/gui/config/measurement_config.rst b/docs/user/gui/config/measurement_config.rst deleted file mode 100644 index b0c0d27..0000000 --- a/docs/user/gui/config/measurement_config.rst +++ /dev/null @@ -1,113 +0,0 @@ -.. _measurement_config: - -######################### -Measurement configuration -######################### - -The measurement configuration dialogs are used to set up :ref:`input variables ` for an application. - -.. note:: - If the plots cannot be displayed (most likely due to a required plotting library not being installed), the plots and all plot-specific options will be omitted. However, the measurements can still be configured for acquisition. - -.. _measurement_config_scalar: - -Scalar measurement -****************** - -A scalar measurement consists of a single data point for each point in time. - -.. figure:: measurement_config_scalar.* - :alt: Scalar measurement configuration. - - .. - - 1. Whether the measurement should be used during a sweep. - 2. The label of the resource from which this measurement should acquire data. - 3. A unique label to identify the measurement. This name appears, for example, as a column heading when capturing data. - 4. :ref:`measurement_config_scalar_resource_scaling` dialog. - 5. Historical view of the measurement. - 6. The last value read from the resource. - 7. "Run" and "Pause" control the live-view when there is no sweep in progress. "Reset" clears the historical view. - 8. :ref:`measurement_config_scalar_plot_settings` dialog. - -.. _measurement_config_scalar_resource_scaling: - -Resource scaling -================ - -Resources can be scaled as desired after they come in from a device, but before they are used. - -.. figure:: measurement_config_scaling.* - :alt: Scalar measurement scaling. - -The resource used for the measurement is scaled according to the formula:: - - {new value} = {linear scale} * {old value} * 10 ^ {exponential scale} + {offset} - -.. note:: - This scaling affects the *recorded* and *exported* values as well as the displayed values. - -.. _measurement_config_scalar_plot_settings: - -Plot settings -============= - -The historical plot can be configured, or disabled outright. - -.. figure:: measurement_config_scalar_settings.* - :alt: Scalar measurement plot settings. - - .. - - * Enabled: Whether the plot should be updated at all. - * Capture - - * Points: The number of historical values to display. The maximum value is 10,000. - * Delay: In live-view mode, the delay between successive acquisitions. The minimum value is 200 ms. - - * Axes - - * x - - * Autofit: Whether to automatically re-scale the x axis as values change. - * Value: The unit (time or data point number) along the x axis. - * Mode: Whether to use relative or absolute values. - - * y - - * Autofit: Whether to automatically re-scale the y axis as values change. - * Conversion - - * Exp. scale: The incoming value is scaled according to the formula:: - - {new value} = {old value} * 10 ^ {exp scale} - - * Units: The incoming units are assumed to be the "From" units and the displayed value is adjusted to match the "To" units. - - .. note:: - These conversion settings affect only the *displayed* values. - -List measurement -**************** - -A list measurement consists of a list of data point for each point in time. - -.. figure:: measurement_config_list.* - :alt: List measurement configuration. - -The list measurement configuration is identical to :ref:`scalar measurement configuration `, but with fewer options. - -Plot settings -============= - -The historical plot can be configured, or disabled outright. - -.. figure:: measurement_config_list_settings.* - :alt: List measurement plot settings. - - .. - - * Enabled: Whether the plot should be updated at all. - * Capture - - * Lines: The number of lines of historical data to display. diff --git a/docs/user/gui/config/measurement_config_list.png b/docs/user/gui/config/measurement_config_list.png deleted file mode 100644 index 5b51be48bad4df0bfbfd68f6680d614ab1cf339c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140457 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

    |Cu%Zu?-D{K3aR4br=C7zs9=^u?Uo5CQy7wf#)6 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 diff --git a/docs/user/gui/config/measurement_config_list_settings.png b/docs/user/gui/config/measurement_config_list_settings.png deleted file mode 100644 index 9461cdce5a16e9fcd19e1f918cfcea7d8908f928..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7521 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

    C5f1hlzBp%3{M~t<5%_|%Pz~C$%3f$_c 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 From bf6ac71b155fee217cde52be5a08f49bbad0c3c4 Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:29:42 -0400 Subject: [PATCH 04/15] Delete examples directory --- examples/SpanishAcquisition.egg-info/PKG-INFO | 15 -- .../SpanishAcquisition.egg-info/SOURCES.txt | 4 - .../dependency_links.txt | 1 - .../SpanishAcquisition.egg-info/top_level.txt | 1 - examples/__init__.py | 0 examples/acquisition.py | 154 ----------- examples/data_explorer.py | 252 ------------------ .../dist/SpanishAcquisition-2.0.2b-py2.6.egg | Bin 1042 -> 0 bytes examples/virtual_setup.py | 151 ----------- 9 files changed, 578 deletions(-) delete mode 100644 examples/SpanishAcquisition.egg-info/PKG-INFO delete mode 100644 examples/SpanishAcquisition.egg-info/SOURCES.txt delete mode 100644 examples/SpanishAcquisition.egg-info/dependency_links.txt delete mode 100644 examples/SpanishAcquisition.egg-info/top_level.txt delete mode 100644 examples/__init__.py delete mode 100755 examples/acquisition.py delete mode 100755 examples/data_explorer.py delete mode 100644 examples/dist/SpanishAcquisition-2.0.2b-py2.6.egg delete mode 100644 examples/virtual_setup.py diff --git a/examples/SpanishAcquisition.egg-info/PKG-INFO b/examples/SpanishAcquisition.egg-info/PKG-INFO deleted file mode 100644 index c5bfc29..0000000 --- a/examples/SpanishAcquisition.egg-info/PKG-INFO +++ /dev/null @@ -1,15 +0,0 @@ -Metadata-Version: 1.0 -Name: SpanishAcquisition -Version: 2.0.2b -Summary: Package for interfacing with devices and building user interfaces. -Home-page: http://ghwatson.github.com/SpanishAcquisitionIQC/ -Author: Kaveh Gharavi -Author-email: kayghar@gmail.com -License: BSD -Description: UNKNOWN -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Science/Research -Classifier: License :: OSI Approved :: BSD License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 2 diff --git a/examples/SpanishAcquisition.egg-info/SOURCES.txt b/examples/SpanishAcquisition.egg-info/SOURCES.txt deleted file mode 100644 index fa977ee..0000000 --- a/examples/SpanishAcquisition.egg-info/SOURCES.txt +++ /dev/null @@ -1,4 +0,0 @@ -SpanishAcquisition.egg-info/PKG-INFO -SpanishAcquisition.egg-info/SOURCES.txt -SpanishAcquisition.egg-info/dependency_links.txt -SpanishAcquisition.egg-info/top_level.txt \ No newline at end of file diff --git a/examples/SpanishAcquisition.egg-info/dependency_links.txt b/examples/SpanishAcquisition.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/examples/SpanishAcquisition.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/SpanishAcquisition.egg-info/top_level.txt b/examples/SpanishAcquisition.egg-info/top_level.txt deleted file mode 100644 index 8b13789..0000000 --- a/examples/SpanishAcquisition.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/examples/acquisition.py b/examples/acquisition.py deleted file mode 100755 index b1023ce..0000000 --- a/examples/acquisition.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env python2 - -import logging -logging.basicConfig(level=logging.WARNING) - -import wx - -from spacq import VERSION -from spacq.gui.action.data_capture import DataCapturePanel -from spacq.gui.action.smooth_reset import SmoothResetPanel -from spacq.gui.config.devices import DeviceConfigFrame -from spacq.gui.config.pulse import PulseProgramFrame -from spacq.gui.config.variables import VariablesPanel -from spacq.gui.display.plot.live.list import ListMeasurementFrame -from spacq.gui.display.plot.live.scalar import ScalarMeasurementFrame -from spacq.gui.global_store import GlobalStore -from spacq.gui.tool.box import MessageDialog - - -class SweepingAcquisitionFrame(wx.Frame): - def __init__(self, parent, global_store, *args, **kwargs): - wx.Frame.__init__(self, parent, *args, **kwargs) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - ## Variables. - self.variables_panel = VariablesPanel(self, global_store) - self.variables_panel.SetMinSize((800, 300)) - frame_box.Add(self.variables_panel, proportion=1, flag=wx.EXPAND) - - ## Bottom. - bottom_box = wx.BoxSizer(wx.HORIZONTAL) - frame_box.Add(bottom_box, flag=wx.EXPAND) - - ### Data capture. - self.data_capture_panel = DataCapturePanel(self, global_store, style=wx.BORDER_RAISED) - bottom_box.Add(self.data_capture_panel, proportion=1, flag=wx.EXPAND) - - ### Smooth reset. - self.smooth_reset_panel = SmoothResetPanel(self, global_store, style=wx.BORDER_RAISED) - bottom_box.Add(self.smooth_reset_panel, flag=wx.EXPAND) - - self.SetSizerAndFit(frame_box) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - def OnClose(self, evt): - if self.data_capture_panel.capture_dialogs > 0: - msg = 'Cannot exit, as a sweep is currently in progress.' - MessageDialog(self, msg, 'Sweep in progress').Show() - - evt.Veto() - else: - evt.Skip() - - -class AcquisitionApp(wx.App): - def OnInit(self): - self.global_store = GlobalStore() - - # Frames. - self.acq_frame = SweepingAcquisitionFrame(None, self.global_store, title='Acquisition (v{0})'.format(VERSION)) - self.device_config_frame = None - self.pulse_program_frame = None - - # Menu. - menuBar = wx.MenuBar() - - ## Configuration. - menu = wx.Menu() - menuBar.Append(menu, '&Configuration') - - ### Devices. - item = menu.Append(wx.ID_ANY, '&Devices...') - self.Bind(wx.EVT_MENU, self.OnMenuConfigurationDevices, item) - - ### Measurements. - submenu = wx.Menu() - menu.AppendMenu(wx.ID_ANY, '&Measurements', submenu) - - item = submenu.Append(wx.ID_ANY, 'Add &scalar...') - self.Bind(wx.EVT_MENU, self.OnMenuConfigurationMeasurementsAddScalar, item) - - item = submenu.Append(wx.ID_ANY, 'Add &list...') - self.Bind(wx.EVT_MENU, self.OnMenuConfigurationMeasurementsAddList, item) - - ### Pulse program. - item = menu.Append(wx.ID_ANY, '&Pulse program...') - self.Bind(wx.EVT_MENU, self.OnMenuConfigurationPulseProgram, item) - - ## Help. - menu = wx.Menu() - menuBar.Append(menu, '&Help') - - ### About. - item = menu.Append(wx.ID_ABOUT, '&About...') - self.Bind(wx.EVT_MENU, self.OnMenuHelpAbout, item) - - self.acq_frame.SetMenuBar(menuBar) - - # Display. - self.acq_frame.SetSizerAndFit(self.acq_frame.Sizer) - self.acq_frame.Show() - self.SetTopWindow(self.acq_frame) - self.acq_frame.Raise() - - return True - - def OnMenuConfigurationDevices(self, evt=None): - def close_callback(): - self.device_config_frame = None - - if self.device_config_frame is None: - self.device_config_frame = DeviceConfigFrame(self.acq_frame, self.global_store, close_callback) - self.device_config_frame.Fit() - self.device_config_frame.Show() - - self.device_config_frame.Raise() - - def OnMenuConfigurationMeasurementsAddScalar(self, evt=None): - measurement_frame = ScalarMeasurementFrame(self.acq_frame, self.global_store) - measurement_frame.Show() - - def OnMenuConfigurationMeasurementsAddList(self, evt=None): - measurement_frame = ListMeasurementFrame(self.acq_frame, self.global_store) - measurement_frame.Show() - - def OnMenuConfigurationPulseProgram(self, evt=None): - def close_callback(): - self.pulse_program_frame = None - - if self.pulse_program_frame is None: - self.pulse_program_frame = PulseProgramFrame(self.acq_frame, self.global_store, close_callback) - self.pulse_program_frame.Fit() - self.pulse_program_frame.Show() - - self.pulse_program_frame.Raise() - - def OnMenuHelpAbout(self, evt=None): - info = wx.AboutDialogInfo() - info.SetName('Acquisition') - info.SetDescription( - 'An application for sweeping device values and acquiring data.\n' - '\n' - 'Using Spanish Acquisition version {0}.'.format(VERSION) - ) - - wx.AboutBox(info) - - -if __name__ == "__main__": - app = AcquisitionApp() - app.MainLoop() diff --git a/examples/data_explorer.py b/examples/data_explorer.py deleted file mode 100755 index 0ab8af0..0000000 --- a/examples/data_explorer.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env python2 - -import logging -logging.basicConfig(level=logging.WARNING) - -from functools import partial -import wx - -from numpy import concatenate - -from spacq import VERSION -from spacq.gui.display.plot.static.delegator import formats, available_formats -from spacq.gui.display.table.filter import FilterListDialog -from spacq.gui.display.table.generic import TabularDisplayFrame -from spacq.gui.display.plot.plotmath.derivative import DerivativeMathSetupDialog -from spacq.gui.display.plot.plotmath.function import FunctionMathSetupDialog, FunctionMathSetupDialog2arg - -from spacq.gui.tool.box import load_csv, MessageDialog - - -class DataExplorerApp(wx.App): - default_title = 'Data Explorer' - - def OnInit(self): - self.filters = {} - self.filter_columns = {} - self.filter_dialog = None - - # Frames. - self.csv_frame = TabularDisplayFrame(None, title=self.default_title) - - # Menu. - menuBar = wx.MenuBar() - - ## File. - menu = wx.Menu() - menuBar.Append(menu, '&File') - - item = menu.Append(wx.ID_OPEN, '&Open...') - self.Bind(wx.EVT_MENU, self.OnMenuFileOpen, item) - - item = menu.Append(wx.ID_CLOSE, '&Close') - self.Bind(wx.EVT_MENU, self.OnMenuFileClose, item) - - menu.AppendSeparator() - - self.filter_menu_item = menu.Append(wx.ID_ANY, '&Filters...') - self.filter_menu_item.Enable(False) - self.Bind(wx.EVT_MENU, self.OnMenuFileFilters, self.filter_menu_item) - - menu.AppendSeparator() - - item = menu.Append(wx.ID_EXIT, 'E&xit') - self.Bind(wx.EVT_MENU, self.OnMenuFileExit, item) - - ## Plot. - menu = wx.Menu() - menuBar.Append(menu, '&Plot') - - menu.Append(wx.ID_ANY, ' 2D:').Enable(False) - - self.two_dimensional_menu = menu.Append(wx.ID_ANY, '&Curve...') - self.Bind(wx.EVT_MENU, partial(self.create_plot, formats.two_dimensional), - self.two_dimensional_menu) - - menu.AppendSeparator() - - menu.Append(wx.ID_ANY, ' 3D:').Enable(False) - - self.colormapped_menu = menu.Append(wx.ID_ANY, '&Colormapped...') - self.Bind(wx.EVT_MENU, partial(self.create_plot, formats.colormapped), - self.colormapped_menu) - - self.surface_menu = menu.Append(wx.ID_ANY, '&Surface...') - self.Bind(wx.EVT_MENU, partial(self.create_plot, formats.surface), - self.surface_menu) - - menu.AppendSeparator() - - menu.Append(wx.ID_ANY, ' List:').Enable(False) - - self.waveforms_menu = menu.Append(wx.ID_ANY, '&Waveforms...') - self.Bind(wx.EVT_MENU, partial(self.create_plot, formats.waveforms, type='list'), - self.waveforms_menu) - - ## Math. - menu = wx.Menu() - menuBar.Append(menu, '&Math') - - item = menu.Append(wx.ID_ANY, '&Derivative...') - self.Bind(wx.EVT_MENU, self.OnMenuMathDerivative, item) - - item = menu.Append(wx.ID_ANY, '&Function f: y=f(X)...') - self.Bind(wx.EVT_MENU, self.OnMenuMathFunction, item) - - item = menu.Append(wx.ID_ANY, '&Function f: z=f(X,Y)...') - self.Bind(wx.EVT_MENU, self.OnMenuMathFunction2arg, item) - - ## Help. - menu = wx.Menu() - menuBar.Append(menu, '&Help') - - ### About. - item = menu.Append(wx.ID_ABOUT, '&About...') - self.Bind(wx.EVT_MENU, self.OnMenuHelpAbout, item) - - self.csv_frame.SetMenuBar(menuBar) - - self.update_plot_menus(False) - - # Display. - self.csv_frame.Show() - self.csv_frame.SetSize((800, 600)) - - self.SetTopWindow(self.csv_frame) - self.csv_frame.Raise() - - return True - - def update_plot_menus(self, status): - """ - If status is True, enable the plot menus corresponding to the available formats. Otherwise, disable all. - """ - - pairs = [ - (formats.two_dimensional, self.two_dimensional_menu), - (formats.colormapped, self.colormapped_menu), - (formats.surface, self.surface_menu), - (formats.waveforms, self.waveforms_menu), - ] - - for format, menu in pairs: - if not status or format in available_formats: - menu.Enable(status) - - def create_plot(self, format, evt=None, type='scalar'): - """ - Open up a dialog to configure the selected plot format. - """ - - headings, rows, types = self.csv_frame.display_panel.GetValue(types=[type]) - available_formats[format](self.csv_frame, headings, rows).Show() - - def OnMenuFileOpen(self, evt=None): - try: - result = load_csv(self.csv_frame) - except IOError as e: - MessageDialog(self.csv_frame, str(e), 'Could not load data').Show() - return - - if result is None: - return - else: - self.OnMenuFileClose() - - has_header, values, filename = result - - self.csv_frame.display_panel.from_csv_data(has_header, values) - self.csv_frame.Title = '{0} - {1}'.format(filename, self.default_title) - - self.update_plot_menus(len(self.csv_frame.display_panel) > 0) - - self.filter_menu_item.Enable(True) - - def OnMenuFileClose(self, evt=None): - self.csv_frame.display_panel.SetValue([], []) - self.csv_frame.Title = self.default_title - - self.update_plot_menus(False) - - self.filter_menu_item.Enable(False) - - if self.filter_dialog is not None: - self.filter_dialog.Close() - - self.filters = {} - self.filter_columns = {} - - def OnMenuFileFilters(self, evt=None): - def close_callback(dlg): - self.filters = dlg.filters - self.filter_columns = dlg.filter_columns - - self.filter_dialog = None - - if self.filter_dialog is None: - self.filter_dialog = FilterListDialog(self.csv_frame, self.csv_frame.display_panel.table, - close_callback, self.filters, self.filter_columns) - self.filter_dialog.Show() - - self.filter_dialog.Raise() - - def OnMenuFileExit(self, evt=None): - if self.csv_frame: - self.csv_frame.Close() - - def OnMenuMathDerivative(self, format, evt=None, type='scalar'): - """ - Open up a dialog to calculate derivative - """ - headings, rows, types = self.csv_frame.display_panel.GetValue(types=[type]) - dmath = DerivativeMathSetupDialog(self.csv_frame, headings, rows) - dmath_open = dmath.ShowModal() - - new_headings = headings - new_headings.append(dmath.dheading) - new_rows = concatenate([rows.astype(float),dmath.ddata],1) - - self.csv_frame.display_panel.SetValue(new_headings,new_rows) - - def OnMenuMathFunction(self, format, evt=None, type='scalar'): - """ - Open up a dialog to apply a scalar function of one variable - """ - headings, rows, types = self.csv_frame.display_panel.GetValue(types=[type]) - dmath = FunctionMathSetupDialog(self.csv_frame, headings, rows) - dmath_open = dmath.ShowModal() - - new_headings = headings - new_headings.append(dmath.dheading) - new_rows = concatenate([rows.astype(float),dmath.ddata],1) - - self.csv_frame.display_panel.SetValue(new_headings,new_rows) - - def OnMenuMathFunction2arg(self, format, evt=None, type='scalar'): - """ - Open up a dialog to apply a scalar function of two variables - """ - headings, rows, types = self.csv_frame.display_panel.GetValue(types=[type]) - dmath = FunctionMathSetupDialog2arg(self.csv_frame, headings, rows) - dmath_open = dmath.ShowModal() - - new_headings = headings - new_headings.append(dmath.dheading) - new_rows = concatenate([rows.astype(float),dmath.ddata],1) - - self.csv_frame.display_panel.SetValue(new_headings,new_rows) - - def OnMenuHelpAbout(self, evt=None): - info = wx.AboutDialogInfo() - info.SetName('Data Explorer') - info.SetDescription('An application for displaying data in tabular and graphical form.\n' - '\n' - 'Using Spanish Acquisition version {0}.'.format(VERSION) - ) - - wx.AboutBox(info) - - -if __name__ == "__main__": - app = DataExplorerApp() - app.MainLoop() diff --git a/examples/dist/SpanishAcquisition-2.0.2b-py2.6.egg b/examples/dist/SpanishAcquisition-2.0.2b-py2.6.egg deleted file mode 100644 index cc13335ac70929168571780ab63a4573c876f572..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1042 zcmWIWW@Zs#U|`^2SdiD}Hks|Z5i^j-2*iRw?CS2W>*?p_uV0l}pj(`nmim~9ApoZi zF=QPj`33Pgsb#4-dL{r*Et(Dg||9;Z* zMhR1+b8#AKkLR|0e&)00;-nV61;&{XE57GUdf3!;e`RIywKL_5PG3v1@wl<5>hQdT z_sSB-MZT}-dHwM42ZmW{i!~Uncz5Tx#ZH{_B7aJda^Bvhks3-uY|j*KZW^awrC z!>jW&r%eelY3%=PyZ-&P+1Iz;=sS_B{`>k)IYZUW<{@{cnOuC)ta|){VUeGLYRmzi zZ5Lm!Srr!DaMwe9PiW-r%u5?i+?o&^k~!CE+FN1OLo3>ZkCk7t+Wst(o8B%^+{jqAH{KZ>dJKV27|24ODlkF7gT~TL`s-s4F zTF8I*5MZn=2Vzj-fhUt-|Ii?3*I-bjaqD@UIjwW9z(-H#tj;-|lV{Ff_V)F^e)f#_ z>FfL-%C3L?QvCTj*RjPKH|$ML+%%0?zhn{1`dw45G+o&<<;tEXr+&;iv*%4xla$vd z?nlKzr-O2>?{ejtO JEl?c;0{|syS`PpK diff --git a/examples/virtual_setup.py b/examples/virtual_setup.py deleted file mode 100644 index 66e7fcf..0000000 --- a/examples/virtual_setup.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python2 - -import logging -logging.basicConfig(level=logging.WARNING) - -import wx -import numpy - -from spacq import VERSION -from spacq.gui.global_store import GlobalStore -from spacq.gui.config.virtual_variables import MultipleVariableConfigPanel, DependentVariableConfigPanel -from spacq.iteration.virtual_variables import virtLinSpaceConfig, DependentConfig, virtSweepController, sort_output_variables -from spacq.gui.tool.box import Dialog, MessageDialog, save_csv - - -class VariableSweepFrame(wx.Frame): - def __init__(self, parent, *args, **kwargs): - wx.Frame.__init__(self, parent, *args, **kwargs) - - # for evaluation MessageDialog - self.parent = parent - - # Frame - frame_box = wx.FlexGridSizer(4,1) - - # top = ButtonPanel(self) - self.virtual_panel = MultipleVariableConfigPanel(self) - # self.variables_panel = VariablesPanel(self, global_store) - # self.variables_panel.SetMinSize((800, 300)) - - self.dependent_panel = DependentVariableConfigPanel(self) - - frame_box.Add(self.virtual_panel, 1) #, wx.EXPAND) - frame_box.Add(self.dependent_panel, 1, flag=wx.EXPAND|wx.ALIGN_CENTER_VERTICAL) - # frame_box.Add(self.variables_panel, 1, wx.EXPAND) - - button = wx.Button(self, label='Evaluate / Write CSV') - frame_box.Add(button, 1, wx.EXPAND) - self.Bind(wx.EVT_BUTTON, self.OnButton, button) - - - self.SetSizerAndFit(frame_box) - - def OnButton(self, evt=None): - - # Retrieve info from virtual variable config - tempVars = self.virtual_panel.GetValue() - virtVars = [None]*self.virtual_panel.var_count.Value - - for i in range(0,self.virtual_panel.var_count.Value): - name = tempVars[0][i] - initial = tempVars[1][i] - final = tempVars[2][i] - steps = tempVars[3][i] - order = tempVars[4][i] - - virtVars[i] = virtLinSpaceConfig(name, initial, final, steps, order) - - variables,num_periods = sort_output_variables(virtVars) - - # Sort by order and write array of virtual variables - sweepSweep = virtSweepController(variables,num_periods) - sweepSweep.sweepTable() - - # Get info from dependent variable definitions - realVarInfo = self.dependent_panel.GetValue() - realVars = [None]*self.dependent_panel.dependent_count.Value - - for i in range(0,self.dependent_panel.dependent_count.Value): - name = realVarInfo[0][i] - expression = realVarInfo[1][i] - - realVars[i] = DependentConfig(name, expression) - - # Evaluate expressions - RetrieveValues = [var.DependentFunctionMath(sweepSweep.names,sweepSweep.value_history) for var in realVars] - dependentNames = [var.name for var in realVars] - - # Reconfigure shape - dependentValues = RetrieveValues[0] - - if len(RetrieveValues) == 1: - # Go from list to numpy column - dependentValues = numpy.c_[dependentValues] - else: - for values in RetrieveValues[1:]: - dependentValues = numpy.column_stack((dependentValues, values)) - - # Prepare headings and virtual and dependent values for csv writing - PrintNames = sweepSweep.names - PrintNames.extend(dependentNames) - - outputTable = numpy.append(sweepSweep.value_history,dependentValues, axis=1) - - # TODO: need to sort out parent stuff for message dialog - try: - result = save_csv(self, outputTable, PrintNames) - - except IOError as e: - # May need to be self.parent if this error ever gets called - # TODO: probably doesnt work hard to test - MessageDialog(self, str(e), 'Could not save data').Show() - return - -class VirtualSetupApp(wx.App): - def OnInit(self): - - #Frames - self.exp_frame = VariableSweepFrame(None, title='Virtual Setup') - - # Menu. - menuBar = wx.MenuBar() - - ## Configuration. - menu = wx.Menu() - - ## Help. - menu = wx.Menu() - menuBar.Append(menu, '&Help') - - ### About. - item = menu.Append(wx.ID_ABOUT, '&About...') - self.Bind(wx.EVT_MENU, self.OnMenuHelpAbout, item) - - self.exp_frame.SetMenuBar(menuBar) - - # Display. - self.exp_frame.SetSizerAndFit(self.exp_frame.Sizer) - self.exp_frame.Show() - self.SetTopWindow(self.exp_frame) - self.exp_frame.Raise() - - self.exp_frame.Show(True) - - return True - - def OnMenuHelpAbout(self, evt=None): - info = wx.AboutDialogInfo() - info.SetName('Virtual Setup') - info.SetDescription( - 'An application for setting up linear sweeps of virtual\n' - 'variables for output variables.\n' - '\n' - 'Using Spanish Acquisition version {0}.'.format(VERSION) - ) - - wx.AboutBox(info) - -if __name__ == "__main__": - app = VirtualSetupApp() - app.MainLoop() From 6bfe0b64e84dc30225317ee8d7a2945d910613c6 Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:29:55 -0400 Subject: [PATCH 05/15] Delete spacq directory --- spacq/__init__.py | 1 - spacq/devices/__init__.py | 2 - spacq/devices/abstract_device.py | 529 ---------- spacq/devices/agilent/__init__.py | 13 - spacq/devices/agilent/dm34401a.py | 119 --- spacq/devices/agilent/dm34410a.py | 116 --- spacq/devices/agilent/mock/__init__.py | 0 spacq/devices/agilent/mock/mock_dm34401a.py | 72 -- spacq/devices/agilent/mock/mock_dm34410a.py | 62 -- spacq/devices/agilent/mock/mock_nwa8753et.py | 26 - spacq/devices/agilent/mock/tests/__init__.py | 0 .../agilent/mock/tests/test_mock_dm34401a.py | 26 - .../agilent/mock/tests/test_mock_dm34410a.py | 26 - spacq/devices/agilent/nwa8753et.py | 120 --- spacq/devices/agilent/tests/__init__.py | 0 .../devices/agilent/tests/server/__init__.py | 0 .../agilent/tests/server/test_dm34401a.py | 65 -- .../agilent/tests/server/test_dm34410a.py | 65 -- spacq/devices/basel/__init__.py | 12 - spacq/devices/basel/dacsp927.py | 122 --- spacq/devices/basel/mock/__init__.py | 0 spacq/devices/basel/mock/mock_dacsp927.py | 27 - spacq/devices/config.py | 236 ----- spacq/devices/cryomagnetics/__init__.py | 13 - spacq/devices/cryomagnetics/gui/__init__.py | 0 spacq/devices/cryomagnetics/gui/model4g.py | 560 ----------- spacq/devices/cryomagnetics/mock/__init__.py | 0 .../cryomagnetics/mock/mock_model4g.py | 236 ----- .../cryomagnetics/mock/tests/__init__.py | 0 .../mock/tests/test_mock_model4g.py | 25 - spacq/devices/cryomagnetics/model4g.py | 798 --------------- spacq/devices/cryomagnetics/tests/__init__.py | 0 .../cryomagnetics/tests/server/__init__.py | 0 .../tests/server/test_model4g.py | 250 ----- spacq/devices/iqc/__init__.py | 13 - spacq/devices/iqc/ch4_voltage_source.py | 324 ------ spacq/devices/iqc/ch6_voltage_source.py | 326 ------- spacq/devices/iqc/gui/__init__.py | 0 spacq/devices/iqc/gui/ch4_voltage_source.py | 360 ------- spacq/devices/iqc/gui/ch6_voltage_source.py | 363 ------- spacq/devices/iqc/gui/voltage_source.py | 365 ------- spacq/devices/iqc/mock/__init__.py | 0 .../iqc/mock/mock_ch4_voltage_source.py | 114 --- .../iqc/mock/mock_ch6_voltage_source.py | 114 --- spacq/devices/iqc/mock/mock_voltage_source.py | 114 --- spacq/devices/iqc/mock/tests/__init__.py | 0 .../tests/test_mock_ch6_voltage_source.py | 26 - .../mock/tests/test_mock_voltage_source.py | 26 - spacq/devices/iqc/tests/__init__.py | 0 spacq/devices/iqc/tests/server/__init__.py | 0 .../tests/server/test_ch6_voltage_source.py | 43 - .../iqc/tests/server/test_voltage_source.py | 43 - .../iqc/tests/test_ch6_voltage_source.py | 98 -- .../devices/iqc/tests/test_voltage_source.py | 98 -- spacq/devices/iqc/voltage_source.py | 334 ------- spacq/devices/keithley/__init__.py | 13 - spacq/devices/keithley/mock/__init__.py | 0 .../keithley/mock/mock_sourceMeter2401.py | 43 - .../keithley/mock/mock_sourceMeter2450.py | 43 - .../keithley/mock/mock_voltagesource230.py | 43 - spacq/devices/keithley/mock/tests/__init__.py | 0 .../mock/tests/test_mock_voltagesource230.py | 26 - spacq/devices/keithley/sourceMeter2401.py | 146 --- spacq/devices/keithley/sourceMeter2450.py | 92 -- spacq/devices/keithley/tests/__init__.py | 0 .../devices/keithley/tests/server/__init__.py | 0 .../tests/server/test_voltagesource230.py | 47 - spacq/devices/keithley/voltagesource230.py | 151 --- spacq/devices/lakeshore/__init__.py | 13 - spacq/devices/lakeshore/mock/__init__.py | 0 spacq/devices/lakeshore/mock/mock_model218.py | 39 - spacq/devices/lakeshore/mock/mock_tc335.py | 42 - .../devices/lakeshore/mock/tests/__init__.py | 0 .../lakeshore/mock/tests/test_mock_tc335.py | 26 - spacq/devices/lakeshore/model218.py | 93 -- spacq/devices/lakeshore/tc335.py | 76 -- spacq/devices/lakeshore/tests/__init__.py | 0 .../lakeshore/tests/server/__init__.py | 0 .../lakeshore/tests/server/test_tc335.py | 47 - spacq/devices/mock/__init__.py | 0 spacq/devices/mock/mock_abstract_device.py | 156 --- spacq/devices/mock/tests/__init__.py | 0 .../mock/tests/test_mock_abstract_device.py | 57 -- spacq/devices/oxford/__init__.py | 13 - spacq/devices/oxford/ips120_10.py | 258 ----- spacq/devices/oxford/mock/__init__.py | 0 spacq/devices/oxford/mock/mock_ips120_10.py | 91 -- spacq/devices/oxford/mock/tests/__init__.py | 0 .../oxford/mock/tests/test_mock_ips120_10.py | 26 - spacq/devices/oxford/tests/__init__.py | 0 spacq/devices/oxford/tests/server/__init__.py | 0 .../oxford/tests/server/test_ips120_10.py | 86 -- spacq/devices/rohde_schwarz/__init__.py | 13 - spacq/devices/rohde_schwarz/mock/__init__.py | 0 .../rohde_schwarz/mock/mock_smf100a.py | 55 -- .../rohde_schwarz/mock/tests/__init__.py | 0 .../mock/tests/test_mock_smf100a.py | 26 - spacq/devices/rohde_schwarz/smf100a.py | 100 -- spacq/devices/rohde_schwarz/tests/__init__.py | 0 .../rohde_schwarz/tests/server/__init__.py | 0 .../tests/server/test_smf100a.py | 65 -- spacq/devices/sample/__init__.py | 13 - spacq/devices/sample/abc1234.py | 80 -- spacq/devices/sample/mock/__init__.py | 0 spacq/devices/sample/mock/mock_abc1234.py | 43 - spacq/devices/sample/mock/tests/__init__.py | 0 .../sample/mock/tests/test_mock_abc1234.py | 26 - spacq/devices/sample/tests/__init__.py | 0 spacq/devices/sample/tests/server/__init__.py | 0 .../sample/tests/server/test_abc1234.py | 47 - .../stanford_research_systems/__init__.py | 12 - .../mock/__init__.py | 0 .../mock/mock_sg382.py | 27 - .../mock/mock_sim900.py | 27 - .../mock/mock_sr830dsp.py | 26 - .../stanford_research_systems/sg382.py | 303 ------ .../stanford_research_systems/sim900.py | 129 --- .../stanford_research_systems/sr830dsp.py | 167 ---- spacq/devices/tektronix/__init__.py | 13 - spacq/devices/tektronix/awg5014b.py | 440 --------- spacq/devices/tektronix/dpo7104.py | 435 --------- spacq/devices/tektronix/mock/__init__.py | 0 spacq/devices/tektronix/mock/mock_awg5014b.py | 245 ----- spacq/devices/tektronix/mock/mock_dpo7104.py | 194 ---- .../devices/tektronix/mock/tests/__init__.py | 0 .../mock/tests/test_mock_awg5014b.py | 26 - .../tektronix/mock/tests/test_mock_dpo7104.py | 26 - spacq/devices/tektronix/tests/__init__.py | 0 .../tektronix/tests/server/__init__.py | 0 .../tektronix/tests/server/test_awg5014b.py | 132 --- .../tektronix/tests/server/test_dpo7104.py | 81 -- spacq/devices/tests/__init__.py | 0 spacq/devices/tests/server/__init__.py | 0 .../tests/server/test_abstract_device.py | 112 --- spacq/devices/tests/server/test_config.py | 106 -- spacq/devices/tests/test_abstract_device.py | 136 --- spacq/devices/tests/test_config.py | 110 --- spacq/devices/tests/test_tools.py | 135 --- spacq/devices/tools.py | 268 ----- spacq/gui/__init__.py | 0 spacq/gui/action/__init__.py | 0 spacq/gui/action/data_capture.py | 639 ------------ spacq/gui/action/smooth_reset.py | 131 --- spacq/gui/config/__init__.py | 0 spacq/gui/config/device/__init__.py | 0 spacq/gui/config/device/device_config.py | 399 -------- spacq/gui/config/device/resource_tree.py | 363 ------- spacq/gui/config/devices.py | 259 ----- spacq/gui/config/measurement.py | 213 ---- spacq/gui/config/pulse.py | 768 --------------- spacq/gui/config/scaling.py | 101 -- spacq/gui/config/variables.py | 920 ------------------ spacq/gui/config/virtual_variables.py | 267 ----- spacq/gui/display/__init__.py | 0 spacq/gui/display/plot/__init__.py | 0 spacq/gui/display/plot/colormapped.py | 100 -- spacq/gui/display/plot/common/__init__.py | 0 spacq/gui/display/plot/common/chaco_plot.py | 72 -- spacq/gui/display/plot/live/__init__.py | 0 spacq/gui/display/plot/live/list.py | 430 -------- spacq/gui/display/plot/live/list_3d.py | 306 ------ spacq/gui/display/plot/live/scalar.py | 588 ----------- spacq/gui/display/plot/plotmath/__init__.py | 0 .../display/plot/plotmath/common/__init__.py | 0 .../plot/plotmath/common/math_setup.py | 159 --- spacq/gui/display/plot/plotmath/derivative.py | 74 -- spacq/gui/display/plot/plotmath/function.py | 62 -- spacq/gui/display/plot/static/__init__.py | 0 spacq/gui/display/plot/static/colormapped.py | 131 --- .../display/plot/static/common/__init__.py | 0 .../display/plot/static/common/plot_setup.py | 210 ---- spacq/gui/display/plot/static/delegator.py | 41 - spacq/gui/display/plot/static/surface.py | 114 --- .../display/plot/static/two_dimensional.py | 63 -- spacq/gui/display/plot/surface.py | 114 --- spacq/gui/display/plot/two_dimensional.py | 82 -- spacq/gui/display/table/__init__.py | 0 spacq/gui/display/table/filter.py | 196 ---- spacq/gui/display/table/generic.py | 204 ---- spacq/gui/display/waveform.py | 88 -- spacq/gui/global_store.py | 27 - spacq/gui/tool/__init__.py | 0 spacq/gui/tool/box.py | 245 ----- spacq/gui/tool/tests/__init__.py | 0 spacq/gui/tool/tests/test_box.py | 58 -- spacq/interface/__init__.py | 0 spacq/interface/list_columns.py | 29 - spacq/interface/pulse/__init__.py | 0 spacq/interface/pulse/parser.py | 165 ---- spacq/interface/pulse/program.py | 155 --- spacq/interface/pulse/tests/__init__.py | 0 .../interface/pulse/tests/resources/01.pulse | 35 - .../interface/pulse/tests/resources/02.pulse | 1 - .../pulse/tests/resources/non-square | 3 - spacq/interface/pulse/tests/test_parser.py | 301 ------ spacq/interface/pulse/tests/test_program.py | 152 --- spacq/interface/pulse/tests/test_tree.py | 353 ------- spacq/interface/pulse/tool/__init__.py | 0 spacq/interface/pulse/tool/box.py | 65 -- spacq/interface/pulse/tool/tests/__init__.py | 0 spacq/interface/pulse/tool/tests/test_box.py | 98 -- spacq/interface/pulse/tree.py | 602 ------------ spacq/interface/resources.py | 315 ------ spacq/interface/tests/__init__.py | 0 spacq/interface/tests/test_list_columns.py | 48 - spacq/interface/tests/test_resources.py | 457 --------- spacq/interface/tests/test_units.py | 251 ----- spacq/interface/tests/test_waveform.py | 79 -- spacq/interface/units.py | 320 ------ spacq/interface/waveform.py | 194 ---- spacq/iteration/__init__.py | 0 spacq/iteration/sweep.py | 642 ------------ spacq/iteration/tests/__init__.py | 0 spacq/iteration/tests/resources/01.pulse | 11 - spacq/iteration/tests/test_sweep.py | 512 ---------- spacq/iteration/tests/test_variables.py | 286 ------ spacq/iteration/variables.py | 327 ------- spacq/iteration/virtual_variables.py | 260 ----- spacq/tests/__init__.py | 0 spacq/tests/tool/__init__.py | 0 spacq/tests/tool/box.py | 80 -- spacq/tool/__init__.py | 0 spacq/tool/box.py | 211 ---- spacq/tool/tests/__init__.py | 0 spacq/tool/tests/test_box.py | 324 ------ 225 files changed, 25227 deletions(-) delete mode 100644 spacq/__init__.py delete mode 100755 spacq/devices/__init__.py delete mode 100644 spacq/devices/abstract_device.py delete mode 100644 spacq/devices/agilent/__init__.py delete mode 100644 spacq/devices/agilent/dm34401a.py delete mode 100644 spacq/devices/agilent/dm34410a.py delete mode 100644 spacq/devices/agilent/mock/__init__.py delete mode 100644 spacq/devices/agilent/mock/mock_dm34401a.py delete mode 100644 spacq/devices/agilent/mock/mock_dm34410a.py delete mode 100644 spacq/devices/agilent/mock/mock_nwa8753et.py delete mode 100644 spacq/devices/agilent/mock/tests/__init__.py delete mode 100644 spacq/devices/agilent/mock/tests/test_mock_dm34401a.py delete mode 100644 spacq/devices/agilent/mock/tests/test_mock_dm34410a.py delete mode 100644 spacq/devices/agilent/nwa8753et.py delete mode 100644 spacq/devices/agilent/tests/__init__.py delete mode 100644 spacq/devices/agilent/tests/server/__init__.py delete mode 100644 spacq/devices/agilent/tests/server/test_dm34401a.py delete mode 100644 spacq/devices/agilent/tests/server/test_dm34410a.py delete mode 100644 spacq/devices/basel/__init__.py delete mode 100644 spacq/devices/basel/dacsp927.py delete mode 100644 spacq/devices/basel/mock/__init__.py delete mode 100644 spacq/devices/basel/mock/mock_dacsp927.py delete mode 100644 spacq/devices/config.py delete mode 100755 spacq/devices/cryomagnetics/__init__.py delete mode 100644 spacq/devices/cryomagnetics/gui/__init__.py delete mode 100755 spacq/devices/cryomagnetics/gui/model4g.py delete mode 100755 spacq/devices/cryomagnetics/mock/__init__.py delete mode 100755 spacq/devices/cryomagnetics/mock/mock_model4g.py delete mode 100755 spacq/devices/cryomagnetics/mock/tests/__init__.py delete mode 100755 spacq/devices/cryomagnetics/mock/tests/test_mock_model4g.py delete mode 100755 spacq/devices/cryomagnetics/model4g.py delete mode 100755 spacq/devices/cryomagnetics/tests/__init__.py delete mode 100755 spacq/devices/cryomagnetics/tests/server/__init__.py delete mode 100755 spacq/devices/cryomagnetics/tests/server/test_model4g.py delete mode 100644 spacq/devices/iqc/__init__.py delete mode 100644 spacq/devices/iqc/ch4_voltage_source.py delete mode 100644 spacq/devices/iqc/ch6_voltage_source.py delete mode 100644 spacq/devices/iqc/gui/__init__.py delete mode 100644 spacq/devices/iqc/gui/ch4_voltage_source.py delete mode 100644 spacq/devices/iqc/gui/ch6_voltage_source.py delete mode 100644 spacq/devices/iqc/gui/voltage_source.py delete mode 100644 spacq/devices/iqc/mock/__init__.py delete mode 100644 spacq/devices/iqc/mock/mock_ch4_voltage_source.py delete mode 100644 spacq/devices/iqc/mock/mock_ch6_voltage_source.py delete mode 100644 spacq/devices/iqc/mock/mock_voltage_source.py delete mode 100644 spacq/devices/iqc/mock/tests/__init__.py delete mode 100644 spacq/devices/iqc/mock/tests/test_mock_ch6_voltage_source.py delete mode 100644 spacq/devices/iqc/mock/tests/test_mock_voltage_source.py delete mode 100644 spacq/devices/iqc/tests/__init__.py delete mode 100644 spacq/devices/iqc/tests/server/__init__.py delete mode 100644 spacq/devices/iqc/tests/server/test_ch6_voltage_source.py delete mode 100644 spacq/devices/iqc/tests/server/test_voltage_source.py delete mode 100644 spacq/devices/iqc/tests/test_ch6_voltage_source.py delete mode 100644 spacq/devices/iqc/tests/test_voltage_source.py delete mode 100644 spacq/devices/iqc/voltage_source.py delete mode 100644 spacq/devices/keithley/__init__.py delete mode 100644 spacq/devices/keithley/mock/__init__.py delete mode 100644 spacq/devices/keithley/mock/mock_sourceMeter2401.py delete mode 100644 spacq/devices/keithley/mock/mock_sourceMeter2450.py delete mode 100644 spacq/devices/keithley/mock/mock_voltagesource230.py delete mode 100644 spacq/devices/keithley/mock/tests/__init__.py delete mode 100644 spacq/devices/keithley/mock/tests/test_mock_voltagesource230.py delete mode 100644 spacq/devices/keithley/sourceMeter2401.py delete mode 100644 spacq/devices/keithley/sourceMeter2450.py delete mode 100644 spacq/devices/keithley/tests/__init__.py delete mode 100644 spacq/devices/keithley/tests/server/__init__.py delete mode 100644 spacq/devices/keithley/tests/server/test_voltagesource230.py delete mode 100644 spacq/devices/keithley/voltagesource230.py delete mode 100644 spacq/devices/lakeshore/__init__.py delete mode 100644 spacq/devices/lakeshore/mock/__init__.py delete mode 100644 spacq/devices/lakeshore/mock/mock_model218.py delete mode 100644 spacq/devices/lakeshore/mock/mock_tc335.py delete mode 100644 spacq/devices/lakeshore/mock/tests/__init__.py delete mode 100644 spacq/devices/lakeshore/mock/tests/test_mock_tc335.py delete mode 100644 spacq/devices/lakeshore/model218.py delete mode 100644 spacq/devices/lakeshore/tc335.py delete mode 100644 spacq/devices/lakeshore/tests/__init__.py delete mode 100644 spacq/devices/lakeshore/tests/server/__init__.py delete mode 100644 spacq/devices/lakeshore/tests/server/test_tc335.py delete mode 100644 spacq/devices/mock/__init__.py delete mode 100644 spacq/devices/mock/mock_abstract_device.py delete mode 100644 spacq/devices/mock/tests/__init__.py delete mode 100644 spacq/devices/mock/tests/test_mock_abstract_device.py delete mode 100644 spacq/devices/oxford/__init__.py delete mode 100644 spacq/devices/oxford/ips120_10.py delete mode 100644 spacq/devices/oxford/mock/__init__.py delete mode 100644 spacq/devices/oxford/mock/mock_ips120_10.py delete mode 100644 spacq/devices/oxford/mock/tests/__init__.py delete mode 100644 spacq/devices/oxford/mock/tests/test_mock_ips120_10.py delete mode 100644 spacq/devices/oxford/tests/__init__.py delete mode 100644 spacq/devices/oxford/tests/server/__init__.py delete mode 100644 spacq/devices/oxford/tests/server/test_ips120_10.py delete mode 100644 spacq/devices/rohde_schwarz/__init__.py delete mode 100644 spacq/devices/rohde_schwarz/mock/__init__.py delete mode 100644 spacq/devices/rohde_schwarz/mock/mock_smf100a.py delete mode 100644 spacq/devices/rohde_schwarz/mock/tests/__init__.py delete mode 100644 spacq/devices/rohde_schwarz/mock/tests/test_mock_smf100a.py delete mode 100644 spacq/devices/rohde_schwarz/smf100a.py delete mode 100644 spacq/devices/rohde_schwarz/tests/__init__.py delete mode 100644 spacq/devices/rohde_schwarz/tests/server/__init__.py delete mode 100644 spacq/devices/rohde_schwarz/tests/server/test_smf100a.py delete mode 100644 spacq/devices/sample/__init__.py delete mode 100644 spacq/devices/sample/abc1234.py delete mode 100644 spacq/devices/sample/mock/__init__.py delete mode 100644 spacq/devices/sample/mock/mock_abc1234.py delete mode 100644 spacq/devices/sample/mock/tests/__init__.py delete mode 100644 spacq/devices/sample/mock/tests/test_mock_abc1234.py delete mode 100644 spacq/devices/sample/tests/__init__.py delete mode 100644 spacq/devices/sample/tests/server/__init__.py delete mode 100644 spacq/devices/sample/tests/server/test_abc1234.py delete mode 100644 spacq/devices/stanford_research_systems/__init__.py delete mode 100644 spacq/devices/stanford_research_systems/mock/__init__.py delete mode 100644 spacq/devices/stanford_research_systems/mock/mock_sg382.py delete mode 100644 spacq/devices/stanford_research_systems/mock/mock_sim900.py delete mode 100644 spacq/devices/stanford_research_systems/mock/mock_sr830dsp.py delete mode 100644 spacq/devices/stanford_research_systems/sg382.py delete mode 100644 spacq/devices/stanford_research_systems/sim900.py delete mode 100644 spacq/devices/stanford_research_systems/sr830dsp.py delete mode 100644 spacq/devices/tektronix/__init__.py delete mode 100644 spacq/devices/tektronix/awg5014b.py delete mode 100644 spacq/devices/tektronix/dpo7104.py delete mode 100644 spacq/devices/tektronix/mock/__init__.py delete mode 100644 spacq/devices/tektronix/mock/mock_awg5014b.py delete mode 100644 spacq/devices/tektronix/mock/mock_dpo7104.py delete mode 100644 spacq/devices/tektronix/mock/tests/__init__.py delete mode 100644 spacq/devices/tektronix/mock/tests/test_mock_awg5014b.py delete mode 100644 spacq/devices/tektronix/mock/tests/test_mock_dpo7104.py delete mode 100644 spacq/devices/tektronix/tests/__init__.py delete mode 100644 spacq/devices/tektronix/tests/server/__init__.py delete mode 100644 spacq/devices/tektronix/tests/server/test_awg5014b.py delete mode 100644 spacq/devices/tektronix/tests/server/test_dpo7104.py delete mode 100644 spacq/devices/tests/__init__.py delete mode 100644 spacq/devices/tests/server/__init__.py delete mode 100644 spacq/devices/tests/server/test_abstract_device.py delete mode 100644 spacq/devices/tests/server/test_config.py delete mode 100644 spacq/devices/tests/test_abstract_device.py delete mode 100644 spacq/devices/tests/test_config.py delete mode 100644 spacq/devices/tests/test_tools.py delete mode 100755 spacq/devices/tools.py delete mode 100644 spacq/gui/__init__.py delete mode 100644 spacq/gui/action/__init__.py delete mode 100755 spacq/gui/action/data_capture.py delete mode 100644 spacq/gui/action/smooth_reset.py delete mode 100644 spacq/gui/config/__init__.py delete mode 100644 spacq/gui/config/device/__init__.py delete mode 100644 spacq/gui/config/device/device_config.py delete mode 100644 spacq/gui/config/device/resource_tree.py delete mode 100644 spacq/gui/config/devices.py delete mode 100755 spacq/gui/config/measurement.py delete mode 100644 spacq/gui/config/pulse.py delete mode 100644 spacq/gui/config/scaling.py delete mode 100755 spacq/gui/config/variables.py delete mode 100644 spacq/gui/config/virtual_variables.py delete mode 100644 spacq/gui/display/__init__.py delete mode 100644 spacq/gui/display/plot/__init__.py delete mode 100644 spacq/gui/display/plot/colormapped.py delete mode 100644 spacq/gui/display/plot/common/__init__.py delete mode 100644 spacq/gui/display/plot/common/chaco_plot.py delete mode 100644 spacq/gui/display/plot/live/__init__.py delete mode 100644 spacq/gui/display/plot/live/list.py delete mode 100644 spacq/gui/display/plot/live/list_3d.py delete mode 100755 spacq/gui/display/plot/live/scalar.py delete mode 100644 spacq/gui/display/plot/plotmath/__init__.py delete mode 100644 spacq/gui/display/plot/plotmath/common/__init__.py delete mode 100755 spacq/gui/display/plot/plotmath/common/math_setup.py delete mode 100644 spacq/gui/display/plot/plotmath/derivative.py delete mode 100644 spacq/gui/display/plot/plotmath/function.py delete mode 100644 spacq/gui/display/plot/static/__init__.py delete mode 100644 spacq/gui/display/plot/static/colormapped.py delete mode 100644 spacq/gui/display/plot/static/common/__init__.py delete mode 100644 spacq/gui/display/plot/static/common/plot_setup.py delete mode 100644 spacq/gui/display/plot/static/delegator.py delete mode 100644 spacq/gui/display/plot/static/surface.py delete mode 100644 spacq/gui/display/plot/static/two_dimensional.py delete mode 100644 spacq/gui/display/plot/surface.py delete mode 100644 spacq/gui/display/plot/two_dimensional.py delete mode 100644 spacq/gui/display/table/__init__.py delete mode 100644 spacq/gui/display/table/filter.py delete mode 100644 spacq/gui/display/table/generic.py delete mode 100644 spacq/gui/display/waveform.py delete mode 100644 spacq/gui/global_store.py delete mode 100644 spacq/gui/tool/__init__.py delete mode 100644 spacq/gui/tool/box.py delete mode 100644 spacq/gui/tool/tests/__init__.py delete mode 100644 spacq/gui/tool/tests/test_box.py delete mode 100644 spacq/interface/__init__.py delete mode 100644 spacq/interface/list_columns.py delete mode 100644 spacq/interface/pulse/__init__.py delete mode 100644 spacq/interface/pulse/parser.py delete mode 100644 spacq/interface/pulse/program.py delete mode 100644 spacq/interface/pulse/tests/__init__.py delete mode 100644 spacq/interface/pulse/tests/resources/01.pulse delete mode 100644 spacq/interface/pulse/tests/resources/02.pulse delete mode 100644 spacq/interface/pulse/tests/resources/non-square delete mode 100644 spacq/interface/pulse/tests/test_parser.py delete mode 100644 spacq/interface/pulse/tests/test_program.py delete mode 100644 spacq/interface/pulse/tests/test_tree.py delete mode 100644 spacq/interface/pulse/tool/__init__.py delete mode 100644 spacq/interface/pulse/tool/box.py delete mode 100644 spacq/interface/pulse/tool/tests/__init__.py delete mode 100644 spacq/interface/pulse/tool/tests/test_box.py delete mode 100644 spacq/interface/pulse/tree.py delete mode 100755 spacq/interface/resources.py delete mode 100644 spacq/interface/tests/__init__.py delete mode 100644 spacq/interface/tests/test_list_columns.py delete mode 100644 spacq/interface/tests/test_resources.py delete mode 100644 spacq/interface/tests/test_units.py delete mode 100644 spacq/interface/tests/test_waveform.py delete mode 100755 spacq/interface/units.py delete mode 100644 spacq/interface/waveform.py delete mode 100644 spacq/iteration/__init__.py delete mode 100755 spacq/iteration/sweep.py delete mode 100644 spacq/iteration/tests/__init__.py delete mode 100644 spacq/iteration/tests/resources/01.pulse delete mode 100755 spacq/iteration/tests/test_sweep.py delete mode 100755 spacq/iteration/tests/test_variables.py delete mode 100755 spacq/iteration/variables.py delete mode 100644 spacq/iteration/virtual_variables.py delete mode 100644 spacq/tests/__init__.py delete mode 100644 spacq/tests/tool/__init__.py delete mode 100644 spacq/tests/tool/box.py delete mode 100644 spacq/tool/__init__.py delete mode 100644 spacq/tool/box.py delete mode 100644 spacq/tool/tests/__init__.py delete mode 100644 spacq/tool/tests/test_box.py diff --git a/spacq/__init__.py b/spacq/__init__.py deleted file mode 100644 index 8e4933f..0000000 --- a/spacq/__init__.py +++ /dev/null @@ -1 +0,0 @@ -VERSION = '2.1' diff --git a/spacq/devices/__init__.py b/spacq/devices/__init__.py deleted file mode 100755 index 24d2b02..0000000 --- a/spacq/devices/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import agilent, iqc, oxford, rohde_schwarz, tektronix, keithley, cryomagnetics, lakeshore, stanford_research_systems, basel -manufacturers = [agilent, iqc, oxford, rohde_schwarz, tektronix, keithley, cryomagnetics, lakeshore, stanford_research_systems, basel] diff --git a/spacq/devices/abstract_device.py b/spacq/devices/abstract_device.py deleted file mode 100644 index 53b5231..0000000 --- a/spacq/devices/abstract_device.py +++ /dev/null @@ -1,529 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from threading import RLock -from time import time - -from spacq.tool.box import Enum, Synchronized - -from packaging import version - -""" -Hardware device abstraction interface. -""" - - -# PyVISA, Linux GPIB, PyVISA USB. -drivers = Enum(['pyvisa', 'lgpib', 'pyvisa_usb', 'telnet']) - - -# Try to import all available drivers. -available_drivers = [] - -try: - import Gpib - import gpib -except ImportError: - pass -else: - available_drivers.append(drivers.lgpib) - -try: - import telnetlib - import socket -except ImportError: - pass -else: - available_drivers.append(drivers.telnet) - -try: - import visa - try: - legacyVisa = version.parse(visa.__version__) < version.parse('1.5') - except: - legacyVisa = 1 # Some of the mutilated/old visas don't have __version__, but they are all legacy - -except ImportError: - pass -else: - available_drivers.append(drivers.pyvisa) - available_drivers.append(drivers.pyvisa_usb) - - -class DeviceNotFoundError(Exception): - """ - Failure to connect to a device. - """ - - pass - - -class DeviceTimeout(Exception): - """ - Timeout on action. - """ - - pass - - -class IbstaBits(object): - """ - Status bits (in ibsta) as reported by Linux GPIB. - """ - - DCAS = 0x1 - DTAS = 0x2 - LACS = 0x4 - TACS = 0x8 - ATN = 0x10 - CIC = 0x20 - REM = 0x40 - LOK = 0x80 - CMPL = 0x100 - EVENT = 0x200 - SPOLL = 0x400 - RQS = 0x800, - SRQI = 0x1000 - END = 0x2000 - TIMO = 0x4000 - ERR = 0x8000 - - -class SuperDevice(object): - def _setup(self): - """ - Pre-connection setup. - """ - - self.name = self.__class__.__name__ - - self.resources = {} - self.subdevices = {} - - def _connected(self): - """ - Post-connection setup. - """ - - if hasattr(self, 'driver') and self.driver == drivers.lgpib: - # Some devices don't assert the EOI line, so look for their EOS character instead. - if hasattr(self, 'eos_char'): - self.device.config(gpib.IbcEOSchar, ord(self.eos_char)) - self.device.config(gpib.IbcEOSrd, 1) - - # Gpib.Gpib doesn't complain if the device at the PAD doesn't actually exist. - try: - log.debug('GPIB device IDN: {0!r}'.format(self.idn)) - except gpib.GpibError as e: - raise DeviceNotFoundError('Could not open device at "{0}".'.format(self.connection_resource), e) - - # Recursively. - for name, subdev in self.subdevices.items(): - log.debug('Post-connection for subdevice "{0}".'.format(name)) - - subdev._connected() - - -class AbstractDevice(SuperDevice): - """ - A class for controlling devices which can be connected to either via Ethernet and (PyVISA or Telnet) or GPIB and Linux GPIB. - """ - - max_timeout = 15 # s - - def _setup(self): - self.multi_command = None - self.responses_expected = 0 - - SuperDevice._setup(self) - - self.lock = RLock() - - self.status = [] - - def __init__(self, ip_address=None, host_address=None, gpib_board=0, gpib_pad=None, gpib_sad=0, - usb_resource=None, autoconnect=True): - """ - Ethernet (tcpip::::instr): - ip_address: Address on which the device is listening on port 111. - - Telnet (): - host_address: String that list the IP address of the host. - Named this way to avoid issues with ip_address used for ethernet connections - - GPIB (gpib[gpib_board]::[::]::instr): - gpib_board: GPIB board index. Defaults to 0. - gpib_pad: Primary address of the device. - gpib_sad: Secondary address of the device. Defaults to 0. - - USB (usb_resource): - usb_resource: VISA resource of the form: USB[board]::::::[::]::RAW - - autoconnect: Connect to the device upon instantiation. - """ - - self._setup() - - log.info('Creating device "{0}".'.format(self.name)) - - if ip_address is not None: - if drivers.pyvisa in available_drivers: - log.debug('Using PyVISA with ip_address="{0}".'.format(ip_address)) - self.driver = drivers.pyvisa - self.connection_resource = { - 'resource_name': 'tcpip::{0}::instr'.format(ip_address), - } - else: - raise NotImplementedError('PyVISA required, but not available.') - - elif host_address is not None: - if drivers.telnet in available_drivers: - log.debug('Using telnet with host_address="{0}".'.format(host_address)) - self.driver = drivers.telnet - self.connection_resource = { - 'host': '{0}'.format(host_address) - } - else: - raise NotImplementedError('Telnetlib required, but not available.') - - elif gpib_pad is not None: - if drivers.lgpib in available_drivers: - log.debug('Using Linux GPIB with gpib_board="{0}", gpib_pad="{1}", ' - 'gpib_sad="{2}".'.format(gpib_board, gpib_pad, gpib_sad)) - self.driver = drivers.lgpib - self.connection_resource = { - 'name': gpib_board, - 'pad': gpib_pad, - 'sad': gpib_sad, - } - elif drivers.pyvisa in available_drivers: - log.debug('Using PyVISA with gpib_board="{0}", gpib_pad="{1}", ' - 'gpib_sad="{2}".'.format(gpib_board, gpib_pad, gpib_sad)) - self.driver = drivers.pyvisa - self.connection_resource = { - 'resource_name': 'gpib{0}::{1}::{2}::instr'.format(gpib_board, gpib_pad, gpib_sad), - } - else: - raise NotImplementedError('Linux GPIB or PyVISA required, but not available.') - elif usb_resource is not None: - if drivers.pyvisa_usb in available_drivers: - log.debug('Using PyVISA with usb_resource="{0}"'.format(usb_resource)) - self.driver = drivers.pyvisa_usb - self.connection_resource = { - 'resource_name': usb_resource, - } - else: - raise NotImplementedError('PyVISA required, but not available.') - else: - raise ValueError('Either an IP, Host Address, GPIB, or USB address must be specified.') - - if autoconnect: - self.connect() - - def __repr__(self): - return '<{0}>'.format(self.__class__.__name__) - - def connect(self): - """ - Make a connection to the device. - """ - - log.info('Connecting to device "{0}" using {1} at "{2}".'.format(self.name, self.driver, self.connection_resource)) - - if self.driver == drivers.pyvisa: - try: - if not(legacyVisa): - rm = visa.ResourceManager() - self.device = rm.open_resource(**self.connection_resource) - else: - self.device = visa.Instrument(**self.connection_resource) - except visa.VisaIOError as e: - raise DeviceNotFoundError('Could not open device at "{0}".'.format(self.connection_resource), e) - elif self.driver == drivers.telnet: - self.device = telnetlib.Telnet(timeout=2, **self.connection_resource) - - elif self.driver == drivers.lgpib: - try: - self.device = Gpib.Gpib(**self.connection_resource) - except gpib.GpibError as e: - raise DeviceNotFoundError('Could not open device at "{0}".'.format(self.connection_resource), e) - elif self.driver == drivers.pyvisa_usb: - try: - if not(legacyVisa): - rm = visa.ResourceManager() - self.device = rm.open_resource(**self.connection_resource) - else: - class USBDevice(visa.Instrument): - """ - Using USB devices with PyVISA requires a small hack: the object must be an Instrument, but we can't call Instrument.__init__. - """ - - def __init__(self, *args, **kwargs): - # Bypass the initialization in visa.Instrument, due to "send_end" not being valid for USB. - visa.ResourceTemplate.__init__(self, *args, **kwargs) - - self.device = USBDevice(**self.connection_resource) - - except visa.VisaIOError as e: - raise DeviceNotFoundError('Could not open device at "{0}".'.format(self.connection_resource), e) - - try: - self._connected() - except Exception as e: - raise DeviceNotFoundError('Could not finish connection to device at "{0}".'.format(self.connection_resource), e) - - def multi_command_start(self): - """ - Redirect further commands to a buffer. - """ - - log.debug('Starting multi-command message for device "{0}"'.format(self.name)) - - if self.driver not in [drivers.pyvisa, drivers.lgpib]: - raise NotImplementedError('Unsupported driver: "{0}".'.format(self.driver)) - - self.multi_command = [] - self.responses_expected = 0 - - @Synchronized() - def multi_command_stop(self): - """ - Stop redirecting to a buffer, and send the buffered commands. - - Returns the results of queries if any were expected. - """ - - log.debug('Stopping multi-command message for device "{0}"'.format(self.name)) - - if self.multi_command is None: - raise ValueError('Multi-command message not started.') - elif not self.multi_command: - # No commands. - return [] - - commands = self.multi_command - # This ensures that write and ask will not buffer the real message. - self.multi_command = None - - # Only commands not starting with "*" get a ":" prefix. - commands = [cmd if cmd[0] == '*' else ':' + cmd for cmd in commands] - message = ';'.join(commands) - - if self.responses_expected: - result = self.ask(message) - - # FIXME: What if the response contains a meaningful ";" somewhere? - return result.split(';', self.responses_expected - 1) - else: - self.write(message) - - return [] - - @Synchronized() - def write(self, message): - """ - Write to the device. - - Supports multi-command. - """ - - if self.multi_command is not None: - log.debug('Writing to multi-command buffer for device "{0}": {1!r}'.format(self.name, message)) - - self.multi_command.append(message) - return - - log.debug('Writing to device "{0}": {1!r}'.format(self.name, message)) - - if self.driver == drivers.pyvisa: - try: - self.device.write(message) - except visa.VisaIOError as e: - if e.error_code == visa.VI_ERROR_TMO: - raise DeviceTimeout(e) - else: - raise - - elif self.driver == drivers.telnet: - try: - self.device.write(message) - except Exception: - if e is socket.timeout: - raise DeviceTimeout(e) - else: - raise - - elif self.driver == drivers.lgpib: - try: - self.device.write(message) - except gpib.GpibError as e: - if 'timeout' in e.message: - raise DeviceTimeout(e) - else: - raise - - elif self.driver == drivers.pyvisa_usb: - # Send the message raw. - if not(legacyVisa): - self.device.write_raw(message) - else: - visa.vpp43.write(self.device.vi, message) - - @Synchronized() - def read_raw(self, chunk_size=512): - """ - Read everything the device has to say and return it exactly. - """ - - log.debug('Reading from device "{0}".'.format(self.name)) - - buf = '' - - if self.driver in [drivers.pyvisa, drivers.pyvisa_usb]: - try: - buf = self.device.read_raw() - except visa.VisaIOError as e: - raise - - elif self.driver == drivers.telnet: - try: - buf = self.device.read_until('\r\n') - except Exception as e: - if e is socket.timeout: - raise DeviceTimeout(e) - else: - raise - - elif self.driver == drivers.lgpib: - status = 0 - while status == 0: - try: - buf += self.device.read(len=chunk_size) - except gpib.GpibError as e: - if 'timeout' in e.message: - raise DeviceTimeout(e) - else: - raise - - status = self.device.ibsta() & IbstaBits.END - - log.debug('Read from device "{0}": {1!r}'.format(self.name, buf)) - - return buf - - def read(self): - """ - Read from the device, but strip terminating whitespace. - """ - - return self.read_raw().rstrip() - - @Synchronized() - def ask_raw(self, message): - """ - Write, then read_raw. - """ - - self.write(message) - return self.read_raw() - - @Synchronized() - def ask(self, message): - """ - Write, then read. - - Supports multi-command. - """ - - self.write(message) - - if self.multi_command is None: - return self.read() - else: - self.responses_expected += 1 - - def close(self): - """ - Close the connection, if possible. - """ - - log.debug('Closing device: {0}'.format(self.name)) - - if self.driver in [drivers.pyvisa, drivers.pyvisa_usb]: - self.device.close() - - def find_resource(self, path): - """ - Return a Resource given a resource path spec. - - eg. ('subdevice A', 'subdevice B', 'resource C') -> Resource - """ - - log.debug('Looking for resource {0}.'.format(path)) - - if len(path) < 1: - raise ValueError('No path provided.') - - # Keep track of the last device in the tree so far. - dev = self - # Keep track of the path so far. - traversed = () - - while len(path) > 1: - try: - dev = dev.subdevices[path[0]] - except KeyError: - raise ValueError('No subdevice "{0}" in {1}.'.format(path[0], traversed)) - - traversed += path[:1] - path = path[1:] - - try: - return dev.resources[path[0]] - except KeyError: - raise ValueError('No resource "{0}" in {1}.'.format(path[0], traversed)) - - @property - def idn(self): - """ - Ask the device for identification. - """ - - if self.driver in [drivers.pyvisa, drivers.lgpib]: - return self.ask('*idn?') - - @property - def opc(self): - """ - Wait until the device is done. - """ - - end_time = time() + self.max_timeout - - while True: - if self.driver in [drivers.pyvisa, drivers.lgpib]: - try: - self.ask('*opc?') - except DeviceTimeout: - if time() > end_time: - raise - else: - break - - -class AbstractSubdevice(SuperDevice): - """ - A subdevice (eg. channel) of a hardware device. - """ - - def _setup(self): - SuperDevice._setup(self) - - # Synchronized methods should use the device lock. - self.lock = self.device.lock if self.device else None - - def __init__(self, device): - self.device = device - - self._setup() - diff --git a/spacq/devices/agilent/__init__.py b/spacq/devices/agilent/__init__.py deleted file mode 100644 index dbb94ac..0000000 --- a/spacq/devices/agilent/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -log = logging.getLogger(__name__) - - -name = 'Agilent' - -from . import dm34410a, dm34401a, nwa8753et -models = [dm34410a, dm34401a, nwa8753et] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_dm34410a, mock_dm34401a, mock_nwa8753et -mock_models = [mock_dm34410a, mock_dm34401a, mock_nwa8753et] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/agilent/dm34401a.py b/spacq/devices/agilent/dm34401a.py deleted file mode 100644 index c61ca92..0000000 --- a/spacq/devices/agilent/dm34401a.py +++ /dev/null @@ -1,119 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped - -""" -Agilent 34401A Digital Multimeter - -Obtain measurements from the multimeter. -""" - - -class DM34401A(AbstractDevice): - """ - Interface for Agilent 34401A DM. - - Note: Currently supports only DC voltage readings. - """ - - allowed_nplc = set([0.02, 0.2, 1.0, 10.0, 100.0]) - allowed_auto_zero = set(['off', 'on', 'once']) - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - read_only = ['reading'] - for name in read_only: - self.resources[name] = Resource(self, name) - - read_write = ['integration_time', 'auto_zero'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['reading'].units = 'V' - self.resources['integration_time'].converter = float - self.resources['integration_time'].allowed_values = self.allowed_nplc - self.resources['auto_zero'].allowed_values = self.allowed_auto_zero - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - # TODO: Allow for measurements other than DC voltage. - self.write('configure:voltage:dc') - self.integration_time = 1.0 - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - def integration_time(self): - """ - The integration time of the multimeter in terms of PLC. - """ - - return float(self.ask('sense:voltage:dc:nplc?')) - - @integration_time.setter - def integration_time(self, value): - if value not in self.allowed_nplc: - raise ValueError('Invalid NPLC value: {0}'.format(value)) - - self.write('sense:voltage:dc:nplc {0}'.format(value)) - - @property - def auto_zero(self): - """ - The auto zero state. - """ - - # result = self.ask('sense:voltage:dc:zero:auto?') - result = self.ask('sense:zero:auto?') - - if result == '0': - return 'off' - elif result == '1': - return 'on' - - @auto_zero.setter - def auto_zero(self, value): - if value not in self.allowed_auto_zero: - raise ValueError('Invalid setting: {0}'.format(value)) - - #self.write('sense:voltage:dc:zero:auto {0}'.format(value)) - self.write('sense:zero:auto {0}'.format(value)) - - @property - @quantity_wrapped('V') - @Synchronized() - def reading(self): - """ - The value measured by the device, as a quantity in V. - """ - - self.status.append('Taking reading') - - try: - log.debug('Getting reading.') - result = float(self.ask('read?')) - log.debug('Got reading: {0}'.format(result)) - - return result - finally: - self.status.pop() - - -name = '34401A' -implementation = DM34401A diff --git a/spacq/devices/agilent/dm34410a.py b/spacq/devices/agilent/dm34410a.py deleted file mode 100644 index 8d6119a..0000000 --- a/spacq/devices/agilent/dm34410a.py +++ /dev/null @@ -1,116 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped - -""" -Agilent 34410A Digital Multimeter - -Obtain measurements from the multimeter. -""" - - -class DM34410A(AbstractDevice): - """ - Interface for Agilent 34410A DM. - - Note: Currently supports only DC voltage readings. - """ - - allowed_nplc = set([0.006, 0.02, 0.06, 0.2, 1.0, 2.0, 10.0, 100.0]) - allowed_auto_zero = set(['off', 'on', 'once']) - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - read_only = ['reading'] - for name in read_only: - self.resources[name] = Resource(self, name) - - read_write = ['integration_time', 'auto_zero'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['reading'].units = 'V' - self.resources['integration_time'].converter = float - self.resources['integration_time'].allowed_values = self.allowed_nplc - self.resources['auto_zero'].allowed_values = self.allowed_auto_zero - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - # TODO: Allow for measurements other than DC voltage. - self.write('configure:voltage:dc') - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - def integration_time(self): - """ - The integration time of the multimeter in terms of PLC. - """ - - return float(self.ask('sense:voltage:dc:nplc?')) - - @integration_time.setter - def integration_time(self, value): - if value not in self.allowed_nplc: - raise ValueError('Invalid NPLC value: {0}'.format(value)) - - self.write('sense:voltage:dc:nplc {0}'.format(value)) - - @property - def auto_zero(self): - """ - The auto zero state. - """ - - result = self.ask('sense:voltage:dc:zero:auto?') - - if result == '0': - return 'off' - elif result == '1': - return 'on' - - @auto_zero.setter - def auto_zero(self, value): - if value not in self.allowed_auto_zero: - raise ValueError('Invalid setting: {0}'.format(value)) - - self.write('sense:voltage:dc:zero:auto {0}'.format(value)) - - @property - @quantity_wrapped('V') - @Synchronized() - def reading(self): - """ - The value measured by the device, as a quantity in V. - """ - - self.status.append('Taking reading') - - try: - log.debug('Getting reading.') - result = float(self.ask('read?')) - log.debug('Got reading: {0}'.format(result)) - - return result - finally: - self.status.pop() - - -name = '34410A' -implementation = DM34410A diff --git a/spacq/devices/agilent/mock/__init__.py b/spacq/devices/agilent/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/agilent/mock/mock_dm34401a.py b/spacq/devices/agilent/mock/mock_dm34401a.py deleted file mode 100644 index 309450d..0000000 --- a/spacq/devices/agilent/mock/mock_dm34401a.py +++ /dev/null @@ -1,72 +0,0 @@ -import random - -from ...mock.mock_abstract_device import MockAbstractDevice -from ..dm34401a import DM34401A - -""" -Mock Agilent 34401A Digital Multimeter - -Control a fake multimeter. -""" - - -class MockDM34401A(MockAbstractDevice, DM34401A): - """ - Mock interface for Agilent 34401A Digital Multimeter. - """ - - def __init__(self, *args, **kwargs): - self.mocking = DM34401A - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['mode'] = 'dc' - self.mock_state['nplc'] = '1' - self.mock_state['auto_zero'] = 1 - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'configure': - if cmd[1] == 'voltage' and cmd[2] == 'dc': - self.mock_state['mode'] = 'dc' - done = True - elif cmd[0] == 'sense': - if cmd[1] == 'voltage' and cmd[2] == 'dc': - if cmd[3] == 'nplc': - if query: - result = self.mock_state['nplc'] - else: - self.mock_state['nplc'] = args - done = True - #elif cmd[3] == 'zero' and cmd[4] == 'auto': - # if query: - # result = self.mock_state['auto_zero'] - # else: - # if args in ['on']: - # value = 1 - # elif args in ['once', 'off']: - # value = 0 - # self.mock_state['auto_zero'] = value - # done = True - elif cmd[1] == 'zero' and cmd[2] == 'auto': - if query: - result = self.mock_state['auto_zero'] - else: - if args in ['on']: - value = 1 - elif args in ['once', 'off']: - value = 0 - self.mock_state['auto_zero'] = value - done = True - elif cmd[0] == 'read' and query: - result = '-1.{0:04d}0000E-02'.format(random.randint(0, 9999)) - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = '34401A' -implementation = MockDM34401A diff --git a/spacq/devices/agilent/mock/mock_dm34410a.py b/spacq/devices/agilent/mock/mock_dm34410a.py deleted file mode 100644 index 569a176..0000000 --- a/spacq/devices/agilent/mock/mock_dm34410a.py +++ /dev/null @@ -1,62 +0,0 @@ -import random - -from ...mock.mock_abstract_device import MockAbstractDevice -from ..dm34410a import DM34410A - -""" -Mock Agilent 34410A Digital Multimeter - -Control a fake multimeter. -""" - - -class MockDM34410A(MockAbstractDevice, DM34410A): - """ - Mock interface for Agilent 34410A Digital Multimeter. - """ - - def __init__(self, *args, **kwargs): - self.mocking = DM34410A - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['mode'] = 'dc' - self.mock_state['nplc'] = '1' - self.mock_state['auto_zero'] = 1 - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'configure': - if cmd[1] == 'voltage' and cmd[2] == 'dc': - self.mock_state['mode'] = 'dc' - done = True - elif cmd[0] == 'sense': - if cmd[1] == 'voltage' and cmd[2] == 'dc': - if cmd[3] == 'nplc': - if query: - result = self.mock_state['nplc'] - else: - self.mock_state['nplc'] = args - done = True - elif cmd[3] == 'zero' and cmd[4] == 'auto': - if query: - result = self.mock_state['auto_zero'] - else: - if args in ['on']: - value = 1 - elif args in ['once', 'off']: - value = 0 - self.mock_state['auto_zero'] = value - done = True - elif cmd[0] == 'read' and query: - result = '-1.{0:04d}0000E-02'.format(random.randint(0, 9999)) - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = '34410A' -implementation = MockDM34410A diff --git a/spacq/devices/agilent/mock/mock_nwa8753et.py b/spacq/devices/agilent/mock/mock_nwa8753et.py deleted file mode 100644 index eb44a73..0000000 --- a/spacq/devices/agilent/mock/mock_nwa8753et.py +++ /dev/null @@ -1,26 +0,0 @@ -import random - -from ...mock.mock_abstract_device import MockAbstractDevice -from ..nwa8753et import NWA8753ET - -""" -Mock -""" - - -class MockNWA8753ET(MockAbstractDevice, NWA8753ET): - """ - Mock interface for Agilent 34401A Digital Multimeter. - """ - - def __init__(self, *args, **kwargs): - self.mocking = DM34401A - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - MockAbstractDevice.write(self, 'Test', 2, done) - - -name = '8753ET' -implementation = MockNWA8753ET diff --git a/spacq/devices/agilent/mock/tests/__init__.py b/spacq/devices/agilent/mock/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/agilent/mock/tests/test_mock_dm34401a.py b/spacq/devices/agilent/mock/tests/test_mock_dm34401a.py deleted file mode 100644 index 228d64f..0000000 --- a/spacq/devices/agilent/mock/tests/test_mock_dm34401a.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import dm34401a -from .. import mock_dm34401a - -from ...tests.server.test_dm34401a import DM34401ATest - - -# Don't lose the real device. -real_DM34401A = dm34401a.DM34401A -is_mock = DM34401ATest.mock - - -def setup(): - # Run the tests with a fake device. - dm34401a.DM34401A = mock_dm34401a.MockDM34401A - DM34401ATest.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - dm34401a.DM34401A = real_DM34401A - DM34401ATest.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/agilent/mock/tests/test_mock_dm34410a.py b/spacq/devices/agilent/mock/tests/test_mock_dm34410a.py deleted file mode 100644 index 4b37ed7..0000000 --- a/spacq/devices/agilent/mock/tests/test_mock_dm34410a.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import dm34410a -from .. import mock_dm34410a - -from ...tests.server.test_dm34410a import DM34410ATest - - -# Don't lose the real device. -real_DM34410A = dm34410a.DM34410A -is_mock = DM34410ATest.mock - - -def setup(): - # Run the tests with a fake device. - dm34410a.DM34410A = mock_dm34410a.MockDM34410A - DM34410ATest.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - dm34410a.DM34410A = real_DM34410A - DM34410ATest.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/agilent/nwa8753et.py b/spacq/devices/agilent/nwa8753et.py deleted file mode 100644 index bf420aa..0000000 --- a/spacq/devices/agilent/nwa8753et.py +++ /dev/null @@ -1,120 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped, quantity_unwrapped - -""" -Agilent 8753ET Network Analyzer - -Current capabilities: Set CW frequency of network analyzer -ToDo: Could add sweep measurements (note: 8753ET locks GPIB while running sweep, so can't use it as a sweep and measure system) -""" - - -class NWA8753ET(AbstractDevice): - """ - Interface for Agilent 8753ET - """ - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - read_only = ['marker'] - for name in read_only: - self.resources[name] = Resource(self, name) - - read_write = ['cwFreq','power','markFreq'] - for name in read_write: - self.resources[name] = Resource(self, name, name, name) - - self.resources['marker'].converter = float - self.resources['cwFreq'].units = 'Hz' - self.resources['power'].converter = float - self.resources['markFreq'].units = 'Hz' - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - self.ask('opc?;pres') - self.write('chan1') - self.write('auxcoff') - # For now, just set minimum power - self.write('powe-20') - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - @quantity_wrapped('Hz') - def cwFreq(self): - """ - The frequency of the networkAnalyzer output - """ - - return float(self.ask('cwfreq?')) - - @cwFreq.setter - @quantity_unwrapped('Hz') - def cwFreq(self, value): - self.write('cwfreq{0}'.format(value)) - - @property - def power(self): - """ - The power (in dB) - """ - - return float(self.ask('powe?')) - - @power.setter - def power(self, value): - self.write('powe{0}'.format(value)) - - @property - @Synchronized() - def marker(self): - """ - The value measured by the device at marker, as a quantity in dB. - """ - - self.status.append('Taking reading') - - try: - log.debug('Getting reading.') - result_raw = self.ask('OUTPMARK') - result = float([x.strip() for x in result_raw.split(',')][0]) - log.debug('Got reading: {0}'.format(result)) - - return result - finally: - self.status.pop() - - @property - @quantity_wrapped('Hz') - def markFreq(self): - """ - The frequency of the marker in Hz - """ - - return float(self.ask('MARK1?')) - - @markFreq.setter - @quantity_unwrapped('Hz') - def markFreq(self, value): - self.write('MARK1{0}'.format(value)) - - - -name = '8753ET' -implementation = NWA8753ET diff --git a/spacq/devices/agilent/tests/__init__.py b/spacq/devices/agilent/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/agilent/tests/server/__init__.py b/spacq/devices/agilent/tests/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/agilent/tests/server/test_dm34401a.py b/spacq/devices/agilent/tests/server/test_dm34401a.py deleted file mode 100644 index e45e2a8..0000000 --- a/spacq/devices/agilent/tests/server/test_dm34401a.py +++ /dev/null @@ -1,65 +0,0 @@ -from nose.tools import eq_ -from unittest import main - -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import dm34401a - - -class DM34401ATest(DeviceServerTestCase): - def obtain_device(self): - dev = DeviceServerTestCase.obtain_device(self, impl=dm34401a.DM34401A, - manufacturer='Agilent', model='34401A') - dev.reset() - - return dev - - def testAutoZero(self): - """ - Test the auto zero setting. - """ - - dm = self.obtain_device() - - dm.auto_zero = 'once' - eq_(dm.auto_zero, 'off') - - dm.auto_zero = 'on' - eq_(dm.auto_zero, 'on') - - try: - dm.auto_zero = 'something else' - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - def testIntegrationTime(self): - """ - Test the integration time setting. - """ - - dm = self.obtain_device() - - dm.integration_time = 100 - eq_(dm.integration_time, 100) - - try: - dm.integration_time = -999 - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - def testGetValues(self): - """ - Obtain some values. - """ - - dm = self.obtain_device() - - dm.reading.assert_dimensions('V') - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/agilent/tests/server/test_dm34410a.py b/spacq/devices/agilent/tests/server/test_dm34410a.py deleted file mode 100644 index 9e66419..0000000 --- a/spacq/devices/agilent/tests/server/test_dm34410a.py +++ /dev/null @@ -1,65 +0,0 @@ -from nose.tools import eq_ -from unittest import main - -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import dm34410a - - -class DM34410ATest(DeviceServerTestCase): - def obtain_device(self): - dev = DeviceServerTestCase.obtain_device(self, impl=dm34410a.DM34410A, - manufacturer='Agilent', model='34410A') - dev.reset() - - return dev - - def testAutoZero(self): - """ - Test the auto zero setting. - """ - - dm = self.obtain_device() - - dm.auto_zero = 'once' - eq_(dm.auto_zero, 'off') - - dm.auto_zero = 'on' - eq_(dm.auto_zero, 'on') - - try: - dm.auto_zero = 'something else' - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - def testIntegrationTime(self): - """ - Test the integration time setting. - """ - - dm = self.obtain_device() - - dm.integration_time = 100 - eq_(dm.integration_time, 100) - - try: - dm.integration_time = -999 - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - def testGetValues(self): - """ - Obtain some values. - """ - - dm = self.obtain_device() - - dm.reading.assert_dimensions('V') - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/basel/__init__.py b/spacq/devices/basel/__init__.py deleted file mode 100644 index 48693af..0000000 --- a/spacq/devices/basel/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -name = 'Physics Basel' - -from . import dacsp927 -models = [dacsp927] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_dacsp927 -mock_models = [mock_dacsp927] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/basel/dacsp927.py b/spacq/devices/basel/dacsp927.py deleted file mode 100644 index 1358c23..0000000 --- a/spacq/devices/basel/dacsp927.py +++ /dev/null @@ -1,122 +0,0 @@ -from __future__ import division - -import logging -log = logging.getLogger(__name__) - -import numpy -import time - -from spacq.interface.resources import Resource -from spacq.interface.units import Quantity -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice, AbstractSubdevice -from ..tools import quantity_unwrapped, quantity_wrapped, BinaryEncoder - -""" -Physics Basel Low Noise/High Resolution DAC SP 927 -Control the output voltages on all the ports. -""" - -class Port(AbstractSubdevice): - """ - An output port on the voltage source. - """ - def _setup(self): - AbstractSubdevice._setup(self) - - # Resources. - read_write = ['voltage'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['voltage'].units = 'V' - - @Synchronized() - def _connected(self): - AbstractSubdevice._connected(self) - - # Turn on port - output = self.device.ask_raw('{0} ON\r\n'.format(self.num)).strip('\r\n') - if output != 0: - Warning("Device not connected!") - # Gets current voltage in hex, convert it to volts and save the result - result_hex = self.device.ask_raw('{0} V?\r\n'.format(self.num)).strip('\r\n') - if result_hex is not "": - result = self.hex_to_voltage(result_hex) - self.currentVoltage = result - else: - self.currentVoltage = 0 - - # Converts from hex to voltage (see user manual for more info) - def hex_to_voltage(self, number): - return int(number,16)/838848 - 10 - - # Converts from hex to voltage (see user manual for more info) - def voltage_to_hex(self, voltage): - return hex(int(round(((voltage+10)*838848), 0)))[2:] #remove 0x that python adds automatically to hex - - def __init__(self, device, num, *args, **kwargs): - """ - Initialize the output port. - device: The voltage source to which this Port belongs. - num: The index of this port. - """ - AbstractSubdevice.__init__(self, device, *args, **kwargs) - self.num = num - - @property - @quantity_wrapped('V') - def voltage(self): - return self.currentVoltage - - @voltage.setter - @quantity_unwrapped('V') - def voltage(self, value): - """ - Set the voltage on this port, as a quantity in V. - """ - value_hex = self.voltage_to_hex(value) - output = self.device.ask_raw('{0} {1}\r\n'.format(self.num, value_hex)).strip('\r\n') - if output != 0: - Warning("Voltage not properly set!") - - result_hex = self.device.ask_raw('{0} V?\r\n'.format(self.num)).strip('\r\n') - result = self.hex_to_voltage(result_hex) - self.currentVoltage = result - -class dacsp927(AbstractDevice): - """ - Interface for the Physics Basel DAC SP 927 voltage source - """ - - def _setup(self): - AbstractDevice._setup(self) - - self.ports = [] - for num in range(8): - port = Port(self, num+1, **self.port_settings) # Naming convention for DAC SP 927 goes 1 to 8 - self.ports.append(port) - self.subdevices['port{0}'.format(num+1)] = port - - def __init__(self, port_settings=None, *args, **kwargs): - """ - Initialize the voltage source and all its ports. - port_settings: A dictionary of values to give to each port upon creation. - """ - - if port_settings is None: - self.port_settings = {} - else: - self.port_settings = port_settings - - AbstractDevice.__init__(self, *args, **kwargs) - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - # Add any initialization here. Careful: if you reconnect it will run this - - -name = 'dacsp927 Voltage Source' -implementation = dacsp927 diff --git a/spacq/devices/basel/mock/__init__.py b/spacq/devices/basel/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/basel/mock/mock_dacsp927.py b/spacq/devices/basel/mock/mock_dacsp927.py deleted file mode 100644 index cb8235c..0000000 --- a/spacq/devices/basel/mock/mock_dacsp927.py +++ /dev/null @@ -1,27 +0,0 @@ -from ...mock.mock_abstract_device import MockAbstractDevice -from ..dacsp927 import dacsp927 - -""" -Mock DAC SP 927 voltage source -*******This is not complete************ -""" - - -class mockdacsp927(MockAbstractDevice, dacsp927): - """ - Mock interface for the DAC SP 927 voltage source - """ - - def __init__(self, *args, **kwargs): - self.mocking = dacsp927 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['frequency'] = 1000 # Hz - self.mock_state['BNCAmplitude'] = 0.5 # V - - - -name = 'dacsp927 Voltage Source' -implementation = mockdacsp927 diff --git a/spacq/devices/config.py b/spacq/devices/config.py deleted file mode 100644 index 8c5ee58..0000000 --- a/spacq/devices/config.py +++ /dev/null @@ -1,236 +0,0 @@ -from spacq.tool.box import Enum - -from .abstract_device import DeviceNotFoundError - -""" -Device configuration. -""" - - -cached_tree = None - -def device_tree(): - """ - Build a device tree from the existing devices. - """ - - global cached_tree - - if cached_tree is not None: - return cached_tree - - from .. import devices - - tree = {} - - for manufacturer in devices.manufacturers: - subtree = {} - - for model, mock_model in zip(manufacturer.models, manufacturer.mock_models): - if model is None and mock_model is None: - continue - elif model is not None and mock_model is None: - name = model.name - elif model is None and mock_model is not None: - name = mock_model.name - elif model is not None and mock_model is not None: - if model.name != mock_model.name: - raise ValueError('Different device names: "{0}" and ' - '"{1}".'.format(model.name, mock_model.name)) - - name = model.name - - subtree[name] = {} - - if model is not None: - subtree[name]['real'] = model.implementation - - if mock_model is not None: - subtree[name]['mock'] = mock_model.implementation - - tree[manufacturer.name] = subtree - - cached_tree = tree - return cached_tree - - -class ConnectionError(Exception): - """ - Unable to connect. - """ - - pass - - -class DeviceConfig(object): - """ - Description for a device. - """ - - address_modes = Enum([ - 'ethernet', - 'telnet', - 'gpib', - 'usb', - ]) - - def __init__(self, name): - self.name = name - - # Connection configuration. - self.address_mode = None - self.host_address = None - self.ip_address = None - self.gpib_board = 0 - self.gpib_pad = 0 - self.gpib_sad = 0 - self.usb_resource = None - - # Information about module that implements this device. - self.manufacturer = None - self.model = None - self.mock = False - - # Device-specific GUI setup. - self.gui_setup = None - - # Resource path to label mappings. - self.resource_labels = {} - - # The connected device object. - self._device = None - - # Label to resource object mappings. - self.resources = {} - - def __getstate__(self): - """ - Return a modified dictionary for pickling. - """ - - result = self.__dict__.copy() - - # Do not pickle references to mutable objects. - del result['_device'] - del result['resources'] - - return result - - def __setstate__(self, dict): - """ - Revert the changes done by __getstate__. - """ - - self.__dict__ = dict - - # Set missing values to defaults. - self._device = None - self.resources = {} - - @property - def device(self): - """ - The connected device object. - """ - - return self._device - - @device.setter - def device(self, value): - self._device = value - - self.gui_setup = None - try: - self.gui_setup = self._device._gui_setup - except AttributeError: - # No device or no GUI setup available. - pass - - def diff_resources(self, new): - """ - Compare the resources belonging to 2 DeviceConfig objects. - - The result is a tuple of: - resources which appear - resources which change - resources which disappear - where all "resources" are resource labels. - """ - - old_labels, new_labels = set(self.resources), set(new.resources) - - appeared = new_labels - old_labels - disappeared = old_labels - new_labels - - changed = set() - possibly_changed = old_labels.intersection(new_labels) - for p in possibly_changed: - if self.resources[p] is not new.resources[p]: - changed.add(p) - - return (appeared, changed, disappeared) - - def connect(self): - """ - Create an instance of the implementation and connect to it. - """ - - if self.address_mode not in self.address_modes: - raise ConnectionError('Invalid address mode specified.') - - if self.manufacturer is None or self.model is None: - raise ConnectionError('No implementation specified.') - - address = {} - - if not self.mock: - if self.address_mode == self.address_modes.ethernet: - if self.ip_address is None: - raise ConnectionError('No IP address specified.') - - address['ip_address'] = self.ip_address - - elif self.address_mode == self.address_modes.telnet: - if self.host_address is None: - raise ConnectionError('No Host address specified.') - address['host_address'] = self.host_address - elif self.address_mode == self.address_modes.gpib: - address['gpib_board'] = self.gpib_board - address['gpib_pad'] = self.gpib_pad - address['gpib_sad'] = self.gpib_sad - elif self.address_mode == self.address_modes.usb: - if self.usb_resource is None: - raise ConnectionError('No USB resource specified.') - - address['usb_resource'] = self.usb_resource - - tree = device_tree() - - try: - subtree = tree[self.manufacturer] - except KeyError: - raise ConnectionError('Unknown manufacturer: {0}'.format(self.manufacturer)) - - try: - subtree = subtree[self.model] - except KeyError: - raise ConnectionError('Unknown model: {0}'.format(self.model)) - - kind = 'mock' if self.mock else 'real' - - try: - implementation = subtree[kind] - except KeyError: - raise ConnectionError('Unknown kind: {0}'.format(kind)) - - try: - device = implementation(autoconnect=False, **address) - except (ValueError, NotImplementedError) as e: - raise ConnectionError('Unable to create device.', e) - - try: - device.connect() - except DeviceNotFoundError as e: - raise ConnectionError('Unable to make connection to device.', e) - - self.device = device diff --git a/spacq/devices/cryomagnetics/__init__.py b/spacq/devices/cryomagnetics/__init__.py deleted file mode 100755 index c452246..0000000 --- a/spacq/devices/cryomagnetics/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -log = logging.getLogger(__name__) - - -name = 'Cryomagnetics' - -from . import model4g -models = [model4g] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_model4g -mock_models = [mock_model4g] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/cryomagnetics/gui/__init__.py b/spacq/devices/cryomagnetics/gui/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/cryomagnetics/gui/model4g.py b/spacq/devices/cryomagnetics/gui/model4g.py deleted file mode 100755 index 04f504b..0000000 --- a/spacq/devices/cryomagnetics/gui/model4g.py +++ /dev/null @@ -1,560 +0,0 @@ -from functools import partial -from pubsub import pub -from threading import Lock -from threading import Thread -import wx - -from spacq.gui.tool.box import Dialog, OK_BACKGROUND_COLOR, MessageDialog -from spacq.interface.units import Quantity, IncompatibleDimensions -from spacq.interface.resources import AcquisitionThread - -""" -Magnet control front panel. - -Dev notes: -Temporary design. Magnet control is planned to be integrated with - the resource automation functionality of this program. - -spacq.devices.iqc.gui.voltage_source.py was used as a guide - in creating this. - -the live aspects of this gui are derived from gui.display.plot.live.scalar.py -""" - -class Model4GChannelPanel(wx.Panel): - - def __init__(self, parent, global_store, subdevice, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self.delay = Quantity(1.0, 's') - - # This lock blocks the acquisition thread from acquiring. - self.running_lock = Lock() - self.channel_subdevice = subdevice - - self.displays = {} - self.control_state_displays = {} - self.readout_displays = {} - self.control_state_list = ['persistent_switch_heater','virt_sync_currents'] - self.readout_list = [ 'magnet_current','power_supply_current', - 'persistent_switch_heater', 'high_limit','low_limit','sweep', - 'rate_0','rate_1','rate_2','rate_3','rate_4', 'virt_sync_currents'] - self.measurement_resources = [] - for name in self.readout_list: - self.displays[name] = [] - self.measurement_resources.append((name, self.channel_subdevice.resources[name])) - # A list to save acquired data to before outputting to GUI. - self.measurements = [None] * len(self.measurement_resources) - - # Main Box. - - main_box = wx.BoxSizer(wx.VERTICAL) - - # Channel Header Box. - - channel_header_box = wx.BoxSizer(wx.HORIZONTAL) - main_box.Add(channel_header_box, flag=wx.EXPAND) - - self.channel_button = wx.ToggleButton(self, label='Channel {0} Toggle'.format(self.channel_subdevice.channel)) - self.Bind(wx.EVT_TOGGLEBUTTON, self.OnChannelToggle, self.channel_button) - self.channel_button.SetValue(False) - channel_header_box.Add(self.channel_button) - - ## Control states. - - control_state_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) - channel_header_box.Add((0, 0), 1, wx.EXPAND) - channel_header_box.Add(control_state_grid, flag=wx.ALIGN_RIGHT, border = 20) - - for control_state_name in self.control_state_list: - control_state_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) - control_state_display.BackgroundColour = wx.LIGHT_GREY - control_state_grid.Add(control_state_display, flag=wx.ALIGN_RIGHT) - self.displays[control_state_name].append(control_state_display) - self.control_state_displays[control_state_name] = control_state_display - - # reverse our dictionary for key retrieval by item. - self.inv_control_state_displays = dict((v,k) for k, v in self.control_state_displays.iteritems()) - - # Readouts. - - readout_static_box = wx.StaticBox(self, label = 'Readouts') - readout_box = wx.StaticBoxSizer(readout_static_box, wx.VERTICAL) - main_box.Add(readout_box, flag=wx.EXPAND, proportion=1) - -# readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=2, hgap=1) - readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=3, hgap=1) #TODO: for debugging model4g GUI...replace when no longer needed. - readout_box.Add(readout_grid, flag=wx.ALIGN_RIGHT) - - self.checkboxes = {} - - ## Setup individual labels + displays - - for resource_name in self.readout_list: - - ### Checkbox. #TODO: for debugging model4g GUI...remove when no longer needed. - checkbox = wx.CheckBox(self) - readout_grid.Add(checkbox, flag = wx.ALIGN_LEFT) - self.checkboxes[resource_name] = checkbox - - ### Label. - label = resource_name.replace('_',' ').title() - readout_grid.Add(wx.StaticText(self, label=label + ':'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - - ### Display. - display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) - display.BackgroundColour = wx.LIGHT_GREY - self.displays[resource_name].append(display) - self.readout_displays[resource_name] = display - - ### Connect display to GUI. - readout_grid.Add(self.displays[resource_name][-1], flag=wx.ALIGN_RIGHT) - - # reverse our dictionary for key retrieval by item. - self.inv_readout_displays = dict((v,k) for k, v in self.readout_displays.iteritems()) - - # Controls. - - self.control_static_box = wx.StaticBox(self, label='Controls') - self.control_box=wx.StaticBoxSizer(self.control_static_box, wx.VERTICAL) - main_box.Add(self.control_box, flag=wx.EXPAND) - - ## Persistent Heater Switch. - - heater_box = wx.BoxSizer(wx.HORIZONTAL) - self.control_box.Add(heater_box, flag=wx.ALIGN_RIGHT) - - heater_box.Add(wx.StaticText(self, label='Persistent Switch Heater:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - - self.heater_toggle = wx.ToggleButton(self, label='on/off', size=(100,-1)) - initial_state = self.channel_subdevice.persistent_switch_heater - self.heater_toggle.SetValue(True if initial_state == 1 else 0) - self.Bind(wx.EVT_TOGGLEBUTTON, self.OnHeaterToggle, self.heater_toggle) - heater_box.Add(self.heater_toggle,flag=wx.ALIGN_RIGHT) - - ## Sweeper Control Box. - - sweeper_static_box = wx.StaticBox(self, label = 'Sweep') - sweeper_box = wx.StaticBoxSizer(sweeper_static_box, wx.VERTICAL) - self.control_box.Add(sweeper_box, flag=wx.EXPAND) - - sweep_buttons_box = wx.BoxSizer(wx.HORIZONTAL) - sweeper_box.Add(sweep_buttons_box, flag = wx.CENTER|wx.ALL) - - ### Sweep buttons. - - sweep_buttons_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) - sweep_buttons_box.Add(sweep_buttons_grid, flag=wx.CENTER|wx.ALL) - - sweepup_button = wx.Button(self, label='up') - sweepzero_button = wx.Button(self, label='zero') - sweepdown_button = wx.Button(self, label='down') - sweeppause_button = wx.Button(self, label='pause') - self.Bind(wx.EVT_BUTTON, self.OnSweepUp, sweepup_button) - self.Bind(wx.EVT_BUTTON, self.OnSweepZero, sweepzero_button) - self.Bind(wx.EVT_BUTTON, self.OnSweepDown, sweepdown_button) - self.Bind(wx.EVT_BUTTON, self.OnSweepPause, sweeppause_button) - sweep_buttons_grid.Add(sweepup_button) - sweep_buttons_grid.Add(sweepzero_button) - sweep_buttons_grid.Add(sweepdown_button) - sweep_buttons_grid.Add(sweeppause_button) - - ### Current syncing. - - ####some space - - - sync_button = wx.Button(self, label='sync currents') - self.Bind(wx.EVT_BUTTON, self.OnSyncCurrents, sync_button) - sweep_buttons_box.Add(sync_button, flag=wx.LEFT|wx.CENTER, border = 20) - - ## Limits. - - limit_static_box = wx.StaticBox(self, label = 'Limits') - limit_box = wx.StaticBoxSizer(limit_static_box, wx.VERTICAL) - self.control_box.Add(limit_box,flag=wx.EXPAND) - - limits_grid = wx.FlexGridSizer(rows=2, cols=3, hgap=1) - limits_grid.AddGrowableCol(1,1) - - limit_box.Add(limits_grid, flag=wx.ALIGN_RIGHT) - - ### High Limit - - limits_grid.Add(wx.StaticText(self, label='High Limit:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - - set_hilim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) - self.Bind(wx.EVT_BUTTON, self.OnSetHighLimit, set_hilim_button) - limits_grid.Add(set_hilim_button,flag=wx.ALIGN_RIGHT) - - self.hilim_input = wx.TextCtrl(self, size=(100, -1)) - limits_grid.Add(self.hilim_input, flag=wx.EXPAND) - - ### Low Limit - - limits_grid.Add(wx.StaticText(self, label='Low Limit:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - - set_lolim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) - self.Bind(wx.EVT_BUTTON, self.OnSetLowLimit, set_lolim_button) - limits_grid.Add(set_lolim_button,flag=wx.ALIGN_RIGHT) - - self.lolim_input = wx.TextCtrl(self, size=(100, -1)) - limits_grid.Add(self.lolim_input) - - ## Rates. - - rates_static_box = wx.StaticBox(self, label = 'Rates') - rates_box = wx.StaticBoxSizer(rates_static_box, wx.VERTICAL) - self.control_box.Add(rates_box,flag=wx.EXPAND) - - ## used to have content of rates box all right aligned. - rates_inner_box = wx.BoxSizer(wx.HORIZONTAL) - rates_box.Add(rates_inner_box, flag=wx.ALIGN_RIGHT) - - menu_items = [] - for resource in self.readout_list: - if resource.startswith('rate_'): - menu_items.append(resource) - - self.rates_menu = wx.ComboBox(self,choices = menu_items, style=wx.CB_READONLY) - self.rates_menu.SetStringSelection(menu_items[0]) - rates_inner_box.Add(self.rates_menu, flag=wx.ALIGN_RIGHT) - - set_rate_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) - self.Bind(wx.EVT_BUTTON, self.OnSetRate, set_rate_button) - rates_inner_box.Add(set_rate_button,flag=wx.ALIGN_RIGHT) - - self.rate_input = wx.TextCtrl(self, size=(100, -1)) - rates_inner_box.Add(self.rate_input, flag=wx.ALIGN_RIGHT) - - # Finish GUI building. - - self.SetSizerAndFit(main_box) - - # Default behaviour. - - ## start with... - ### ...the threads locked out of acquisition. - self.running_lock.acquire() - ### ...controls disabled. - self.RecursiveEnableSizer(self.control_box,False) - - # Threading. - - self.acqthreads = [] - #TODO: implement with a normal thread instead, to avoid the use of a dummy call to a resource. - guicallback = partial(wx.CallAfter, self.Update) - self.guiupdatethread = AcquisitionThread(self.delay, guicallback, resource=self.channel_subdevice.resources['persistent_switch_heater'], running_lock=self.running_lock) - self.acqthreads.append(self.guiupdatethread) - self.guiupdatethread.daemon = True - self.guiupdatethread.start() - - def __del__(self): - try: - -# if self.channel_button.GetValue() == False: -# self.running_lock.release() - for thread in self.acqthreads: - thread.resource = None - thread.done = True - thread.join() - del thread - -# self.close() - except Exception: - pass - - def UpdateReadouts(self, resource_name, value): - """ - Update appropriate readouts with a new resource value. - Also update button permissions. - """ - if resource_name in self.readout_displays.keys(): - if self.checkboxes[resource_name].Value == False: #TODO: for debugging model4g GUI...remove when no longer needed. - return - - for display in self.displays[resource_name]: - - #perform alterations to output based on where the resource is being readout in GUI. - - inv_cont_dict = self.inv_control_state_displays - if display in inv_cont_dict.keys(): - if inv_cont_dict[display] == 'persistent_switch_heater': - if value == 'on': - display.BackgroundColour = OK_BACKGROUND_COLOR - elif value == 'off': - display.BackgroundColour = wx.LIGHT_GREY - - value_readout = 'heater {0}'.format(value) - - elif inv_cont_dict[display] == 'virt_sync_currents': - if value == 'synced': - display.BackgroundColour = OK_BACKGROUND_COLOR - elif value == 'not synced': - display.BackgroundColour = wx.LIGHT_GREY - #display as-is - value_readout = value - elif display in self.inv_readout_displays.keys(): - #display as-is - value_readout = value - - display.SetValue(str(value_readout)) - - # User Permissions - - # if currents don't match, heater toggle should be disabled. - if display in inv_cont_dict.keys(): - if inv_cont_dict[display] == 'virt_sync_currents': - if value == 'synced': - self.heater_toggle.Enable() - else: - self.heater_toggle.Disable() - - - def Update(self, dummyval): - """ - Acquire data and then update the GUI with this data. - - Note: code taken from sweep controller. - """ - - #this loop sets up separate threads for each resource. - thrs = [] - for i, (name, resource) in enumerate(self.measurement_resources): - if resource is not None: - def save_callback(value, i=i): - self.measurements[i] = value - - callback = partial(wx.CallAfter, partial(self.read_resource, name, resource, save_callback)) - thr = Thread(target=callback) - thrs.append(thr) - thr.daemon = True - thr.start() - - for thr in thrs: - thr.join() - - #this code saves the values to the GUI readouts. - for i, (name,_) in enumerate(self.measurement_resources): - self.UpdateReadouts(name, self.measurements[i]) - - - def read_resource(self, name, resource, save_callback): - """ - Read a value from a resource and handle exceptions. - - Note: code taken from sweep controller. - """ - - if name in self.readout_displays.keys(): - if self.checkboxes[name].Value == False: #TODO: for debugging model4g GUI...remove when no longer needed. - return - - try: - value = resource.value - except Exception as e: - if self.resource_exception_handler is not None: - self.resource_exception_handler(name, e, write=False) - return - - save_callback(value) - - def OnChannelToggle(self, evt=None): - toggle = self.channel_button.GetValue() - if toggle == True: - self.running_lock.release() - elif toggle == False: - self.running_lock.acquire() - - self.RecursiveEnableSizer(self.control_box, toggle) - - #permission defaults. - self.heater_toggle.Disable() - - - def OnSweepDown(self, evt=None): - self.channel_subdevice.resources['sweep'].value = 'down' - - def OnSweepUp(self, evt=None): - self.channel_subdevice.resources['sweep'].value = 'up' - - def OnSweepZero(self, evt=None): - self.channel_subdevice.resources['sweep'].value = 'zero' - - def OnSweepPause(self, evt=None): - self.channel_subdevice.resources['sweep'].value = 'pause' - - def OnSetRate(self, evt=None): - try: - Quantity(self.rate_input.GetValue()) - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return False - - range_id = self.rates_menu.GetCurrentSelection() - resource = self.channel_subdevice.resources['rate_{0}'.format(range_id)] - new_value = self.rate_input.GetValue() - try: - resource.value = resource.convert(new_value) - except IncompatibleDimensions: - MessageDialog(self, ValueError('Expected dimensions to match "{0}"'.format(resource.units))).Show() - - def OnHeaterToggle(self, evt=None): - if self.heater_toggle.GetValue() == True: - new_value = 'on' - if self.heater_toggle.GetValue() == False: - new_value = 'off' - self.channel_subdevice.resources['persistent_switch_heater'].value = new_value - - def OnSetHighLimit(self, evt=None): - try: - Quantity(self.hilim_input.GetValue()) - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return False - - new_value = self.hilim_input.GetValue() - resource = self.channel_subdevice.resources['high_limit'] - try: - resource.value = resource.convert(new_value) - except IncompatibleDimensions: - MessageDialog(self, str(ValueError('Expected dimensions to match "{0}"'.format(resource.units)))).Show() - - def OnSetLowLimit(self, evt=None): - try: - Quantity(self.lolim_input.GetValue()) - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return False - - new_value = self.lolim_input.GetValue() - resource = self.channel_subdevice.resources['low_limit'] - try: - resource.value = resource.convert(new_value) - except IncompatibleDimensions: - MessageDialog(self, ValueError('Expected dimensions to match "{0}"'.format(resource.units))).Show() - - - def OnSyncCurrents(self, evt=None): - self.channel_subdevice.resources['virt_sync_currents'].value = 'start' - - def close(self): - """ - Perform cleanup. - """ - # Ensure the threads exits. - if self.channel_button.GetValue() == False: - self.running_lock.release() - for thread in self.acqthreads: - thread.resource = None - thread.done = True - thread.join() - del thread - - def RecursiveEnableSizer(self,wx_sizer, toggle): - ''' - Helper function that accesses all subwindows of a wxPython - sizer, and enables or disables them based on toggle. - ''' - children = wx_sizer.GetChildren() - for item in children: - - window = item.GetWindow() - sizer = item.GetSizer() - - if sizer: - #recurse - self.RecursiveEnableSizer(sizer,toggle) - elif window: - window.Enable(toggle) - - -class Model4GFrontPanel(wx.Panel): - """ - GUI for controlling the magnet. - """ - - def __init__(self, parent, global_store, model4g, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self.model4g = model4g - self.running = False - - - # Main Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Channels box. - channels_box = wx.BoxSizer(wx.HORIZONTAL) - panel_box.Add(channels_box) - - ### Channel boxes. - self.channel_panels = [] - for channel_subdevice in self.model4g.channels: - if channel_subdevice is not None: - - channel_static_box = wx.StaticBox(self) - channel_box_sizer = wx.StaticBoxSizer(channel_static_box, wx.VERTICAL) - - #### Channel Inputs/Outputs. - channel_panel = Model4GChannelPanel(self, global_store, channel_subdevice) - channel_box_sizer.Add(channel_panel) - channels_box.Add(channel_box_sizer) - - self.channel_panels.append(channel_panel) - - - - self.SetSizerAndFit(panel_box) - - def close(self): - #TODO: wxPython would probably have a nicer way of sending a close down through children. - for channel_panel in self.channel_panels: - channel_panel.close() - -class Model4GFrontPanelDialog(Dialog): - """ - A wrapper for Model4GFrontPanel. - """ - - def __init__(self, parent, global_store, model4g_name, *args, **kwargs): - # If the device doesn't exist, give up. - try: - model4g = global_store.devices[model4g_name].device - except (KeyError, AttributeError): - self.Destroy() - - return - - Dialog.__init__(self, parent, title='Model4G Front Panel', *args, **kwargs) - - self.model4g_name = model4g_name - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Settings panel. - self.panel = Model4GFrontPanel(self, global_store, model4g) - dialog_box.Add(self.panel) - - self.SetSizerAndFit(dialog_box) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - # Subscriptions. - pub.subscribe(self.msg_device, 'device.added') - pub.subscribe(self.msg_device, 'device.removed') - - def msg_device(self, name, value=None): - if name == self.model4g_name: - # Device has changed, so we can't trust it anymore. - self.Destroy() - - return - - def OnClose(self, evt): - self.panel.close() - evt.Skip() \ No newline at end of file diff --git a/spacq/devices/cryomagnetics/mock/__init__.py b/spacq/devices/cryomagnetics/mock/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/spacq/devices/cryomagnetics/mock/mock_model4g.py b/spacq/devices/cryomagnetics/mock/mock_model4g.py deleted file mode 100755 index bef1775..0000000 --- a/spacq/devices/cryomagnetics/mock/mock_model4g.py +++ /dev/null @@ -1,236 +0,0 @@ -from ...mock.mock_abstract_device import MockAbstractDevice -from ..model4g import Model4G -import time - -""" -Mock Model4G Power Supply -""" - -class MockChannel(object): - """ - A mock channel for a mock 4G power supply. - """ - def __init__(self, device, channel, *args, **kwargs): - self.mock_state = {} - - # Internals. (not read or write) - self.mock_state['device'] = device - self.mock_state['channel'] = channel - self.mock_state['units'] = 'G' - - # Read-only. - self.mock_state['magnet_current'] = 0 - self.mock_state['power_supply_current'] = 0 - - # Read-write. - self.mock_state['high_limit'] = 20 - self.mock_state['low_limit'] = 10 - self.mock_state['sweep'] = 'Standby' - self.mock_state['persistent_switch_heater'] = 0 - self.mock_state['rate_0'] = 0.001 - self.mock_state['rate_1'] = 0.002 - self.mock_state['rate_2'] = 0.003 - self.mock_state['rate_3'] = 0.004 - self.mock_state['rate_4'] = 0.005 - - self.mockdata = MockTimeData(self.mock_state['power_supply_current'],self.mock_state['power_supply_current'],1) - -class MockData(object): - ''' - Simulates the data that can be acquired from a sweep. - It wraps a list that contains N datapoints in min to max, including min and max. - Note that it is possible to have max < min. This will merely make the list go in - descending order (such as if the 4G was sweeping down). - ''' - - def __init__(self, data_min, data_max, N): - - self.index = 0 - # We map a range [0,1,2,...,N] to the range [min,max]. - if N > 1: - self.data_list = map( (lambda n:(data_max-data_min)*(float(n)/(N-1))+data_min), range(N)) - elif N == 1: - # if we have only 1 data point, then we just set it to min. - self.data_list = [data_min] - - def GetDatum(self): - - result = self.data_list[self.index] - - #once the magnet sweeps to the last value, if one keeps retrieving values, it will stay - #at the last value. - if (self.index + 1) < len(self.data_list): - self.index += 1 - - - return result - - def Reset(self): - self.index = 0 - -class MockTimeData(object): - ''' - Simulates the data that can be acquired from a sweep. - It will return data as a linear function of time such that: - t = 0 <-> data_min - t = time_to_max <-> data_max - where t is the time since instantiation - ''' - - def __init__(self, data_min, data_max, time_to_max): - - self.t_init = time.time() - - def data_fn(t): - if (t > time_to_max): - return data_max - else: - return (data_max-data_min)*(float(t)/time_to_max)+data_min - - self.f = data_fn - - def GetDatum(self): - result = self.f(time.time() - self.t_init) - return result - - def Reset(self): - self.t_init = time.time() - - - -class MockModel4G(MockAbstractDevice, Model4G): - """ - Mock interface for Model 4G Power Supply. - """ - - def __init__(self, *args, **kwargs): - self.mocking = Model4G - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - # Many other state properties are in the mock subdevice class MockChannel. - - self.mock_state['channels'] = [None] # There is no channel 0. - for x in xrange(1, 3): - self.mock_state['channels'].append(MockChannel(self,x)) - - self.mock_state['active_channel'] = 1 - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - mock_chan = self.mock_state['channels'][int(self.mock_state['active_channel'])] - - #should really take in kilogaussian and output in kilogaussian... - multiplier = None - output_unit = None - if mock_chan.mock_state['units'] == 'G': - multiplier = 1. - output_unit = 'kG' - elif mock_chan.mock_state['units'] == 'A': - multiplier = 1. - output_unit = 'A' - - - - if cmd[0] == 'ulim': - if query: - result = '{0} {1}'.format(mock_chan.mock_state['high_limit'] * multiplier, output_unit) - else: - if float(args) >= mock_chan.mock_state['low_limit']: - mock_chan.mock_state['high_limit'] = float(args) - done = True - if cmd[0] == 'llim': - if query: - result = '{0} {1}'.format(mock_chan.mock_state['low_limit'] * multiplier, output_unit) - else: - if float(args) <= mock_chan.mock_state['high_limit']: - mock_chan.mock_state['low_limit'] = float(args) - done = True - if cmd[0] == 'pshtr': - if query: - result = mock_chan.mock_state['persistent_switch_heater'] - else: - if args == 'on': - mock_chan.mock_state['persistent_switch_heater'] = 1 - elif args == 'off': - mock_chan.mock_state['persistent_switch_heater'] = 0 - done = True - elif cmd[0] == 'sweep': - if query: - result = mock_chan.mock_state['sweep'] - else: - last_state = mock_chan.mock_state['sweep'] - - #if sweep changes, then the data that will acquired is changed. - if args == 'up' and last_state != 'up' : #up - new_min = mock_chan.mock_state['power_supply_current'] - new_max = mock_chan.mock_state['high_limit'] - mock_chan.mockdata = MockTimeData(new_min,new_max,1) - elif args == 'zero' and last_state != 'zero': #zero - new_min = mock_chan.mock_state['power_supply_current'] - new_max = 0 - mock_chan.mockdata = MockTimeData(new_min,new_max,1) - elif args == 'down' and last_state != 'down': #down - new_min = mock_chan.mock_state['power_supply_current'] - new_max = mock_chan.mock_state['low_limit'] - mock_chan.mockdata = MockTimeData(new_min,new_max,1) - elif args == 'pause' and last_state != 'paused': #paused - new_min = new_max = mock_chan.mock_state['power_supply_current'] - mock_chan.mockdata = MockTimeData(new_min,new_max,1) - - if args == 'pause': - mock_chan.mock_state['sweep'] = 'Pause' - elif args == 'zero': - mock_chan.mock_state['sweep'] = 'Sweeping to zero' - else: - mock_chan.mock_state['sweep'] = 'Sweeping {0}'.format(args) - done = True - elif cmd[0] == 'chan': - if query: - result = self.mock_state['active_channel'] - else: - self.mock_state['active_channel'] = args - done = True - elif cmd[0] == 'units': - if query: - result = mock_chan.mock_state['units'] - else: - mock_chan.mock_state['units'] = args - done = True - elif cmd[0] == 'rate': - if query: - result = mock_chan.mock_state['rate_{0}'.format(args[0])] - else: - args = args.split() - mock_chan.mock_state['rate_{0}'.format(args[0])] = args[1] - done = True - elif cmd[0] == 'imag': - if query: - if mock_chan.mock_state['persistent_switch_heater'] == 1: - mock_chan.mock_state['power_supply_current'] = mock_chan.mockdata.GetDatum() - mock_chan.mock_state['magnet_current'] = mock_chan.mock_state['power_supply_current'] - result = '{0} {1}'.format(mock_chan.mock_state['magnet_current'] * multiplier, output_unit) - else: - result = '{0} {1}'.format(mock_chan.mock_state['magnet_current'] * multiplier, output_unit) - done = True - elif cmd[0] == 'iout': - if query: - mock_chan.mock_state['power_supply_current'] = mock_chan.mockdata.GetDatum() - result = '{0} {1}'.format(mock_chan.mock_state['power_supply_current'] * multiplier, output_unit) - done = True - - # Perform rounding as defined by the precision of the actual device. Rounds to nearest Gaussian. - if result is not None and ' kG' in str(result): - rounded_number = round(float(result.replace(' kG','')), 3) - result = '{0} {1}'.format(rounded_number, 'kG') - else: - pass #TODO: need to support rounding on amps. - - MockAbstractDevice.write(self, message, result, done) - - -name = 'Model 4G' -implementation = MockModel4G diff --git a/spacq/devices/cryomagnetics/mock/tests/__init__.py b/spacq/devices/cryomagnetics/mock/tests/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/spacq/devices/cryomagnetics/mock/tests/test_mock_model4g.py b/spacq/devices/cryomagnetics/mock/tests/test_mock_model4g.py deleted file mode 100755 index 7a7b35f..0000000 --- a/spacq/devices/cryomagnetics/mock/tests/test_mock_model4g.py +++ /dev/null @@ -1,25 +0,0 @@ -from unittest import main - -from ... import model4g -from .. import mock_model4g - -from ...tests.server.test_model4g import Model4GTest - -# Don't lose the real device. -real_Model4G = model4g.Model4G -is_mock = Model4GTest.mock - - -def setup(): - # Run the tests with a fake device. - model4g.Model4G = mock_model4g.MockModel4G - Model4GTest.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - model4g.Model4G = real_Model4G - Model4GTest.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/cryomagnetics/model4g.py b/spacq/devices/cryomagnetics/model4g.py deleted file mode 100755 index ae8f6b3..0000000 --- a/spacq/devices/cryomagnetics/model4g.py +++ /dev/null @@ -1,798 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized -from spacq.interface.units import Quantity -from time import sleep -from functools import wraps - -from ..abstract_device import AbstractDevice, AbstractSubdevice -from ..tools import quantity_wrapped, quantity_unwrapped -from ..tools import dynamic_quantity_wrapped, dynamic_converted_quantity_unwrapped - - -""" -Cryomagnetics model 4G Device -""" - -class Channel(AbstractSubdevice): - """ - Interface for a channel on the Model4G - """ - - - allowed_switch_heater = set(['on','off']) - allowed_heater_wait_mode = set(['on','off']) - allowed_sweep = set(['up','zero','down', 'pause']) - allowed_sync = set(['start','stop']) - allowed_energysave_mode = set([0,1,2,3,4]) - allowed_units = set(['kG','A']) - - def _setup(self): - AbstractSubdevice._setup(self) - - # Resources. - - read_only = ['magnet_current','power_supply_current'] - for name in read_only: - self.resources[name] = Resource(self, name) - - read_write = ['persistent_switch_heater', 'high_limit','low_limit','sweep', - 'rate_0','rate_1','rate_2','rate_3','rate_4', 'virt_sync_currents', 'virt_imag', 'virt_iout', - 'virt_imag_sweep_to','virt_iout_sweep_to','virt_energysave_mode', 'virt_heater_wait_mode' - ,'virt_sweep_sleep', 'units'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - # Resources with their units controlled by self._units. - self.dynamic_unit_resources = ['virt_imag_sweep_to','virt_iout_sweep_to','virt_imag','virt_iout','magnet_current', - 'power_supply_current','high_limit','low_limit' ] - for name in self.dynamic_unit_resources: - self.resources[name].units = self._units - - self.resources['virt_sweep_sleep'].units = 's' - for x in range(5): - self.resources['rate_{0}'.format(x)].units = 'A.s-1' - - self.resources['virt_heater_wait_mode'].allowed_values = self.allowed_heater_wait_mode - self.resources['virt_energysave_mode'].allowed_values = self.allowed_energysave_mode - self.resources['virt_energysave_mode'].converter = int - self.resources['persistent_switch_heater'].allowed_values = self.allowed_switch_heater - self.resources['sweep'].allowed_values = self.allowed_sweep - self.resources['virt_sync_currents'].allowed_values = self.allowed_sync - self.resources['units'].allowed_values = self.allowed_units - - def __init__(self, device, channel, *args, **kwargs): - self.channel = channel - - # internal attributes used for virtual features not present in actual device. - self._units = 'kG' # default units. - self._energysave_mode = 0 - self._iout_target = None - self._imag_target = None - self._heater_wait_mode = 'on' - self._sweep_sleep = 1. - - AbstractSubdevice.__init__(self, device, *args, **kwargs) - - def _connected(self): - # Initializations requiring GPIB. - self.units = self._units - self._iout_target = self.power_supply_current.original_value - self._imag_target = self.magnet_current.original_value - - def _wait_for_sweep(self): - ''' - This is an internal function that loops until a sweep is complete. - This allows some of the virtual features to wait until a sweep is complete before performing other commands. - ''' - - for i in xrange(0,2): - current_sweep = self.sweep - if current_sweep == 'Sweeping up': - while current_sweep != 'Pause' and self.power_supply_current != self.high_limit: - sleep(0.1) #give the GPIB some breathing space. - current_sweep = self.sweep - elif current_sweep == 'Sweeping to zero': - while current_sweep != 'Pause' and self.power_supply_current != Quantity(0,self._units): - sleep(0.1) - current_sweep = self.sweep - elif current_sweep == 'Sweeping down': - while current_sweep != 'Pause' and self.power_supply_current != self.low_limit: - sleep(0.1) - current_sweep = self.sweep - if i == 0 and self.virt_sweep_sleep.value != 0: - sleep(self.virt_sweep_sleep.value) # we sleep, then check once more to ensure the sweep has stabilized. - - - class _set_channel(object): - """ - A decorator which prefixes a method with a setting of the channel. - Note: Usually, this decorator should be wrapped underneath the @Synchronized() method - """ - @staticmethod - def __call__(f): - @wraps(f) - def decorated(self, *args, **kwargs): - - # Set channel here. - if self.device.active_channel_store is not self.channel: - self.device.active_channel = self.channel - - return f(self, *args, **kwargs) - - return decorated - - - # Low-level Direct Controls. #################################################################################### - - - @property - @Synchronized() - @dynamic_quantity_wrapped('_units') - @_set_channel() - def high_limit(self): - """ - The upper limit on the magnetic current - """ - response = self.device.ask('ulim?') - stripped_response = Quantity.from_string(response)[0] - return stripped_response - - @high_limit.setter - @Synchronized() - @dynamic_converted_quantity_unwrapped('_units') - @_set_channel() - def high_limit(self,value): - self.device.write('ulim {0}'.format(value)) - - @property - @Synchronized() - @dynamic_quantity_wrapped('_units') - @_set_channel() - def low_limit(self): - """ - The lower limit on the magnetic current - """ - response = self.device.ask('llim?') - stripped_response = Quantity.from_string(response)[0] - return stripped_response - - @low_limit.setter - @Synchronized() - @dynamic_converted_quantity_unwrapped('_units') - @_set_channel() - def low_limit(self,value): - - self.device.write('llim {0}'.format(value)) - - @property - @Synchronized() - @dynamic_quantity_wrapped('_units') - @_set_channel() - def magnet_current(self): - """ - This is the persistent magnet current setting - """ - response = self.device.ask('imag?') - stripped_response = Quantity.from_string(response)[0] - return stripped_response - - @property - @Synchronized() - @_set_channel() - def persistent_switch_heater(self): - """ - The persistent switch heater - """ - response = float(self.device.ask('pshtr?')) - - # when getting from the device immediately after a set, the power supply returns a 2 if it has not fully - # changed - while response == 2: - sleep(0.5) - response = float(self.device.ask('pshtr?')) - - - response_dict = {0:'off', 1:'on'} - - return response_dict[response] - - @persistent_switch_heater.setter - @Synchronized() - @_set_channel() - def persistent_switch_heater(self,value): - - if value not in self.allowed_switch_heater: - raise ValueError('Invalid heater switch value: {0}'.format(value)) - - # don't allow the heater switch to go on if currents not synced - if self.virt_sync_currents != 'synced' and value == 'on': - raise ValueError('Currents are not synced in channel {0}.'.format(self.channel)) - - self.device.write('pshtr {0}'.format(value)) - - # Give the heaters time to warm up, if desired. - if self.virt_heater_wait_mode == 'on': - sleep(1) - - @property - @Synchronized() - @dynamic_quantity_wrapped('_units') - @_set_channel() - def power_supply_current(self): - """ - The power supply output current - """ - response = self.device.ask('iout?') - stripped_response = Quantity.from_string(response)[0] - return stripped_response - - @quantity_wrapped('A') - @Synchronized() - @_set_channel() - def ranges(self,range_id): - ''' - Used to grab the range for a given range id. - ''' - if range_id is not 0: - lower = self.device.ask('range? {0}'.format(range_id-1)) - else: - lower = 0 - - upper = self.device.ask('range? {0}'.format(range_id)) - - return ('{0} to {1}'.format(lower,upper)) - - @property - @quantity_wrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_0(self): - """ - A rate for a rate range - """ - return float(self.device.ask('rate? 0')) - - @rate_0.setter - @quantity_unwrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_0(self,value): - - self.device.write('rate 0 {0}'.format(value)) - - @property - @quantity_wrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_1(self): - """ - A rate for a rate range - """ - return float(self.device.ask('rate? 1')) - - @rate_1.setter - @quantity_unwrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_1(self,value): - - self.device.write('rate 1 {0}'.format(value)) - - @property - @quantity_wrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_2(self): - """ - A rate for a rate range - """ - return float(self.device.ask('rate? 2')) - - @rate_2.setter - @quantity_unwrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_2(self,value): - - self.device.write('rate 2 {0}'.format(value)) - - @property - @quantity_wrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_3(self): - """ - A rate for a rate range - """ - - return float(self.device.ask('rate? 3')) - - @rate_3.setter - @quantity_unwrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_3(self,value): - - self.device.write('rate 3 {0}'.format(value)) - - @property - @quantity_wrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_4(self): - """ - A rate for a rate range - """ - return float(self.device.ask('rate? 4')) - - @rate_4.setter - @quantity_unwrapped('A.s-1') - @Synchronized() - @_set_channel() - def rate_4(self,value): - - self.device.write('rate 4 {0}'.format(value)) - - @property - @Synchronized() - @_set_channel() - def sweep(self): - """ - The sweeper control. - """ - response = str(self.device.ask('sweep?')) - return response - - @sweep.setter - @Synchronized() - @_set_channel() - def sweep(self,value): - - if value not in self.allowed_sweep: - raise ValueError('Invalid sweep value: {0}'.format(value)) - - self.device.write('sweep {0}'.format(value)) - - @property - @Synchronized() - @_set_channel() - def units(self): - """ - Get current units of the device. - """ - self.device.active_channel = self.channel - - response = self.device.ask('units?') - - # We perform a conversion between the GUI and the device with this dict. - read_dict = {'G':'kG','A':'A'} - - # if the device is different from the local copy, then we convert device to local copy. - if self._units != read_dict[response]: - self.units = self._units - - return read_dict[response] - - - @units.setter - @Synchronized() - @_set_channel() - def units(self,value): - """ - Set current units. - """ - if value not in self.allowed_units: - raise ValueError('Invalid units: {0}'.format(value)) - - # Perform a conversion between the GUI and the device with this dict. - write_dict = {'kG':'G','A':'A'} - - self.device.write('units {0}'.format(write_dict[value])) - - # Change our locally stored copy to the new user-defined units. - self._units = value - - # Change all the appropriate resources' units - for name in self.dynamic_unit_resources: - self.resources[name].units = self._units - - - # High-level Virtual Controls. ################################################################################## - - - @property - def virt_energysave_mode(self): - ''' - If enabled: after incrementing virt_imag_sweep_to, the magnet will go into persistent mode, and then - start a sweep of the power supply current to 0 - ''' - return self._energysave_mode - - @virt_energysave_mode.setter - def virt_energysave_mode(self, value): - ''' - If enabled: after incrementing virt_imag_sweep_to, the magnet will go into persistent mode, and then - start a sweep of the power supply current to 0 - ''' - self._energysave_mode = value - - @property - @Synchronized() - @dynamic_quantity_wrapped('_units') - def virt_imag(self): - ''' - Getter: - Simply returns the magnet current. - Setter: - This wraps virt_iout_sweep_to with current syncing, as well as optional energy saving. - ''' - return self.magnet_current.original_value - - @virt_imag.setter - @dynamic_converted_quantity_unwrapped('_units') - def virt_imag(self, value): - - if self.virt_energysave_mode == 4: - self.virt_sync_currents = 'start' - self._wait_for_sweep() - self.persistent_switch_heater = 'on' - self.virt_iout = Quantity(value,self._units) - self.persistent_switch_heater = 'off' - self.sweep = 'zero' - elif self.virt_energysave_mode == 3: - self.virt_sync_currents = 'start' - self._wait_for_sweep() - self.device.virt_both_persistent_switch_heaters = 'on' - self.virt_iout = Quantity(value,self._units) - self.device.virt_both_persistent_switch_heaters = 'off' - self.sweep = 'zero' - elif self.virt_energysave_mode == 2: - self.persistent_switch_heater = 'on' - self.virt_iout = Quantity(value,self._units) - self.persistent_switch_heater = 'off' - elif self.virt_energysave_mode == 1: - self.device.virt_both_persistent_switch_heaters = 'on' - self.virt_iout = Quantity(value,self._units) - self.device.virt_both_persistent_switch_heaters = 'off' - elif self.virt_energysave_mode == 0: - if self.persistent_switch_heater != 'on': - raise ValueError('Heater switch is not on in channel {0}.'.format(self.channel)) - self.virt_iout = Quantity(value, self._units) - - @property - @dynamic_quantity_wrapped('_units') - def virt_imag_sweep_to(self): - ''' - Getter: - Simply returns the magnet current. - Setter: - This wraps virt_iout_sweep_to with current syncing, as well as optional energy saving. - ''' - return self._imag_target - - @virt_imag_sweep_to.setter - @dynamic_converted_quantity_unwrapped('_units') - def virt_imag_sweep_to(self, value): - - if self.persistent_switch_heater != 'on': - self.persistent_switch_heater = 'on' - - self.virt_iout_sweep_to = Quantity(value, self._units) - - if self._units == 'kG': - self._imag_target = round(value,3) #round to the nearest Gaussian. - else: - self._imag_target = value #TODO: need to support rounding on amps. - - @property - @Synchronized() - @dynamic_quantity_wrapped('_units') - def virt_iout(self): - """ - Getter: - Simply returns the power supply current. - Setter: - Used to increment the output power supply current using lower level controls. - - Note: power_supply_current is already wrapped with units, so no need to wrap this function. - """ - return self.power_supply_current.original_value - - @virt_iout.setter - @dynamic_converted_quantity_unwrapped('_units') - def virt_iout(self, value): - - # determine whether to set the hilim or lolim for increment, then sweep - if value == 0: - self.sweep = 'zero' - elif self.power_supply_current.value < value: - - if self.low_limit > Quantity(value, self._units): - self.low_limit = Quantity(value, self._units) - - self.high_limit = Quantity(value, self._units) - self.sweep = 'up' - - elif self.power_supply_current.value > value: - - if self.high_limit < Quantity(value, self._units): - self.high_limit = Quantity(value, self._units) - - self.low_limit = Quantity(value, self._units) - self.sweep = 'down' - - self._wait_for_sweep() - - - - @property - @Synchronized() - @dynamic_quantity_wrapped('_units') - def virt_iout_sweep_to(self): - """ - Getter: - Simply returns the power supply current. - Setter: - Used to increment the output power supply current using lower level controls. - - Note: power_supply_current is already wrapped with units, so no need to wrap this function. - """ - return self._iout_target - - @virt_iout_sweep_to.setter - @dynamic_converted_quantity_unwrapped('_units') - def virt_iout_sweep_to(self, value): - - # determine whether to set the hilim or lolim for increment, then sweep - if value == 0: - self.sweep = 'zero' - elif self.power_supply_current.value < value: - - if self.low_limit > Quantity(value, self._units): - self.low_limit = Quantity(value, self._units) #we put the low limit to zero - - self.high_limit = Quantity(value, self._units) - self.sweep = 'up' - - elif self.power_supply_current.value > value: - - if self.high_limit < Quantity(value, self._units): - self.high_limit = Quantity(value, self._units) - - self.low_limit = Quantity(value, self._units) - self.sweep = 'down' - - - if self._units == 'kG': - self._iout_target = round(value,3) - else: - self._iout_target = value #TODO: need to support rounding on amps. - - @property - @quantity_wrapped('s') - def virt_sweep_sleep(self): - """ - Debugging purposes, although potentially the basis for a feature in the future. - Changes the sleep period in _wait_for_sweep after detecting the sweep is done. - """ - return self._sweep_sleep - - @virt_sweep_sleep.setter - @quantity_unwrapped('s') - def virt_sweep_sleep(self,value): - - self._sweep_sleep = value - - @property - @Synchronized() - def virt_sync_currents(self): - """ - Used to sync the currents. Note this is a virtual feature not present in the actual device. - """ - if self.magnet_current == self.power_supply_current: - return 'synced' - else: - return 'not synced' - - @virt_sync_currents.setter - @Synchronized() - def virt_sync_currents(self, value): - - if value == 'start': - self.virt_iout_sweep_to = self.magnet_current - elif value == 'stop': - self.sweep = 'pause' - - @property - def virt_heater_wait_mode(self): - - return self._heater_wait_mode - - @virt_heater_wait_mode.setter - def virt_heater_wait_mode(self, value): - - if value not in self.allowed_heater_wait_mode: - raise ValueError('Invalid heater wait mode value: {0}'.format(value)) - - self._heater_wait_mode = value - - -class Model4G(AbstractDevice): - """ - Interface for Model 4G - """ - allowed_active_channel = set([1,2]) - allowed_both_heaters = set(['on','off']) - allowed_both_units = set(['kG','A']) - - @property - def _gui_setup(self): - try: - from .gui.model4g import Model4GFrontPanelDialog - - return Model4GFrontPanelDialog - except ImportError as e: - log.debug('Could not load GUI setup for device "{0}": {1}'.format(self.name, str(e))) - - return None - - def _setup(self): - AbstractDevice._setup(self) - - # Channel subdevices. - self.channels = [None] # There is no channel 0. - for chan_num in xrange(1, 3): - channel = Channel(self,chan_num) - self.subdevices['channel{0}'.format(chan_num)] = channel - # this list is useful as it is actually ordered, as opposed to the dict above. - self.channels.append(channel) - - #This saves the last channel set to the device for programming purposes...its a way of minimizing - #calls to the device. - self.active_channel_store = None - - # Resources. - - read_write = ['active_channel', 'virt_both_persistent_switch_heaters', 'virt_both_units'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['active_channel'].allowed_values = self.allowed_active_channel - self.resources['active_channel'].converter = int - self.resources['virt_both_persistent_switch_heaters'].allowed_values = self.allowed_both_heaters - self.resources['virt_both_units'].allowed_values = self.allowed_both_units - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - # Start off with active channel as 1. Doing this to initialize active_channel_store. - self.active_channel = 1 - - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - #TODO: test if the *rst actually DOES do something to the magnet controller. - - self.virt_both_units = 'kG' - - for channel in [1,2]: - - subdev = self.channels[channel] - - subdev.virt_energysave_mode = 0 - subdev.virt_sync_currents = 'start' - subdev._wait_for_sweep() - subdev.persistent_switch_heater = 'on' - subdev.virt_imag = Quantity('0 kG') - - subdev.low_limit = Quantity('0 kG') - subdev.high_limit = Quantity('0 kG') - - subdev.virt_imag_sweep_to = Quantity('0 kG') - subdev.virt_iout_sweep_to = Quantity('0 kG') - subdev.persistent_switch_heater = 'off' - - #ensure the defaults set appropriately for both channels - for channel in [1,2]: - subdev = self.channels[channel] - - resource_check_dict = { subdev.virt_energysave_mode:0, - subdev.virt_sync_currents:'synced', - self.virt_both_units:'kG', - subdev.power_supply_current:Quantity('0 kG'), - subdev.magnet_current:Quantity('0 kG'), - subdev.persistent_switch_heater:'off', - subdev.high_limit:Quantity('0 kG'), - subdev.low_limit:Quantity('0 kG'), - subdev.virt_imag_sweep_to:Quantity('0 kG'), - subdev.virt_iout_sweep_to:Quantity('0 kG') - } - - for resource, value in resource_check_dict.items(): - if resource != value: - raise ValueError('A resource with value {0} did not set to its default value of {1}'.format(resource, value)) - - - @property - def active_channel(self): - """ - The active device channel. - """ - return float(self.ask('chan?')) - - @active_channel.setter - def active_channel(self,value): - if value not in self.allowed_active_channel: - raise ValueError('Invalid channel value: {0}'.format(value)) - - self.write('chan {0}'.format(value)) - self.active_channel_store = value - - @property - @Synchronized() - def virt_both_persistent_switch_heaters(self): - """ - The heaters on both channels. Control over both is desirable in order to avoid eddy currents in the system. - """ - - heaters = [channel.persistent_switch_heater for channel in self.channels[1:]] - all_heaters_same = all(heaters[0] == heater for heater in heaters) - - # Output based on states. - if all_heaters_same == True: - return heaters[0] - else: - return 'unequal' - - @virt_both_persistent_switch_heaters.setter - @Synchronized() - def virt_both_persistent_switch_heaters(self,value): - if value not in self.allowed_both_heaters: - raise ValueError('Invalid heater switch value: {0}'.format(value)) - - #can set value on or off and it will write to all channels - for channel in self.channels[1:]: - channel.persistent_switch_heater = value - - @property - @Synchronized() - def virt_both_units(self): - """ - Get current units of both device's channels. - """ - both_units = [channel.units for channel in self.channels[1:]] - all_units_same = all(both_units[0] == units for units in both_units) - - # Output based on states. - if all_units_same == True: - return both_units[0] - else: - return 'unequal' - - - @virt_both_units.setter - def virt_both_units(self,value): - """ - Set current units on both channels. - """ - if value not in self.allowed_both_units: - raise ValueError('Invalid units: {0}'.format(value)) - - #write to all channels - for channel in self.channels[1:]: - channel.units = value - - -name = 'Model 4G' -implementation = Model4G diff --git a/spacq/devices/cryomagnetics/tests/__init__.py b/spacq/devices/cryomagnetics/tests/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/spacq/devices/cryomagnetics/tests/server/__init__.py b/spacq/devices/cryomagnetics/tests/server/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/spacq/devices/cryomagnetics/tests/server/test_model4g.py b/spacq/devices/cryomagnetics/tests/server/test_model4g.py deleted file mode 100755 index 764abfd..0000000 --- a/spacq/devices/cryomagnetics/tests/server/test_model4g.py +++ /dev/null @@ -1,250 +0,0 @@ -from nose.tools import eq_ -from unittest import main -from time import sleep - -from spacq.tests.tool.box import DeviceServerTestCase -from spacq.interface.units import Quantity, IncompatibleDimensions - -from ... import model4g - -""" -model4g: --go over code looking for missed cases. -""" - -class Model4GTest(DeviceServerTestCase): - - channel_to_test = 1 - - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=model4g.Model4G, - manufacturer='Cryomagnetics', model='Model 4G') - - def testLimits(self): - """ - Test the limits. - """ - magctrler = self.obtain_device() - magctrler.reset() - chanctrler = magctrler.channels[self.channel_to_test] - - # Test setting the high limit. - - chanctrler.high_limit = Quantity('50 G') - eq_(chanctrler.high_limit, Quantity('50 G')) - - try: - chanctrler.high_limit = 55 - except AttributeError: - pass - else: - assert False, 'Expected AttributeError.' - - # ...and the low limit. - - chanctrler.low_limit = Quantity('40 G') - eq_(chanctrler.low_limit, Quantity('40 G')) - - try: - chanctrler.low_limit = 35 - except AttributeError: - pass - else: - assert False, 'Expected AttributeError.' - - # Test the limit restrictions. - - chanctrler.low_limit = Quantity('60 G') - eq_(chanctrler.low_limit, Quantity('40 G')) - - chanctrler.high_limit = Quantity('30 G') - eq_(chanctrler.high_limit, Quantity('50 G')) - - def testHeaters(self): - """ - Test the heaters. - """ - magctrler = self.obtain_device() - magctrler.reset() - chanctrler = magctrler.channels[self.channel_to_test] - - # Test turning a heater on and off. - - chanctrler.persistent_switch_heater = 'on' - eq_(chanctrler.persistent_switch_heater,'on') - - chanctrler.persistent_switch_heater = 'off' - eq_(chanctrler.persistent_switch_heater,'off') - - try: - chanctrler.persistent_switch_heater = 'something else' - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - # Test both heaters option. - - magctrler.virt_both_persistent_switch_heaters = 'on' - eq_(magctrler.virt_both_persistent_switch_heaters, 'on') - - chanctrler.persistent_switch_heater = 'off' - eq_(magctrler.virt_both_persistent_switch_heaters, 'unequal') - - - def testUnits(self): - """ - Test the units. - """ - magctrler = self.obtain_device() - magctrler.reset() - chanctrler = magctrler.channels[self.channel_to_test] - - # Test turning a heater on and off. - - chanctrler.units = 'A' - eq_(chanctrler.units,'A') - - chanctrler.units = 'kG' - eq_(chanctrler.units,'kG') - - try: - chanctrler.units = 'T' - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - # Test both heaters option. - - magctrler.virt_both_units = 'A' - eq_(magctrler.virt_both_units, 'A') - - chanctrler.units = 'kG' - eq_(magctrler.virt_both_units, 'unequal') - - # Try setting hilim with something not in the right units, and something that is in related units. - try: - chanctrler.high_limit = Quantity('5 A') - except IncompatibleDimensions: - pass - else: - assert False, 'Expected IncompatibleDimensions.' - - chanctrler.high_limit = Quantity('0.5 T') - eq_(chanctrler.high_limit, Quantity('5 kG')) - - def testCurrentSync(self): - """ - Test to see if the current syncing works in a variety of circumstances. - """ - magctrler = self.obtain_device() - magctrler.reset() - chanctrler = magctrler.channels[self.channel_to_test] - - # set the curs different. - # check to see if not synced. - chanctrler.virt_iout = Quantity('20 G') - chanctrler.virt_sync_currents = 'start' - chanctrler._wait_for_sweep() - eq_(chanctrler.virt_iout,chanctrler.virt_imag) - - def testRounding(self): - """ - The model4g rounds to a nearest gaussian, and amp (??). Test this since the code relies on - this with the sweep_to higher order functions. - """ - - magctrler = self.obtain_device() - magctrler.reset() - chanctrler = magctrler.channels[self.channel_to_test] - - chanctrler.virt_iout = Quantity('20.19 G') - eq_(chanctrler.virt_iout, Quantity('20 G')) - - - def testVirtIout(self): - """ - Test the resource virt_iout. - """ - magctrler = self.obtain_device() - magctrler.reset() - chanctrler = magctrler.channels[self.channel_to_test] - - chanctrler.virt_iout = Quantity('20 G') - eq_(chanctrler.virt_iout, Quantity('20 G')) - - try: - chanctrler.virt_iout = 55 - except AttributeError: - pass - else: - assert False, 'Expected AttributeError.' - - def testVirtImag(self): - """ - Test the resource virt_imag. - """ - - magctrler = self.obtain_device() - magctrler.reset() - chanctrler = magctrler.channels[self.channel_to_test] - - # Test a simple setting of the magnetic field. - - chanctrler.virt_energysave_mode = 0 - chanctrler.virt_sync_currents = 'start' - chanctrler.persistent_switch_heater = 'on' - chanctrler.virt_imag = Quantity('20 G') - - eq_(chanctrler.virt_imag, Quantity('20 G')) - - try: - chanctrler.virt_imag = 55 - except AttributeError: - pass - else: - assert False, 'Expected AttributeError.' - - # Energy mode 1. - - chanctrler.virt_energysave_mode = 1 - chanctrler.persistent_switch_heater = 'off' - chanctrler.virt_imag = Quantity('30 G') - - ## Check if it was set and if the heater was turned off. - eq_(magctrler.virt_both_persistent_switch_heaters, 'off') - eq_(chanctrler.virt_imag, Quantity('30 G')) - - # Energy mode 3. - - chanctrler.virt_energysave_mode = 3 - chanctrler.persistent_switch_heater = 'off' - chanctrler.virt_imag = Quantity('10 G') - - ## Check if the heater is off. - eq_(magctrler.virt_both_persistent_switch_heaters, 'off') - ## Check if the power is going down. - sleep(0.5) - eq_(chanctrler.magnet_current > chanctrler.power_supply_current, True) - ## Check if it was set properly. - eq_(chanctrler.virt_imag, Quantity('10 G')) - - - def testWaitForSweep(self): - """ - Tests the function _wait_for_sweep() - """ - magctrler = self.obtain_device() - magctrler.reset() - chanctrler = magctrler.channels[self.channel_to_test] - - chanctrler.virt_iout_sweep_to = Quantity('20 G') - chanctrler._wait_for_sweep() - - # Check if sweep is actually done several times. - for _ in xrange(1,10): - eq_(chanctrler.power_supply_current, Quantity('20 G')) - -if __name__ == '__main__': - main() diff --git a/spacq/devices/iqc/__init__.py b/spacq/devices/iqc/__init__.py deleted file mode 100644 index 591f81c..0000000 --- a/spacq/devices/iqc/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -log = logging.getLogger(__name__) - - -name = 'IQC' - -from . import voltage_source, ch6_voltage_source, ch4_voltage_source -models = [voltage_source, ch6_voltage_source, ch4_voltage_source] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_voltage_source, mock_ch6_voltage_source, mock_ch4_voltage_source -mock_models = [mock_voltage_source, mock_ch6_voltage_source, mock_ch4_voltage_source] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/iqc/ch4_voltage_source.py b/spacq/devices/iqc/ch4_voltage_source.py deleted file mode 100644 index 4ed0858..0000000 --- a/spacq/devices/iqc/ch4_voltage_source.py +++ /dev/null @@ -1,324 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -import numpy -import time - -from spacq.interface.resources import Resource -from spacq.interface.units import Quantity -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice, AbstractSubdevice -from ..tools import quantity_unwrapped, quantity_wrapped, BinaryEncoder - -""" -Custom 4 channel voltage source - -Control the output voltages on all the ports. -""" - - -class Port(AbstractSubdevice): - """ - An output port on the voltage source. - """ - - # Since there is no way to determine whether calibration has completed, - # wait this long and hope for the best. - calibration_delay = 2 # s - - @staticmethod - def format_for_dac(msg): - """ - Perform some formatting to make the device happy: - flip all the bits in the message - pad messages until their length in bytes is a multiple of 4 - """ - - log.debug('Formatting for DAC: {0}'.format(msg)) - - msg_encoded = BinaryEncoder.encode(msg) - # Flip each byte separately. - msg_flipped = [chr(~ord(x) & 0xff) for x in msg_encoded] - - missing_bytes = (4 - len(msg_encoded) % 4) % 4 - - result = BinaryEncoder.decode(msg_flipped + ['\x00'] * missing_bytes) - - log.debug('Formatted for DAC (padded with {0} bytes): {1}'.format(missing_bytes, result)) - - return result - - def _setup(self): - AbstractSubdevice._setup(self) - - # These values are used to tune the input values according to empirical error. - self.gain = 1.0 - self.offset = 0.0 - - # Resources. - read_write = ['voltage'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['voltage'].units = 'V' - - @Synchronized() - def _connected(self): - AbstractSubdevice._connected(self) - - if self.do_apply_settings: - self.apply_settings(calibrate=False) - - def __init__(self, device, num, resolution=20, apply_settings=True, min_value=-10, - max_value=+10, adaptive_filtering=True, calibrate_connected=False, - fast_settling=True, freq=100, *args, **kwargs): - """ - Initialize the output port. - - device: The ch4VoltageSource to which this Port belongs. - num: The index of this port. - resolution: How many bits the output value contains. - apply_settings: Whether to automatically apply all the settings. - min_value: Smallest value the port can produce. - max_value: Largest value the port can produce. - adaptive_filtering: Enable adaptive filtering. - calibrate_connected: Do not disconnect output while calibrating. - fast_settling: Enable fast settling. - freq: Clock rate in kHz. - """ - - AbstractSubdevice.__init__(self, device, *args, **kwargs) - - if resolution not in [16, 20]: - raise ValueError('Unsupported resolution: {0}'.format(resolution)) - - self.num = num - self.resolution = resolution - self.do_apply_settings = apply_settings - self.min_value = min_value - self.max_value = max_value - self.adaptive_filtering = adaptive_filtering - self.calibrate_connected = calibrate_connected - self.fast_settling = fast_settling - self.freq = freq - - def calculate_voltage(self, voltage): - """ - Determine the value corresponding to the given voltage. - """ - - try: - voltage_adjusted = voltage * self.gain + self.offset - except TypeError: - raise ValueError('Voltage must be a number. Given: {0}'.format(voltage)) - - if voltage_adjusted < self.min_value or voltage_adjusted > self.max_value: - raise ValueError('Adjusted voltage must be within [{0}, {1}]. ' - 'Given: {2}; adjusted to: {3}.'.format(self.min_value, - self.max_value, voltage, voltage_adjusted)) - - # Store the currentOutput for future reads - self.currentVoltage = voltage_adjusted - - max_converted = (1 << self.resolution) - 1 - value_span = self.max_value - self.min_value - - # Map [-min_value, max_value] onto [0x0, 0xff...] depending on the resolution. - # First negate the voltage, so that flipping the bits later will make it correct. - return int(round(float(-voltage_adjusted + self.max_value) / value_span * max_converted, 0)) - - @Synchronized() - def write_to_dac(self, message): - """ - Write a message to the DAC of the port. - - Voodoo programming follows, thanks to: - NI's lack of support for anything non-Windows in this case - my lack of time & desire to properly reverse engineer the ni845x DLL - - If the conversation does not go according to plan, bails out with an AssertionError! - """ - - message_length = BinaryEncoder.length(message) - - if message_length > 4: - raise ValueError('Message is longer than 4 bytes: {0}'.format(message)) - - message_formatted = self.format_for_dac(message) - - # The reply always comes back with as many bits set to 1 as were sent. - expected_reply = self.format_for_dac('00' * message_length) - - # Lots of assertions so we can bail ASAP to avoid crashing anything. - # Note[Kyle Willick]: These Hex strings come from using NI-Spy while communicating with the NI-8451 labview VIs - self.device.ask_encoded('0000 000c 0008 0100 0000 0000', - '0000 001c 0018 0100 0000 0002 0200 1000 0110 c000 0100 c000 0002 0000') - log.debug('Ask: 0000 000c 0008 0100 0000 0000 \n Receive: 0000 001c 0018 0100 0000 0002 0200 1000 0100 c001 0100 c000 0002 0000') - self.device.ask_encoded('0000 0014 0010 0110 0260 0000 00 {0:02x} {1:04x} 0700 0000'.format(self.num, self.freq), - '0000 000c 0008 0100 0000 0002') - log.debug('Ask: 0000 0014 0010 0110 0260 0000 00 {0:02x} {1:04x} 0700 0000 \n Receive: 0000 000c 0008 0100 0000 0002'.format(self.num, self.freq)) - self.device.ask_encoded('0000 0014 0010 0111 0260 0000 0003 {0:02x} 00 {1}'.format(message_length,message_formatted), - '0000 0014 0010 0100 0000 0002 {0:02x} 00 0000 {1}'.format(message_length, expected_reply)) - log.debug('Ask: 0000 0014 0010 0111 0260 0000 0003 {0:02x} 00 {1} \n Receive: 0000 0014 0010 0100 0000 0002 {2:02x} 00 0000 {3}'.format(message_length,message_formatted,message_length, expected_reply)) - - def apply_settings(self, calibrate=False): - """ - Apply the settings for the DAC on this port. - - calibrate: Run self-calibration on this port as well. - - Note: If self-calibrating, it is essential to wait the calibration_delay after this method returns. - """ - - flags = ((not self.adaptive_filtering) << 15 | - self.calibrate_connected << 14 | - (not self.fast_settling) << 4) - - if calibrate: - flags |= 0b01 - - # Write 16 bits to the top of the DIR: 0010 0100 xx10 0000 101x 00xx - self.write_to_dac('24 {0:04x}'.format(0x20a0 | flags)) - - @property - @quantity_wrapped('V') - def voltage(self): - return self.currentVoltage - - @voltage.setter - @quantity_unwrapped('V') - def voltage(self, value): - """ - Set the voltage on this port, as a quantity in V. - """ - - # Left-align the bits within the value: - # 20-bit: VVVV VVVV VVVV VVVV VVVV xxxx - # 16-bit: VVVV VVVV VVVV VVVV xxxx xxxx - # where the 'x's are don't-cares, so we just set them to 0 by shifting. - resulting_voltage = self.calculate_voltage(value) << (20 - self.resolution) - - # Write 24 bits to the top of the DIR: 0100 0000 xxxx xxxx xxxx xxxx xxxx xxxx - self.write_to_dac('40 {0:05x} f'.format(resulting_voltage)) - - @Synchronized() - def autotune(self, voltage_resource, min_value=None, max_value=None, final_value=0, set_result=True): - """ - Take some measured data and solve for the gain and offset. - - voltage_resource: A resource which provides the realtime measured data for this port. - min_value: Smallest value to take into account. - max_value: Largest value to take into account. - final_value: Value to set port to after all measurements are taken. - set_result: Whether to apply the resulting gain and offset. - """ - - self.device.status.append('Autotuning port {0}'.format(self.num)) - - try: - if min_value is None: - min_value = self.min_value - if max_value is None: - max_value = self.max_value - - # Test with raw values. - old_gain, old_offset = self.gain, self.offset - self.gain, self.offset = 1, 0 - - if max_value < min_value: - raise ValueError('{0} > {1}'.format(min_value, max_value)) - elif max_value == min_value: - num_points = 1 - else: - num_points = 21 - - # Obtain data. - real = numpy.linspace(min_value, max_value, num_points) - measured = [] - - for x in real: - self.voltage = Quantity(x, 'V') - time.sleep(0.2) - measured.append(voltage_resource.value.value) - - # Solve. - A = numpy.vstack([measured, numpy.ones(len(measured))]).T - gain, offset = numpy.linalg.lstsq(A, real)[0] - - if set_result: - self.gain, self.offset = gain, offset - else: - self.gain, self.offset = old_gain, old_offset - - # Set the voltage after the gain and offset, so that it is potentially more correct. - self.voltage = Quantity(final_value, 'V') - - return (gain, offset) - finally: - self.device.status.pop() - - -class ch4VoltageSource(AbstractDevice): - """ - Interface for the custom voltage source. - - It uses several TI DAC1220 chips and an NI USB-8451 to interface with them over SPI. - """ - - @property - def _gui_setup(self): - try: - from .gui.ch4_voltage_source import ch4VoltageSourceSettingsDialog - - return ch4VoltageSourceSettingsDialog - except ImportError as e: - log.debug('Could not load GUI setup for device "{0}": {1}'.format(self.name, str(e))) - - return None - - def _setup(self): - AbstractDevice._setup(self) - - self.ports = [] - for num in xrange(4): - port = Port(self, num, **self.port_settings) - self.ports.append(port) - self.subdevices['port{0:02}'.format(num)] = port - - def __init__(self, port_settings=None, *args, **kwargs): - """ - Initialize the voltage source and all its ports. - - port_settings: A dictionary of values to give to each port upon creation. - """ - - if port_settings is None: - self.port_settings = {} - else: - self.port_settings = port_settings - - AbstractDevice.__init__(self, *args, **kwargs) - - @Synchronized() - def ask_encoded(self, msg, assertion=None): - """ - Encode and write the message; then read and decode the answer. - """ - - self.write(BinaryEncoder.encode(msg)) - result = BinaryEncoder.decode(self.read_raw()) - - if assertion is not None: - # Ensure that extra formatting doesn't trigger an assertion failure. - formatted_assertion = BinaryEncoder.decode(BinaryEncoder.encode(assertion)) - - assert result == formatted_assertion, ( - 'Device in unknown state; expect general failure. ' - 'Asserted: {0}; observed: {1}.'.format(assertion, result)) - - return result - - -name = 'Four channel voltage source' -implementation = ch4VoltageSource diff --git a/spacq/devices/iqc/ch6_voltage_source.py b/spacq/devices/iqc/ch6_voltage_source.py deleted file mode 100644 index 173e685..0000000 --- a/spacq/devices/iqc/ch6_voltage_source.py +++ /dev/null @@ -1,326 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -import numpy -import time - -from spacq.interface.resources import Resource -from spacq.interface.units import Quantity -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice, AbstractSubdevice -from ..tools import quantity_unwrapped, quantity_wrapped, BinaryEncoder - -""" -Custom voltage source - -Control the output voltages on all the ports. -""" - - -class Port(AbstractSubdevice): - """ - An output port on the voltage source. - """ - - # Since there is no way to determine whether calibration has completed, - # wait this long and hope for the best. - calibration_delay = 2 # s - - @staticmethod - def format_for_dac(msg): - """ - Perform some formatting to make the device happy: - flip all the bits in the message - pad messages until their length in bytes is a multiple of 4 - """ - - log.debug('Formatting for DAC: {0}'.format(msg)) - - msg_encoded = BinaryEncoder.encode(msg) - # Flip each byte separately. - msg_flipped = [chr(~ord(x) & 0xff) for x in msg_encoded] - - missing_bytes = (4 - len(msg_encoded) % 4) % 4 - - result = BinaryEncoder.decode(msg_flipped + ['\x00'] * missing_bytes) - - log.debug('Formatted for DAC (padded with {0} bytes): {1}'.format(missing_bytes, result)) - - return result - - def _setup(self): - AbstractSubdevice._setup(self) - - # These values are used to tune the input values according to empirical error. - self.gain = 1.0 - self.offset = 0.0 - - # Resources. - read_write = ['voltage'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['voltage'].units = 'V' - - @Synchronized() - def _connected(self): - AbstractSubdevice._connected(self) - - if self.do_apply_settings: - self.apply_settings(calibrate=False) - - def __init__(self, device, num, max_value, resolution=20, apply_settings=True, adaptive_filtering=True, calibrate_connected=False, - fast_settling=True, freq=100, *args, **kwargs): - """ - Initialize the output port. - - device: The ch6VoltageSource to which this Port belongs. - num: The index of this port. - resolution: How many bits the output value contains. - apply_settings: Whether to automatically apply all the settings. - min_value: Smallest value the port can produce. - max_value: Largest value the port can produce. - adaptive_filtering: Enable adaptive filtering. - calibrate_connected: Do not disconnect output while calibrating. - fast_settling: Enable fast settling. - freq: Clock rate in kHz. - """ - - AbstractSubdevice.__init__(self, device, *args, **kwargs) - - if resolution not in [16, 20]: - raise ValueError('Unsupported resolution: {0}'.format(resolution)) - - self.num = num - self.resolution = resolution - self.do_apply_settings = apply_settings - self.min_value = -max_value - self.max_value = max_value - self.adaptive_filtering = adaptive_filtering - self.calibrate_connected = calibrate_connected - self.fast_settling = fast_settling - self.freq = freq - - def calculate_voltage(self, voltage): - """ - Determine the value corresponding to the given voltage. - """ - - try: - voltage_adjusted = voltage * self.gain + self.offset - except TypeError: - raise ValueError('Voltage must be a number. Given: {0}'.format(voltage)) - - if voltage_adjusted < self.min_value or voltage_adjusted > self.max_value: - raise ValueError('Adjusted voltage must be within [{0}, {1}]. ' - 'Given: {2}; adjusted to: {3}.'.format(self.min_value, - self.max_value, voltage, voltage_adjusted)) - - # Store the currentOutput for future reads - self.currentVoltage = voltage_adjusted - - max_converted = (1 << self.resolution) - 1 - value_span = self.max_value - self.min_value - - # Map [-min_value, max_value] onto [0x0, 0xff...] depending on the resolution. - # First negate the voltage, so that flipping the bits later will make it correct. - return int(round(float(-voltage_adjusted + self.max_value) / value_span * max_converted, 0)) - - @Synchronized() - def write_to_dac(self, message): - """ - Write a message to the DAC of the port. - - Voodoo programming follows, thanks to: - NI's lack of support for anything non-Windows in this case - my lack of time & desire to properly reverse engineer the ni845x DLL - - If the conversation does not go according to plan, bails out with an AssertionError! - """ - - message_length = BinaryEncoder.length(message) - - if message_length > 4: - raise ValueError('Message is longer than 4 bytes: {0}'.format(message)) - - message_formatted = self.format_for_dac(message) - - # The reply always comes back with as many bits set to 1 as were sent. - expected_reply = self.format_for_dac('00' * message_length) - - # Lots of assertions so we can bail ASAP to avoid crashing anything. - # Note[Kyle Willick]: These Hex strings come from using NI-Spy while communicating with the NI-8451 labview VIs - self.device.ask_encoded('0000 000c 0008 0100 0000 0000', - '0000 001c 0018 0100 0000 0002 0200 1000 0100 c001 0100 c000 0002 0000') - log.debug('Ask: 0000 000c 0008 0100 0000 0000 \n Receive: 0000 001c 0018 0100 0000 0002 0200 1000 0100 c001 0100 c000 0002 0000') - self.device.ask_encoded('0000 0014 0010 0110 0260 0000 00 {0:02x} {1:04x} 0700 0000'.format(self.num, self.freq), - '0000 000c 0008 0100 0000 0002') - log.debug('Ask: 0000 0014 0010 0110 0260 0000 00 {0:02x} {1:04x} 0700 0000 \n Receive: 0000 000c 0008 0100 0000 0002'.format(self.num, self.freq)) - self.device.ask_encoded('0000 0014 0010 0111 0260 0000 0003 {0:02x} 00 {1}'.format(message_length,message_formatted), - '0000 0014 0010 0100 0000 0002 {0:02x} 00 0000 {1}'.format(message_length, expected_reply)) - log.debug('Ask: 0000 0014 0010 0111 0260 0000 0003 {0:02x} 00 {1} \n Receive: 0000 0014 0010 0100 0000 0002 {2:02x} 00 0000 {3}'.format(message_length,message_formatted,message_length, expected_reply)) - - def apply_settings(self, calibrate=False): - """ - Apply the settings for the DAC on this port. - - calibrate: Run self-calibration on this port as well. - - Note: If self-calibrating, it is essential to wait the calibration_delay after this method returns. - """ - - flags = ((not self.adaptive_filtering) << 15 | - self.calibrate_connected << 14 | - (not self.fast_settling) << 4) - - if calibrate: - flags |= 0b01 - - # Write 16 bits to the top of the DIR: 0010 0100 xx10 0000 101x 00xx - self.write_to_dac('24 {0:04x}'.format(0x20a0 | flags)) - - @property - @quantity_wrapped('V') - def voltage(self): - return self.currentVoltage - - @voltage.setter - @quantity_unwrapped('V') - def voltage(self, value): - """ - Set the voltage on this port, as a quantity in V. - """ - - # Left-align the bits within the value: - # 20-bit: VVVV VVVV VVVV VVVV VVVV xxxx - # 16-bit: VVVV VVVV VVVV VVVV xxxx xxxx - # where the 'x's are don't-cares, so we just set them to 0 by shifting. - resulting_voltage = self.calculate_voltage(value) << (20 - self.resolution) - - # Write 24 bits to the top of the DIR: 0100 0000 xxxx xxxx xxxx xxxx xxxx xxxx - self.write_to_dac('40 {0:05x} f'.format(resulting_voltage)) - - @Synchronized() - def autotune(self, voltage_resource, min_value=None, max_value=None, final_value=0, set_result=True): - """ - Take some measured data and solve for the gain and offset. - - voltage_resource: A resource which provides the realtime measured data for this port. - min_value: Smallest value to take into account. - max_value: Largest value to take into account. - final_value: Value to set port to after all measurements are taken. - set_result: Whether to apply the resulting gain and offset. - """ - - self.device.status.append('Autotuning port {0}'.format(self.num)) - - try: - if min_value is None: - min_value = self.min_value - if max_value is None: - max_value = self.max_value - - # Test with raw values. - old_gain, old_offset = self.gain, self.offset - self.gain, self.offset = 1, 0 - - if max_value < min_value: - raise ValueError('{0} > {1}'.format(min_value, max_value)) - elif max_value == min_value: - num_points = 1 - else: - num_points = 21 - - # Obtain data. - real = numpy.linspace(min_value, max_value, num_points) - measured = [] - - for x in real: - self.voltage = Quantity(x, 'V') - time.sleep(0.2) - measured.append(voltage_resource.value.value) - - # Solve. - A = numpy.vstack([measured, numpy.ones(len(measured))]).T - gain, offset = numpy.linalg.lstsq(A, real)[0] - - if set_result: - self.gain, self.offset = gain, offset - else: - self.gain, self.offset = old_gain, old_offset - - # Set the voltage after the gain and offset, so that it is potentially more correct. - self.voltage = Quantity(final_value, 'V') - - return (gain, offset) - finally: - self.device.status.pop() - - -class ch6VoltageSource(AbstractDevice): - """ - Interface for the custom voltage source. - - It uses several TI DAC1220 chips and an NI USB-8451 to interface with them over SPI. - """ - - @property - def _gui_setup(self): - try: - from .gui.ch6_voltage_source import ch6VoltageSourceSettingsDialog - - return ch6VoltageSourceSettingsDialog - except ImportError as e: - log.debug('Could not load GUI setup for device "{0}": {1}'.format(self.name, str(e))) - - return None - - def _setup(self): - AbstractDevice._setup(self) - - self.ports = [] - for num in xrange(6): - if num < 4: - port = Port(self, num, 5, **self.port_settings) # for v0 to v3, max Voltage is 5 - else: - port = Port(self, num, 10, **self.port_settings) # for v4 and v5, max Voltage is 10 - self.ports.append(port) - self.subdevices['port{0:02}'.format(num)] = port - - def __init__(self, port_settings=None, *args, **kwargs): - """ - Initialize the voltage source and all its ports. - - port_settings: A dictionary of values to give to each port upon creation. - """ - - if port_settings is None: - self.port_settings = {} - else: - self.port_settings = port_settings - - AbstractDevice.__init__(self, *args, **kwargs) - - @Synchronized() - def ask_encoded(self, msg, assertion=None): - """ - Encode and write the message; then read and decode the answer. - """ - - self.write(BinaryEncoder.encode(msg)) - result = BinaryEncoder.decode(self.read_raw()) - - if assertion is not None: - # Ensure that extra formatting doesn't trigger an assertion failure. - formatted_assertion = BinaryEncoder.decode(BinaryEncoder.encode(assertion)) - - assert result == formatted_assertion, ( - 'Device in unknown state; expect general failure. ' - 'Asserted: {0}; observed: {1}.'.format(assertion, result)) - - return result - - -name = 'Six channel voltage source' -implementation = ch6VoltageSource diff --git a/spacq/devices/iqc/gui/__init__.py b/spacq/devices/iqc/gui/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/iqc/gui/ch4_voltage_source.py b/spacq/devices/iqc/gui/ch4_voltage_source.py deleted file mode 100644 index bcef972..0000000 --- a/spacq/devices/iqc/gui/ch4_voltage_source.py +++ /dev/null @@ -1,360 +0,0 @@ -from functools import partial -from pubsub import pub -from threading import Thread -from time import sleep -import wx -from wx.lib.agw.floatspin import FloatSpin - -from spacq.gui.tool.box import load_csv, save_csv, Dialog, MessageDialog -from spacq.interface.units import Quantity - -""" -Configuration for a ch4VoltageSource. -""" - - -class ch4VoltageSourceTunerDialog(Dialog): - """ - A dialog for tuning a voltage source port. - """ - - def __init__(self, parent, global_store, ok_callback, port, *args, **kwargs): - Dialog.__init__(self, parent, title='Port {0} tuning'.format(port.num)) - - self.global_store = global_store - self.ok_callback = ok_callback - self.port = port - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Self-calibration. - calibration_static_box = wx.StaticBox(self, label='DAC self-calibration') - calibration_box = wx.StaticBoxSizer(calibration_static_box, wx.VERTICAL) - dialog_box.Add(calibration_box, flag=wx.EXPAND|wx.ALL, border=5) - - self.calibrate_button = wx.Button(self, label='Self-calibrate') - self.Bind(wx.EVT_BUTTON, self.OnCalibrate, self.calibrate_button) - calibration_box.Add(self.calibrate_button, flag=wx.EXPAND) - - ## Tuning. - tuning_static_box = wx.StaticBox(self, label='Tuning') - tuning_box = wx.StaticBoxSizer(tuning_static_box, wx.VERTICAL) - dialog_box.Add(tuning_box, flag=wx.EXPAND) - - ### Autotune. - autotuning_static_box = wx.StaticBox(self, label='Autotuning') - autotuning_box = wx.StaticBoxSizer(autotuning_static_box, wx.VERTICAL) - tuning_box.Add(autotuning_box, flag=wx.EXPAND|wx.ALL, border=5) - - autotuning_sizer = wx.FlexGridSizer(rows=3, cols=2, hgap=5) - autotuning_box.Add(autotuning_sizer, flag=wx.CENTER) - - autotuning_sizer.Add(wx.StaticText(self, label='Resource name:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.resource_name_input = wx.TextCtrl(self, size=(300,-1)) - autotuning_sizer.Add(self.resource_name_input) - - autotuning_sizer.Add(wx.StaticText(self, label='Max:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.automax_input = FloatSpin(self, value=1, min_val=-10, max_val=10, increment=1, - digits=5) - autotuning_sizer.Add(self.automax_input) - - autotuning_sizer.Add(wx.StaticText(self, label='Min:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.automin_input = FloatSpin(self, value=-1, min_val=-10, max_val=10, increment=1, - digits=5) - autotuning_sizer.Add(self.automin_input) - - self.autotune_button = wx.Button(self, label='Autotune') - self.Bind(wx.EVT_BUTTON, self.OnAutotune, self.autotune_button) - autotuning_box.Add(self.autotune_button, flag=wx.EXPAND) - - ### Manual tune. - tuning_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - tuning_box.Add(tuning_sizer, flag=wx.CENTER) - - tuning_sizer.Add(wx.StaticText(self, label='Gain:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.gain_input = FloatSpin(self, value=0, min_val=-1e6, max_val=1e6, increment=1, - digits=5) - tuning_sizer.Add(self.gain_input) - - tuning_sizer.Add(wx.StaticText(self, label='Offset:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.offset_input = FloatSpin(self, value=0, min_val=-1e6, max_val=1e6, increment=1, - digits=5) - tuning_sizer.Add(self.offset_input) - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def autotune(self, resource): - gain, offset = self.port.autotune(resource, set_result=False, - min_value=self.automin_input.GetValue(), - max_value=self.automax_input.GetValue()) - - wx.CallAfter(self.gain_input.SetValue, gain) - wx.CallAfter(self.offset_input.SetValue, offset) - wx.CallAfter(self.autotune_button.Enable) - - def self_calbrate(self): - self.port.apply_settings(calibrate=True) - - sleep(self.port.calibration_delay) - wx.CallAfter(self.calibrate_button.Enable) - - def SetValue(self, gain, offset): - self.gain_input.SetValue(gain) - self.offset_input.SetValue(offset) - - def GetValue(self): - return (self.gain_input.GetValue(), self.offset_input.GetValue()) - - def OnAutotune(self, evt=None): - name = self.resource_name_input.Value - - if not name: - MessageDialog(self, 'No resource provided').Show() - return - - try: - resource = self.global_store.resources[name] - except KeyError: - MessageDialog(self, name, 'Missing resource').Show() - return - - if not resource.readable: - MessageDialog(self, name, 'Unreadable resource').Show() - return - - self.autotune_button.Disable() - - thr = Thread(target=self.autotune, args=(resource,)) - thr.daemon = True - thr.start() - - def OnCalibrate(self, evt=None): - self.calibrate_button.Disable() - - thr = Thread(target=self.self_calbrate) - thr.daemon = True - thr.start() - - def OnOk(self, evt=None): - self.ok_callback(self) - - self.Destroy() - - -class ch4VoltageSourceSettingsPanel(wx.Panel): - """ - All the settings for a voltage source. - """ - - def __init__(self, parent, global_store, vsrc, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self.vsrc = vsrc - - self.port_value_inputs = [] - self.port_buttons = [] - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Ports. - ports_box = wx.FlexGridSizer(rows=3, cols=2) - panel_box.Add(ports_box) - - for port in xrange(4): - port_static_box = wx.StaticBox(self, label='Port {0} '.format(port)) - port_box = wx.StaticBoxSizer(port_static_box, wx.HORIZONTAL) - ports_box.Add(port_box, flag=wx.ALL, border=5) - - spin = FloatSpin(self, value=0, min_val=-10, max_val=10, increment=1, digits=6) - self.port_value_inputs.append(spin) - port_box.Add(spin) - - port_box.Add(wx.StaticText(self, label='V')) - - set_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) - set_button.Bind(wx.EVT_BUTTON, partial(self.OnSetVoltage, port)) - port_box.Add(set_button) - - tune_button = wx.Button(self, label='Tune...', style=wx.BU_EXACTFIT) - tune_button.Bind(wx.EVT_BUTTON, partial(self.OnTune, port)) - port_box.Add(tune_button) - - self.port_buttons.append((set_button, tune_button)) - - ## All ports. - button_static_box = wx.StaticBox(self, label='All ports') - button_box = wx.StaticBoxSizer(button_static_box, wx.HORIZONTAL) - panel_box.Add(button_box, flag=wx.CENTER) - - ### Zero. - zero_all_button = wx.Button(self, label='Zero') - self.Bind(wx.EVT_BUTTON, self.OnZeroAll, zero_all_button) - button_box.Add(zero_all_button, flag=wx.CENTER) - - ### Self-calibrate. - self.calibrate_all_button = wx.Button(self, label='Self-calibrate') - self.Bind(wx.EVT_BUTTON, self.OnCalibrateAll, self.calibrate_all_button) - button_box.Add(self.calibrate_all_button, flag=wx.CENTER) - - ### Load tuning. - tuning_data_static_box = wx.StaticBox(self, label='Tuning data') - tuning_data_box = wx.StaticBoxSizer(tuning_data_static_box, wx.HORIZONTAL) - button_box.Add(tuning_data_box) - - #### Save. - tuning_data_save_button = wx.Button(self, label='Save...') - self.Bind(wx.EVT_BUTTON, self.OnSave, tuning_data_save_button) - tuning_data_box.Add(tuning_data_save_button) - - #### Load. - tuning_data_load_button = wx.Button(self, label='Load...') - self.Bind(wx.EVT_BUTTON, self.OnLoad, tuning_data_load_button) - tuning_data_box.Add(tuning_data_load_button) - - self.SetSizer(panel_box) - - def self_calbrate_all(self): - delay = 0 # s - - for port in self.vsrc.ports: - # Use the largest delay. - if port.calibration_delay > delay: - delay = port.calibration_delay - - port.apply_settings(calibrate=True) - - sleep(delay) - wx.CallAfter(self.calibrate_all_button.Enable) - - def zero_all(self): - for port in self.vsrc.ports: - port.voltage = Quantity(0.0, 'V') - - def OnSetVoltage(self, port_num, evt=None): - try: - self.vsrc.ports[port_num].voltage = Quantity(self.port_value_inputs[port_num].GetValue(), 'V') - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - - def OnTune(self, port_num, evt=None): - port = self.vsrc.ports[port_num] - - def ok_callback(dlg): - port.gain, port.offset = dlg.GetValue() - - dlg = ch4VoltageSourceTunerDialog(self, self.global_store, ok_callback, port) - dlg.SetValue(port.gain, port.offset) - dlg.Show() - - def OnCalibrateAll(self, evt=None): - self.calibrate_all_button.Disable() - - thr = Thread(target=self.self_calbrate_all) - thr.daemon = True - thr.start() - - def OnZeroAll(self, evt=None): - thr = Thread(target=self.zero_all) - thr.daemon = True - thr.start() - - def OnSave(self, evt=None): - values = [[port.gain, port.offset] for port in self.vsrc.ports] - - try: - save_csv(self, values) - except IOError as e: - MessageDialog(self, str(e), 'Save error').Show() - return - - def OnLoad(self, evt=None): - try: - result = load_csv(self) - - if result is None: - return - - has_header, values, _ = result - - if has_header: - port_values = values[1:] - else: - port_values = values - - if len(port_values) != len(self.vsrc.ports): - raise ValueError('Invalid number of ports.') - - for i, port_value in enumerate(port_values): - if len(port_value) != 2: - raise ValueError('Invalid number of settings for port {0}.'.format(i)) - - try: - float(port_value[0]) - float(port_value[1]) - except TypeError: - raise ValueError('Not a number for port {0}.'.format(i)) - except (IOError, ValueError) as e: - MessageDialog(self, str(e), 'Load error').Show() - return - - for port, values in zip(self.vsrc.ports, port_values): - port.gain = float(values[0]) - port.offset = float(values[1]) - - -class ch4VoltageSourceSettingsDialog(Dialog): - """ - A wrapper for ch4VoltageSourceSettingsPanel. - """ - - def __init__(self, parent, global_store, vsrc_name, *args, **kwargs): - # If the device doesn't exist, give up. - try: - vsrc = global_store.devices[vsrc_name].device - except (KeyError, AttributeError): - self.Destroy() - - return - - Dialog.__init__(self, parent, title='Four channel voltage source settings', *args, **kwargs) - - self.vsrc_name = vsrc_name - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Settings panel. - self.panel = ch4VoltageSourceSettingsPanel(self, global_store, vsrc) - dialog_box.Add(self.panel) - - self.SetSizerAndFit(dialog_box) - - # Subscriptions. - pub.subscribe(self.msg_device, 'device.added') - pub.subscribe(self.msg_device, 'device.removed') - - def msg_device(self, name, value=None): - if name == self.vsrc_name: - # Device has changed, so we can't trust it anymore. - self.Destroy() - - return diff --git a/spacq/devices/iqc/gui/ch6_voltage_source.py b/spacq/devices/iqc/gui/ch6_voltage_source.py deleted file mode 100644 index 6614263..0000000 --- a/spacq/devices/iqc/gui/ch6_voltage_source.py +++ /dev/null @@ -1,363 +0,0 @@ -from functools import partial -from pubsub import pub -from threading import Thread -from time import sleep -import wx -from wx.lib.agw.floatspin import FloatSpin - -from spacq.gui.tool.box import load_csv, save_csv, Dialog, MessageDialog -from spacq.interface.units import Quantity - -""" -Configuration for a ch6VoltageSource. -""" - - -class ch6VoltageSourceTunerDialog(Dialog): - """ - A dialog for tuning a voltage source port. - """ - - def __init__(self, parent, global_store, ok_callback, port, *args, **kwargs): - Dialog.__init__(self, parent, title='Port {0} tuning'.format(port.num)) - - self.global_store = global_store - self.ok_callback = ok_callback - self.port = port - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Self-calibration. - calibration_static_box = wx.StaticBox(self, label='DAC self-calibration') - calibration_box = wx.StaticBoxSizer(calibration_static_box, wx.VERTICAL) - dialog_box.Add(calibration_box, flag=wx.EXPAND|wx.ALL, border=5) - - self.calibrate_button = wx.Button(self, label='Self-calibrate') - self.Bind(wx.EVT_BUTTON, self.OnCalibrate, self.calibrate_button) - calibration_box.Add(self.calibrate_button, flag=wx.EXPAND) - - ## Tuning. - tuning_static_box = wx.StaticBox(self, label='Tuning') - tuning_box = wx.StaticBoxSizer(tuning_static_box, wx.VERTICAL) - dialog_box.Add(tuning_box, flag=wx.EXPAND) - - ### Autotune. - autotuning_static_box = wx.StaticBox(self, label='Autotuning') - autotuning_box = wx.StaticBoxSizer(autotuning_static_box, wx.VERTICAL) - tuning_box.Add(autotuning_box, flag=wx.EXPAND|wx.ALL, border=5) - - autotuning_sizer = wx.FlexGridSizer(rows=3, cols=2, hgap=5) - autotuning_box.Add(autotuning_sizer, flag=wx.CENTER) - - autotuning_sizer.Add(wx.StaticText(self, label='Resource name:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.resource_name_input = wx.TextCtrl(self, size=(300,-1)) - autotuning_sizer.Add(self.resource_name_input) - - autotuning_sizer.Add(wx.StaticText(self, label='Max:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.automax_input = FloatSpin(self, value=1, min_val=-10, max_val=10, increment=1, - digits=5) - autotuning_sizer.Add(self.automax_input) - - autotuning_sizer.Add(wx.StaticText(self, label='Min:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.automin_input = FloatSpin(self, value=-1, min_val=-10, max_val=10, increment=1, - digits=5) - autotuning_sizer.Add(self.automin_input) - - self.autotune_button = wx.Button(self, label='Autotune') - self.Bind(wx.EVT_BUTTON, self.OnAutotune, self.autotune_button) - autotuning_box.Add(self.autotune_button, flag=wx.EXPAND) - - ### Manual tune. - tuning_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - tuning_box.Add(tuning_sizer, flag=wx.CENTER) - - tuning_sizer.Add(wx.StaticText(self, label='Gain:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.gain_input = FloatSpin(self, value=0, min_val=-1e6, max_val=1e6, increment=1, - digits=5) - tuning_sizer.Add(self.gain_input) - - tuning_sizer.Add(wx.StaticText(self, label='Offset:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.offset_input = FloatSpin(self, value=0, min_val=-1e6, max_val=1e6, increment=1, - digits=5) - tuning_sizer.Add(self.offset_input) - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def autotune(self, resource): - gain, offset = self.port.autotune(resource, set_result=False, - min_value=self.automin_input.GetValue(), - max_value=self.automax_input.GetValue()) - - wx.CallAfter(self.gain_input.SetValue, gain) - wx.CallAfter(self.offset_input.SetValue, offset) - wx.CallAfter(self.autotune_button.Enable) - - def self_calbrate(self): - self.port.apply_settings(calibrate=True) - - sleep(self.port.calibration_delay) - wx.CallAfter(self.calibrate_button.Enable) - - def SetValue(self, gain, offset): - self.gain_input.SetValue(gain) - self.offset_input.SetValue(offset) - - def GetValue(self): - return (self.gain_input.GetValue(), self.offset_input.GetValue()) - - def OnAutotune(self, evt=None): - name = self.resource_name_input.Value - - if not name: - MessageDialog(self, 'No resource provided').Show() - return - - try: - resource = self.global_store.resources[name] - except KeyError: - MessageDialog(self, name, 'Missing resource').Show() - return - - if not resource.readable: - MessageDialog(self, name, 'Unreadable resource').Show() - return - - self.autotune_button.Disable() - - thr = Thread(target=self.autotune, args=(resource,)) - thr.daemon = True - thr.start() - - def OnCalibrate(self, evt=None): - self.calibrate_button.Disable() - - thr = Thread(target=self.self_calbrate) - thr.daemon = True - thr.start() - - def OnOk(self, evt=None): - self.ok_callback(self) - - self.Destroy() - - -class ch6VoltageSourceSettingsPanel(wx.Panel): - """ - All the settings for a voltage source. - """ - - def __init__(self, parent, global_store, vsrc, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self.vsrc = vsrc - - self.port_value_inputs = [] - self.port_buttons = [] - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Ports. - ports_box = wx.FlexGridSizer(rows=3, cols=2) - panel_box.Add(ports_box) - - for port in xrange(6): - port_static_box = wx.StaticBox(self, label='Port {0} '.format(port)) - port_box = wx.StaticBoxSizer(port_static_box, wx.HORIZONTAL) - ports_box.Add(port_box, flag=wx.ALL, border=5) - - if port < 4: - spin = FloatSpin(self, value=0, min_val=-5, max_val=5, increment=1, digits=6) - else: - spin = FloatSpin(self, value=0, min_val=-10, max_val=10, increment=1, digits=6) - self.port_value_inputs.append(spin) - port_box.Add(spin) - - port_box.Add(wx.StaticText(self, label='V')) - - set_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) - set_button.Bind(wx.EVT_BUTTON, partial(self.OnSetVoltage, port)) - port_box.Add(set_button) - - tune_button = wx.Button(self, label='Tune...', style=wx.BU_EXACTFIT) - tune_button.Bind(wx.EVT_BUTTON, partial(self.OnTune, port)) - port_box.Add(tune_button) - - self.port_buttons.append((set_button, tune_button)) - - ## All ports. - button_static_box = wx.StaticBox(self, label='All ports') - button_box = wx.StaticBoxSizer(button_static_box, wx.HORIZONTAL) - panel_box.Add(button_box, flag=wx.CENTER) - - ### Zero. - zero_all_button = wx.Button(self, label='Zero') - self.Bind(wx.EVT_BUTTON, self.OnZeroAll, zero_all_button) - button_box.Add(zero_all_button, flag=wx.CENTER) - - ### Self-calibrate. - self.calibrate_all_button = wx.Button(self, label='Self-calibrate') - self.Bind(wx.EVT_BUTTON, self.OnCalibrateAll, self.calibrate_all_button) - button_box.Add(self.calibrate_all_button, flag=wx.CENTER) - - ### Load tuning. - tuning_data_static_box = wx.StaticBox(self, label='Tuning data') - tuning_data_box = wx.StaticBoxSizer(tuning_data_static_box, wx.HORIZONTAL) - button_box.Add(tuning_data_box) - - #### Save. - tuning_data_save_button = wx.Button(self, label='Save...') - self.Bind(wx.EVT_BUTTON, self.OnSave, tuning_data_save_button) - tuning_data_box.Add(tuning_data_save_button) - - #### Load. - tuning_data_load_button = wx.Button(self, label='Load...') - self.Bind(wx.EVT_BUTTON, self.OnLoad, tuning_data_load_button) - tuning_data_box.Add(tuning_data_load_button) - - self.SetSizer(panel_box) - - def self_calbrate_all(self): - delay = 0 # s - - for port in self.vsrc.ports: - # Use the largest delay. - if port.calibration_delay > delay: - delay = port.calibration_delay - - port.apply_settings(calibrate=True) - - sleep(delay) - wx.CallAfter(self.calibrate_all_button.Enable) - - def zero_all(self): - for port in self.vsrc.ports: - port.voltage = Quantity(0.0, 'V') - - def OnSetVoltage(self, port_num, evt=None): - try: - self.vsrc.ports[port_num].voltage = Quantity(self.port_value_inputs[port_num].GetValue(), 'V') - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - - def OnTune(self, port_num, evt=None): - port = self.vsrc.ports[port_num] - - def ok_callback(dlg): - port.gain, port.offset = dlg.GetValue() - - dlg = ch6VoltageSourceTunerDialog(self, self.global_store, ok_callback, port) - dlg.SetValue(port.gain, port.offset) - dlg.Show() - - def OnCalibrateAll(self, evt=None): - self.calibrate_all_button.Disable() - - thr = Thread(target=self.self_calbrate_all) - thr.daemon = True - thr.start() - - def OnZeroAll(self, evt=None): - thr = Thread(target=self.zero_all) - thr.daemon = True - thr.start() - - def OnSave(self, evt=None): - values = [[port.gain, port.offset] for port in self.vsrc.ports] - - try: - save_csv(self, values) - except IOError as e: - MessageDialog(self, str(e), 'Save error').Show() - return - - def OnLoad(self, evt=None): - try: - result = load_csv(self) - - if result is None: - return - - has_header, values, _ = result - - if has_header: - port_values = values[1:] - else: - port_values = values - - if len(port_values) != len(self.vsrc.ports): - raise ValueError('Invalid number of ports.') - - for i, port_value in enumerate(port_values): - if len(port_value) != 2: - raise ValueError('Invalid number of settings for port {0}.'.format(i)) - - try: - float(port_value[0]) - float(port_value[1]) - except TypeError: - raise ValueError('Not a number for port {0}.'.format(i)) - except (IOError, ValueError) as e: - MessageDialog(self, str(e), 'Load error').Show() - return - - for port, values in zip(self.vsrc.ports, port_values): - port.gain = float(values[0]) - port.offset = float(values[1]) - - -class ch6VoltageSourceSettingsDialog(Dialog): - """ - A wrapper for ch6VoltageSourceSettingsPanel. - """ - - def __init__(self, parent, global_store, vsrc_name, *args, **kwargs): - # If the device doesn't exist, give up. - try: - vsrc = global_store.devices[vsrc_name].device - except (KeyError, AttributeError): - self.Destroy() - - return - - Dialog.__init__(self, parent, title='Six channel voltage source settings', *args, **kwargs) - - self.vsrc_name = vsrc_name - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Settings panel. - self.panel = ch6VoltageSourceSettingsPanel(self, global_store, vsrc) - dialog_box.Add(self.panel) - - self.SetSizerAndFit(dialog_box) - - # Subscriptions. - pub.subscribe(self.msg_device, 'device.added') - pub.subscribe(self.msg_device, 'device.removed') - - def msg_device(self, name, value=None): - if name == self.vsrc_name: - # Device has changed, so we can't trust it anymore. - self.Destroy() - - return diff --git a/spacq/devices/iqc/gui/voltage_source.py b/spacq/devices/iqc/gui/voltage_source.py deleted file mode 100644 index 4857e17..0000000 --- a/spacq/devices/iqc/gui/voltage_source.py +++ /dev/null @@ -1,365 +0,0 @@ -from functools import partial -from pubsub import pub -from threading import Thread -from time import sleep -import wx -from wx.lib.agw.floatspin import FloatSpin - -from spacq.gui.tool.box import load_csv, save_csv, Dialog, MessageDialog -from spacq.interface.units import Quantity - -""" -Configuration for a VoltageSource. -""" - - -class VoltageSourceTunerDialog(Dialog): - """ - A dialog for tuning a voltage source port. - """ - - def __init__(self, parent, global_store, ok_callback, port, *args, **kwargs): - Dialog.__init__(self, parent, title='Port {0} tuning'.format(port.num)) - - self.global_store = global_store - self.ok_callback = ok_callback - self.port = port - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Self-calibration. - calibration_static_box = wx.StaticBox(self, label='DAC self-calibration') - calibration_box = wx.StaticBoxSizer(calibration_static_box, wx.VERTICAL) - dialog_box.Add(calibration_box, flag=wx.EXPAND|wx.ALL, border=5) - - self.calibrate_button = wx.Button(self, label='Self-calibrate') - self.Bind(wx.EVT_BUTTON, self.OnCalibrate, self.calibrate_button) - calibration_box.Add(self.calibrate_button, flag=wx.EXPAND) - - ## Tuning. - tuning_static_box = wx.StaticBox(self, label='Tuning') - tuning_box = wx.StaticBoxSizer(tuning_static_box, wx.VERTICAL) - dialog_box.Add(tuning_box, flag=wx.EXPAND) - - ### Autotune. - autotuning_static_box = wx.StaticBox(self, label='Autotuning') - autotuning_box = wx.StaticBoxSizer(autotuning_static_box, wx.VERTICAL) - tuning_box.Add(autotuning_box, flag=wx.EXPAND|wx.ALL, border=5) - - autotuning_sizer = wx.FlexGridSizer(rows=3, cols=2, hgap=5) - autotuning_box.Add(autotuning_sizer, flag=wx.CENTER) - - autotuning_sizer.Add(wx.StaticText(self, label='Resource name:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.resource_name_input = wx.TextCtrl(self, size=(300,-1)) - autotuning_sizer.Add(self.resource_name_input) - - autotuning_sizer.Add(wx.StaticText(self, label='Max:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.automax_input = FloatSpin(self, value=1, min_val=-10, max_val=10, increment=1, - digits=5) - autotuning_sizer.Add(self.automax_input) - - autotuning_sizer.Add(wx.StaticText(self, label='Min:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.automin_input = FloatSpin(self, value=-1, min_val=-10, max_val=10, increment=1, - digits=5) - autotuning_sizer.Add(self.automin_input) - - self.autotune_button = wx.Button(self, label='Autotune') - self.Bind(wx.EVT_BUTTON, self.OnAutotune, self.autotune_button) - autotuning_box.Add(self.autotune_button, flag=wx.EXPAND) - - ### Manual tune. - tuning_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - tuning_box.Add(tuning_sizer, flag=wx.CENTER) - - tuning_sizer.Add(wx.StaticText(self, label='Gain:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.gain_input = FloatSpin(self, value=0, min_val=-1e6, max_val=1e6, increment=1, - digits=5) - tuning_sizer.Add(self.gain_input) - - tuning_sizer.Add(wx.StaticText(self, label='Offset:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.offset_input = FloatSpin(self, value=0, min_val=-1e6, max_val=1e6, increment=1, - digits=5) - tuning_sizer.Add(self.offset_input) - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def autotune(self, resource): - gain, offset = self.port.autotune(resource, set_result=False, - min_value=self.automin_input.GetValue(), - max_value=self.automax_input.GetValue()) - - wx.CallAfter(self.gain_input.SetValue, gain) - wx.CallAfter(self.offset_input.SetValue, offset) - wx.CallAfter(self.autotune_button.Enable) - - def self_calbrate(self): - self.port.apply_settings(calibrate=True) - - sleep(self.port.calibration_delay) - wx.CallAfter(self.calibrate_button.Enable) - - def SetValue(self, gain, offset): - self.gain_input.SetValue(gain) - self.offset_input.SetValue(offset) - - def GetValue(self): - return (self.gain_input.GetValue(), self.offset_input.GetValue()) - - def OnAutotune(self, evt=None): - name = self.resource_name_input.Value - - if not name: - MessageDialog(self, 'No resource provided').Show() - return - - try: - resource = self.global_store.resources[name] - except KeyError: - MessageDialog(self, name, 'Missing resource').Show() - return - - if not resource.readable: - MessageDialog(self, name, 'Unreadable resource').Show() - return - - self.autotune_button.Disable() - - thr = Thread(target=self.autotune, args=(resource,)) - thr.daemon = True - thr.start() - - def OnCalibrate(self, evt=None): - self.calibrate_button.Disable() - - thr = Thread(target=self.self_calbrate) - thr.daemon = True - thr.start() - - def OnOk(self, evt=None): - self.ok_callback(self) - - self.Destroy() - - -class VoltageSourceSettingsPanel(wx.Panel): - """ - All the settings for a voltage source. - """ - - def __init__(self, parent, global_store, vsrc, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self.vsrc = vsrc - - self.port_value_inputs = [] - self.port_buttons = [] - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Ports. - ports_box = wx.FlexGridSizer(rows=8, cols=2) - panel_box.Add(ports_box) - - for port in xrange(16): - port_static_box = wx.StaticBox(self, label='Port {0} '.format(port)) - port_box = wx.StaticBoxSizer(port_static_box, wx.HORIZONTAL) - ports_box.Add(port_box, flag=wx.ALL, border=5) - - if port < 6: - spin = FloatSpin(self, value=0, min_val=-1, max_val=1, increment=1, digits=6) - elif port < 12: - spin = FloatSpin(self, value=0, min_val=-2.5, max_val=2.5, increment=1, digits=6) - else: - spin = FloatSpin(self, value=0, min_val=-10, max_val=10, increment=1, digits=6) - self.port_value_inputs.append(spin) - port_box.Add(spin) - - port_box.Add(wx.StaticText(self, label='V')) - - set_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) - set_button.Bind(wx.EVT_BUTTON, partial(self.OnSetVoltage, port)) - port_box.Add(set_button) - - tune_button = wx.Button(self, label='Tune...', style=wx.BU_EXACTFIT) - tune_button.Bind(wx.EVT_BUTTON, partial(self.OnTune, port)) - port_box.Add(tune_button) - - self.port_buttons.append((set_button, tune_button)) - - ## All ports. - button_static_box = wx.StaticBox(self, label='All ports') - button_box = wx.StaticBoxSizer(button_static_box, wx.HORIZONTAL) - panel_box.Add(button_box, flag=wx.CENTER) - - ### Zero. - zero_all_button = wx.Button(self, label='Zero') - self.Bind(wx.EVT_BUTTON, self.OnZeroAll, zero_all_button) - button_box.Add(zero_all_button, flag=wx.CENTER) - - ### Self-calibrate. - self.calibrate_all_button = wx.Button(self, label='Self-calibrate') - self.Bind(wx.EVT_BUTTON, self.OnCalibrateAll, self.calibrate_all_button) - button_box.Add(self.calibrate_all_button, flag=wx.CENTER) - - ### Load tuning. - tuning_data_static_box = wx.StaticBox(self, label='Tuning data') - tuning_data_box = wx.StaticBoxSizer(tuning_data_static_box, wx.HORIZONTAL) - button_box.Add(tuning_data_box) - - #### Save. - tuning_data_save_button = wx.Button(self, label='Save...') - self.Bind(wx.EVT_BUTTON, self.OnSave, tuning_data_save_button) - tuning_data_box.Add(tuning_data_save_button) - - #### Load. - tuning_data_load_button = wx.Button(self, label='Load...') - self.Bind(wx.EVT_BUTTON, self.OnLoad, tuning_data_load_button) - tuning_data_box.Add(tuning_data_load_button) - - self.SetSizer(panel_box) - - def self_calbrate_all(self): - delay = 0 # s - - for port in self.vsrc.ports: - # Use the largest delay. - if port.calibration_delay > delay: - delay = port.calibration_delay - - port.apply_settings(calibrate=True) - - sleep(delay) - wx.CallAfter(self.calibrate_all_button.Enable) - - def zero_all(self): - for port in self.vsrc.ports: - port.voltage = Quantity(0.0, 'V') - - def OnSetVoltage(self, port_num, evt=None): - try: - self.vsrc.ports[port_num].voltage = Quantity(self.port_value_inputs[port_num].GetValue(), 'V') - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - - def OnTune(self, port_num, evt=None): - port = self.vsrc.ports[port_num] - - def ok_callback(dlg): - port.gain, port.offset = dlg.GetValue() - - dlg = VoltageSourceTunerDialog(self, self.global_store, ok_callback, port) - dlg.SetValue(port.gain, port.offset) - dlg.Show() - - def OnCalibrateAll(self, evt=None): - self.calibrate_all_button.Disable() - - thr = Thread(target=self.self_calbrate_all) - thr.daemon = True - thr.start() - - def OnZeroAll(self, evt=None): - thr = Thread(target=self.zero_all) - thr.daemon = True - thr.start() - - def OnSave(self, evt=None): - values = [[port.gain, port.offset] for port in self.vsrc.ports] - - try: - save_csv(self, values) - except IOError as e: - MessageDialog(self, str(e), 'Save error').Show() - return - - def OnLoad(self, evt=None): - try: - result = load_csv(self) - - if result is None: - return - - has_header, values, _ = result - - if has_header: - port_values = values[1:] - else: - port_values = values - - if len(port_values) != len(self.vsrc.ports): - raise ValueError('Invalid number of ports.') - - for i, port_value in enumerate(port_values): - if len(port_value) != 2: - raise ValueError('Invalid number of settings for port {0}.'.format(i)) - - try: - float(port_value[0]) - float(port_value[1]) - except TypeError: - raise ValueError('Not a number for port {0}.'.format(i)) - except (IOError, ValueError) as e: - MessageDialog(self, str(e), 'Load error').Show() - return - - for port, values in zip(self.vsrc.ports, port_values): - port.gain = float(values[0]) - port.offset = float(values[1]) - - -class VoltageSourceSettingsDialog(Dialog): - """ - A wrapper for VoltageSourceSettingsPanel. - """ - - def __init__(self, parent, global_store, vsrc_name, *args, **kwargs): - # If the device doesn't exist, give up. - try: - vsrc = global_store.devices[vsrc_name].device - except (KeyError, AttributeError): - self.Destroy() - - return - - Dialog.__init__(self, parent, title='Voltage source settings', *args, **kwargs) - - self.vsrc_name = vsrc_name - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Settings panel. - self.panel = VoltageSourceSettingsPanel(self, global_store, vsrc) - dialog_box.Add(self.panel) - - self.SetSizerAndFit(dialog_box) - - # Subscriptions. - pub.subscribe(self.msg_device, 'device.added') - pub.subscribe(self.msg_device, 'device.removed') - - def msg_device(self, name, value=None): - if name == self.vsrc_name: - # Device has changed, so we can't trust it anymore. - self.Destroy() - - return diff --git a/spacq/devices/iqc/mock/__init__.py b/spacq/devices/iqc/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/iqc/mock/mock_ch4_voltage_source.py b/spacq/devices/iqc/mock/mock_ch4_voltage_source.py deleted file mode 100644 index d939247..0000000 --- a/spacq/devices/iqc/mock/mock_ch4_voltage_source.py +++ /dev/null @@ -1,114 +0,0 @@ -import re - -from ...mock.mock_abstract_device import MockAbstractDevice -from ...tools import BinaryEncoder -from ..ch4_voltage_source import ch4VoltageSource - -""" -Mock Voltage Source - -Control a fake voltage source. -""" - - -class MockPort(object): - """ - Mock port. - """ - - def __init__(self): - self._voltage = 0 - - def set_voltage(self, voltage): - self._voltage = voltage - - voltage = property(fset=set_voltage) - - -class Mockch4VoltageSource(MockAbstractDevice, ch4VoltageSource): - """ - Mock interface for Tektronix AWG5014B AWG. - """ - - # Not sure what it means, but it comes up a lot. - standard_reply = '0000 000c 0008 0100 0000 0002' - - def __init__(self, *args, **kwargs): - """ - Pretend to connect to the voltage source. - """ - - self.port_settings = {} - - self.mocking = ch4VoltageSource - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['ports'] = [MockPort() for _ in xrange(6)] - - def write(self, message, result=None, done=False): - if done: - MockAbstractDevice.write(self, message, result, done) - - message = BinaryEncoder.decode(message) - - # These all elicit the same response. - uninteresting_messages = [ - '0000 0010 000c 0113 0280 0000 0000 ff01', - '0000 0010 000c 0112 0280 0000 00ff ff00', - '0000 0014 0010 0110 0260 0000 0000 0064 0700 0000', - ] - - if message in uninteresting_messages: - self.output = self.standard_reply - elif message == '0000 000c 0008 0100 0000 0000': - self.output = '0000 001c 0018 0100 0000 0002 0200 1000 0100 c001 0100 c000 0002 000o' - elif message.startswith('0000 0010 000c 0111 0280 0000 00ff '): - m = re.match('0000 0010 000c 0111 0280 0000 00ff ([0-9a-f]{2})00', message) - self.mock_state['port'] = int(m.group(1), 6) - - self.output = self.standard_reply - elif message.startswith('0000 0014 0010 0111 0260 0000 0003 '): - m = re.match('0000 0014 0010 0111 0260 0000 0003 ([0-9a-f]{2})00 ([0-9a-f ]{9})', message) - len = int(m.group(1), 6) - cmd = ''.join(['{0:08b}'.format(~ord(x) & 0xff) for x in BinaryEncoder.encode(m.group(2))[:len]]) - - # Destructure the command. - read = bool(int(cmd[0])) # Read on True, write on False. - num_bytes = 1 + int(cmd[1:3], 2) - register = int(cmd[4:8], 2) - value = int(cmd[8:8+8*num_bytes], 2) - - if not read: - if register >= 0 and register <= 2: - # Data input register. - value = ~value & (2 ** (8 * num_bytes) - 1) - # Assuming here that it's OK to overwrite the lower bits with 0. - value <<= 8 * (3 - register - num_bytes) - - self.mock_state['ports'][self.mock_state['port']].voltage = value - elif register >= 4 and register <= 5: - # Command register. - # Assuming here that it's OK to overwrite the lower bits with 0. - value <<= 8 * (6 - register - num_bytes) - value = '{0:016b}'.format(value) - - # Destructure the CMR values. - resolution = 20 if int(value[9]) else 16 # RES - self.mock_state['ports'][self.mock_state['port']].resolution = resolution - if int(value[10]): # CLR - self.mock_state['ports'][self.mock_state['port']].voltage = 0 - - # Reading never works. - answer = 'ff' * len + '00' * (4 - len) - self.output = '0000 0014 0010 0100 0000 0002 {0:02x}00 0000 {1}'.format(len, answer) - else: - self.output = None - - if self.output is not None: - self.output = BinaryEncoder.encode(self.output) - - -name = 'Four channel voltage source' -implementation = Mockch4VoltageSource diff --git a/spacq/devices/iqc/mock/mock_ch6_voltage_source.py b/spacq/devices/iqc/mock/mock_ch6_voltage_source.py deleted file mode 100644 index c9768fc..0000000 --- a/spacq/devices/iqc/mock/mock_ch6_voltage_source.py +++ /dev/null @@ -1,114 +0,0 @@ -import re - -from ...mock.mock_abstract_device import MockAbstractDevice -from ...tools import BinaryEncoder -from ..ch6_voltage_source import ch6VoltageSource - -""" -Mock Voltage Source - -Control a fake voltage source. -""" - - -class MockPort(object): - """ - Mock port. - """ - - def __init__(self): - self._voltage = 0 - - def set_voltage(self, voltage): - self._voltage = voltage - - voltage = property(fset=set_voltage) - - -class Mockch6VoltageSource(MockAbstractDevice, ch6VoltageSource): - """ - Mock interface for Tektronix AWG5014B AWG. - """ - - # Not sure what it means, but it comes up a lot. - standard_reply = '0000 000c 0008 0100 0000 0002' - - def __init__(self, *args, **kwargs): - """ - Pretend to connect to the voltage source. - """ - - self.port_settings = {} - - self.mocking = ch6VoltageSource - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['ports'] = [MockPort() for _ in xrange(6)] - - def write(self, message, result=None, done=False): - if done: - MockAbstractDevice.write(self, message, result, done) - - message = BinaryEncoder.decode(message) - - # These all elicit the same response. - uninteresting_messages = [ - '0000 0010 000c 0113 0280 0000 0000 ff01', - '0000 0010 000c 0112 0280 0000 00ff ff00', - '0000 0014 0010 0110 0260 0000 0000 0064 0700 0000', - ] - - if message in uninteresting_messages: - self.output = self.standard_reply - elif message == '0000 000c 0008 0100 0000 0000': - self.output = '0000 001c 0018 0100 0000 0002 0200 1000 0100 c001 0100 c000 0002 000o' - elif message.startswith('0000 0010 000c 0111 0280 0000 00ff '): - m = re.match('0000 0010 000c 0111 0280 0000 00ff ([0-9a-f]{2})00', message) - self.mock_state['port'] = int(m.group(1), 6) - - self.output = self.standard_reply - elif message.startswith('0000 0014 0010 0111 0260 0000 0003 '): - m = re.match('0000 0014 0010 0111 0260 0000 0003 ([0-9a-f]{2})00 ([0-9a-f ]{9})', message) - len = int(m.group(1), 6) - cmd = ''.join(['{0:08b}'.format(~ord(x) & 0xff) for x in BinaryEncoder.encode(m.group(2))[:len]]) - - # Destructure the command. - read = bool(int(cmd[0])) # Read on True, write on False. - num_bytes = 1 + int(cmd[1:3], 2) - register = int(cmd[4:8], 2) - value = int(cmd[8:8+8*num_bytes], 2) - - if not read: - if register >= 0 and register <= 2: - # Data input register. - value = ~value & (2 ** (8 * num_bytes) - 1) - # Assuming here that it's OK to overwrite the lower bits with 0. - value <<= 8 * (3 - register - num_bytes) - - self.mock_state['ports'][self.mock_state['port']].voltage = value - elif register >= 4 and register <= 5: - # Command register. - # Assuming here that it's OK to overwrite the lower bits with 0. - value <<= 8 * (6 - register - num_bytes) - value = '{0:016b}'.format(value) - - # Destructure the CMR values. - resolution = 20 if int(value[9]) else 16 # RES - self.mock_state['ports'][self.mock_state['port']].resolution = resolution - if int(value[10]): # CLR - self.mock_state['ports'][self.mock_state['port']].voltage = 0 - - # Reading never works. - answer = 'ff' * len + '00' * (4 - len) - self.output = '0000 0014 0010 0100 0000 0002 {0:02x}00 0000 {1}'.format(len, answer) - else: - self.output = None - - if self.output is not None: - self.output = BinaryEncoder.encode(self.output) - - -name = 'Six channel voltage source' -implementation = Mockch6VoltageSource diff --git a/spacq/devices/iqc/mock/mock_voltage_source.py b/spacq/devices/iqc/mock/mock_voltage_source.py deleted file mode 100644 index b8d8a3a..0000000 --- a/spacq/devices/iqc/mock/mock_voltage_source.py +++ /dev/null @@ -1,114 +0,0 @@ -import re - -from ...mock.mock_abstract_device import MockAbstractDevice -from ...tools import BinaryEncoder -from ..voltage_source import VoltageSource - -""" -Mock Voltage Source - -Control a fake voltage source. -""" - - -class MockPort(object): - """ - Mock port. - """ - - def __init__(self): - self._voltage = 0 - - def set_voltage(self, voltage): - self._voltage = voltage - - voltage = property(fset=set_voltage) - - -class MockVoltageSource(MockAbstractDevice, VoltageSource): - """ - Mock interface for Tektronix AWG5014B AWG. - """ - - # Not sure what it means, but it comes up a lot. - standard_reply = '0000 000c 0008 0100 0000 0002' - - def __init__(self, *args, **kwargs): - """ - Pretend to connect to the voltage source. - """ - - self.port_settings = {} - - self.mocking = VoltageSource - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['ports'] = [MockPort() for _ in xrange(16)] - - def write(self, message, result=None, done=False): - if done: - MockAbstractDevice.write(self, message, result, done) - - message = BinaryEncoder.decode(message) - - # These all elicit the same response. - uninteresting_messages = [ - '0000 0010 000c 0113 0280 0000 0000 ff01', - '0000 0010 000c 0112 0280 0000 00ff ff00', - '0000 0014 0010 0110 0260 0000 0000 0064 0700 0000', - ] - - if message in uninteresting_messages: - self.output = self.standard_reply - elif message == '0000 000c 0008 0100 0000 0000': - self.output = '0000 001c 0018 0100 0000 0002 0200 1000 0100 c001 0100 c000 0002 000o' - elif message.startswith('0000 0010 000c 0111 0280 0000 00ff '): - m = re.match('0000 0010 000c 0111 0280 0000 00ff ([0-9a-f]{2})00', message) - self.mock_state['port'] = int(m.group(1), 16) - - self.output = self.standard_reply - elif message.startswith('0000 0014 0010 0111 0260 0000 0003 '): - m = re.match('0000 0014 0010 0111 0260 0000 0003 ([0-9a-f]{2})00 ([0-9a-f ]{9})', message) - len = int(m.group(1), 16) - cmd = ''.join(['{0:08b}'.format(~ord(x) & 0xff) for x in BinaryEncoder.encode(m.group(2))[:len]]) - - # Destructure the command. - read = bool(int(cmd[0])) # Read on True, write on False. - num_bytes = 1 + int(cmd[1:3], 2) - register = int(cmd[4:8], 2) - value = int(cmd[8:8+8*num_bytes], 2) - - if not read: - if register >= 0 and register <= 2: - # Data input register. - value = ~value & (2 ** (8 * num_bytes) - 1) - # Assuming here that it's OK to overwrite the lower bits with 0. - value <<= 8 * (3 - register - num_bytes) - - self.mock_state['ports'][self.mock_state['port']].voltage = value - elif register >= 4 and register <= 5: - # Command register. - # Assuming here that it's OK to overwrite the lower bits with 0. - value <<= 8 * (6 - register - num_bytes) - value = '{0:016b}'.format(value) - - # Destructure the CMR values. - resolution = 20 if int(value[9]) else 16 # RES - self.mock_state['ports'][self.mock_state['port']].resolution = resolution - if int(value[10]): # CLR - self.mock_state['ports'][self.mock_state['port']].voltage = 0 - - # Reading never works. - answer = 'ff' * len + '00' * (4 - len) - self.output = '0000 0014 0010 0100 0000 0002 {0:02x}00 0000 {1}'.format(len, answer) - else: - self.output = None - - if self.output is not None: - self.output = BinaryEncoder.encode(self.output) - - -name = 'Voltage source' -implementation = MockVoltageSource diff --git a/spacq/devices/iqc/mock/tests/__init__.py b/spacq/devices/iqc/mock/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/iqc/mock/tests/test_mock_ch6_voltage_source.py b/spacq/devices/iqc/mock/tests/test_mock_ch6_voltage_source.py deleted file mode 100644 index 919f29d..0000000 --- a/spacq/devices/iqc/mock/tests/test_mock_ch6_voltage_source.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import ch6_voltage_source -from .. import mock_ch6_voltage_source - -from ...tests.server.test_ch6_voltage_source import ch6VoltageSourceTest - - -# Don't lose the real device. -real_ch6VoltageSource = ch6_voltage_source.ch6VoltageSource -is_mock = ch6VoltageSourceTest.mock - - -def setup(): - # Run the tests with a fake device. - ch6_voltage_source.ch6VoltageSource = mock_ch6_voltage_source.Mockch6VoltageSource - ch6VoltageSourceTest.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - ch6_voltage_source.ch6VoltageSource = real_ch6VoltageSource - ch6VoltageSourceTest.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/iqc/mock/tests/test_mock_voltage_source.py b/spacq/devices/iqc/mock/tests/test_mock_voltage_source.py deleted file mode 100644 index 88a415f..0000000 --- a/spacq/devices/iqc/mock/tests/test_mock_voltage_source.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import voltage_source -from .. import mock_voltage_source - -from ...tests.server.test_voltage_source import VoltageSourceTest - - -# Don't lose the real device. -real_VoltageSource = voltage_source.VoltageSource -is_mock = VoltageSourceTest.mock - - -def setup(): - # Run the tests with a fake device. - voltage_source.VoltageSource = mock_voltage_source.MockVoltageSource - VoltageSourceTest.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - voltage_source.VoltageSource = real_VoltageSource - VoltageSourceTest.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/iqc/tests/__init__.py b/spacq/devices/iqc/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/iqc/tests/server/__init__.py b/spacq/devices/iqc/tests/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/iqc/tests/server/test_ch6_voltage_source.py b/spacq/devices/iqc/tests/server/test_ch6_voltage_source.py deleted file mode 100644 index 6511127..0000000 --- a/spacq/devices/iqc/tests/server/test_ch6_voltage_source.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from unittest import main - -from spacq.interface.units import Quantity -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import ch6_voltage_source - - -class ch6VoltageSourceTest(DeviceServerTestCase): - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=ch6_voltage_source.ch6VoltageSource, - manufacturer='IQC', model='Six channel voltage source') - - def testCalibrate(self): - """ - Self-calibrate all the ports. - """ - - vsrc = self.obtain_device() - - for port in vsrc.ports: - port.apply_settings(calibrate=True) - - def testSetVoltages(self): - """ - Set voltages on all the ports. - - Note: Verification should also be done manually based on the voltage source output. - """ - - vsrc = self.obtain_device() - - test_voltages = list(xrange(-10, 10 + 1, 2)) + list(xrange(5, 0, -1)) - - for port, voltage in zip(xrange(6), test_voltages): - vsrc.ports[port].voltage = Quantity(voltage, 'V') - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/iqc/tests/server/test_voltage_source.py b/spacq/devices/iqc/tests/server/test_voltage_source.py deleted file mode 100644 index 85d227f..0000000 --- a/spacq/devices/iqc/tests/server/test_voltage_source.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from unittest import main - -from spacq.interface.units import Quantity -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import voltage_source - - -class VoltageSourceTest(DeviceServerTestCase): - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=voltage_source.VoltageSource, - manufacturer='IQC', model='Voltage source') - - def testCalibrate(self): - """ - Self-calibrate all the ports. - """ - - vsrc = self.obtain_device() - - for port in vsrc.ports: - port.apply_settings(calibrate=True) - - def testSetVoltages(self): - """ - Set voltages on all the ports. - - Note: Verification should also be done manually based on the voltage source output. - """ - - vsrc = self.obtain_device() - - test_voltages = list(xrange(-10, 10 + 1, 2)) + list(xrange(5, 0, -1)) - - for port, voltage in zip(xrange(16), test_voltages): - vsrc.ports[port].voltage = Quantity(voltage, 'V') - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/iqc/tests/test_ch6_voltage_source.py b/spacq/devices/iqc/tests/test_ch6_voltage_source.py deleted file mode 100644 index 2ca5402..0000000 --- a/spacq/devices/iqc/tests/test_ch6_voltage_source.py +++ /dev/null @@ -1,98 +0,0 @@ -from nose.tools import eq_ -from unittest import main, TestCase - -from .. import ch6_voltage_source - - -class PortTest(TestCase): - def testCalculateVoltage(self): - """ - Trivial conversion. - """ - - port = ch6_voltage_source.Port(None, None, apply_settings=False, resolution=20) - - data = [ - (-10, 0xfffff), - (-5, 0xbffff), - (0, 0x7ffff), - (5, 0x3ffff), - (10, 0x00000), - ] - - for v, r in data: - eq_(port.calculate_voltage(v), r) - - def testResolution(self): - """ - Trivial conversion at another resolution. - """ - - port = ch6_voltage_source.Port(None, None, apply_settings=False, resolution=16) - - data = [ - (-10, 0xffff), - (-5, 0xbfff), - (0, 0x7fff), - (5, 0x3fff), - (10, 0x0000), - ] - - for v, r in data: - eq_(port.calculate_voltage(v), r) - - def testVoltageBounds(self): - """ - Trivial conversion gone wrong. - """ - - port = ch6_voltage_source.Port(None, None, apply_settings=False) - - data = ['x', -10.1, 10.1, None, 42] - - for v in data: - try: - port.calculate_voltage(v) - except ValueError: - pass - else: - assert False, 'Expected ValueError' - - def testFormatForDAC(self): - """ - Make the messages more palatable. - """ - - data = [ - ('', ''), - ('0', 'ff00 0000'), - ('00', 'ff00 0000'), - ('0000 0000', 'ffff ffff'), - ('ffff', '0000 0000'), - ('123456', 'edcb a900'), - ] - - for m, f in data: - eq_(ch6_voltage_source.Port.format_for_dac(m), f) - - - def testCustomBounds(self): - """ - Try conversions with custom minimum and maximum values. - """ - - port = ch6_voltage_source.Port(None, None, apply_settings=False, min_value=-100, max_value=-30) - - data = [ - (-100, 0xfffff), - (-65, 0x7ffff), - (-30.00007, 0x00001), - (-30, 0x00000), - ] - - for v, r in data: - eq_(port.calculate_voltage(v), r) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/iqc/tests/test_voltage_source.py b/spacq/devices/iqc/tests/test_voltage_source.py deleted file mode 100644 index b0aedab..0000000 --- a/spacq/devices/iqc/tests/test_voltage_source.py +++ /dev/null @@ -1,98 +0,0 @@ -from nose.tools import eq_ -from unittest import main, TestCase - -from .. import voltage_source - - -class PortTest(TestCase): - def testCalculateVoltage(self): - """ - Trivial conversion. - """ - - port = voltage_source.Port(None, None, apply_settings=False, resolution=20) - - data = [ - (-10, 0xfffff), - (-5, 0xbffff), - (0, 0x7ffff), - (5, 0x3ffff), - (10, 0x00000), - ] - - for v, r in data: - eq_(port.calculate_voltage(v), r) - - def testResolution(self): - """ - Trivial conversion at another resolution. - """ - - port = voltage_source.Port(None, None, apply_settings=False, resolution=16) - - data = [ - (-10, 0xffff), - (-5, 0xbfff), - (0, 0x7fff), - (5, 0x3fff), - (10, 0x0000), - ] - - for v, r in data: - eq_(port.calculate_voltage(v), r) - - def testVoltageBounds(self): - """ - Trivial conversion gone wrong. - """ - - port = voltage_source.Port(None, None, apply_settings=False) - - data = ['x', -10.1, 10.1, None, 42] - - for v in data: - try: - port.calculate_voltage(v) - except ValueError: - pass - else: - assert False, 'Expected ValueError' - - def testFormatForDAC(self): - """ - Make the messages more palatable. - """ - - data = [ - ('', ''), - ('0', 'ff00 0000'), - ('00', 'ff00 0000'), - ('0000 0000', 'ffff ffff'), - ('ffff', '0000 0000'), - ('123456', 'edcb a900'), - ] - - for m, f in data: - eq_(voltage_source.Port.format_for_dac(m), f) - - - def testCustomBounds(self): - """ - Try conversions with custom minimum and maximum values. - """ - - port = voltage_source.Port(None, None, apply_settings=False, min_value=-100, max_value=-30) - - data = [ - (-100, 0xfffff), - (-65, 0x7ffff), - (-30.00007, 0x00001), - (-30, 0x00000), - ] - - for v, r in data: - eq_(port.calculate_voltage(v), r) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/iqc/voltage_source.py b/spacq/devices/iqc/voltage_source.py deleted file mode 100644 index f11ff02..0000000 --- a/spacq/devices/iqc/voltage_source.py +++ /dev/null @@ -1,334 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -import numpy -import time - -from spacq.interface.resources import Resource -from spacq.interface.units import Quantity -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice, AbstractSubdevice -from ..tools import quantity_unwrapped, quantity_wrapped, BinaryEncoder - -""" -Custom voltage source - -Control the output voltages on all the ports. -""" - - -class Port(AbstractSubdevice): - """ - An output port on the voltage source. - """ - - # Since there is no way to determine whether calibration has completed, - # wait this long and hope for the best. - calibration_delay = 2 # s - - @staticmethod - def format_for_dac(msg): - """ - Perform some formatting to make the device happy: - flip all the bits in the message - pad messages until their length in bytes is a multiple of 4 - """ - - log.debug('Formatting for DAC: {0}'.format(msg)) - - msg_encoded = BinaryEncoder.encode(msg) - # Flip each byte separately. - msg_flipped = [chr(~ord(x) & 0xff) for x in msg_encoded] - - missing_bytes = (4 - len(msg_encoded) % 4) % 4 - - result = BinaryEncoder.decode(msg_flipped + ['\x00'] * missing_bytes) - - log.debug('Formatted for DAC (padded with {0} bytes): {1}'.format(missing_bytes, result)) - - return result - - def _setup(self): - AbstractSubdevice._setup(self) - - # These values are used to tune the input values according to empirical error. - self.gain = 1.0 - self.offset = 0.0 - - # Resources. - read_write = ['voltage'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['voltage'].units = 'V' - - @Synchronized() - def _connected(self): - AbstractSubdevice._connected(self) - - if self.do_apply_settings: - self.apply_settings(calibrate=False) - - def __init__(self, device, num, max_value, resolution=20, apply_settings=True, adaptive_filtering=True, calibrate_connected=False, - fast_settling=True, freq=100, *args, **kwargs): - """ - Initialize the output port. - - device: The VoltageSource to which this Port belongs. - num: The index of this port. - resolution: How many bits the output value contains. - apply_settings: Whether to automatically apply all the settings. - min_value: Smallest value the port can produce. - max_value: Largest value the port can produce. - adaptive_filtering: Enable adaptive filtering. - calibrate_connected: Do not disconnect output while calibrating. - fast_settling: Enable fast settling. - freq: Clock rate in kHz. - """ - - AbstractSubdevice.__init__(self, device, *args, **kwargs) - - if resolution not in [16, 20]: - raise ValueError('Unsupported resolution: {0}'.format(resolution)) - - self.num = num - self.resolution = resolution - self.do_apply_settings = apply_settings - self.min_value = -max_value - self.max_value = max_value - self.adaptive_filtering = adaptive_filtering - self.calibrate_connected = calibrate_connected - self.fast_settling = fast_settling - self.freq = freq - - def calculate_voltage(self, voltage): - """ - Determine the value corresponding to the given voltage. - """ - - try: - voltage_adjusted = voltage * self.gain + self.offset - except TypeError: - raise ValueError('Voltage must be a number. Given: {0}'.format(voltage)) - - if voltage_adjusted < self.min_value or voltage_adjusted > self.max_value: - raise ValueError('Adjusted voltage must be within [{0}, {1}]. ' - 'Given: {2}; adjusted to: {3}.'.format(self.min_value, - self.max_value, voltage, voltage_adjusted)) - - # Store the currentOutput for future reads - self.currentVoltage = voltage_adjusted - - max_converted = (1 << self.resolution) - 1 - value_span = self.max_value - self.min_value - - # Map [-min_value, max_value] onto [0x0, 0xff...] depending on the resolution. - # First negate the voltage, so that flipping the bits later will make it correct. - return int(float(-voltage_adjusted + self.max_value) / value_span * max_converted) - - @Synchronized() - def write_to_dac(self, message): - """ - Write a message to the DAC of the port. - - Voodoo programming follows, thanks to: - NI's lack of support for anything non-Windows in this case - my lack of time & desire to properly reverse engineer the ni845x DLL - - If the conversation does not go according to plan, bails out with an AssertionError! - """ - - message_length = BinaryEncoder.length(message) - - if message_length > 4: - raise ValueError('Message is longer than 4 bytes: {0}'.format(message)) - - message_formatted = self.format_for_dac(message) - - # The reply always comes back with as many bits set to 1 as were sent. - expected_reply = self.format_for_dac('00' * message_length) - - # Lots of assertions so we can bail ASAP to avoid crashing anything. - self.device.ask_encoded('0000 000c 0008 0100 0000 0000', - '0000 001c 0018 0100 0000 0002 0200 1000 0100 c001 0100 c000 0002 0000') - self.device.ask_encoded('0000 0010 000c 0113 0280 0000 0000 ff01', - '0000 000c 0008 0100 0000 0002') - self.device.ask_encoded('0000 0010 000c 0112 0280 0000 00ff ff00', - '0000 000c 0008 0100 0000 0002') - self.device.ask_encoded('0000 0010 000c 0111 0280 0000 00ff {0:02x} 00'.format(self.num), - '0000 000c 0008 0100 0000 0002') - self.device.ask_encoded('0000 000c 0008 0100 0000 0000', - '0000 001c 0018 0100 0000 0002 0200 1000 0100 c001 0100 c000 0002 0000') - self.device.ask_encoded('0000 0014 0010 0110 0260 0000 0000 {0:04x} 0700 0000'.format(self.freq), - '0000 000c 0008 0100 0000 0002') - self.device.ask_encoded('0000 0014 0010 0111 0260 0000 0003 {0:02x} 00 {1}'.format(message_length, - message_formatted), - '0000 0014 0010 0100 0000 0002 {0:02x} 00 0000 {1}'.format(message_length, expected_reply)) - - def apply_settings(self, calibrate=False): - """ - Apply the settings for the DAC on this port. - - calibrate: Run self-calibration on this port as well. - - Note: If self-calibrating, it is essential to wait the calibration_delay after this method returns. - """ - - flags = ((not self.adaptive_filtering) << 15 | - self.calibrate_connected << 14 | - (not self.fast_settling) << 4) - - if calibrate: - flags |= 0b01 - - # Write 16 bits to the top of the DIR: 0010 0100 xx10 0000 101x 00xx - self.write_to_dac('24 {0:04x}'.format(0x20a0 | flags)) - - @property - @quantity_wrapped('V') - def voltage(self): - return self.currentVoltage - - @voltage.setter - @quantity_unwrapped('V') - def voltage(self, value): - """ - Set the voltage on this port, as a quantity in V. - """ - - # Left-align the bits within the value: - # 20-bit: VVVV VVVV VVVV VVVV VVVV xxxx - # 16-bit: VVVV VVVV VVVV VVVV xxxx xxxx - # where the 'x's are don't-cares, so we just set them to 0 by shifting. - resulting_voltage = self.calculate_voltage(value) << (24 - self.resolution) - - # Write 24 bits to the top of the DIR: 0100 0000 xxxx xxxx xxxx xxxx xxxx xxxx - self.write_to_dac('40 {0:06x}'.format(resulting_voltage)) - - - @Synchronized() - def autotune(self, voltage_resource, min_value=None, max_value=None, final_value=0, set_result=True): - """ - Take some measured data and solve for the gain and offset. - - voltage_resource: A resource which provides the realtime measured data for this port. - min_value: Smallest value to take into account. - max_value: Largest value to take into account. - final_value: Value to set port to after all measurements are taken. - set_result: Whether to apply the resulting gain and offset. - """ - - self.device.status.append('Autotuning port {0}'.format(self.num)) - - try: - if min_value is None: - min_value = self.min_value - if max_value is None: - max_value = self.max_value - - # Test with raw values. - old_gain, old_offset = self.gain, self.offset - self.gain, self.offset = 1, 0 - - if max_value < min_value: - raise ValueError('{0} > {1}'.format(min_value, max_value)) - elif max_value == min_value: - num_points = 1 - else: - num_points = 21 - - # Obtain data. - real = numpy.linspace(min_value, max_value, num_points) - measured = [] - - for x in real: - self.voltage = Quantity(x, 'V') - time.sleep(0.2) - measured.append(voltage_resource.value.value) - - # Solve. - A = numpy.vstack([measured, numpy.ones(len(measured))]).T - gain, offset = numpy.linalg.lstsq(A, real)[0] - - if set_result: - self.gain, self.offset = gain, offset - else: - self.gain, self.offset = old_gain, old_offset - - # Set the voltage after the gain and offset, so that it is potentially more correct. - self.voltage = Quantity(final_value, 'V') - - return (gain, offset) - finally: - self.device.status.pop() - - -class VoltageSource(AbstractDevice): - """ - Interface for the custom voltage source. - - It uses several TI DAC1220 chips and an NI USB-8451 to interface with them over SPI. - """ - - @property - def _gui_setup(self): - try: - from .gui.voltage_source import VoltageSourceSettingsDialog - - return VoltageSourceSettingsDialog - except ImportError as e: - log.debug('Could not load GUI setup for device "{0}": {1}'.format(self.name, str(e))) - - return None - - def _setup(self): - AbstractDevice._setup(self) - - self.ports = [] - for num in xrange(16): - if num < 6: - port = Port(self, num, 1, **self.port_settings) - elif num < 12: - port = Port(self, num, 2.5, **self.port_settings) - else: - port = Port(self, num, 10, **self.port_settings) - self.ports.append(port) - self.subdevices['port{0:02}'.format(num)] = port - - def __init__(self, port_settings=None, *args, **kwargs): - """ - Initialize the voltage source and all its ports. - - port_settings: A dictionary of values to give to each port upon creation. - """ - - if port_settings is None: - self.port_settings = {} - else: - self.port_settings = port_settings - - AbstractDevice.__init__(self, *args, **kwargs) - - @Synchronized() - def ask_encoded(self, msg, assertion=None): - """ - Encode and write the message; then read and decode the answer. - """ - - self.write(BinaryEncoder.encode(msg)) - result = BinaryEncoder.decode(self.read_raw()) - - if assertion is not None: - # Ensure that extra formatting doesn't trigger an assertion failure. - formatted_assertion = BinaryEncoder.decode(BinaryEncoder.encode(assertion)) - - assert result == formatted_assertion, ( - 'Device in unknown state; expect general failure. ' - 'Asserted: {0}; observed: {1}.'.format(assertion, result)) - - return result - - -name = 'Voltage source' -implementation = VoltageSource diff --git a/spacq/devices/keithley/__init__.py b/spacq/devices/keithley/__init__.py deleted file mode 100644 index 53f06ca..0000000 --- a/spacq/devices/keithley/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -log = logging.getLogger(__name__) - - -name = 'Keithley' - -from . import voltagesource230, sourceMeter2450, sourceMeter2401 -models = [voltagesource230, sourceMeter2450, sourceMeter2401] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_voltagesource230, mock_sourceMeter2450, mock_sourceMeter2401 -mock_models = [mock_voltagesource230, mock_sourceMeter2450, mock_sourceMeter2401] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/keithley/mock/__init__.py b/spacq/devices/keithley/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/keithley/mock/mock_sourceMeter2401.py b/spacq/devices/keithley/mock/mock_sourceMeter2401.py deleted file mode 100644 index 773ff02..0000000 --- a/spacq/devices/keithley/mock/mock_sourceMeter2401.py +++ /dev/null @@ -1,43 +0,0 @@ -import random - -from ...mock.mock_abstract_device import MockAbstractDevice -from ..sourceMeter2401 import sm2401 - -""" -Mock Sample ABC1234 -""" - - -class MockSourceMeter2401(MockAbstractDevice, sm2401): - """ - Mock interface for the Sample ABC1234. - """ - - def __init__(self, *args, **kwargs): - self.mocking = voltageSource230 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['setting'] = 'default value' - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'some': - if cmd[1] == 'setting': - if query: - result = self.mock_state['setting'] - else: - self.mock_state['setting'] = args - done = True - elif cmd[0] == 'read' and query: - result = '-1.{0:04d}0000E-02'.format(random.randint(0, 9999)) - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = 'sourceMeter 2401' -implementation = MockSourceMeter2401 diff --git a/spacq/devices/keithley/mock/mock_sourceMeter2450.py b/spacq/devices/keithley/mock/mock_sourceMeter2450.py deleted file mode 100644 index e5ceec9..0000000 --- a/spacq/devices/keithley/mock/mock_sourceMeter2450.py +++ /dev/null @@ -1,43 +0,0 @@ -import random - -from ...mock.mock_abstract_device import MockAbstractDevice -from ..sourceMeter2450 import sm2450 - -""" -Mock Sample ABC1234 -""" - - -class MockSourceMeter2450(MockAbstractDevice, sm2450): - """ - Mock interface for the Sample ABC1234. - """ - - def __init__(self, *args, **kwargs): - self.mocking = voltageSource230 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['setting'] = 'default value' - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'some': - if cmd[1] == 'setting': - if query: - result = self.mock_state['setting'] - else: - self.mock_state['setting'] = args - done = True - elif cmd[0] == 'read' and query: - result = '-1.{0:04d}0000E-02'.format(random.randint(0, 9999)) - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = 'sourceMeter 2450' -implementation = MockSourceMeter2450 diff --git a/spacq/devices/keithley/mock/mock_voltagesource230.py b/spacq/devices/keithley/mock/mock_voltagesource230.py deleted file mode 100644 index a1fbd9a..0000000 --- a/spacq/devices/keithley/mock/mock_voltagesource230.py +++ /dev/null @@ -1,43 +0,0 @@ -import random - -from ...mock.mock_abstract_device import MockAbstractDevice -from ..voltagesource230 import voltageSource230 - -""" -Mock Sample ABC1234 -""" - - -class MockvoltageSource230(MockAbstractDevice, voltageSource230): - """ - Mock interface for the Sample ABC1234. - """ - - def __init__(self, *args, **kwargs): - self.mocking = voltageSource230 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['setting'] = 'default value' - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'some': - if cmd[1] == 'setting': - if query: - result = self.mock_state['setting'] - else: - self.mock_state['setting'] = args - done = True - elif cmd[0] == 'read' and query: - result = '-1.{0:04d}0000E-02'.format(random.randint(0, 9999)) - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = '230 Programmable Voltage Source' -implementation = MockvoltageSource230 diff --git a/spacq/devices/keithley/mock/tests/__init__.py b/spacq/devices/keithley/mock/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/keithley/mock/tests/test_mock_voltagesource230.py b/spacq/devices/keithley/mock/tests/test_mock_voltagesource230.py deleted file mode 100644 index 88afa7d..0000000 --- a/spacq/devices/keithley/mock/tests/test_mock_voltagesource230.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import voltagesource230 -from .. import mock_voltagesource230 - -from ...tests.server.test_voltagesource230 import voltageSource230Test - - -# Don't lose the real device. -real_voltageSource230 = voltagesource230.voltageSource230 -is_mock = voltageSource230Test.mock - - -def setup(): - # Run the tests with a fake device. - voltagesource230.voltageSource230 = mock_voltagesource230.MockvoltageSource230 - voltageSource230Test.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - voltagesource230.voltageSource230 = real_voltageSource230 - voltageSource230Test.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/keithley/sourceMeter2401.py b/spacq/devices/keithley/sourceMeter2401.py deleted file mode 100644 index c95afff..0000000 --- a/spacq/devices/keithley/sourceMeter2401.py +++ /dev/null @@ -1,146 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped -from ..tools import quantity_unwrapped - -""" -Keithley 2401 SourceMeasure Unit -Apply voltage/current bias and obtain measurements. -""" - - -class sm2401(AbstractDevice): - """ - Interface for Keithley 2401 - Note: Limited implementation, many features are not included - """ - - #allowedSourceType = set(['Voltage','Current']) - #allowedSenseType = set(['Voltage','Current']) - allowedOutput = set(['on','off']) - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - read_only = ['voltageIn', 'currentIn'] - for name in read_only: - self.resources[name] = Resource(self, name) - - #read_write = ['voltageOut', 'currentOut','sourceType','senseType'] - read_write = ['voltageOut', 'currentOut','output'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['voltageIn'].units = 'V' - self.resources['voltageOut'].units = 'V' - self.resources['currentIn'].units = 'A' - self.resources['currentOut'].units = 'A' - self.resources['output'].allowed_values = self.allowedOutput - self.currOutputState = -1 - self.currentOutputCurrent = -88 - self.currentOutputVoltage = -88 - #self.resources['sourceType'].allowed_values = self.allowedSourceType - #self.resources['senseType'].allowed_values = self.allowedSenseType - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - #self.write('configure:voltage:dc') - # TODO: Create a default setup? - - # Get output on/off state to determine if we can ask certain things - self.currOutputState = int(self.ask('OUTP?')) - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - def output(self): - # Coded setting of the output on/off (1 = on, 0 = off) - result = self.ask('OUTP?') - if result == '0': - self.currOutputState = 0 - return 'off' - elif result == '1': - self.currOutputState = 1 - return 'on' - - @output.setter - def output(self,value): - if value not in self.allowedOutput: - raise ValueError('Invalid Output State: {0}'.format(value)) - - if value == 'off': - outCode = 0 - self.currOutputState = 0 - elif value == 'on': - outCode = 1 - self.currOutputState = 1 - - self.write('OUTP {0}'.format(outCode)) - - @property - @quantity_wrapped('V') - def voltageIn(self): - if self.currOutputState: - outString = self.ask('READ?') - # The output returns a 5 number string, first number is voltage, second is current, third is res - return float(outString.split(',')[0]) - else: - return -999 - - @property - @quantity_wrapped('A') - def currentIn(self): - if self.currOutputState: - outString = self.ask('READ?') - return float(outString.split(',')[1]) - else: - return -999 - - @property - @quantity_wrapped('V') - def voltageOut(self): - if self.currOutputState: - return float(self.ask('SOUR:VOLT?')) - else: - return self.currentOutputVoltage - - @voltageOut.setter - @quantity_unwrapped('V') - def voltageOut(self,value): - self.write('SOUR:FUNC VOLT') - self.write('SOUR:VOLT:LEV {0}'.format(value)) - self.currentOutputVoltage = value - - @property - @quantity_wrapped('A') - def currentOut(self): - if self.currOutputState: - return float(self.ask('SOUR:CURR?')) - else: - return self.currentOutputCurrent - - @currentOut.setter - @quantity_unwrapped('A') - def currentOut(self,value): - #self.write('SOUR:FUNC CURR') - self.write('SOUR:CURR:LEV {0}'.format(value)) - self.currentOutputCurrent = value - - -name = 'sourceMeter 2401' -implementation = sm2401 diff --git a/spacq/devices/keithley/sourceMeter2450.py b/spacq/devices/keithley/sourceMeter2450.py deleted file mode 100644 index c7dfad8..0000000 --- a/spacq/devices/keithley/sourceMeter2450.py +++ /dev/null @@ -1,92 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped -from ..tools import quantity_unwrapped - -""" -Keithley 2450 SourceMeasure Unit -Apply voltage/current bias and obtain measurements. -""" - - -class sm2450(AbstractDevice): - """ - Interface for Keithley 2450 - Note: Limited implementation, many features are not included - """ - - #allowed_nplc = set([0.006, 0.02, 0.06, 0.2, 1.0, 2.0, 10.0, 100.0]) - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - read_only = ['voltageIn', 'currentIn'] - for name in read_only: - self.resources[name] = Resource(self, name) - - write_only = ['voltageOut', 'currentOut'] - for name in write_only: - self.resources[name] = Resource(self, None, name) - - self.resources['voltageIn'].units = 'V' - self.resources['voltageOut'].units = 'V' - self.resources['currentIn'].units = 'A' - self.resources['currentOut'].units = 'A' - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - #self.write('configure:voltage:dc') - # TODO: Create a default setup? - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - @quantity_wrapped('V') - def voltageIn(self): - return float(self.ask('MEAS:VOLT?')) - - @property - @quantity_wrapped('A') - def currentIn(self): - return float(self.ask('MEAS:CURR?')) - - @property - @quantity_wrapped('V') - def voltageOut(self): - return self.currentOutputVoltage - - @voltageOut.setter - @quantity_unwrapped('V') - def voltageOut(self,value): - self.write('SOUR:VOLT {0}'.format(value)) - self.currentOutputVoltage = value - - @property - @quantity_wrapped('A') - def currentOut(self): - return self.currentOutputCurrent - - @currentOut.setter - @quantity_unwrapped('A') - def currentOut(self,value): - self.write('SOUR:CURR {0}'.format(value)) - self.currentOutputCurrent = value - - -name = 'sourceMeter 2450' -implementation = sm2450 diff --git a/spacq/devices/keithley/tests/__init__.py b/spacq/devices/keithley/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/keithley/tests/server/__init__.py b/spacq/devices/keithley/tests/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/keithley/tests/server/test_voltagesource230.py b/spacq/devices/keithley/tests/server/test_voltagesource230.py deleted file mode 100644 index 1d8c39e..0000000 --- a/spacq/devices/keithley/tests/server/test_voltagesource230.py +++ /dev/null @@ -1,47 +0,0 @@ -from nose.tools import eq_ -from numbers import Real -from unittest import main - -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import voltagesource230 - - -class ABC1234Test(DeviceServerTestCase): - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=voltagesource230.voltageSource230, - manufacturer='Keithley', model='230 Programmable Voltage Source') - - def testSetting(self): - """ - Test the setting. - """ - - abc = self.obtain_device() - abc.reset() - - eq_(abc.setting, 'default value') - - abc.setting = 'something else' - eq_(abc.setting, 'something else') - - try: - abc.setting = 'another thing' - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - def testGetValues(self): - """ - Obtain some values. - """ - - abc = self.obtain_device() - abc.reset() - - isinstance(abc.reading, Real) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/keithley/voltagesource230.py b/spacq/devices/keithley/voltagesource230.py deleted file mode 100644 index e1d9c80..0000000 --- a/spacq/devices/keithley/voltagesource230.py +++ /dev/null @@ -1,151 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_unwrapped - -""" -Keithley 230 Programmable Voltage Source -""" - -class voltageSource230(AbstractDevice): - """ - Interface for the Keithly 230 Programmable Voltage Source - """ - - allowed_I_limit = set([2, 20, 100]) - - def _setup(self): - AbstractDevice._setup(self) - - # These values are used to tune the input values according to empirical error. - self.gain = 1.0 - self.offset = 0.0 - - # Test - self.default_Ilim = 2.0 - self.default_dwelltime = 0.01 - - # Resources. - write_only = ['voltage'] - for name in write_only: - self.resources[name] = Resource(self, None, name) - - read_write = ['I_limit','dwell_time'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['voltage'].units = 'V' - self.resources['I_limit'].converter = float - self.resources['I_limit'].allowed_values = self.allowed_I_limit - self.resources['dwell_time'].converter = float - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - # Clear the GPIB for this device - self.device.clear() - - # Set the device to operational - self.device.write('K0X') - self.device.write('F1X') - - def __init__(self, vMin = -101, vMax = 101, dwellMin = 0.003, dwellMax = 0.9999, *args, **kwargs): - """ - Initialize the voltage source and all its ports. - """ - AbstractDevice.__init__(self, *args, **kwargs) - - self.vMin = vMin - self.vMax = vMax - self.dwellMin = dwellMin - self.dwellMax = dwellMax - - @Synchronized() - def reset(self): - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - def calculate_voltage(self, voltage): - """ - Determine the value corresponding to the given voltage. - """ - - try: - voltage_adjusted = voltage * self.gain + self.offset - except TypeError: - raise ValueError('Voltage must be a number. Given: {0}'.format(voltage)) - - if voltage_adjusted < self.vMin or voltage_adjusted > self.vMax: - raise ValueError('Adjusted voltage must be within [{0}, {1}]. ' - 'Given: {2}; adjusted to: {3}.'.format(self.vMin, - self.vMax, voltage, voltage_adjusted)) - - return voltage_adjusted - - @quantity_unwrapped('V') - def set_voltage(self, voltage): - """ - Set the voltage on this port, as a quantity in V. - """ - - # Left-align the bits within the value: - # 20-bit: VVVV VVVV VVVV VVVV VVVV xxxx - # 16-bit: VVVV VVVV VVVV VVVV xxxx xxxx - # where the 'x's are don't-cares, so we just set them to 0 by shifting. - resulting_voltage = self.calculate_voltage(voltage) - - # Write 24 bits to the top of the DIR: 0100 0000 xxxx xxxx xxxx xxxx xxxx xxxx - #self.write('L1 B1 V{0:.4E}XI{1:.2E}XW{2:.3E}X'.format(resulting_voltage,self.I_limit_code,self.dwelltime)) - self.write('V{0:.4E}X'.format(resulting_voltage)) - #self.write('L1 B1 V{0:.4E}XI{1:.2E}XW{2:.3E}X'.format(resulting_voltage,0,3)) - - voltage = property(fset=set_voltage) - - @property - def I_limit(self): - """ - The current limit (in mA) - """ - # If no I limit has been set, use the 2mA default - if not hasattr(self, 'currentILimit'): - self.currentILimit = 2 - self.I_limit_code = 0 - - return self.currentILimit - - @I_limit.setter - def I_limit(self, value): - if value not in self.allowed_I_limit: - raise ValueError('Invalid I Limit value: {0}'.format(value)) - - if value == 2: - self.I_limit_code = 0 - elif value == 20: - self.I_limit_code = 1 - elif value == 100: - self.I_limit_code = 2 - self.currentILimit = value - - @property - def dwell_time(self): - """ - The dwell time of the vsrc - """ - # If no dwell time has been set, use 10ms default - if not hasattr(self, 'dwelltime'): - self.dwelltime = 0.01 - return self.dwelltime - - @dwell_time.setter - def dwell_time(self, value): - if value < self.dwellMin or value > self.dwellMax: - raise ValueError('Dwell time must be within [{0}, {1}]. Given: {2}.'.format(self.dwellMin, self.dwellMax, value)) - self.dwelltime = value - -name = '230 Programmable Voltage Source' -implementation = voltageSource230 diff --git a/spacq/devices/lakeshore/__init__.py b/spacq/devices/lakeshore/__init__.py deleted file mode 100644 index 04e421d..0000000 --- a/spacq/devices/lakeshore/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -log = logging.getLogger(__name__) - - -name = 'Lakeshore' - -from . import tc335, model218 -models = [tc335, model218] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_tc335, mock_model218 -mock_models = [mock_tc335, mock_model218] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/lakeshore/mock/__init__.py b/spacq/devices/lakeshore/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/lakeshore/mock/mock_model218.py b/spacq/devices/lakeshore/mock/mock_model218.py deleted file mode 100644 index 0616a1d..0000000 --- a/spacq/devices/lakeshore/mock/mock_model218.py +++ /dev/null @@ -1,39 +0,0 @@ -import random - -from ...mock.mock_abstract_device import MockAbstractDevice -from ..model218 import model218 - -""" -Mock Lakeshore 335 Temperature Controller -""" - -class MockModel218(MockAbstractDevice, model218): - """ - Mock interface for Lakeshore 335 Temperature Controller. - """ - - def __init__(self, *args, **kwargs): - self.mocking = model218 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - self.mock_state = {} - self.mock_state['readingstatus'] = 0 - self.mock_state['read_only'] = ['temperature'] - - def _reset(self): - pass - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'krdg' and query: - result = random.randint(5,100) - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = 'Model 218 Temperature Monitor' -implementation = MockModel218 diff --git a/spacq/devices/lakeshore/mock/mock_tc335.py b/spacq/devices/lakeshore/mock/mock_tc335.py deleted file mode 100644 index 26f999a..0000000 --- a/spacq/devices/lakeshore/mock/mock_tc335.py +++ /dev/null @@ -1,42 +0,0 @@ -import random - -from ...mock.mock_abstract_device import MockAbstractDevice -from ..tc335 import TC335 - -""" -Mock Lakeshore 335 Temperature Controller -""" - -class MockTC335(MockAbstractDevice, TC335): - """ - Mock interface for Lakeshore 335 Temperature Controller. - """ - - def __init__(self, *args, **kwargs): - self.mocking = TC335 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - self.mock_state = {} - self.mock_state['readingstatus'] = 0 - self.mock_state['read_only'] = ['temperature'] - - def _reset(self): - pass - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'rdgst' and query: - result = self.mock_state['readingstatus'] - done = True - elif cmd[0] == 'krdg' and query: - result = random.randint(5,100) - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = '335 Temperature Controller' -implementation = MockTC335 diff --git a/spacq/devices/lakeshore/mock/tests/__init__.py b/spacq/devices/lakeshore/mock/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/lakeshore/mock/tests/test_mock_tc335.py b/spacq/devices/lakeshore/mock/tests/test_mock_tc335.py deleted file mode 100644 index 95afc8b..0000000 --- a/spacq/devices/lakeshore/mock/tests/test_mock_tc335.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import tc335 -from .. import mock_tc335 - -from ...tests.server.test_tc335 import TC335Test - - -# Don't lose the real device. -real_TC335 = tc335.TC335 -is_mock = TC335Test.mock - - -def setup(): - # Run the tests with a fake device. - tc335.TC335 = mock_tc335.MockTC335 - TC335Test.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - tc335.TC335 = real_TC335 - TC335Test.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/lakeshore/model218.py b/spacq/devices/lakeshore/model218.py deleted file mode 100644 index 50a34ad..0000000 --- a/spacq/devices/lakeshore/model218.py +++ /dev/null @@ -1,93 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped - -""" -Lakeshore Model 218 Temperature Monitor - -Read temperature of up to 8 channels -""" - -class model218(AbstractDevice): - """ - Interface for Lakeshore Model 218 Temperature Monitor - """ - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - self.read_only = ['temperature1','temperature2','temperature3','temperature4','temperature5','temperature6','temperature7','temperature8'] - for name in self.read_only: - self.resources[name] = Resource(self, name) - self.resources[name].units = 'K' - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - @quantity_wrapped('K') - @Synchronized() - def temperature1(self): - - return float(self.ask('krdg? 1')) - - @property - @quantity_wrapped('K') - @Synchronized() - def temperature2(self): - return float(self.ask('krdg? 2')) - - @property - @quantity_wrapped('K') - @Synchronized() - def temperature3(self): - return float(self.ask('krdg? 3')) - - @property - @quantity_wrapped('K') - @Synchronized() - def temperature4(self): - return float(self.ask('krdg? 4')) - - @property - @quantity_wrapped('K') - @Synchronized() - def temperature5(self): - return float(self.ask('krdg? 5')) - - @property - @quantity_wrapped('K') - @Synchronized() - def temperature6(self): - return float(self.ask('krdg? 6')) - - @property - @quantity_wrapped('K') - @Synchronized() - def temperature7(self): - return float(self.ask('krdg? 7')) - - @property - @quantity_wrapped('K') - @Synchronized() - def temperature8(self): - return float(self.ask('krdg? 8')) - -name = 'Model 218 Temperature Monitor' -implementation = model218 diff --git a/spacq/devices/lakeshore/tc335.py b/spacq/devices/lakeshore/tc335.py deleted file mode 100644 index cb0fbf9..0000000 --- a/spacq/devices/lakeshore/tc335.py +++ /dev/null @@ -1,76 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped - -""" -Lakeshore 335 Temperature Controller - -Read temperature (TODO: Add more functionality) -""" - -class TC335(AbstractDevice): - """ - Interface for Lakeshore 335 Temperature Controller - """ - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - self.read_only = ['temperature'] - for name in self.read_only: - self.resources[name] = Resource(self, name) - - self.resources['temperature'].units = 'K' - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - # TODO: Allow for measurements other than DC voltage. - # self.write('configure:voltage:dc') - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - - @property - @quantity_wrapped('K') - @Synchronized() - def temperature(self): - """ - The value measured by the device, as a quantity in V. - """ - - self.status.append('Taking reading') - - try: - log.debug('Getting reading.') - status = int(self.ask('rdgst?')) - if status == 0: - result = float(self.ask('krdg?')) - elif status <= 17: - result = -1 #This corresponds to T.UNDER - else: - result = -2 # This should correspond to S.OVER, or other errors - - log.debug('Got reading: {0}'.format(result)) - - return result - finally: - self.status.pop() - - -name = '335 Temperature Controller' -implementation = TC335 diff --git a/spacq/devices/lakeshore/tests/__init__.py b/spacq/devices/lakeshore/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/lakeshore/tests/server/__init__.py b/spacq/devices/lakeshore/tests/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/lakeshore/tests/server/test_tc335.py b/spacq/devices/lakeshore/tests/server/test_tc335.py deleted file mode 100644 index bbebeab..0000000 --- a/spacq/devices/lakeshore/tests/server/test_tc335.py +++ /dev/null @@ -1,47 +0,0 @@ -from nose.tools import eq_ -from numbers import Real -from unittest import main - -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import tc335 - - -class TC335Test(DeviceServerTestCase): - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=tc335.TC335, - manufacturer='Lakeshore', model='335 Temperature Controller') - - def testSetting(self): - """ - Test the setting. - """ - - abc = self.obtain_device() - abc.reset() - - eq_(abc.setting, 'default value') - - abc.setting = 'something else' - eq_(abc.setting, 'something else') - - try: - abc.setting = 'another thing' - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - def testGetValues(self): - """ - Obtain some values. - """ - - abc = self.obtain_device() - abc.reset() - - isinstance(abc.reading, Real) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/mock/__init__.py b/spacq/devices/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/mock/mock_abstract_device.py b/spacq/devices/mock/mock_abstract_device.py deleted file mode 100644 index bd391e8..0000000 --- a/spacq/devices/mock/mock_abstract_device.py +++ /dev/null @@ -1,156 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from ..abstract_device import AbstractDevice - -""" -Mock hardware device. -""" - - -class MockAbstractDevice(AbstractDevice): - """ - A class for controlling fake devices. - """ - - output = None - - @staticmethod - def _split_message(message): - """ - Split a message into usable components. - """ - - message = message.split(None, 1) - try: - cmd, args = message[0], message[1].strip() - except IndexError: - cmd, args = message[0], '' - - if cmd[-1] == '?': - query = True - cmd = cmd[:-1] - else: - query = False - cmd = cmd.split(':') - - return cmd, args, query - - def __init__(self, autoconnect=True, *args, **kwargs): - """ - Create a device, always successfully. - """ - - log.info('Creating mock device.') - - self.multi_command_responses = None - self.mock_state = {} - - try: - self._reset() - self.mocking._setup(self) - except AttributeError: - # An instance of MockAbstractDevice itself. - AbstractDevice._setup(self) - - if autoconnect: - self.connect() - - log.info('Created mock device "{0}".'.format(self.name)) - - def _reset(self): - """ - Reset to a known blank state. - """ - - # Each mock device has its own mock_state to worry about. - pass - - def connect(self): - """ - Pretend to connect. - """ - - self._connected() - - def multi_command_start(self): - """ - Start buffering responses. - """ - - self.multi_command_responses = [] - - def multi_command_stop(self): - """ - Return buffered responses. - """ - - responses = self.multi_command_responses - self.multi_command_responses = None - - return responses - - def write(self, message, result=None, done=False): - """ - Act on what is being written. - """ - - log.debug('Writing to device: {0!r}'.format(message)) - - if not done: - if message == '*idn?': - result = self.name - done = True - elif message == '*opc?': - result = 1 - done = True - elif message in ['*rst', 'system:preset']: - self._reset() - done = True - elif message == 'system:version?': - result = '42' - done = True - - if not done: - raise NotImplementedError('Cannot understand message: {0!r}'.format(message)) - - if result is None: - self.output = None - else: - self.output = str(result) + '\n' - - def read_raw(self, **kwargs): - """ - Return the result of the last write operation. - """ - - log.debug('Read from device: {0!r}'.format(self.output)) - - return self.output - - def ask(self, *args, **kwargs): - """ - Ask, but possibly buffer the answer. - """ - - result = AbstractDevice.ask(self, *args, **kwargs) - - if self.multi_command_responses is None: - return result - else: - self.multi_command_responses.append(result) - - def close(self): - """ - Pretend to close the connection. - """ - - log.debug('Closing device: {0}'.format(self.name)) - - @property - def idn(self): - self.ask('*idn?') - - @property - def opc(self): - self.ask('*opc?') diff --git a/spacq/devices/mock/tests/__init__.py b/spacq/devices/mock/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/mock/tests/test_mock_abstract_device.py b/spacq/devices/mock/tests/test_mock_abstract_device.py deleted file mode 100644 index fe3ea68..0000000 --- a/spacq/devices/mock/tests/test_mock_abstract_device.py +++ /dev/null @@ -1,57 +0,0 @@ -from nose.tools import assert_raises, eq_ -from unittest import main, TestCase - -from .. import mock_abstract_device - - -class MockAbstractDeviceTest(TestCase): - def testInit(self): - """ - Should always succeed. - """ - - mock_abstract_device.MockAbstractDevice() - mock_abstract_device.MockAbstractDevice(ip_address='1234') - mock_abstract_device.MockAbstractDevice(board=12345, pad=67890) - - def testNotImplemented(self): - """ - Ensure that an exception is raised for a non-implemented reqest. - """ - - dev = mock_abstract_device.MockAbstractDevice() - - assert_raises(NotImplementedError, dev.ask_raw, 'This isn\'t right.') - - def testAskRaw(self): - """ - Converse briefly with a fake device. - """ - - dev = mock_abstract_device.MockAbstractDevice() - - msg = dev.ask_raw('*idn?') - eq_(msg, 'MockAbstractDevice\n') - - def testMulti(self): - """ - Ensure that requests for multi-command messages work. - """ - - dev = mock_abstract_device.MockAbstractDevice() - - expected = ['42', '42', 'MockAbstractDevice'] - - dev.multi_command_start() - dev.ask('system:version?') - dev.write('*rst') - dev.write('system:preset') - dev.ask('system:version?') - dev.ask('*idn?') - result = dev.multi_command_stop() - - eq_(result, expected) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/oxford/__init__.py b/spacq/devices/oxford/__init__.py deleted file mode 100644 index 499646f..0000000 --- a/spacq/devices/oxford/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -log = logging.getLogger(__name__) - - -name = 'Oxford Instruments' - -from . import ips120_10 -models = [ips120_10] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_ips120_10 -mock_models = [mock_ips120_10] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/oxford/ips120_10.py b/spacq/devices/oxford/ips120_10.py deleted file mode 100644 index a3d9dfd..0000000 --- a/spacq/devices/oxford/ips120_10.py +++ /dev/null @@ -1,258 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from collections import namedtuple -from time import sleep - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import str_to_bool, quantity_wrapped, quantity_unwrapped - -""" -Oxford Instruments IPS120-10 Superconducting Magnet Power Supply -""" - - -Status = namedtuple('Status', 'system_status, limits, activity, remote_status, heater, mode, mode_sweep') - - -class IPS120_10(AbstractDevice): - """ - Interface for the Oxford Instruments IPS120-10. - """ - - allowed_settings = ['default value', 'something else'] - - activities = ['hold', 'to_set', 'to_zero', 'clamped'] - - heater_delay = 10 # s - - def _setup(self): - AbstractDevice._setup(self) - - self._perma_hot = True - - # Resources. - read_write = ['perma_hot', 'sweep_rate', 'field'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['perma_hot'].converter = str_to_bool - self.resources['sweep_rate'].units = 'T.s-1' - self.resources['field'].units = 'T' - - @Synchronized() - def _connected(self): - self.eos_char = '\r' - - AbstractDevice._connected(self) - - self.write('$Q4') # Extended resolution. - self.write('$C3') # Remote & unlocked. - self.write('$M9') # Display in Tesla. - - if self.device_status.activity == 4: - self.write('$A0') # Unclamp. - - # Ensure some initial sanity. - assert self.device_status.activity == 0, 'Not on hold.' - - def write(self, message): - """ - Append the "\r" that the device requires. - """ - - AbstractDevice.write(self, message + '\r') - - @property - def device_status(self): - """ - All the status information for the device. - """ - - result = self.ask('X') - - system_status = int(result[1]) - limits = int(result[2]) - activity = int(result[4]) - remote_status = int(result[6]) - heater = int(result[8]) - mode = int(result[10]) - mode_sweep = int(result[11]) - # The polarity status is deprecated. - - return Status(system_status, limits, activity, remote_status, heater, mode, mode_sweep) - - @property - def activity(self): - """ - What the device is currently up to. - """ - - return self.activities[self.device_status.activity] - - @activity.setter - def activity(self, value): - self.write('$A{0}'.format(self.activities.index(value))) - - @property - def heater_on(self): - """ - Whether the heater is enabled. - """ - - return bool(self.device_status.heater & 1) - - @heater_on.setter - def heater_on(self, value): - self.status.append('Turning heater o{0}'.format('n' if value else 'ff')) - - try: - self.write('$H{0}'.format(int(value))) - - # Allow the heater to go to the correct setting. - log.debug('Waiting for heater for {0} s.'.format(self.heater_delay)) - sleep(self.heater_delay) - finally: - self.status.pop() - - @property - def perma_hot(self): - """ - Whether the heater should always remain on. - """ - - return self._perma_hot - - @perma_hot.setter - def perma_hot(self, value): - self._perma_hot = value - - @property - # The value used on the device is in T/min. - @quantity_wrapped('T.s-1', 1./60) - def sweep_rate(self): - """ - The rate of the field sweep, as a quantity in T/s. - """ - - return float(self.ask('R9')[1:]) - - @sweep_rate.setter - @quantity_unwrapped('T.s-1', 60) - def sweep_rate(self, value): - if value <= 0: - raise ValueError('Sweep rate must be positive, not {0}.'.format(value)) - - self.write('$T{0:f}'.format(value)) - - @property - @quantity_wrapped('T') - def persistent_field(self): - """ - The output field when the heater was last disabled, as a quantity in T. - """ - - return float(self.ask('R18')[1:]) - - @property - @quantity_wrapped('T') - def output_field(self): - """ - The actual field due to the output current in T. - """ - - return float(self.ask('R7')[1:]) - - @property - @quantity_wrapped('T') - def set_point(self): - """ - The set point, as a quantity in T. - """ - - return float(self.ask('R8')[1:]) - - @set_point.setter - @quantity_unwrapped('T') - def set_point(self, value): - self.write('$J{0}'.format(value)) - - @property - def field(self): - """ - The magnetic field, as a quantity in T. - """ - - return self.output_field - - def set_field(self, value): - """ - Go through all the steps for setting the output field. - """ - - if self.output_field == value: - return - - self.status.append('Setting field to {0}'.format(value)) - - try: - set_delay = abs(value - self.output_field).value / self.sweep_rate.value # s - - self.set_point = value - self.activity = 'to_set' - - # If the heater is on, the sweep rate is used, so wait. - if self.heater_on: - log.debug('Waiting for sweep for {0} s.'.format(set_delay)) - sleep(set_delay) - - # Ensure that the sweep is actually over. - while self.device_status.mode_sweep != 0: - sleep(0.1) - - self.activity = 'hold' - finally: - self.status.pop() - - @field.setter - @Synchronized() - def field(self, value): - status = self.device_status - assert status.system_status == 0, 'System status: {0}'.format(status.system_status) - assert status.limits == 0, 'Limits: {0}'.format(status.limits) - assert status.mode_sweep == 0, 'Mode sweep: {0}'.format(status.mode_sweep) - assert self.activity == 'hold', 'Activity: {0}'.format(self.activity) - - # Return to the last field. - if not self.heater_on: - self.set_field(self.persistent_field) - self.heater_on = True - - # Change to the new field. - self.set_field(value) - - if not self.perma_hot: - self.heater_on = False - - @property - def idn(self): - """ - *idn? substitute for this non-SCPI device. - """ - - return self.ask('V') - - @property - def opc(self): - """ - *opc? substitute for this non-SCPI device. - """ - - return 1 - - -name = 'IPS120-10' -implementation = IPS120_10 diff --git a/spacq/devices/oxford/mock/__init__.py b/spacq/devices/oxford/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/oxford/mock/mock_ips120_10.py b/spacq/devices/oxford/mock/mock_ips120_10.py deleted file mode 100644 index 032d090..0000000 --- a/spacq/devices/oxford/mock/mock_ips120_10.py +++ /dev/null @@ -1,91 +0,0 @@ -from ...mock.mock_abstract_device import MockAbstractDevice -from ..ips120_10 import IPS120_10 - -""" -Mock Sample IPS120_10 -""" - - -class MockIPS120_10(MockAbstractDevice, IPS120_10): - """ - Mock interface for the Sample IPS120_10. - """ - - def __init__(self, *args, **kwargs): - self.mocking = IPS120_10 - - self.heater_delay = 0 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['heater_on'] = False - self.mock_state['activity'] = 4 - self.mock_state['mode'] = 0 - - self.mock_state['output_field'] = 0.0 - self.mock_state['set_point'] = 0.0 - self.mock_state['sweep_rate'] = 1.0 - self.mock_state['persistent_field'] = 0.0 - - def _split_message(self, message): - if message[0] == '$': - query = False - message = message[1:] - else: - query = True - - cmd = message[0] - args = message[1:] - - return (cmd, args, query) - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd in ['C', 'M', 'Q']: - done = True - elif cmd == 'A' and not query: - self.mock_state['activity'] = int(args) - if self.mock_state['activity'] == 0: - done = True - elif self.mock_state['activity'] == 1: - self.mock_state['output_field'] = self.mock_state['set_point'] - done = True - elif cmd == 'H' and not query: - self.mock_state['heater_on'] = (args == '1') - if not self.mock_state['heater_on']: - self.mock_state['persistent_field'] = self.mock_state['output_field'] - done = True - elif cmd == 'J' and not query: - self.mock_state['set_point'] = float(args) - done = True - elif cmd == 'R': - if query: - result = 'R' - if args == '7': - result += str(self.mock_state['output_field']) - if args == '8': - result += str(self.mock_state['set_point']) - if args == '9': - result += str(self.mock_state['sweep_rate']) - elif args == '18': - result += str(self.mock_state['persistent_field']) - done = True - elif cmd == 'T' and not query: - self.mock_state['sweep_rate'] = float(args) - done = True - elif cmd == 'X': - if query: - # 9 is used for the don't-cares - result = 'X00A{0}C9H{1}M9{2}'.format(self.mock_state['activity'], - str(int(self.mock_state['heater_on'])), self.mock_state['mode']) - done = True - - - MockAbstractDevice.write(self, message, result, done) - - -name = 'IPS120-10' -implementation = MockIPS120_10 diff --git a/spacq/devices/oxford/mock/tests/__init__.py b/spacq/devices/oxford/mock/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/oxford/mock/tests/test_mock_ips120_10.py b/spacq/devices/oxford/mock/tests/test_mock_ips120_10.py deleted file mode 100644 index 87800d8..0000000 --- a/spacq/devices/oxford/mock/tests/test_mock_ips120_10.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import ips120_10 -from .. import mock_ips120_10 - -from ...tests.server.test_ips120_10 import IPS120_10Test - - -# Don't lose the real device. -real_IPS120_10 = ips120_10.IPS120_10 -is_mock = IPS120_10Test.mock - - -def setup(): - # Run the tests with a fake device. - ips120_10.IPS120_10 = mock_ips120_10.MockIPS120_10 - IPS120_10Test.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - ips120_10.IPS120_10 = real_IPS120_10 - IPS120_10Test.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/oxford/tests/__init__.py b/spacq/devices/oxford/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/oxford/tests/server/__init__.py b/spacq/devices/oxford/tests/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/oxford/tests/server/test_ips120_10.py b/spacq/devices/oxford/tests/server/test_ips120_10.py deleted file mode 100644 index e9b2159..0000000 --- a/spacq/devices/oxford/tests/server/test_ips120_10.py +++ /dev/null @@ -1,86 +0,0 @@ -from nose.tools import eq_ -from time import time -from unittest import main - -from spacq.interface.units import Quantity -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import ips120_10 - - -class IPS120_10Test(DeviceServerTestCase): - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=ips120_10.IPS120_10, - manufacturer='Oxford Instruments', model='IPS120-10') - - def testScenario(self): - """ - Change the sweep rate and output field. - - Note: Verification should also be done manually based on the output. - """ - - ips = self.obtain_device() - ips.perma_hot = True - - # Turn off the heater for now. - ips.heater_on = False - assert not ips.heater_on - - start_field = ips.field - - # Sweep slowly. - ips.sweep_rate = Quantity(0.1 / 60, 'T.s-1') - - field1 = Quantity(0.005, 'T') - - start_time = time() - ips.field = field1 - elapsed_time = time() - start_time - - eq_(ips.set_point, field1) - eq_(ips.field, field1) - - expected_time = 60 * abs(field1 - start_field).value / 0.1 - assert elapsed_time >= expected_time, 'Took {0} s, expected at least {1} s.'.format(elapsed_time, expected_time) - - # Make sure it stayed on. - assert ips.heater_on - - # Turn it off next time. - ips.perma_hot = False - - # Sweep quickly. - ips.sweep_rate = Quantity(0.5 / 60, 'T.s-1') - - field2 = Quantity(-0.015, 'T') - - start_time = time() - ips.field = field2 - elapsed_time = time() - start_time - - eq_(ips.field, field2) - - expected_time = 60 * abs(field2 - field1).value / 0.5 - assert elapsed_time >= expected_time, 'Took {0} s, expected at least {1} s.'.format(elapsed_time, expected_time) - - # Make sure it turned off. - assert not ips.heater_on - - ips.perma_hot = True - - # Reset back to zero. - field3 = Quantity(0.0, 'T') - - start_time = time() - ips.field = field3 - elapsed_time = time() - start_time - - eq_(ips.field, field3) - - expected_time = 60 * abs(field3 - field2).value / 0.5 - assert elapsed_time >= expected_time, 'Took {0} s, expected at least {1} s.'.format(elapsed_time, expected_time) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/rohde_schwarz/__init__.py b/spacq/devices/rohde_schwarz/__init__.py deleted file mode 100644 index 425d015..0000000 --- a/spacq/devices/rohde_schwarz/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -log = logging.getLogger(__name__) - - -name = 'Rohde & Schwarz' - -from . import smf100a -models = [smf100a] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_smf100a -mock_models = [mock_smf100a] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/rohde_schwarz/mock/__init__.py b/spacq/devices/rohde_schwarz/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/rohde_schwarz/mock/mock_smf100a.py b/spacq/devices/rohde_schwarz/mock/mock_smf100a.py deleted file mode 100644 index 4653408..0000000 --- a/spacq/devices/rohde_schwarz/mock/mock_smf100a.py +++ /dev/null @@ -1,55 +0,0 @@ -from ...mock.mock_abstract_device import MockAbstractDevice -from ..smf100a import SMF100A - -""" -Mock SMF100A signal generator. -""" - - -class MockSMF100A(MockAbstractDevice, SMF100A): - """ - Mock interface for the R&S SMF100A. - """ - - def __init__(self, *args, **kwargs): - self.mocking = SMF100A - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['enabled'] = 0 - self.mock_state['power'] = 0.007 # V - self.mock_state['frequency'] = 1e10 # Hz - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'output': - if cmd[1] == 'state': - if query: - result = self.mock_state['enabled'] - else: - self.mock_state['enabled'] = int(args) - done = True - elif cmd[0] == 'source': - if cmd[1] == 'power' and cmd[2] == 'power': - if query: - result = self.mock_state['power'] - else: - self.mock_state['power'] = float(args) - done = True - elif cmd[1] == 'frequency' and cmd[2] == 'cw': - if query: - result = self.mock_state['frequency'] - else: - self.mock_state['frequency'] = float(args) - done = True - elif cmd[0] == 'unit': - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = 'SMF100A' -implementation = MockSMF100A diff --git a/spacq/devices/rohde_schwarz/mock/tests/__init__.py b/spacq/devices/rohde_schwarz/mock/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/rohde_schwarz/mock/tests/test_mock_smf100a.py b/spacq/devices/rohde_schwarz/mock/tests/test_mock_smf100a.py deleted file mode 100644 index f7f5f2f..0000000 --- a/spacq/devices/rohde_schwarz/mock/tests/test_mock_smf100a.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import smf100a -from .. import mock_smf100a - -from ...tests.server.test_smf100a import SMF100ATest - - -# Don't lose the real device. -real_SMF100A = smf100a.SMF100A -is_mock = SMF100ATest.mock - - -def setup(): - # Run the tests with a fake device. - smf100a.SMF100A = mock_smf100a.MockSMF100A - SMF100ATest.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - smf100a.SMF100A = real_SMF100A - SMF100ATest.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/rohde_schwarz/smf100a.py b/spacq/devices/rohde_schwarz/smf100a.py deleted file mode 100644 index 9a6df8c..0000000 --- a/spacq/devices/rohde_schwarz/smf100a.py +++ /dev/null @@ -1,100 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import str_to_bool, quantity_wrapped, quantity_unwrapped - -""" -Interface for R&S SMF100A signal generator. -""" - - -class SMF100A(AbstractDevice): - """ - Interface for the SMF100A. - """ - - min_power = 0.007 # V - max_power = 7.071 # V - - min_freq = 1e9 # Hz - max_freq = 22e9 # Hz - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - read_write = ['enabled', 'power', 'frequency'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['enabled'].converter = str_to_bool - self.resources['power'].units = 'V' - self.resources['frequency'].units = 'Hz' - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - # Set the units for communication. - self.write('unit:power v') - - @Synchronized() - def reset(self): - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - def enabled(self): - """ - Whether the RF output is enabled. - """ - - return bool(int(self.ask('output:state?'))) - - @enabled.setter - def enabled(self, value): - self.write('output:state {0}'.format(int(value))) - - @property - @quantity_wrapped('V') - def power(self): - """ - The RF output power, as a quantity in V. - """ - - return float(self.ask('source:power:power?')) - - @power.setter - @quantity_unwrapped('V') - def power(self, value): - if value < self.min_power or value > self.max_power: - raise ValueError('Value {0} not within the allowed bounds: {1} to {2}'.format(value, - self.min_power, self.max_power)) - - self.write('source:power:power {0}'.format(value)) - - @property - @quantity_wrapped('Hz') - def frequency(self): - """ - The RF output frequency, as a quantity in Hz. - """ - - return float(self.ask('source:frequency:cw?')) - - @frequency.setter - @quantity_unwrapped('Hz') - def frequency(self, value): - if value < self.min_freq or value > self.max_freq: - raise ValueError('Value {0} not within the allowed bounds: {1} to {2}'.format(value, - self.min_freq, self.max_freq)) - - self.write('source:frequency:cw {0}'.format(value)) - - -name = 'SMF100A' -implementation = SMF100A diff --git a/spacq/devices/rohde_schwarz/tests/__init__.py b/spacq/devices/rohde_schwarz/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/rohde_schwarz/tests/server/__init__.py b/spacq/devices/rohde_schwarz/tests/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/rohde_schwarz/tests/server/test_smf100a.py b/spacq/devices/rohde_schwarz/tests/server/test_smf100a.py deleted file mode 100644 index 7d0faf8..0000000 --- a/spacq/devices/rohde_schwarz/tests/server/test_smf100a.py +++ /dev/null @@ -1,65 +0,0 @@ -from nose.tools import assert_almost_equal, eq_ -from unittest import main - -from spacq.interface.units import Quantity -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import smf100a - - -class SMF100ATest(DeviceServerTestCase): - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=smf100a.SMF100A, - manufacturer='Rohde & Schwarz', model='SMF100A') - - def testScenario(self): - """ - Change the settings. - """ - - sg = self.obtain_device() - sg.reset() - - sg.write('unit:power v') - - assert not sg.enabled - eq_(sg.frequency.value, 1e10) - assert_almost_equal(sg.power.value, 0.007, 3) - - sg.frequency = Quantity(5.6789, 'GHz') - sg.power = Quantity(100, 'mV') - sg.enabled = True - - assert sg.enabled - eq_(sg.frequency.value, 5.6789e9) - eq_(sg.power.value, 0.1) - - sg.enabled = False - assert not sg.enabled - - def testIllegal(self): - """ - These values aren't allowed. - """ - - sg = self.obtain_device() - - sg.write('unit:power v') - - try: - sg.power = Quantity(9.001, 'V') - except ValueError: - pass - else: - assert False, 'Expected ValueError' - - try: - sg.frequency = Quantity(0, 'Hz') - except ValueError: - pass - else: - assert False, 'Expected ValueError' - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/sample/__init__.py b/spacq/devices/sample/__init__.py deleted file mode 100644 index 8eeac36..0000000 --- a/spacq/devices/sample/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -log = logging.getLogger(__name__) - - -name = 'Sample' - -from . import abc1234 -models = [abc1234] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_abc1234 -mock_models = [mock_abc1234] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/sample/abc1234.py b/spacq/devices/sample/abc1234.py deleted file mode 100644 index a051314..0000000 --- a/spacq/devices/sample/abc1234.py +++ /dev/null @@ -1,80 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped - -""" -Sample ABC1234 Device -""" - - -class ABC1234(AbstractDevice): - """ - Interface for the Sample ABC1234. - """ - - allowed_settings = ['default value', 'something else', '...'] - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - read_only = ['reading'] - for name in read_only: - self.resources[name] = Resource(self, name) - - read_write = ['setting'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['reading'].units = 'A' - self.resources['setting'].allowed_values = self.allowed_settings - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - - # Override the default. - self.setting = '...' - - @Synchronized() - def reset(self): - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - def setting(self): - """ - This is a generic setting. - """ - - return self.ask('some:setting?') - - @setting.setter - def setting(self, value): - if value not in self.allowed_settings: - raise ValueError('Invalid setting: {0}'.format(value)) - - self.write('some:setting {0}'.format(value)) - - @property - @quantity_wrapped('A') - @Synchronized() - def reading(self): - """ - The value measured by the device. - """ - - log.debug('Getting reading.') - result = float(self.ask('read?')) - log.debug('Got reading: {0}'.format(result)) - - return result - - -name = 'ABC1234' -implementation = ABC1234 diff --git a/spacq/devices/sample/mock/__init__.py b/spacq/devices/sample/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/sample/mock/mock_abc1234.py b/spacq/devices/sample/mock/mock_abc1234.py deleted file mode 100644 index 67fc8b9..0000000 --- a/spacq/devices/sample/mock/mock_abc1234.py +++ /dev/null @@ -1,43 +0,0 @@ -import random - -from ...mock.mock_abstract_device import MockAbstractDevice -from ..abc1234 import ABC1234 - -""" -Mock Sample ABC1234 -""" - - -class MockABC1234(MockAbstractDevice, ABC1234): - """ - Mock interface for the Sample ABC1234. - """ - - def __init__(self, *args, **kwargs): - self.mocking = ABC1234 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['setting'] = 'default value' - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'some': - if cmd[1] == 'setting': - if query: - result = self.mock_state['setting'] - else: - self.mock_state['setting'] = args - done = True - elif cmd[0] == 'read' and query: - result = '-1.{0:04d}0000E-02'.format(random.randint(0, 9999)) - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = 'ABC1234' -implementation = MockABC1234 diff --git a/spacq/devices/sample/mock/tests/__init__.py b/spacq/devices/sample/mock/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/sample/mock/tests/test_mock_abc1234.py b/spacq/devices/sample/mock/tests/test_mock_abc1234.py deleted file mode 100644 index 891363e..0000000 --- a/spacq/devices/sample/mock/tests/test_mock_abc1234.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import abc1234 -from .. import mock_abc1234 - -from ...tests.server.test_abc1234 import ABC1234Test - - -# Don't lose the real device. -real_ABC1234 = abc1234.ABC1234 -is_mock = ABC1234Test.mock - - -def setup(): - # Run the tests with a fake device. - abc1234.ABC1234 = mock_abc1234.MockABC1234 - ABC1234Test.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - abc1234.ABC1234 = real_ABC1234 - ABC1234Test.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/sample/tests/__init__.py b/spacq/devices/sample/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/sample/tests/server/__init__.py b/spacq/devices/sample/tests/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/sample/tests/server/test_abc1234.py b/spacq/devices/sample/tests/server/test_abc1234.py deleted file mode 100644 index 30d3b22..0000000 --- a/spacq/devices/sample/tests/server/test_abc1234.py +++ /dev/null @@ -1,47 +0,0 @@ -from nose.tools import eq_ -from numbers import Real -from unittest import main - -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import abc1234 - - -class ABC1234Test(DeviceServerTestCase): - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=abc1234.ABC1234, - manufacturer='Sample', model='ABC1234') - - def testSetting(self): - """ - Test the setting. - """ - - abc = self.obtain_device() - abc.reset() - - eq_(abc.setting, 'default value') - - abc.setting = 'something else' - eq_(abc.setting, 'something else') - - try: - abc.setting = 'another thing' - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - def testGetValues(self): - """ - Obtain some values. - """ - - abc = self.obtain_device() - abc.reset() - - isinstance(abc.reading, Real) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/stanford_research_systems/__init__.py b/spacq/devices/stanford_research_systems/__init__.py deleted file mode 100644 index 59e22af..0000000 --- a/spacq/devices/stanford_research_systems/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -name = 'Stanford Research Systems' - -from . import sr830dsp, sg382, sim900 -models = [sr830dsp, sg382, sim900] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_sr830dsp, mock_sg382, mock_sim900 -mock_models = [mock_sr830dsp, mock_sg382, mock_sim900] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/stanford_research_systems/mock/__init__.py b/spacq/devices/stanford_research_systems/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/stanford_research_systems/mock/mock_sg382.py b/spacq/devices/stanford_research_systems/mock/mock_sg382.py deleted file mode 100644 index 75d495f..0000000 --- a/spacq/devices/stanford_research_systems/mock/mock_sg382.py +++ /dev/null @@ -1,27 +0,0 @@ -from ...mock.mock_abstract_device import MockAbstractDevice -from ..sg382 import SG382 - -""" -Mock SG382 Signal Generator -*******This is not complete************ -""" - - -class MockSG382(MockAbstractDevice, SG382): - """ - Mock interface for the SRS SG382 - """ - - def __init__(self, *args, **kwargs): - self.mocking = SG382 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['frequency'] = 1000 # Hz - self.mock_state['BNCAmplitude'] = 0.5 # V - - - -name = 'SG382 Signal Generator' -implementation = MockSG382 diff --git a/spacq/devices/stanford_research_systems/mock/mock_sim900.py b/spacq/devices/stanford_research_systems/mock/mock_sim900.py deleted file mode 100644 index 579340d..0000000 --- a/spacq/devices/stanford_research_systems/mock/mock_sim900.py +++ /dev/null @@ -1,27 +0,0 @@ -from ...mock.mock_abstract_device import MockAbstractDevice -from ..sim900 import sim900 - -""" -Mock Sim900+Sim928 voltage source -*******This is not complete************ -""" - - -class Mock_sim900(MockAbstractDevice, sim900): - """ - Mock interface for the SRS SG382 - """ - - def __init__(self, *args, **kwargs): - self.mocking = sim900 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['frequency'] = 1000 # Hz - self.mock_state['BNCAmplitude'] = 0.5 # V - - - -name = 'SIM900+SIM928 Voltage Source' -implementation = Mock_sim900 diff --git a/spacq/devices/stanford_research_systems/mock/mock_sr830dsp.py b/spacq/devices/stanford_research_systems/mock/mock_sr830dsp.py deleted file mode 100644 index 2c6eedc..0000000 --- a/spacq/devices/stanford_research_systems/mock/mock_sr830dsp.py +++ /dev/null @@ -1,26 +0,0 @@ -from ...mock.mock_abstract_device import MockAbstractDevice -from ..sr830dsp import SR830DSP - -""" -Mock SR830 DSP Lockin Amplifier -""" - - -class MockSR830DSP(MockAbstractDevice, SR830DSP): - """ - Mock interface for the SRS SR830 DSP - """ - - def __init__(self, *args, **kwargs): - self.mocking = SR830DSP - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['reference_freq'] = 1000 # Hz - self.mock_state['reference_amplitude'] = 0.5 # V - - - -name = 'SR830 DSP' -implementation = MockSR830DSP diff --git a/spacq/devices/stanford_research_systems/sg382.py b/spacq/devices/stanford_research_systems/sg382.py deleted file mode 100644 index acaecbd..0000000 --- a/spacq/devices/stanford_research_systems/sg382.py +++ /dev/null @@ -1,303 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped, quantity_unwrapped - -""" -Stanford Research Systems SG382 Signal Generator - -Set output frequencies, modulation, etc. (not fully implemented) -""" - - -class SG382(AbstractDevice): - """ - Interface for Stanford Research Systems SG382 - """ - - allowedEnable = set(['off','on']) - allowedModType = set(['0','1','2','3','4','5','6']) # AM, FM, Phase, Sweep, Pulse, Blank, IQ - allowedModType = set(['AM','FM','Phase','Sweep','Pulse','Blank','IQ']) - allowedModFunc = set(['0','1','2','3','4','5']) # Sine, Ramp, Triangle, Square, Noise, External - allowedModFunc = set(['Sine','Ramp','Triangle','Square','Noise','External']) - - minBNCAmp = 0.0028 # Vpp (peak-to-peak) - maxBNCAmp = 2.82 # Vpp - minTypeNAmp = 2e-6 # Vpp - maxTypeNAmp = 4.24 # Vpp - - minFreq = 1 # Hz - maxFreq = 2e9 # Hz - - maxPhase = 180.0 - minPhase = -180.0 - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - - read_write = ['frequency', 'phase', 'BNCAmplitude', 'typeNAmplitude','modulationType','modulationEnabled','BNCEnable','typeNEnable','modulationFunction','FMDeviation','AMDepth'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - #write_only = ['BNCEnable','typeNEnable'] - #for name in write_only: - # self.resources[name] = Resource(self, None, name) - - self.resources['frequency'].units = 'Hz' - self.resources['BNCAmplitude'].units = 'V' - self.resources['typeNAmplitude'].units = 'V' - self.resources['FMDeviation'].units='Hz' - self.resources['BNCEnable'].allowed_values = self.allowedEnable - self.resources['typeNEnable'].allowed_values = self.allowedEnable - self.resources['modulationEnabled'].allowed_values = self.allowedEnable - self.resources['modulationType'].allowed_values = self.allowedModType - self.resources['modulationFunction'].allowed_values = self.allowedModFunc - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - # Add any initialization here. Careful: if you reconnect it will run this - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*RST') - - @property - @quantity_wrapped('Hz') - def frequency(self): - """ - The output frequency of the signal generator - """ - - return float(self.ask('FREQ?')) - - @frequency.setter - @quantity_unwrapped('Hz') - def frequency(self, value): - if value < self.minFreq or value > self.maxFreq: - raise ValueError('Value {0} not within the allowed bounds: {1} to {2}'.format(value, self.minFreq, self.maxFreq)) - - self.write('FREQ {0}'.format(value)) - - @property - @quantity_wrapped('Hz') - def FMDeviation(self): - """ - The amplitude of the frequency modulation - """ - - return float(self.ask('FDEV?')) - - @FMDeviation.setter - @quantity_unwrapped('Hz') - def FMDeviation(self, value): - self.write('FDEV {0}'.format(value)) - - @property - def AMDepth(self): - """ - The amplitude of amplitude modulation (as a percentage of total signal [eg, 90.0 is 90%] - """ - - return float(self.ask('ADEP?')) - - @AMDepth.setter - def AMDepth(self, value): - self.write('ADEP {0}'.format(value)) - - @property - def phase(self): - # The phase of the output, (in degrees but SpanishAcquisition uses no units for this TODO: fix this) - return float(self.ask('PHAS?')) - - @phase.setter - def phase(self,value): - if float(value) < self.minPhase or float(value) > self.maxPhase: - raise ValueError('Value {0} not within the allowed bounds: {1} to {2}'.format(value, self.minPhase, self.maxPhase)) - - self.write('PHAS {0}'.format(value)) - - @property - @quantity_wrapped('V') - def BNCAmplitude(self): - # The amplitude of the output on the BNC output line (if enabled) - # Currently using Vpp value, as Spanish Acquisition doesn't handle dBm currently (TODO: add dBm units) - return float(self.ask('AMPL? VPP')) - - @BNCAmplitude.setter - @quantity_unwrapped('V') - def BNCAmplitude(self,value): - if value < self.minBNCAmp or value > self.maxBNCAmp: - raise ValueError('Value {0} not within the allowed bounds: {1} to {2}'.format(value, self.minBNCAmp, self.maxBNCAmp)) - - self.write('AMPL {0} VPP'.format(value)) - - @property - @quantity_wrapped('V') - def typeNAmplitude(self): - # The amplitude of the output on the Type N output line (if enabled) - # Currently using Vpp value, as Spanish Acquisition doesn't handle dBm currently (TODO: add dBm units) - return float(self.ask('AMPR? VPP')) - - @typeNAmplitude.setter - @quantity_unwrapped('V') - def typeNAmplitude(self,value): - if value < self.minTypeNAmp or value > self.maxTypeNAmp: - raise ValueError('Value {0} not within the allowed bounds: {1} to {2}'.format(value, self.minTypeNAmp, self.maxTypeNAmp)) - - self.write('AMPR {0} VPP'.format(value)) - - @property - def modulationType(self): - # Coded setting of the modulation type (eg, 1 = Amplitude Modulation) - result = self.ask('TYPE?') - if result == '0': - return 'AM' - elif result == '1': - return 'FM' - elif result == '2': - return 'Phase' - elif result == '3': - return 'Sweep' - elif result == '4': - return 'Pulse' - elif result == '5': - return 'Blank' - elif result == '6': - return 'IQ' - - @modulationType.setter - def modulationType(self,value): - if value not in self.allowedModType: - raise ValueError('Invalid Modulation Type: {0}'.format(value)) - - if value == 'AM': - outCode = 0 - elif value == 'FM': - outCode = 1 - elif value == 'Phase': - outCode = 2 - elif value == 'Sweep': - outCode = 3 - elif value == 'Pulse': - outCode = 4 - elif value == 'Blank': - outCode = 5 - elif value == 'IQ': - outCode = 6 - - self.write('TYPE {0}'.format(outCode)) - - @property - def modulationFunction(self): - # Coded setting of the modulation type (eg, 1 = Amplitude Modulation) - result = self.ask('MFNC?') - if result == '0': - return 'Sine' - elif result == '1': - return 'Ramp' - elif result == '2': - return 'Triangle' - elif result == '3': - return 'Square' - elif result == '4': - return 'Noise' - elif result == '5': - return 'External' - - @modulationFunction.setter - def modulationFunction(self,value): - if value not in self.allowedModFunc: - raise ValueError('Invalid Modulation Function: {0}'.format(value)) - - if value == 'Sine': - outCode = 0 - elif value == 'Ramp': - outCode = 1 - elif value == 'Triangle': - outCode = 2 - elif value == 'Square': - outCode = 3 - elif value == 'Noise': - outCode = 4 - elif value == 'External': - outCode = 5 - - self.write('MFNC {0}'.format(outCode)) - - @property - def modulationEnabled(self): - # Turn on/off modulation of signal - result = self.ask('MODL?') - if result == '0': - return 'off' - elif result == '1': - return 'on' - - @modulationEnabled.setter - def modulationEnabled(self,value): - if value not in self.allowedEnable: - raise ValueError('Invalid modulation enable setting: {0}'.format(value)) - - if value == 'off': - outCode = 0 - elif value == 'on': - outCode = 1 - - self.write('MODL {0}'.format(outCode)) - - @property - def BNCEnable(self): - # Turn on/off the BNC output (it is automatically off above 62.5 MHz) - result = self.ask('ENBL?') - if result == '0': - return 'off' - elif result == '1': - return 'on' - - @BNCEnable.setter - def BNCEnable(self,value): - if value not in self.allowedEnable: - raise ValueError('Invalid BNC enable setting: {0}'.format(value)) - - if value == 'off': - outCode = 0 - elif value == 'on': - outCode = 1 - - self.write('ENBL {0}'.format(outCode)) - - @property - def typeNEnable(self): - # Turn on/off the type-N output - result = self.ask('ENBR?') - if result == '0': - return 'off' - elif result == '1': - return 'on' - - @typeNEnable.setter - def typeNEnable(self,value): - if value not in self.allowedEnable: - raise ValueError('Invalid type-N enable setting: {0}'.format(value)) - - if value == 'off': - outCode = 0 - elif value == 'on': - outCode = 1 - - self.write('ENBR {0}'.format(outCode)) - -name = 'SG382 Signal Generator' -implementation = SG382 diff --git a/spacq/devices/stanford_research_systems/sim900.py b/spacq/devices/stanford_research_systems/sim900.py deleted file mode 100644 index 0ff073f..0000000 --- a/spacq/devices/stanford_research_systems/sim900.py +++ /dev/null @@ -1,129 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -import numpy -import time - -from spacq.interface.resources import Resource -from spacq.interface.units import Quantity -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice, AbstractSubdevice -from ..tools import quantity_unwrapped, quantity_wrapped, BinaryEncoder - -""" -SRS Sim900+Sim928 module voltage sources -Control the output voltages on all the ports. -""" - -class Port(AbstractSubdevice): - """ - An output port on the voltage source. - """ - def _setup(self): - AbstractSubdevice._setup(self) - - # These values are used to tune the input values according to empirical error. - self.gain = 1.0 - self.offset = 0.0 - - # Resources. - read_write = ['voltage'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['voltage'].units = 'V' - - @Synchronized() - def _connected(self): - AbstractSubdevice._connected(self) - - # Take an initial voltage reading (these get slow, so only use these to start, then just track what we've set) - self.device.write('SNDT {0}, "VOLT?"'.format(self.num)) - outputMessage = self.device.ask_raw('GETN? {0}, 80'.format(self.num)) # This is a read command to port num - - # Parse the output message - if outputMessage: #not empty - try: - byteCount = int(outputMessage[2:5])-2 # The last two bytes are the escape sequence - if byteCount > 7: # There is a strange error will it will double read - byteCount = (byteCount-2)/2 - result = float(outputMessage[5:(5+byteCount)]) - except: - result = -888 - else: - result = -999 - self.currentVoltage = result - - - def __init__(self, device, num, max_value, *args, **kwargs): - """ - Initialize the output port. - device: The ch6VoltageSource to which this Port belongs. - num: The index of this port. - max_value: Largest value the port can produce. - """ - - AbstractSubdevice.__init__(self, device, *args, **kwargs) - - self.num = num - self.min_value = -max_value - self.max_value = max_value - self.currVoltage = -999 - - @property - @quantity_wrapped('V') - def voltage(self): - - return self.currentVoltage - - @voltage.setter - @quantity_unwrapped('V') - def voltage(self, value): - """ - Set the voltage on this port, as a quantity in V. - """ - - resulting_voltage = value - self.currentVoltage = value - - # Write out through Sim900, to port, and set voltage -# self.device.write('SNDT {0}, "OPON"'.format(self.num)) - self.device.write('SNDT {0}, "VOLT {1}"'.format(self.num,resulting_voltage)) - -class sim900(AbstractDevice): - """ - Interface for the SRS Sim900+Sim928 voltage source - """ - - def _setup(self): - AbstractDevice._setup(self) - - self.ports = [] - for num in xrange(8): - port = Port(self, num+1, 20, **self.port_settings) # Naming convention on sim900 goes 1 to 8 - self.ports.append(port) - self.subdevices['port{0:02}'.format(num+1)] = port - - def __init__(self, port_settings=None, *args, **kwargs): - """ - Initialize the voltage source and all its ports. - port_settings: A dictionary of values to give to each port upon creation. - """ - - if port_settings is None: - self.port_settings = {} - else: - self.port_settings = port_settings - - AbstractDevice.__init__(self, *args, **kwargs) - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - # Add any initialization here. Careful: if you reconnect it will run this - self.write('*RST') - - -name = 'SIM900+SIM928 Voltage Source' -implementation = sim900 diff --git a/spacq/devices/stanford_research_systems/sr830dsp.py b/spacq/devices/stanford_research_systems/sr830dsp.py deleted file mode 100644 index 958275a..0000000 --- a/spacq/devices/stanford_research_systems/sr830dsp.py +++ /dev/null @@ -1,167 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice -from ..tools import quantity_wrapped, quantity_unwrapped - -""" -Agilent 34401A Digital Multimeter - -Obtain measurements from the multimeter. -""" - - -class SR830DSP(AbstractDevice): - """ - Interface for Stanford Research Systems SR830 DSP Lockin Amplifier - - NOTE: This implementation is currently very specific on its task, it could be made more flexible if desired - """ - - #allowed_nplc = set([0.02, 0.2, 1.0, 10.0, 100.0]) - #allowed_auto_zero = set(['off', 'on', 'once']) - - min_amp = 0.00 # V (actual minimum is 0.004) - max_amp = 5.000 # V - - min_freq = .001 # Hz - max_freq = 102000 # Hz - - min_phase = -360 # degrees - max_phase = 729 # degrees - - def _setup(self): - AbstractDevice._setup(self) - - # Resources. - #read_only = ['reading'] - #for name in read_only: - # self.resources[name] = Resource(self, name) - - read_write = ['reference_freq', 'reference_amplitude', 'reference_phase'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - read_only = ['amplitude_x', 'amplitude_y', 'amplitude_R', 'angle_theta'] - for name in read_only: - self.resources[name] = Resource(self, name, name, name, name) - - self.resources['reference_amplitude'].units = 'V' - self.resources['reference_freq'].units = 'Hz' - self.resources['amplitude_x'].units = 'V' - self.resources['amplitude_y'].units = 'V' - self.resources['amplitude_R'].units = 'V' - - @Synchronized() - def _connected(self): - AbstractDevice._connected(self) - self.write('OUTX 1') # Sets interface of Lockin to GPIB - self.write('FMOD 1') # Sets to use the internal oscillator - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - @Synchronized() - @quantity_wrapped('Hz') - def reference_freq(self): - """ - The frequency of the internal oscillator - """ - - return float(self.ask('FREQ?')) - - @reference_freq.setter - @Synchronized() - @quantity_unwrapped('Hz') - def reference_freq(self, value): - if value < self.min_freq or value > self.max_freq: - raise ValueError('Value {0} not within the allowed bounds: {1} to {2}'.format(value, self.min_freq, self.max_freq)) - - self.write('FREQ {0}'.format(value)) - - @property - def reference_phase(self): - """ - The phase of the internal oscillator - """ - - return float(self.ask('PHAS?')) - - @reference_phase.setter - def reference_phase(self, value): - if float(value) < self.min_phase or float(value) > self.max_phase: - raise ValueError('Value {0} not within the allowed bounds: {1} to {2}'.format(value, self.min_phase, self.max_phase)) - - self.write('PHAS {0}'.format(value)) - - @property - @Synchronized() - @quantity_wrapped('V') - def reference_amplitude(self): - """ - The amplitude of the internal oscillator sine wave - """ - - return float(self.ask('SLVL?')) - - @reference_amplitude.setter - @Synchronized() - @quantity_unwrapped('V') - def reference_amplitude(self, value): - if value < self.min_amp or value > self.max_amp: - raise ValueError('Value {0} not within the allowed bounds: {1} to {2}'.format(value, self.min_amp, self.max_amp)) - - self.write('SLVL {0}'.format(value)) - - @property - @Synchronized() - @quantity_wrapped('V') - def amplitude_x(self): - """ - The amplitude of the x component of lock-in signal - """ - - return float(self.ask('OUTP? 1')) - - @property - @Synchronized() - @quantity_wrapped('V') - def amplitude_y(self): - """ - The amplitude of the y component of lock-in signal - """ - - return float(self.ask('OUTP? 2')) - - @property - @Synchronized() - @quantity_wrapped('V') - def amplitude_R(self): - """ - The amplitude of the R magnitude of lock-in signal - """ - - return float(self.ask('OUTP? 3')) - - @property - @Synchronized() - def angle_theta(self): - """ - The amplitude of the angle theta of lock-in signal - """ - - return float(self.ask('OUTP? 4')) - - -name = 'SR830 DSP' -implementation = SR830DSP diff --git a/spacq/devices/tektronix/__init__.py b/spacq/devices/tektronix/__init__.py deleted file mode 100644 index 19cb7d5..0000000 --- a/spacq/devices/tektronix/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -log = logging.getLogger(__name__) - - -name = 'Tektronix' - -from . import awg5014b, dpo7104 -models = [awg5014b, dpo7104] -log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) - -from .mock import mock_awg5014b, mock_dpo7104 -mock_models = [mock_awg5014b, mock_dpo7104] -log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/tektronix/awg5014b.py b/spacq/devices/tektronix/awg5014b.py deleted file mode 100644 index cb314a5..0000000 --- a/spacq/devices/tektronix/awg5014b.py +++ /dev/null @@ -1,440 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -import struct - -from spacq.interface.resources import Resource -from spacq.interface.units import Quantity -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice, AbstractSubdevice -from ..tools import str_to_bool, quantity_wrapped, quantity_unwrapped, BlockData - -""" -Tektronix AWG5014B Arbitrary Waveform Generator - -Control the AWG's settings and output waveforms. -""" - - -class Marker(AbstractSubdevice): - """ - Marker channel of an output channel. - """ - - def _setup(self): - AbstractSubdevice._setup(self) - - # Resources. - read_write = ['delay', 'high', 'low'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['delay'].units = 's' - self.resources['high'].units = 'V' - self.resources['low'].units = 'V' - - def __init__(self, device, channel, number, *args, **kwargs): - AbstractSubdevice.__init__(self, device, *args, **kwargs) - - self.channel = channel - self.number = number - - @property - @quantity_wrapped('s') - def delay(self): - """ - The marker delay, as a quantity in s. - """ - - return float(self.device.ask('source{0}:marker{1}:delay?'.format(self.channel, self.number))) - - @delay.setter - @quantity_unwrapped('s') - def delay(self, v): - self.device.write('source{0}:marker{1}:delay {2}'.format(self.channel, self.number, v)) - - @property - @quantity_wrapped('V') - def high(self): - """ - The marker high voltage, as a quantity in V. - """ - - return float(self.device.ask('source{0}:marker{1}:voltage:high?'.format(self.channel, self.number))) - - @high.setter - @quantity_unwrapped('V') - def high(self, v): - self.device.write('source{0}:marker{1}:voltage:high {2}'.format(self.channel, self.number, v)) - - @property - @quantity_wrapped('V') - def low(self): - """ - The marker low voltage, as a quantity in V. - """ - - return float(self.device.ask('source{0}:marker{1}:voltage:low?'.format(self.channel, self.number))) - - @low.setter - @quantity_unwrapped('V') - def low(self, v): - self.device.write('source{0}:marker{1}:voltage:low {2}'.format(self.channel, self.number, v)) - - -class Channel(AbstractSubdevice): - """ - Output channel of the AWG. - """ - - # Zero-to-peak amplitude range. - min_amplitude = 0.01 # V - max_amplitude = 2.25 # V - - def _setup(self): - AbstractSubdevice._setup(self) - - self.markers = [None] # There is no marker 0. - for mark in xrange(1, 3): - marker = Marker(self.device, self.channel, mark) - self.markers.append(marker) - self.subdevices['marker{0}'.format(mark)] = marker - - # Resources. - read_write = ['waveform_name', 'enabled', 'amplitude'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['enabled'].converter = str_to_bool - self.resources['amplitude'].units = 'V' - - def __init__(self, device, channel, *args, **kwargs): - self.channel = channel - - AbstractSubdevice.__init__(self, device, *args, **kwargs) - - @property - def waveform_name(self): - """ - The name of the output waveform for the channel. - """ - - result = self.device.ask('source{0}:waveform?'.format(self.channel)) - # The name is in quotes. - return result[1:-1] - - @waveform_name.setter - def waveform_name(self, v): - self.device.write('source{0}:waveform "{1}"'.format(self.channel, v)) - - @waveform_name.deleter - def waveform_name(self): - self.waveform_name = '' - - @property - def enabled(self): - """ - The output state (on/off) of the channel. - """ - - result = self.device.ask('output{0}:state?'.format(self.channel)) - # The value is a string holding a digit. - return bool(int(result)) - - @enabled.setter - def enabled(self, v): - self.device.write('output{0}:state {1}'.format(self.channel, int(v))) - - @property - @quantity_wrapped('V') - def amplitude(self): - """ - The zero-to-peak amplitude of the channel in V. - """ - - # Convert peak-to-peak to zero-to-peak. - return float(self.device.ask('source{0}:voltage?'.format(self.channel))) / 2 - - @amplitude.setter - @quantity_unwrapped('V') - def amplitude(self, v): - # Convert zero-to-peak to peak-to-peak. - self.device.write('source{0}:voltage {1:E}'.format(self.channel, 2 * v)) - - def set_waveform(self, waveform, markers=None, name=None): - """ - Set the waveform on this channel. - - The waveform data should be in V. - """ - - if name is None: - name = 'Channel {0}'.format(self.channel) - - # Clear existing. - if name in self.device.waveform_names: - self.device.delete_waveform(name) - - # Normalize waveform. - max_amp = max(abs(x) for x in waveform) - if max_amp > self.max_amplitude: - raise ValueError('Amplitude {0} V exceeds maximum of {1} V'.format(max_amp, self.max_amplitude)) - elif max_amp > 0: - if max_amp < self.min_amplitude: - max_amp = self.min_amplitude - - waveform = [x / max_amp for x in waveform] - - self.amplitude = Quantity(max_amp, 'V') - - # Create new. - self.device.create_waveform(name, waveform, markers) - self.waveform_name = name - - -class AWG5014B(AbstractDevice): - """ - Interface for Tektronix AWG5014B AWG. - """ - - allowed_run_modes = set(['continuous', 'triggered', 'gated', 'sequence']) - - def _setup(self): - AbstractDevice._setup(self) - - self.channels = [None] # There is no channel 0. - for chan in xrange(1, 5): - channel = Channel(self, chan) - self.channels.append(channel) - self.subdevices['channel{0}'.format(chan)] = channel - - # Resources. - read_write = ['sampling_rate', 'run_mode', 'enabled'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['sampling_rate'].units = 'Hz' - self.resources['run_mode'].allowed_values = self.allowed_run_modes - self.resources['enabled'].converter = str_to_bool - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - @property - def data_bits(self): - """ - How many bits of each data point represent the data itself. - """ - - return 14 - - @property - def value_range(self): - """ - The range of values possible for each data point. - """ - - # The sent values are unsigned. - return (0, 2 ** self.data_bits - 1) - - @property - @quantity_wrapped('Hz') - def sampling_rate(self): - """ - The sampling rate of the AWG in Hz. - """ - - return float(self.ask('source1:frequency?')) - - @sampling_rate.setter - @quantity_unwrapped('Hz') - def sampling_rate(self, value): - if value < 1e7 or value > 1.2e9: - raise ValueError('Sampling rate must be between 1e7 Hz and 1.2e9 Hz, not {0:n} Hz'.format(value)) - - self.write('source1:frequency {0:E}'.format(value)) - - @property - def run_mode(self): - """ - The run mode of the AWG. One of: continuous, triggered, gated, sequence. - """ - - mode = self.ask('awgcontrol:rmode?').lower() - - if mode.startswith('cont'): - return 'continuous' - elif mode.startswith('trig'): - return 'triggered' - elif mode.startswith('gat'): - return 'gated' - elif mode.startswith('seq'): - return 'sequence' - else: - ValueError('Unknown mode: {0}'.format(mode)) - - @run_mode.setter - def run_mode(self, value): - if value not in self.allowed_run_modes: - raise ValueError('Invalid run mode: {0}'.format(value)) - - self.write('awgcontrol:rmode {0}'.format(value)) - - @property - @Synchronized() - def waveform_names(self): - """ - A list of all waveforms in the AWG. - """ - - num_waveforms = int(self.ask('wlist:size?')) - - self.multi_command_start() - # Waveforms on the AWG are numbered from 0. - for i in xrange(num_waveforms): - self.ask('wlist:name? {0}'.format(i)) - names = self.multi_command_stop() - - # Names are in quotes. - return [name[1:-1] for name in names] - - @Synchronized() - def get_waveform(self, name): - self.status.append('Getting waveform "{0}"'.format(name)) - - try: - log.debug('Getting waveform "{0}" from device "{1}".'.format(name, self.name)) - - block_data = self.ask_raw('wlist:waveform:data? "{0}"'.format(name)) - packed_data = BlockData.from_block_data(block_data) - waveform_length = len(packed_data) / 2 - data = struct.unpack('<{0}H'.format(waveform_length), packed_data) - data = [x & 2 ** 14 - 1 for x in data] # Filter out marker data. - - min_value, max_value = self.value_range - range_diff = max_value - min_value - data = [2.0 * (x - min_value) / range_diff - 1.0 for x in data] - - log.debug('Got waveform "{0}" from device "{1}": {2!r}'.format(name, self.name, data)) - - return data - finally: - self.status.pop() - - @Synchronized() - def create_waveform(self, name, data, markers=None): - """ - Create a new waveform on the AWG. - - The waveform data should be on [-1, 1]. - """ - - self.status.append('Creating waveform "{0}"'.format(name)) - - try: - log.debug('Creating waveform "{0}" on device "{1}" with data: {2!r}'.format(name, self.name, data)) - - min_value, max_value = self.value_range - range_diff = max_value - min_value - data = [min_value + int(range_diff * (x + 1.0) / 2.0) for x in data] - - waveform_length = len(data) - self.write('wlist:waveform:new "{0}", {1}, integer'.format(name, waveform_length)) - - if markers: - # The markers are in the top 2 bits. - for marker_num, marker_bit in zip([1, 2], [1 << 14, 1 << 15]): - try: - for i, marker_datum in enumerate(markers[marker_num]): - if marker_datum: - data[i] += marker_bit - log.debug('Added marker {0} to waveform "{1}" device "{1}": {2!r}'.format(marker_num, - name, self.name, markers[marker_num])) - except KeyError: - pass - - extra_markers = set(markers) - set([1, 2]) - for extra in extra_markers: - log.warning('Marker {0} ignored: {1!r}'.format(extra, markers[extra])) - - # Always 16-bit, unsigned, little-endian. - packed_data = struct.pack('<{0}H'.format(waveform_length), *data) - block_data = BlockData.to_block_data(packed_data) - - log.debug('Sending packed block waveform data for "{0}" on device "{1!r}": {2}'.format(name, - self.name, block_data)) - - self.write('wlist:waveform:data "{0}", {1}'.format(name, block_data)) - finally: - self.status.pop() - - def delete_waveform(self, name): - """ - Remove a waveform on the AWG. - """ - - if name not in self.waveform_names: - raise ValueError('No such waveform "{0}"'.format(name)) - - self.write('wlist:waveform:delete "{0}"'.format(name)) - - @property - def enabled(self): - """ - The continuous run state (on/off) of the AWG. - """ - - state = self.ask('awgcontrol:rstate?') - - if state == '0': - return False - elif state in ['1', '2']: - # Either on or waiting for trigger. - return True - else: - raise ValueError('State "{0}" not implemented.'.format(state)) - - @enabled.setter - def enabled(self, v): - if v: - log.debug('Enabling "{0}".'.format(self.name)) - - self.write('awgcontrol:run') - else: - log.debug('Disabling "{0}".'.format(self.name)) - - self.write('awgcontrol:stop') - - @property - def waiting_for_trigger(self): - """ - Whether the AWG is waiting for a trigger. - """ - - return '1' == self.ask('awgcontrol:rstate?') - - def trigger(self): - """ - Force a trigger event. - """ - - self.write('*trg') - - def clear_channels(self): - """ - Delete all waveforms from all channels. - """ - - for channel in self.channels[1:]: - del channel.waveform_name - - -name = 'AWG5014B' -implementation = AWG5014B diff --git a/spacq/devices/tektronix/dpo7104.py b/spacq/devices/tektronix/dpo7104.py deleted file mode 100644 index 45375e4..0000000 --- a/spacq/devices/tektronix/dpo7104.py +++ /dev/null @@ -1,435 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from math import ceil -from numpy import linspace -import struct - -from spacq.interface.resources import Resource -from spacq.tool.box import Synchronized - -from ..abstract_device import AbstractDevice, AbstractSubdevice -from ..tools import str_to_bool, quantity_wrapped, quantity_unwrapped, BlockData - -""" -Tektronix DPO7104 Digital Phosphor Oscilloscope - -Control the DPO's settings and input waveforms. -""" - - -class Channel(AbstractSubdevice): - """ - Input channel of the DPO. - """ - - def _setup(self): - AbstractSubdevice._setup(self) - - # Resources. - read_only = ['waveform'] - for name in read_only: - self.resources[name] = Resource(self, name) - - read_write = ['enabled'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['waveform'].slow = True - self.resources['waveform'].display_units = 'V' - self.resources['enabled'].converter = str_to_bool - - def __init__(self, device, channel, *args, **kwargs): - self.channel = channel - - AbstractSubdevice.__init__(self, device, *args, **kwargs) - - @property - def acquisition_window(self): - """ - The minimum and maximum obtainable values in V. - """ - - # 10 divisions total. - max_value = 5 * self.scale.value - min_value = -max_value - - offset = self.offset.value - - return (min_value + offset, max_value + offset) - - def transform_waveform(self, waveform): - """ - Transform some curve data onto the true amplitude interval in V, and intermix time values in s. - """ - - value_min, value_max = self.device.value_range - value_diff = value_max - value_min - - real_min, real_max = self.acquisition_window - real_diff = real_max - real_min - - times = linspace(0, self.device.time_scale.value, len(waveform)) - - return [(time, real_diff * float(x - value_min) / value_diff + real_min) for time, x in zip(times, waveform)] - - @property - def enabled(self): - """ - The input state (on/off) of the channel. - """ - - result = self.device.ask('select:ch{0}?'.format(self.channel)) - return bool(int(result)) - - @enabled.setter - def enabled(self, value): - self.device.write('select:ch{0} {1}'.format(self.channel, 'on' if value else 'off')) - - @property - @Synchronized() - def waveform(self): - """ - A waveform acquired by the scope. - - Values are returned in the format [(time1, value1), (time2, value2), ...]. - """ - - self.device.status.append('Getting waveform for channel {0}'.format(self.channel)) - - try: - self.device.data_source = self.channel - - if self.device.fastframe: - # Only get the last frame. - frame = self.device.fastframe_count - else: - frame = 1 - - self.device.fastframe_start = frame - self.device.fastframe_stop = frame - - # Receive in chunks. - num_data_points = self.device.record_length - num_transmissions = int(ceil(num_data_points / self.device.max_receive_samples)) - - curve = [] - for i in xrange(num_transmissions): - self.device.data_start = int(i * self.device.max_receive_samples) + 1 - self.device.data_stop = int((i + 1) * self.device.max_receive_samples) - - curve_raw = self.device.ask_raw('curve?') - curve.append(BlockData.from_block_data(curve_raw)) - - curve = ''.join(curve) - - format_code = self.device.byte_format_letters[self.device.waveform_bytes] - curve_data = struct.unpack('!{0}{1}'.format(num_data_points, format_code), curve) - - return self.transform_waveform(curve_data) - finally: - self.device.status.pop() - - @property - @quantity_wrapped('V') - def scale(self): - """ - Vertical scale for the channel, as a quantity in V. - - Note: This is for a single division, of which there are 10. - """ - - return float(self.device.ask('ch{0}:scale?'.format(self.channel))) - - @scale.setter - @quantity_unwrapped('V') - def scale(self, value): - self.device.write('ch{0}:scale {1}'.format(self.channel, value)) - - @property - @quantity_wrapped('V') - def offset(self): - """ - Vertical offset for the channel, as a quantity in V. - """ - - return float(self.device.ask('ch{0}:offset?'.format(self.channel))) - - @offset.setter - @quantity_unwrapped('V') - def offset(self, value): - self.device.write('ch{0}:offset {1}'.format(self.channel, value)) - - -class DPO7104(AbstractDevice): - """ - Interface for Tektronix DPO7104 DPO. - """ - - byte_format_letters = [None, 'b', 'h'] - - # The upper limit to the number of samples to be received per transmission. - max_receive_samples = 1e7 - - allowed_stopafters = ['runstop', 'sequence'] - allowed_waveform_bytes = [1, 2] # Channel data only. - allowed_fastframe_sums = set(['none', 'average', 'envelope']) - - def _setup(self): - AbstractDevice._setup(self) - - self.channels = [None] # There is no channel 0. - for chan in xrange(1, 5): - channel = Channel(self, chan) - self.channels.append(channel) - self.subdevices['channel{0}'.format(chan)] = channel - - # Resources. - read_write = ['sample_rate', 'time_scale'] - for name in read_write: - self.resources[name] = Resource(self, name, name) - - self.resources['sample_rate'].units = 'Hz' - self.resources['time_scale'].units = 's' - - @Synchronized() - def reset(self): - """ - Reset the device to its default state. - """ - - log.info('Resetting "{0}".'.format(self.name)) - self.write('*rst') - - def autoset(self): - """ - Autoset the scaling. - """ - - self.write('autoset execute') - - @property - def stopafter(self): - """ - The acqusition mode. - """ - - value = self.ask('acquire:stopafter?').lower() - - if value.startswith('runst'): - return 'runstop' - elif value.startswith('seq'): - return 'sequence' - - @stopafter.setter - def stopafter(self, value): - if value not in self.allowed_stopafters: - raise ValueError('Invalid acquisition mode: {0}'.format(value)) - - self.write('acquire:stopafter {0}'.format(value)) - - @property - def waveform_bytes(self): - """ - Number of bytes per data point in the acquired waveforms. - """ - - return int(self.ask('wfmoutpre:byt_nr?')) - - @waveform_bytes.setter - def waveform_bytes(self, value): - self.write('wfmoutpre:byt_nr {0}'.format(value)) - - @property - def value_range(self): - """ - Range of values possible for each data point. - """ - - # The returned values are signed. - bits = 8 * self.waveform_bytes - 1 - max_val = 2 ** bits - - return (-max_val, max_val - 1) - - @property - def acquiring(self): - """ - Whether the device is currently acquiring data. - """ - - result = self.ask('acquire:state?') - return bool(int(result)) - - @acquiring.setter - def acquiring(self, value): - self.write('acquire:state {0}'.format(str(int(value)))) - - @property - @quantity_wrapped('Hz') - def sample_rate(self): - """ - The sample rate in s-1. - """ - - return float(self.ask('horizontal:mode:samplerate?')) - - @sample_rate.setter - @quantity_unwrapped('Hz') - def sample_rate(self, value): - self.write('horizontal:mode:samplerate {0}'.format(value)) - - @property - @quantity_wrapped('s') - def time_scale(self): - """ - The length for a waveform. - """ - - return float(self.ask('horizontal:divisions?')) * float(self.ask('horizontal:mode:scale?')) - - @time_scale.setter - @quantity_unwrapped('s') - def time_scale(self, value): - self.write('horizontal:mode:scale {0}'.format(value / float(self.ask('horizontal:divisions?')))) - - @property - def data_source(self): - """ - The source from which to transfer data. - """ - - result = self.ask('data:source?') - assert len(result) == 3 and result.startswith('CH') - - return int(result[2]) - - @data_source.setter - def data_source(self, value): - self.write('data:source ch{0}'.format(value)) - - @property - def data_start(self): - """ - The first data point to transfer. - """ - - return int(self.ask('data:start?')) - - @data_start.setter - def data_start(self, value): - self.write('data:start {0}'.format(value)) - - @property - def data_stop(self): - """ - The last data point to transfer. - """ - - return int(self.ask('data:stop?')) - - @data_stop.setter - def data_stop(self, value): - self.write('data:stop {0}'.format(value)) - - @property - def record_length(self): - """ - The number of data points in a waveform. - """ - - return int(self.ask('horizontal:mode:recordlength?')) - - @Synchronized() - def acquire(self): - """ - Cause the DPO to acquire a single waveform. - """ - - self.acquiring = True - - @property - def fastframe(self): - """ - Whether fastframe is enabled. - """ - - return bool(int(self.ask('horizontal:fastframe:state?'))) - - @fastframe.setter - def fastframe(self, value): - return self.write('horizontal:fastframe:state {0}'.format(int(value))) - - @property - def fastframe_sum(self): - """ - The fastframe summary frame. - """ - - result = self.ask('horizontal:fastframe:sumframe?').lower() - - if result.startswith('non'): - return 'none' - elif result.startswith('env'): - return 'envelope' - elif result.startswith('ave'): - return 'average' - else: - ValueError('Unknown summary mode: {0}'.format(result)) - - @fastframe_sum.setter - def fastframe_sum(self, value): - if value not in self.allowed_fastframe_sums: - raise ValueError('Invalid summary frame mode: {0}'.format(value)) - - return self.write('horizontal:fastframe:sumframe {0}'.format(value)) - - @property - def fastframe_count(self): - """ - The number of waveforms to acquire in fastframe mode. - """ - - return int(self.ask('horizontal:fastframe:count?')) - - @fastframe_count.setter - def fastframe_count(self, value): - if value <= 0: - raise ValueError('Must provide a positive integer, not "{0}"'.format(value)) - - self.write('horizontal:fastframe:count {0:d}'.format(value)) - - @property - def fastframe_start(self): - """ - The first frame to transfer. - """ - - return int(self.ask('data:framestart?')) - - @fastframe_start.setter - def fastframe_start(self, value): - self.write('data:framestart {0}'.format(value)) - - @property - def fastframe_stop(self): - """ - The last frame to transfer. - """ - - return int(self.ask('data:framestop?')) - - @fastframe_stop.setter - def fastframe_stop(self, value): - self.write('data:framestop {0}'.format(value)) - - @property - def acquisitions(self): - """ - The number of acquisitions made so far on the oscilloscope. - """ - - return int(self.ask('acquire:numacq?')) - -name = 'DPO7104' -implementation = DPO7104 diff --git a/spacq/devices/tektronix/mock/__init__.py b/spacq/devices/tektronix/mock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/tektronix/mock/mock_awg5014b.py b/spacq/devices/tektronix/mock/mock_awg5014b.py deleted file mode 100644 index 9a234d0..0000000 --- a/spacq/devices/tektronix/mock/mock_awg5014b.py +++ /dev/null @@ -1,245 +0,0 @@ -import struct - -from ...mock.mock_abstract_device import MockAbstractDevice -from ...tools import BlockData -from ..awg5014b import AWG5014B - -""" -Mock Tektronix AWG5014B Arbitrary Waveform Generator - -Control a fake AWG's settings and output waveforms. -""" - - -class Waveform(object): - """ - Mock waveform. - """ - - def __init__(self, name, length): - self.name = name - self.length = length - self._data = [0] * length - - @property - def data(self): - return self._data - - @data.setter - def data(self, v): - new_length = len(v) - - if new_length <= self.length: - self._data[:new_length] = v - - -class MockMarker(object): - """ - A mock marker for a mock channel. - """ - - def __init__(self): - self.delay = 0.0 # s - self.low = 0.0 # V - self.high = 1.0 # V - - -class MockChannel(object): - """ - A mock channel for a mock AWG. - """ - - def __init__(self): - self.enabled = False - self.waveform_name = '' - self.voltage = 1.0 - - self.markers = [None] # There is no marker 0. - for _ in xrange(1, 3): - self.markers.append(MockMarker()) - - -class MockAWG5014B(MockAbstractDevice, AWG5014B): - """ - Mock interface for Tektronix AWG5014B AWG. - """ - - def __init__(self, *args, **kwargs): - """ - Pretend to connect to the AWG, but do initialize with some values. - """ - - self.mocking = AWG5014B - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['run_mode'] = 'continuous' - self.mock_state['run_state'] = '0' - self.mock_state['frequency'] = 1.2e9 # Hz - - self.mock_state['wlist'] = [] - self.mock_state['wlist'].append(Waveform('"predefined waveform"', 5)) - self.mock_state['wlist'][0].data = list(xrange(5)) - - self.mock_state['channels'] = [None] # There is no channel 0. - for _ in xrange(1, 5): - self.mock_state['channels'].append(MockChannel()) - - def find_wave(self, name): - """ - Find a Waveform object by name. - """ - - for wave in self.mock_state['wlist']: - if wave.name == name: - return wave - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == '*trg': - done = True - elif cmd[0] == 'awgcontrol': - if cmd[1] == 'run': - if self.mock_state['run_mode'] in ['triggered', 'gated']: - self.mock_state['run_state'] = '1' - else: - self.mock_state['run_state'] = '2' - done = True - elif cmd[1] == 'stop': - self.mock_state['run_state'] = '0' - done = True - elif cmd[1] == 'rstate' and query: - result = self.mock_state['run_state'] - done = True - elif cmd[1] == 'rmode': - if query: - result = self.mock_state['run_mode'] - done = True - else: - mode = args - self.mock_state['run_mode'] = mode - - if self.mock_state['run_state'] != '0': - if mode in ['triggered', 'gated']: - self.mock_state['run_state'] = '1' - else: - self.mock_state['run_state'] = '2' - done = True - elif cmd[0] == 'wlist': - if cmd[1] == 'size' and query: - result = str(len(self.mock_state['wlist'])) - done = True - elif cmd[1] == 'name' and query: - num = args - name = self.mock_state['wlist'][int(num)].name - result = '"{0}"'.format(name[1:-1]) - done = True - elif cmd[1] == 'waveform' and cmd[2] == 'new': - name, length, kind = [arg.strip() for arg in args.split(',')] - length = int(length) - - if kind == 'integer': - self.mock_state['wlist'].append(Waveform(name, length)) - done = True - elif cmd[1] == 'waveform' and cmd[2] == 'data': - if query: - name = args - - wave = self.find_wave(name) - - data = wave.data - data = [datum + marker * 2 ** 14 for (datum, marker) in zip(data, wave.marker1)] - data = [datum + marker * 2 ** 15 for (datum, marker) in zip(data, wave.marker2)] - - waveform_length = len(data) - packed_data = struct.pack('<{0}H'.format(waveform_length), *data) - result = BlockData.to_block_data(packed_data) - else: - name, block_data = args.split(',', 1) - name = name.strip() - block_data = block_data.lstrip() - packed_data = BlockData.from_block_data(block_data) - waveform_length = len(packed_data) / 2 - data = struct.unpack('<{0}H'.format(waveform_length), packed_data) - - wave = self.find_wave(name) - wave.data = [x & 2 ** 14 - 1 for x in data] - wave.marker1 = [1 if x & 2 ** 14 else 0 for x in data] - wave.marker2 = [1 if x & 2 ** 15 else 0 for x in data] - - done = True - elif cmd[1] == 'waveform' and cmd[2] == 'delete': - self.mock_state['wlist'].remove(self.find_wave(args)) - done = True - elif cmd[0].startswith('source'): - source = int(cmd[0][6]) - channel = self.mock_state['channels'][source] - - if cmd[1] == 'waveform': - if query: - result = channel.waveform_name - else: - name = args - channel.waveform_name = name - if name == '""': - channel.enabled = False - done = True - elif cmd[1] == 'frequency': - if query: - result = self.mock_state['frequency'] - else: - frequency = args - self.mock_state['frequency'] = float(frequency) - done = True - elif cmd[1] == 'voltage': - if query: - result = channel.voltage - else: - voltage = args - channel.voltage = float(voltage) - done = True - elif cmd[1].startswith('marker'): - marker_num = int(cmd[1][6]) - marker = channel.markers[marker_num] - - if cmd[2] == 'delay': - if query: - result = marker.delay - else: - delay = args - marker.delay = float(delay) - done = True - elif cmd[2] == 'voltage' and cmd[3] == 'high': - if query: - result = marker.high - else: - voltage = args - marker.high = float(voltage) - done = True - elif cmd[2] == 'voltage' and cmd[3] == 'low': - if query: - result = marker.low - else: - voltage = args - marker.low = float(voltage) - done = True - elif cmd[0].startswith('output'): - output = int(cmd[0][6]) - channel = self.mock_state['channels'][output] - - if cmd[1] == 'state': - if query: - result = str(int(channel.enabled)) - else: - state = args - channel.enabled = (state == '1') - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = 'AWG5014B' -implementation = MockAWG5014B diff --git a/spacq/devices/tektronix/mock/mock_dpo7104.py b/spacq/devices/tektronix/mock/mock_dpo7104.py deleted file mode 100644 index 17219b8..0000000 --- a/spacq/devices/tektronix/mock/mock_dpo7104.py +++ /dev/null @@ -1,194 +0,0 @@ -from math import pi, sin -from random import randint -from struct import pack - -from ...mock.mock_abstract_device import MockAbstractDevice -from ...tools import BlockData -from ..dpo7104 import DPO7104 - -""" -Mock Tektronix DPO7104 Digital Phosphor Oscilloscope - -Control a fake DPO's settings and input waveforms. -""" - - -class MockChannel(object): - """ - A mock channel for a mock DPO. - """ - - def __init__(self): - self.enabled = False - self.scale = 0.5 - self.offset = 0.0 - - -class MockDPO7104(MockAbstractDevice, DPO7104): - """ - Mock interface for Tektronix DPO7104 DPO. - """ - - def __init__(self, *args, **kwargs): - self.mocking = DPO7104 - - MockAbstractDevice.__init__(self, *args, **kwargs) - - def _reset(self): - self.mock_state['stopafter'] = 'runstop' - self.mock_state['acquire_state'] = True - - self.mock_state['fastframe'] = False - self.mock_state['fastframe_sum'] = 'none' - self.mock_state['fastframe_count'] = 1 - - self.mock_state['samplerate'] = 1e10 # Hz - self.mock_state['horizontal_scale'] = 1e-8 # s - - self.mock_state['waveform_bytes'] = 2 - - self.mock_state['data_start'] = 1 - self.mock_state['data_stop'] = self._record_length - self.mock_state['data_source'] = 1 - self.mock_state['data_framestart'] = 1 - self.mock_state['data_framestop'] = 1 - - self.mock_state['channels'] = [None] # There is no channel 0. - for _ in xrange(1, 5): - self.mock_state['channels'].append(MockChannel()) - self.mock_state['channels'][1].enabled = True - - @property - def _record_length(self): - return int(10 * self.mock_state['samplerate'] * self.mock_state['horizontal_scale']) - - def write(self, message, result=None, done=False): - if not done: - cmd, args, query = self._split_message(message) - - if cmd[0] == 'autoset': - if args == 'execute': - done = True - elif cmd[0] == 'acquire': - if cmd[1] == 'stopafter': - if query: - result = self.mock_state['stopafter'] - else: - self.mock_state['stopafter'] = args - done = True - elif cmd[1] == 'state': - if query: - result = str(int(self.mock_state['acquire_state'])) - else: - self.mock_state['acquire_state'] = bool(int(args)) - done = True - elif cmd[1] == 'mode': - if query: - result = self.mock_state['acquire_mode'] - else: - self.mock_state['acquire_mode'] = args - done = True - elif cmd[1] == 'numacq' and query: - result = self.mock_state['fastframe_count'] - done = True - elif cmd[0] == 'horizontal': - if cmd[1] == 'mode': - if cmd[2] == 'samplerate': - if query: - result = self.mock_state['samplerate'] - else: - self.mock_state['samplerate'] = float(args) - done = True - elif cmd[2] == 'scale': - if query: - result = self.mock_state['horizontal_scale'] - else: - self.mock_state['horizontal_scale'] = float(args) - done = True - elif cmd[2] == 'recordlength' and query: - result = self._record_length - done = True - elif cmd[1] == 'divisions' and query: - result = 10 - done = True - elif cmd[1] == 'fastframe': - if cmd[2] == 'state': - if query: - result = int(self.mock_state['fastframe']) - else: - self.mock_state['fastframe'] = bool(int(args)) - done = True - elif cmd[0] == 'data': - if cmd[1] == 'start': - if query: - result = self.mock_state['data_start'] - else: - self.mock_state['data_start'] = int(args) - done = True - elif cmd[1] == 'stop': - if query: - result = self.mock_state['data_stop'] - else: - self.mock_state['data_stop'] = int(args) - done = True - elif cmd[1] == 'source': - if query: - result = 'ch{0}'.format(self.mock_state['data_source']) - else: - self.mock_state['data_source'] = int(args[2]) - done = True - if cmd[1] == 'framestart': - if query: - result = self.mock_state['data_framestart'] - else: - self.mock_state['data_framestart'] = int(args) - done = True - elif cmd[1] == 'framestop': - if query: - result = self.mock_state['data_framestop'] - else: - self.mock_state['data_framestop'] = int(args) - done = True - elif cmd[0] == 'curve' and query: - num_points = self._record_length * self.mock_state['waveform_bytes'] - ch = self.mock_state['data_source'] - curve = [int(120 * sin(2 * ch * pi * x / num_points) + randint(-7, 7)) for x in xrange(num_points)] - result = BlockData.to_block_data(pack('!%db' % (num_points), *curve)) - done = True - elif cmd[0] == 'wfmoutpre': - if cmd[1] == 'byt_nr': - if query: - result = self.mock_state['waveform_bytes'] - else: - self.mock_state['waveform_bytes'] = int(args) - done = True - elif cmd[0] == 'select': - if cmd[1].startswith('ch'): - channel = int(cmd[1][2]) - - if query: - result = str(int(self.mock_state['channels'][channel].enabled)) - else: - self.mock_state['channels'][channel].enabled = (args == 'on') - done = True - elif cmd[0].startswith('ch'): - channel = int(cmd[0][2]) - - if cmd[1] == 'scale': - if query: - result = self.mock_state['channels'][channel].scale - else: - self.mock_state['channels'][channel].scale = float(args) - done = True - elif cmd[1] == 'offset': - if query: - result = self.mock_state['channels'][channel].offset - else: - self.mock_state['channels'][channel].offset = float(args) - done = True - - MockAbstractDevice.write(self, message, result, done) - - -name = 'DPO7104' -implementation = MockDPO7104 diff --git a/spacq/devices/tektronix/mock/tests/__init__.py b/spacq/devices/tektronix/mock/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/tektronix/mock/tests/test_mock_awg5014b.py b/spacq/devices/tektronix/mock/tests/test_mock_awg5014b.py deleted file mode 100644 index e3641f3..0000000 --- a/spacq/devices/tektronix/mock/tests/test_mock_awg5014b.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import awg5014b -from .. import mock_awg5014b - -from ...tests.server.test_awg5014b import AWG5014BTest - - -# Don't lose the real device. -real_AWG5014B = awg5014b.AWG5014B -is_mock = AWG5014BTest.mock - - -def setup(): - # Run the tests with a fake device. - awg5014b.AWG5014B = mock_awg5014b.MockAWG5014B - AWG5014BTest.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - awg5014b.AWG5014B = real_AWG5014B - AWG5014BTest.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/tektronix/mock/tests/test_mock_dpo7104.py b/spacq/devices/tektronix/mock/tests/test_mock_dpo7104.py deleted file mode 100644 index 9c47090..0000000 --- a/spacq/devices/tektronix/mock/tests/test_mock_dpo7104.py +++ /dev/null @@ -1,26 +0,0 @@ -from unittest import main - -from ... import dpo7104 -from .. import mock_dpo7104 - -from ...tests.server.test_dpo7104 import DPO7104Test - - -# Don't lose the real device. -real_DPO7104 = dpo7104.DPO7104 -is_mock = DPO7104Test.mock - - -def setup(): - # Run the tests with a fake device. - dpo7104.DPO7104 = mock_dpo7104.MockDPO7104 - DPO7104Test.mock = True - -def teardown(): - # Restore the real device for any remaining tests. - dpo7104.DPO7104 = real_DPO7104 - DPO7104Test.mock = is_mock - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/tektronix/tests/__init__.py b/spacq/devices/tektronix/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/tektronix/tests/server/__init__.py b/spacq/devices/tektronix/tests/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/tektronix/tests/server/test_awg5014b.py b/spacq/devices/tektronix/tests/server/test_awg5014b.py deleted file mode 100644 index 8a019b2..0000000 --- a/spacq/devices/tektronix/tests/server/test_awg5014b.py +++ /dev/null @@ -1,132 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from nose.tools import eq_ -from numpy import linspace -from numpy.testing import assert_array_almost_equal -from unittest import main - -from spacq.interface.units import Quantity -from spacq.tests.tool.box import AssertHandler, DeviceServerTestCase - -from ... import awg5014b - - -class AWG5014BTest(DeviceServerTestCase): - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=awg5014b.AWG5014B, - manufacturer='Tektronix', model='AWG5014B') - - def testMarkerValues(self): - """ - Set the various marker values. - """ - - awg = self.obtain_device() - awg.reset() - - awg.channels[1].markers[1].delay = Quantity(1, 'ns') - awg.channels[1].markers[1].high = Quantity(0.5, 'V') - awg.channels[1].markers[2].delay = Quantity(0.1, 'ns') - awg.channels[2].markers[1].low = Quantity(-100, 'mV') - - eq_(awg.channels[1].markers[1].delay.value, 1e-9) - eq_(awg.channels[1].markers[2].delay.value, 0.1e-9) - eq_(awg.channels[2].markers[1].delay.value, 0) - eq_(awg.channels[2].markers[2].delay.value, 0) - - eq_(awg.channels[1].markers[1].high.value, 0.5) - eq_(awg.channels[1].markers[2].high.value, 1) - eq_(awg.channels[2].markers[1].high.value, 1) - eq_(awg.channels[2].markers[2].high.value, 1) - - eq_(awg.channels[1].markers[1].low.value, 0) - eq_(awg.channels[1].markers[2].low.value, 0) - eq_(awg.channels[2].markers[1].low.value, -0.1) - eq_(awg.channels[2].markers[2].low.value, 0) - - def testScenario(self): - """ - Run through a simple scenario. - - Note: Verification should also be done manually based on the AWG output. - """ - - log = AssertHandler() - - awg = self.obtain_device() - awg.reset() - - assert not awg.enabled - - # Setup - existing_waveforms = awg.waveform_names - - data1 = linspace(-1.0, 1.0, 21) - data2 = linspace(1.0, -1.0, 21) - - log.flush() - awg.channels[1].set_waveform(data1, { - 1: ([1, 1, 1, 0, 0] * len(data1))[:len(data1)], - 2: ([0, 0, 0, 1, 1] * len(data1))[:len(data1)], - 3: [1, 2, 3, 4], - }) - log.assert_logged('warning', 'marker 3 ignored: \[1, 2, 3, 4\]') - - awg.channels[2].set_waveform(data2, name='Test 2') - - awg.sampling_rate = Quantity(200, 'MHz') - - awg.channels[1].enabled = True - awg.channels[1].amplitude = Quantity(0.8, 'V') - - awg.channels[2].enabled = True - awg.channels[2].amplitude = Quantity(0.4, 'V') - - awg.channels[3].waveform_name = 'Test 2' - awg.channels[3].enabled = True - - awg.channels[4].waveform_name = 'Channel 1' - - del awg.channels[3].waveform_name - - awg.run_mode = 'triggered' - awg.enabled = True - - # Verify - eq_(awg.sampling_rate.value, 2e8) - - eq_(awg.waveform_names, existing_waveforms + ['Channel 1', 'Test 2']) - - assert_array_almost_equal(awg.get_waveform('Channel 1'), data1, 4) - eq_(awg.channels[1].amplitude.value, 0.8) - assert_array_almost_equal(awg.get_waveform('Test 2'), data2, 4) - eq_(awg.channels[2].amplitude.value, 0.4) - - for ch in [1, 2]: - eq_(awg.channels[ch].enabled, True) - for ch in [3, 4]: - eq_(awg.channels[ch].enabled, False) - - for ch in [1, 4]: - eq_(awg.channels[ch].waveform_name, 'Channel 1') - eq_(awg.channels[2].waveform_name, 'Test 2') - eq_(awg.channels[3].waveform_name, '') - - eq_(awg.run_mode, 'triggered') - assert awg.waiting_for_trigger - assert awg.enabled - - awg.trigger() - - assert awg.waiting_for_trigger - assert awg.enabled - - awg.run_mode = 'continuous' - - assert not awg.waiting_for_trigger - assert awg.enabled - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/tektronix/tests/server/test_dpo7104.py b/spacq/devices/tektronix/tests/server/test_dpo7104.py deleted file mode 100644 index d39ad75..0000000 --- a/spacq/devices/tektronix/tests/server/test_dpo7104.py +++ /dev/null @@ -1,81 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from nose.tools import eq_ -from unittest import main - -from spacq.interface.units import Quantity -from spacq.tests.tool.box import DeviceServerTestCase - -from ... import dpo7104 - - -class DPO7104Test(DeviceServerTestCase): - def obtain_device(self): - return DeviceServerTestCase.obtain_device(self, impl=dpo7104.DPO7104, - manufacturer='Tektronix', model='DPO7104') - - def testAcquire(self): - """ - Obtain some waveforms. - """ - - dpo = self.obtain_device() - dpo.reset() - - dpo.autoset() - - eq_(dpo.stopafter, 'runstop') - assert dpo.acquiring - - ws = [] - - # 1 is enabled by default. - # 2 and 3 are disabled by default. - dpo.channels[4].enabled = True - - dpo.channels[1].scale = dpo.channels[4].scale = Quantity(500, 'mV') - dpo.channels[1].offset = dpo.channels[4].offset = Quantity(1, 'V') - - # Many records. - dpo.time_scale = Quantity(100, 'ns') - dpo.sample_rate = Quantity(40, 'GHz') - eq_(dpo.record_length, 4e3) - - dpo.acquire() - ws.append(dpo.channels[1].waveform) - ws.append(dpo.channels[4].waveform) - - eq_(dpo.time_scale.value, 1e-7) - eq_(dpo.sample_rate.value, 4e10) - - eq_(len(ws[0]), 4e3) - eq_(len(ws[1]), 4e3) - - # Long sample. - dpo.acquisition_mode = 'sample' - - dpo.time_scale = Quantity(10, 's') - dpo.sample_rate = Quantity(0.1, 'kHz') - eq_(dpo.record_length, 1e3) - - dpo.acquire() - ws.append(dpo.channels[1].waveform) - ws.append(dpo.channels[4].waveform) - - eq_(dpo.time_scale.value, 1e1) - eq_(dpo.sample_rate.value, 1e2) - eq_(len(ws[2]), 1e3) - eq_(len(ws[3]), 1e3) - - # Check the channels. - assert dpo.channels[1].enabled - assert not dpo.channels[2].enabled - assert not dpo.channels[3].enabled - assert dpo.channels[4].enabled - # Check the data. - assert all(x >= -1.5 and x <= 3.5 for w in ws for _, x in w) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/tests/__init__.py b/spacq/devices/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/tests/server/__init__.py b/spacq/devices/tests/server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/devices/tests/server/test_abstract_device.py b/spacq/devices/tests/server/test_abstract_device.py deleted file mode 100644 index 8f61b77..0000000 --- a/spacq/devices/tests/server/test_abstract_device.py +++ /dev/null @@ -1,112 +0,0 @@ -from nose.plugins.skip import SkipTest -from nose.tools import assert_raises, eq_ -from unittest import main, TestCase - -from testconfig import config as tc - -from ... import abstract_device - - -class AbstractDeviceTest(TestCase): - def obtain_device(self, device): - """ - If the given device description is an Ethernet or GPIB device, return a connected device object. - """ - - if ('address' not in device and 'ip_address' not in device['address'] and - 'gpib_pad' not in device['address']): - return None - - try: - return abstract_device.AbstractDevice(**device['address']) - except Exception: - return None - - def testAskRaw(self): - """ - Converse briefly with real devices. - """ - - found_any = False - - # Try all devices to which a connection can be established. - for name, device in tc['devices'].items(): - if not ('ip_address' in device['address'] or 'gpib_pad' in device['address']): - continue - - dev = self.obtain_device(device) - - if dev is None: - continue - - msg = dev.ask_raw('*idn?') - eq_(msg[-1], '\n') - - found_any = True - - if not found_any: - raise SkipTest('Could not connect to any device.') - - def testMultiCommand(self): - """ - Send a multi-command message. - """ - - # Use any device. - for name, device in tc['devices'].items(): - dev = self.obtain_device(device) - - if dev is None: - continue - - # Value to check against. - id = dev.idn - - expected = ['1'] * 3 + [id] - - assert_raises(ValueError, dev.multi_command_stop) - - # Don't actually send anything. - dev.multi_command_start() - responses = dev.multi_command_stop() - - eq_(responses, []) - - # Expect a response. - dev.multi_command_start() - dev.opc - dev.opc - dev.opc - dev.idn - responses = dev.multi_command_stop() - - eq_(responses, expected) - - return - - raise SkipTest('Could not connect to any device.') - - def testClose(self): - """ - Close the device. - """ - - # Use any device. - for name, device in tc['devices'].items(): - dev = self.obtain_device(device) - - if dev is None: - continue - - dev.idn - dev.opc - - dev.close() - - return - - raise SkipTest('Could not connect to any device.') - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/tests/server/test_config.py b/spacq/devices/tests/server/test_config.py deleted file mode 100644 index ae9017c..0000000 --- a/spacq/devices/tests/server/test_config.py +++ /dev/null @@ -1,106 +0,0 @@ -from nose.tools import assert_raises, eq_ -from pickle import dumps, loads -from unittest import main - -from spacq.tests.tool.box import DeviceServerTestCase -from ...abstract_device import AbstractDevice - -from ... import config - - -class DeviceConfigTest(DeviceServerTestCase): - def obtain_device(self): - """ - Get a real device with which to test. - """ - - return DeviceServerTestCase.obtain_device(self) - - def populate_config(self, cfg, addr): - """ - Given an address dictionary, populate the DeviceConfig. - """ - - if 'ip_address' in addr: - cfg.address_mode = cfg.address_modes.ethernet - cfg.ip_address = addr['ip_address'] - elif 'gpib_pad' in addr: - cfg.address_mode = cfg.address_modes.gpib - try: - cfg.gpib_board = addr['gpid_board'] - except KeyError: - pass - cfg.gpib_pad = addr['gpib_pad'] - try: - cfg.gpib_sad = addr['gpid_sad'] - except KeyError: - pass - elif 'usb_resource' in addr: - cfg.address_mode = cfg.address_modes.usb - cfg.usb_resource = addr['usb_resource'] - - def testConnectSuccessful(self): - """ - Connect normally. - """ - - dev = self.obtain_device() - - cfg = config.DeviceConfig(name='Test') - self.populate_config(cfg, dev['address']) - - cfg.manufacturer = dev['manufacturer'] - cfg.model = dev['model'] - - cfg.connect() - - assert isinstance(cfg.device, AbstractDevice) - - return cfg - - def testConnectNoImplementation(self): - """ - Try to connect without an implementation. - """ - - dev = self.obtain_device() - - cfg = config.DeviceConfig(name='Test') - self.populate_config(cfg, dev['address']) - - assert_raises(config.ConnectionError, cfg.connect) - - def testConnectNoAddress(self): - """ - Try to connect without an address. - """ - - dev = self.obtain_device() - - cfg = config.DeviceConfig(name='Test') - - cfg.manufacturer = dev['manufacturer'] - cfg.model = dev['model'] - - assert_raises(config.ConnectionError, cfg.connect) - - def testPickle(self): - """ - Pickle and unpickle a device config. - """ - - cfg = self.testConnectSuccessful() - - expected = cfg.__dict__.copy() - del expected['_device'] - del expected['resources'] - - actual = loads(dumps(cfg)).__dict__ - del actual['_device'] - del actual['resources'] - - eq_(actual, expected) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/tests/test_abstract_device.py b/spacq/devices/tests/test_abstract_device.py deleted file mode 100644 index 58ca161..0000000 --- a/spacq/devices/tests/test_abstract_device.py +++ /dev/null @@ -1,136 +0,0 @@ -from nose.tools import eq_ -from unittest import main, TestCase - -from spacq.interface.resources import Resource - -from .. import abstract_device - - -class AbstractDeviceTest(TestCase): - def testInitNoAddress(self): - """ - No address specified. - """ - - try: - abstract_device.AbstractDevice() - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - def testInitNotFoundIP(self): - """ - Invalid or non-existent IP address. - """ - - try: - abstract_device.AbstractDevice(ip_address='1234') - except abstract_device.DeviceNotFoundError: - pass - else: - assert False, 'Expected DeviceNotFoundError.' - - try: - # Address within TEST-NET-1 is not likely to exist. - abstract_device.AbstractDevice(ip_address='192.0.2.123') - except abstract_device.DeviceNotFoundError: - pass - else: - assert False, 'Expected DeviceNotFoundError.' - - - def testInitNotFoundGPIB(self): - """ - Invalid or non-existent GPIB address. - - Note: There doesn't seem to be a way of disabling the error output from libgpib. - """ - - try: - # Valid PADs are on [0, 30] (5 bits; 31 is reserved). - abstract_device.AbstractDevice(gpib_board=0, gpib_pad=2000) - except abstract_device.DeviceNotFoundError: - pass - else: - assert False, 'Expected DeviceNotFoundError.' - - try: - # Assuming that board number 15 is not used. - abstract_device.AbstractDevice(gpib_board=15, gpib_pad=0) - except abstract_device.DeviceNotFoundError: - pass - else: - assert False, 'Expected DeviceNotFoundError.' - - def testInitNotFoundUSB(self): - """ - Invalid or non-existent USB address. - """ - - try: - abstract_device.AbstractDevice(usb_resource='NOT::USB::RAW') - except abstract_device.DeviceNotFoundError: - pass - else: - assert False, 'Expected DeviceNotFoundError.' - - try: - # Unlikely VID/PID/serial combination. - abstract_device.AbstractDevice(usb_resource='USB::1234::5678::01234567::RAW') - except abstract_device.DeviceNotFoundError: - pass - else: - assert False, 'Expected DeviceNotFoundError.' - - def testFindResource(self): - """ - Attempt to find a resource. - """ - - dev = abstract_device.AbstractDevice(ip_address='192.0.2.123', autoconnect=False) - subdev1 = abstract_device.AbstractSubdevice(dev) - subdev2 = abstract_device.AbstractSubdevice(dev) - subdev3 = abstract_device.AbstractSubdevice(subdev1) - res1 = Resource(object(), '__class__') - res2 = Resource(object(), '__str__') - res3 = Resource(object(), '__doc__') - - dev.subdevices['subdev1'] = subdev1 - dev.resources['res1'] = res1 - dev.subdevices['subdev2'] = subdev2 - subdev1.subdevices['subdev3'] = subdev3 - subdev3.resources['res2'] = res2 - subdev3.resources['res3'] = res3 - - # Success. - found = dev.find_resource(('subdev1', 'subdev3', 'res3')) - eq_(found.value, object.__doc__) - - # No such resource. - try: - dev.find_resource(('subdev1', 'res3')) - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - # No such subdevice. - try: - dev.find_resource(('subdev1', 'subdev2', 'res3')) - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - # Nothing to even try. - try: - dev.find_resource(()) - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/tests/test_config.py b/spacq/devices/tests/test_config.py deleted file mode 100644 index 6385dfe..0000000 --- a/spacq/devices/tests/test_config.py +++ /dev/null @@ -1,110 +0,0 @@ -from nose.tools import eq_ -from unittest import main, TestCase - -from ..mock.mock_abstract_device import MockAbstractDevice - -from .. import config - - -class DeviceTreeTest(TestCase): - def testTree(self): - """ - Ensure the tree looks correct. - """ - - tree = config.device_tree() - - assert 'Agilent' in tree - assert 'Tektronix' in tree - - agi = tree['Agilent'] - tek = tree['Tektronix'] - - assert '34410A' in agi - assert 'AWG5014B' in tek - - dm = agi['34410A'] - awg = tek['AWG5014B'] - - from ..agilent.dm34410a import DM34410A - from ..agilent.mock.mock_dm34410a import MockDM34410A - from ..tektronix.awg5014b import AWG5014B - from ..tektronix.mock.mock_awg5014b import MockAWG5014B - - eq_(dm['real'], DM34410A) - eq_(dm['mock'], MockDM34410A) - eq_(awg['real'], AWG5014B) - eq_(awg['mock'], MockAWG5014B) - - -class DeviceConfigTest(TestCase): - def testMockConnect(self): - """ - Connect to a mock device. - """ - - cfg = config.DeviceConfig(name='Test') - - # These values don't matter. - cfg.address_mode = cfg.address_modes.ethernet - cfg.ip_address = '127.0.0.1' - - tree = config.device_tree() - cfg.manufacturer = tree.keys()[-1] - cfg.model = tree[cfg.manufacturer].keys()[-1] - cfg.mock = True - - cfg.connect() - - assert isinstance(cfg.device, MockAbstractDevice) - - def testInvalidConnect(self): - """ - Fail to connect to a non-existing device. - """ - - cfg = config.DeviceConfig(name='Test') - - # These values don't matter. - cfg.address_mode = cfg.address_modes.ethernet - cfg.ip_address = '127.0.0.1' - - # No implementation. - try: - cfg.connect() - except config.ConnectionError: - pass - else: - assert False, 'Expected ConnectionError.' - - # Incorrect implementation. - cfg.manufacturer = 'Fender' - cfg.model = 'Telecaster' - try: - cfg.connect() - except config.ConnectionError: - pass - else: - assert False, 'Expected ConnectionError.' - - def testDiffResources(self): - """ - Try changing up some resources. - """ - - cfg1 = config.DeviceConfig(name='Test 1') - cfg2 = config.DeviceConfig(name='Test 2') - eq_(cfg1.diff_resources(cfg2), (set(), set(), set())) - - cfg2.resources['something'] = 'new' - eq_(cfg1.diff_resources(cfg2), (set(['something']), set(), set())) - - cfg1.resources['for'] = 'now' - eq_(cfg1.diff_resources(cfg2), (set(['something']), set(), set(['for']))) - - cfg1.resources['something'] = 'now' - eq_(cfg1.diff_resources(cfg2), (set(), set(['something']), set(['for']))) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/tests/test_tools.py b/spacq/devices/tests/test_tools.py deleted file mode 100644 index 89123bb..0000000 --- a/spacq/devices/tests/test_tools.py +++ /dev/null @@ -1,135 +0,0 @@ -from nose.tools import eq_ -from unittest import main, TestCase - -from spacq.tests.tool.box import AssertHandler - -from .. import tools - - -class StrToBoolTest(TestCase): - def testConversion(self): - """ - Try some simple cases. - """ - - eq_(tools.str_to_bool(''), False) - eq_(tools.str_to_bool('False'), False) - eq_(tools.str_to_bool('false'), False) - - eq_(tools.str_to_bool('True'), True) - eq_(tools.str_to_bool('123'), True) - eq_(tools.str_to_bool('Anything'), True) - eq_(tools.str_to_bool('else!'), True) - - -class BlockDataTest(TestCase): - def testToAndFromBlockData(self): - """ - Routine conversions. - """ - - binary_data = ''.join([chr(x) for x in xrange(256)]) - - data = [ - ('', '#10'), - (' ', '#11 '), - ('something longer', '#216something longer'), - ('and binary data: ' + binary_data, '#3273and binary data: ' + binary_data) - ] - - for d, b in data: - eq_(tools.BlockData.to_block_data(d), b) - eq_(tools.BlockData.from_block_data(b), d) - - def testFromIndefiniteBlockData(self): - """ - Indefinite inputs. - """ - - binary_data = ''.join([chr(x) for x in xrange(256)]) - - data = [ - ('', '#0\n'), - (' ', '#0 \n'), - ('something longer', '#0something longer\n'), - ('and binary data: ' + binary_data, '#0and binary data: ' + binary_data + '\n') - ] - - for d, b in data: - eq_(tools.BlockData.from_block_data(b), d) - - def testFromSlightlyBadData(self): - """ - Not valid, but parsable inputs. - """ - - data = [ - ('Too ', '#14Too long.', 'extra data ignored: \'long.\''), - ] - - log = AssertHandler() - - for d, b, msg in data: - log.flush() - eq_(tools.BlockData.from_block_data(b), d) - log.assert_logged('warning', msg) - - def testFromBadBlockData(self): - """ - Invalid inputs. - """ - - data = ['', '123Off to a bad start!', '#', '#0', '#0non-terminated', - '#X123', '#1', '#29', '#24test', '#44444Too short.'] - - for b in data: - try: - tools.BlockData.from_block_data(b) - except tools.BlockDataError: - pass - else: - assert False, 'Expected BlockDataError.' - - -class BinaryBinaryEncoderTest(TestCase): - def testEncodeDecode(self): - """ - Routine conversions. - """ - - data = [ - ('', '', {}, ''), - ('hex', '\x0e', {}, '0e'), - ('1234', '\x12\x34', {}, '1234'), - ('1234 5678 9 0 a b', '\x12\x34\x56\x78\x90\xab', {}, '1234 5678 90ab'), - (' ABCDEF FEDCBA ', '\xab\xcd\xef\xfe\xdc\xba', {}, 'abcd effe dcba'), - ('0001 0203', '\x00\x01\x02\x03', {}, '0001 0203'), - ('00 010203', '\x00\x01\x02\x03', {'pair_size': 3}, '000102 03'), - ('00 01 02 03', '\x00\x01\x02\x03', {'pair_up': False}, '00010203'), - ] - - for u, e, args, d in data: - eq_(tools.BinaryEncoder.encode(u), e) - eq_(tools.BinaryEncoder.decode(e, **args), d) - - def testLength(self): - """ - Routine calculations. - """ - - data = [ - ('', 0), - ('nothing', 0), - ('0000', 2), - ('001', 2), - ('data 0101 234', 5), - ('data 0101 2345', 6), - ('data 0101 2345 6', 6), - ] - - for d, l in data: - eq_(tools.BinaryEncoder.length(d), l) - - -if __name__ == '__main__': - main() diff --git a/spacq/devices/tools.py b/spacq/devices/tools.py deleted file mode 100755 index 701d76f..0000000 --- a/spacq/devices/tools.py +++ /dev/null @@ -1,268 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from functools import wraps -import string - -from spacq.interface.units import Quantity - -""" -Tools for working with hardware devices. -""" - - -def str_to_bool(value): - """ - False and 'False' => False - otherwise => True - """ - - return bool(value) and value.lower() != 'false' - - -def quantity_wrapped(units, multiplier=1.0): - """ - A decorator for getters to wrap the plain device value into a quantity with a unit. - """ - - def wrap(f): - @wraps(f) - def wrapped(self): - return Quantity(f(self) * multiplier, units) - - return wrapped - - return wrap - -def quantity_unwrapped(units, multiplier=1.0): - """ - A decorator for setters to extract the plain device value from the quantity. - """ - - def wrap(f): - @wraps(f) - def wrapped(self, value): - value.assert_dimensions(units) - - return f(self, value.value * multiplier) - - return wrapped - - return wrap - -def converted_quantity_unwrapped(units, multiplier=1.0): - """ - A variation of quantity_unwrapped that extracts the value in the units provided, and then applies the multiplier - if given. - """ - - def wrap(f): - @wraps(f) - def wrapped(self, value): - value.assert_dimensions(units) - - # Perform conversion. Note that this is a bit of a trick. We must use a value of 1.0 because - # normalization messes up for Quantities with 0 magnitude. - new_value = Quantity(1.0, units) - new_value += value - new_value -= Quantity(1.0, units) - - return f(self, new_value.original_value * multiplier) - - return wrapped - - return wrap - - -def dynamic_quantity_wrapped(units_attr_string, multiplier=1.0): - """ - A decorator for getters to wrap the plain device value into a quantity with a unit, where the unit - is defined by an attribute extracted from the device. - - Note: Will work on a chain of dotted attributes. - """ - - def wrap(f): - @wraps(f) - def wrapped(self): - - units = reduce(getattr, units_attr_string.split('.'), self) -# units = getattr(self, units_attr_string) - - return Quantity(f(self) * multiplier, units) - - return wrapped - - return wrap - -def dynamic_converted_quantity_unwrapped(units_attr_string, multiplier=1.0): - """ - A variation of dynamic_quantity_unwrapped that will extract the units from an attribute of the device. - """ - - def wrap(f): - @wraps(f) - def wrapped(self, value): - -# units = getattr(self, units_attr_string) - units = reduce(getattr, units_attr_string.split('.'), self) - - value.assert_dimensions(units) - - # Perform conversion. Note that this is a bit of a trick. We must use a value of 1.0 because - # normalization messes up for Quantities with 0 magnitude. - new_value = Quantity(1.0, units) - new_value += value - new_value -= Quantity(1.0, units) - - return f(self, new_value.original_value * multiplier) - - return wrapped - - return wrap - - -class BlockDataError(Exception): - """ - Problem reading block data. - """ - - pass - - -class BlockData(object): - """ - Utility methods for conversion between binary and 488.2 block data. - """ - - @staticmethod - def to_block_data(data): - """ - Packs binary data into 488.2 block data. - - As per section 7.7.6 of IEEE Std 488.2-1992. - - Note: Does not produce indefinitely-formatted block data. - """ - - log.debug('Converting to block data: {0!r}'.format(data)) - - length = len(data) - length_length = len(str(length)) - - return '#{0}{1}{2}'.format(length_length, length, data) - - @staticmethod - def from_block_data(block_data): - """ - Extracts binary data from 488.2 block data. - - As per section 7.7.6 of IEEE Std 488.2-1992. - """ - - log.debug('Converting from block data: {0!r}'.format(block_data)) - - # Must have at least "#0\n" or "#XX". - if len(block_data) < 3: - raise BlockDataError('Not enough data.') - - if block_data[0] != '#': - raise BlockDataError('Leading character is "{0}", not "#".'.format(block_data[0])) - - if block_data[1] == '0': - log.debug('Indefinite format.') - - if block_data[-1] != '\n': - raise BlockDataError('Final character is "{0}", not NL.'.format(block_data[-1])) - - return block_data[2:-1] - else: - log.debug('Definite format.') - - try: - length_length = int(block_data[1]) - except ValueError: - raise BlockDataError('Length length incorrectly specified: {0}'.format(block_data[1])) - - data_start = 2 + length_length - - if data_start > len(block_data): - raise BlockDataError('Not enough data.') - - try: - length = int(block_data[2:data_start]) - except ValueError: - raise BlockDataError('Length incorrectly specified: {0}'.format(block_data[2:data_start])) - - data_end = data_start + length - - if data_end > len(block_data): - raise BlockDataError('Not enough data.') - elif data_end < len(block_data): - if block_data[data_end:] != '\n': - log.warning('Extra data ignored: {0!r}'.format(block_data[data_end:])) - - return block_data[data_start:data_end] - - -class BinaryEncoder(object): - """ - Utility methods for dealing with encoding and decoding binary data. - """ - - @staticmethod - def encode(msg): - """ - Convert a string of hexadecimal digits to a byte string. - """ - - log.debug('Encoding to byte string: {0}'.format(msg)) - - # Discard non-hexadecimal characters. - msg_filtered = [x for x in msg if x in string.hexdigits] - # Grab pairs. - idxs = xrange(0, len(msg_filtered), 2) - msg_paired = [''.join(msg_filtered[i:i+2]) for i in idxs] - # Convert to bytes. - msg_encoded = ''.join([chr(int(x, 16)) for x in msg_paired]) - - log.debug('Encoded to: {0!r}'.format(msg_encoded)) - - return msg_encoded - - @staticmethod - def decode(msg, pair_size=2, pair_up=True): - """ - Convert a byte string to a string of hexadecimal digits. - """ - - log.debug('Decoding from byte string: {0!r}'.format(msg)) - - # Get the hex string for each byte. - msg_decoded = ['{0:02x}'.format(ord(x)) for x in msg] - - if pair_up: - idxs = xrange(0, len(msg_decoded), pair_size) - msg_formatted = [''.join(msg_decoded[i:i+pair_size]) for i in idxs] - - result = ' '.join(msg_formatted) - else: - result = ''.join(msg_decoded) - - log.debug('Decoded to: {0}'.format(result)) - - return result - - @staticmethod - def length(msg): - """ - Calculate the number of bytes an unencoded message takes up when encoded. - """ - - log.debug('Finding encoded length: {0}'.format(msg)) - - result = len(BinaryEncoder.encode(msg)) - - log.debug('Found encoded length: {0}'.format(result)) - - return result diff --git a/spacq/gui/__init__.py b/spacq/gui/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/action/__init__.py b/spacq/gui/action/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/action/data_capture.py b/spacq/gui/action/data_capture.py deleted file mode 100755 index 02c4f54..0000000 --- a/spacq/gui/action/data_capture.py +++ /dev/null @@ -1,639 +0,0 @@ -import csv -from datetime import timedelta -from functools import partial -import os -from pubsub import pub -from threading import Lock, Thread -from time import localtime, sleep, time -import wx -from wx.lib.filebrowsebutton import DirBrowseButton - -from spacq.interface.pulse.parser import PulseError -from spacq.interface.units import IncompatibleDimensions -from spacq.iteration.sweep import PulseConfiguration, SweepController -from spacq.iteration.variables import sort_output_variables, sort_condition_variables, InputVariable, OutputVariable, ConditionVariable -from spacq.tool.box import flatten, sift - - -from ..tool.box import Dialog, MessageDialog, YesNoQuestionDialog - - -class DataCaptureDialog(Dialog, SweepController): - """ - A progress dialog which runs over iterators, sets the corresponding resources, and captures the measured data. - """ - - max_value_len = 50 # characters - - timer_delay = 50 # ms - stall_time = 2 # s - - status_messages = { - None: 'Starting up', - 'init': 'Initializing', - 'next': 'Getting next values', - 'transition': 'Smooth setting', - 'write': 'Writing to devices', - 'dwell': 'Waiting for resources to settle', - 'pulse': 'Running pulse program', - 'read': 'Taking measurements', - 'condition': 'Testing conditions', - 'condition_dwell': 'Waiting for conditions to settle', - 'ramp_down': 'Smooth setting', - 'end': 'Finishing', - } - - def __init__(self, parent, resources, variables, num_items, measurement_resources, - measurement_variables, condition_resources, condition_variables, pulse_config, continuous=False, - *args, **kwargs): - kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER - - Dialog.__init__(self, parent, title='Sweeping...', *args, **kwargs) - SweepController.__init__(self, resources, variables, num_items, measurement_resources, - measurement_variables, condition_resources, condition_variables, pulse_config, continuous=continuous) - - self.parent = parent - - # Show only elapsed time in continuous mode. - self.show_remaining_time = not self.continuous - - self.last_checked_time = -1 - self.elapsed_time = 0 # us - - self.timer = wx.Timer(self) - self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer) - - self.cancelling = False - - def write_callback(pos, i, value): - self.value_outputs[pos][i].Value = str(value)[:self.max_value_len] - self.write_callback = partial(wx.CallAfter, write_callback) - - def read_callback(i, value): - self.value_inputs[i].Value = str(value)[:self.max_value_len] - self.read_callback = partial(wx.CallAfter, read_callback) - - self.general_exception_handler = partial(wx.CallAfter, self._general_exception_handler) - self.resource_exception_handler = partial(wx.CallAfter, self._resource_exception_handler) - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Progress. - progress_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(progress_box, flag=wx.EXPAND|wx.ALL, border=5) - - ### Message. - self.progress_percent = wx.StaticText(self, label='', size=(40, -1)) - progress_box.Add(self.progress_percent, - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - - ### Bar. - self.progress_bar = wx.Gauge(self, range=num_items, style=wx.GA_HORIZONTAL) - progress_box.Add(self.progress_bar, proportion=1) - - ## Status. - self.status_message_output = wx.TextCtrl(self, style=wx.TE_READONLY) - self.status_message_output.BackgroundColour = wx.LIGHT_GREY - dialog_box.Add(self.status_message_output, flag=wx.EXPAND) - - ## Values. - self.values_box = wx.FlexGridSizer(rows=len(self.variables), cols=2, hgap=20) - self.values_box.AddGrowableCol(1, 1) - dialog_box.Add(self.values_box, flag=wx.EXPAND|wx.ALL, border=5) - - self.value_outputs = [] - for group in self.variables: - group_outputs = [] - - for var in group: - output = wx.TextCtrl(self, style=wx.TE_READONLY) - output.BackgroundColour = wx.LIGHT_GREY - group_outputs.append(output) - - self.values_box.Add(wx.StaticText(self, label=var.name), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.values_box.Add(output, flag=wx.EXPAND) - - self.value_outputs.append(group_outputs) - - # Spacer. - for _ in xrange(2): - self.values_box.Add((-1, 15)) - - # Separator. - for _ in xrange(2): - self.values_box.Add(wx.StaticLine(self), flag=wx.EXPAND|wx.ALL, border=5) - - self.value_inputs = [] - for var in self.measurement_variables: - input = wx.TextCtrl(self, style=wx.TE_READONLY) - input.BackgroundColour = wx.LIGHT_GREY - self.value_inputs.append(input) - - self.values_box.Add(wx.StaticText(self, label=var.name), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.values_box.Add(input, flag=wx.EXPAND) - - ## Times. - times_box = wx.FlexGridSizer(rows=2 if self.show_remaining_time else 1, cols=2, hgap=5) - dialog_box.Add(times_box, proportion=1, flag=wx.CENTER|wx.ALL, border=15) - - ### Elapsed. - times_box.Add(wx.StaticText(self, label='Elapsed time:')) - self.elapsed_time_output = wx.StaticText(self, label='---:--:--') - times_box.Add(self.elapsed_time_output) - - ### Remaining. - if self.show_remaining_time: - times_box.Add(wx.StaticText(self, label='Remaining time:')) - self.remaining_time_output = wx.StaticText(self, label='---:--:--') - times_box.Add(self.remaining_time_output) - - ## Last continuous. - if self.continuous: - self.last_continuous_input = wx.CheckBox(self, label='Last loop of continuous sweep') - dialog_box.Add(self.last_continuous_input, flag=wx.CENTER) - - ## End button. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER) - - self.cancel_button = wx.Button(self, label='Cancel') - self.Bind(wx.EVT_BUTTON, self.OnCancel, self.cancel_button) - button_box.Add(self.cancel_button) - - self.SetSizerAndFit(dialog_box) - - # Try to cancel cleanly instead of giving up. - self.Bind(wx.EVT_CLOSE, self.OnCancel) - - def _general_exception_handler(self, f, e): - """ - Called when a trampolined function raises e. - """ - - MessageDialog(self.parent, '{0}'.format(str(e)), 'Sweep error in "{0}"'.format(f)).Show() - - def _resource_exception_handler(self, resource_name, e, write=True): - """ - Called when a write to or read from a Resource raises e. - """ - - msg = 'Resource: {0}\nError: {1}'.format(resource_name, str(e)) - dir = 'writing to' if write else 'reading from' - MessageDialog(self.parent, msg, 'Error {0} resource'.format(dir)).Show() - - self.abort(fatal=write) - - def start(self): - thr = Thread(target=SweepController.run, args=(self,)) - thr.daemon = True - thr.start() - - self.timer.Start(self.timer_delay) - - def dwell(self): - result = SweepController.dwell(self) - - # Prevent the GUI from locking up. - sleep(0.005) - - return result - - def end(self): - try: - SweepController.end(self) - except AssertionError: - return - - # In case the sweep is too fast, ensure that the user has some time to see the dialog. - span = time() - self.sweep_start_time - if span < self.stall_time: - sleep(self.stall_time - span) - - wx.CallAfter(self.timer.Stop) - wx.CallAfter(self.Destroy) - - def OnCancel(self, evt=None): - if not self.cancel_button.Enabled: - return - - self.cancel_button.Disable() - self.cancelling = True - - def OnTimer(self, evt=None): - self.status_message_output.Value = self.status_messages[self.current_f] - if self.continuous: - self.last_continuous = self.last_continuous_input.Value - - # Update progress. - if self.num_items > 0 and self.item >= 0: - amount_done = float(self.item) / self.num_items - - self.progress_bar.Value = self.item - self.progress_percent.Label = '{0}%'.format(int(100 * amount_done)) - - if self.last_checked_time > 0: - self.elapsed_time += int((time() - self.last_checked_time) * 1e6) - self.elapsed_time_output.Label = str(timedelta(seconds=self.elapsed_time//1e6)) - - self.last_checked_time = time() - - if self.show_remaining_time and amount_done > 0: - total_time = self.elapsed_time / amount_done - remaining_time = int(total_time - self.elapsed_time) - self.remaining_time_output.Label = str(timedelta(seconds=remaining_time//1e6)) - - # Prompt to abort. - if self.cancelling: - def abort(): - self.cancelling = False - - thr = Thread(target=self.abort) - thr.daemon = True - thr.start() - - self.timer.Start(self.timer_delay) - - def resume(): - self.cancelling = False - self.cancel_button.Enable() - - self.unpause() - - self.timer.Start(self.timer_delay) - - self.pause() - - self.last_checked_time = -1 - self.timer.Stop() - - YesNoQuestionDialog(self, 'Abort processing?', abort, resume).Show() - - return - - -class DataCapturePanel(wx.Panel): - """ - A panel to start the data capture process, optionally exporting the results to a file. - """ - - def __init__(self, parent, global_store, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - - self.capture_dialogs = 0 - - # Panel. - panel_box = wx.BoxSizer(wx.HORIZONTAL) - - ## Capture. - capture_static_box = wx.StaticBox(self, label='Capture') - capture_box = wx.StaticBoxSizer(capture_static_box, wx.VERTICAL) - panel_box.Add(capture_box, flag=wx.CENTER|wx.ALL, border=5) - - ### Start. - self.start_button = wx.Button(self, label='Start') - self.Bind(wx.EVT_BUTTON, self.OnBeginCapture, self.start_button) - capture_box.Add(self.start_button, flag=wx.CENTER) - - ### Continuous. - self.continuous_checkbox = wx.CheckBox(self, label='Continuous') - capture_box.Add(self.continuous_checkbox, flag=wx.CENTER) - - ## Export. - export_static_box = wx.StaticBox(self, label='Export') - export_box = wx.StaticBoxSizer(export_static_box, wx.HORIZONTAL) - panel_box.Add(export_box, proportion=1, flag=wx.CENTER|wx.ALL, border=5) - - ### Enabled. - self.export_enabled = wx.CheckBox(self, label='') - self.export_enabled.Value = True - export_box.Add(self.export_enabled, flag=wx.CENTER) - - ### Export path. - export_path_box = wx.BoxSizer(wx.VERTICAL) - export_box.Add(export_path_box, proportion=1, flag=wx.CENTER) - - #### Directory. - self.directory_browse_button = DirBrowseButton(self, labelText='Directory:') - export_path_box.Add(self.directory_browse_button, flag=wx.EXPAND) - - #### Last file. - last_file_box = wx.BoxSizer(wx.HORIZONTAL) - export_path_box.Add(last_file_box, flag=wx.EXPAND) - - last_file_box.Add(wx.StaticText(self, label='Last output: '), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.last_file_name = wx.TextCtrl(self, style=wx.TE_READONLY) - self.last_file_name.BackgroundColour = wx.LIGHT_GREY - last_file_box.Add(self.last_file_name, proportion=1) - - self.SetSizer(panel_box) - - def OnBeginCapture(self, evt=None): - # Prevent accidental double-clicking. - self.start_button.Disable() - def enable_button(): - sleep(1) - wx.CallAfter(self.start_button.Enable) - thr = Thread(target=enable_button) - thr.daemon = True - thr.start() - - all_variables = [var for var in self.global_store.variables.values() if var.enabled] - output_variables = sift(all_variables, OutputVariable) - input_variables = [var for var in sift(all_variables, InputVariable) if var.resource_name != ''] - condition_variables = sift(all_variables, ConditionVariable) - - if not output_variables: - output_variables.append(OutputVariable(order=0, name='', enabled=True)) - - output_variables, num_items = sort_output_variables(output_variables) - condition_variables = sort_condition_variables(condition_variables) - - resource_names = [tuple(var.resource_name for var in group) for group in output_variables] - measurement_resource_names = [var.resource_name for var in input_variables] - condition_resource_names = [tuple(set(flatten([var.resource_names for var in group]))) for group in condition_variables] - - - continuous = self.continuous_checkbox.Value - - missing_resources = set() - unreadable_resources = set() - unwritable_resources = set() - missing_devices = set() - - pulse_program = self.global_store.pulse_program - - if pulse_program is not None: - pulse_program = pulse_program.with_resources - - try: - pulse_program.generate_waveforms(dry_run=True) - except PulseError as e: - MessageDialog(self, '\n'.join(e[0]), 'Pulse program error', monospace=True).Show() - return - except Exception as e: - MessageDialog(self, str(e), 'Pulse program error').Show() - return - - pulse_awg, pulse_oscilloscope = None, None - pulse_channels = {} - - try: - pulse_awg = self.global_store.devices[pulse_program.awg].device - if pulse_awg is None: - raise KeyError - except KeyError: - missing_devices.add(pulse_program.awg) - else: - # Gather used channel numbers. - pulse_channels = dict((k, v) for k, v in pulse_program.output_channels.items() if v is not None) - - actual_channels = range(1, len(pulse_awg.channels)) - invalid_channels = [k for k, v in pulse_channels.items() if v not in actual_channels] - - if invalid_channels: - MessageDialog(self, 'Invalid channels for: {0}'.format(', '.join(invalid_channels)), 'Invalid channels').Show() - return - - try: - pulse_oscilloscope = self.global_store.devices[pulse_program.oscilloscope].device - if pulse_oscilloscope is None: - raise KeyError - except KeyError: - missing_devices.add(pulse_program.oscilloscope) - - try: - pulse_config = PulseConfiguration(pulse_program, pulse_channels, pulse_awg, pulse_oscilloscope) - except TypeError as e: - MessageDialog(self, str(e), 'Device configuration error').Show() - return - else: - pulse_config = None - - resources = [] - for group in resource_names: - group_resources = [] - - for name in group: - if name == '': - group_resources.append((str(len(resources)), None)) - elif name not in self.global_store.resources: - missing_resources.add(name) - else: - resource = self.global_store.resources[name] - - if resource.writable: - group_resources.append((name, resource)) - else: - unwritable_resources.add(name) - - resources.append(tuple(group_resources)) - - measurement_resources = [] - measurement_units = [] - for name in measurement_resource_names: - if name not in self.global_store.resources: - missing_resources.add(name) - else: - resource = self.global_store.resources[name] - - if resource.readable: - measurement_resources.append((name, resource)) - measurement_units.append(resource.display_units) - else: - unreadable_resources.add(name) - - condition_resources = [] - for group in condition_resource_names: - group_resources = [] - - for name in group: - if name not in self.global_store.resources: - missing_resources.add(name) - else: - resource = self.global_store.resources[name] - - if resource.readable: - group_resources.append((name, resource)) - else: - #the name may already have been put here by the loop assigning - #to measurement_resources - if name not in unreadable_resources: - unreadable_resources.add(name) - - condition_resources.append(tuple(group_resources)) - - mismatched_resources = [] - for (res_name, resource), var in zip(flatten(resources), flatten(output_variables)): - if resource is None: - continue - - if resource.units is not None: - if not (var.type == 'quantity' and - resource.verify_dimensions(var.units, exception=False, from_string=True)): - mismatched_resources.append((res_name, var.name)) - else: - if var.type not in ['float', 'integer']: - mismatched_resources.append((res_name, var.name)) - - for items, msg in [ - (missing_resources, 'Missing resources'), - (unreadable_resources, 'Unreadable resources'), - (unwritable_resources, 'Unwritable resources'), - (missing_devices, 'Missing devices')]: - - if items: - MessageDialog(self, ', '.join('"{0}"'.format(x) for x in sorted(items)), msg).Show() - - if mismatched_resources: - MessageDialog(self, ', '.join('Mismatched resource type for resource name {0} with variable name {1}'.format(x[0], x[1]) for x in mismatched_resources), - 'Mismatched resources').Show() - - if (missing_resources or unreadable_resources or unwritable_resources or - missing_devices or mismatched_resources): - return - - # Check that all the condition arguments are compatible with one another. - - for cvar in flatten(condition_variables): - - # Get a condition. - for cond in cvar.conditions: - - value1 = cond.arg1 - value2 = cond.arg2 - resource1 = None - resource2 = None - - # If working with resources, use their values as the values, and make the resource available - - if cond.type1 == 'resource name': - resource1 = [resource for (name, resource) in flatten(condition_resources) if name == cond.arg1][0] - value1 = resource1.value - if cond.type2 == 'resource name': - resource2 = [resource for (name, resource) in flatten(condition_resources) if name == cond.arg2][0] - value2 = resource2.value - - # Check if the other argument is in the allowed values - - if hasattr(resource1,'allowed_values') and resource1.allowed_values is not None: - if value2 not in resource1.allowed_values: - MessageDialog(self, 'In the condition {0}, {1} is not in allowed_values of {2}.'.format(cond, value2, cond.arg1),'Condition error').Show() - return - if hasattr(resource2,'allowed_values') and resource2.allowed_values is not None: - if value1 not in resource2.allowed_values: - MessageDialog(self, 'In the condition {0}, {1} is not in allowed_values of {2}.'.format(cond, value1, cond.arg2),'Condition error').Show() - return - - # Check if units agree. - - if resource1 is not None and resource1.units is not None: - try: - value1.assert_dimensions(value2) - except ValueError: - MessageDialog(self, 'In the condition {0}, {1} does not have a dimension.'.format(cond, value2),'Condition error').Show() - return - except IncompatibleDimensions: - MessageDialog(self, 'In the condition {0}, {1} and {2} do not have matching dimensions.'.format(cond, value1, value2),'Condition error').Show() - return - if resource2 is not None and resource2.units is not None: - try: - value2.assert_dimensions(value1) - except ValueError: - MessageDialog(self, 'In the condition {0}, {1} does not have a dimension.'.format(cond, value1),'Condition error').Show() - return - except IncompatibleDimensions: - MessageDialog(self, 'In the condition {0}, {1} and {2} do not have matching dimensions.'.format(cond, value1, value2),'Condition error').Show() - return - - - exporting = False - if self.export_enabled.Value: - dir = self.directory_browse_button.GetValue() - # YYYY-MM-DD_HH-MM-SS.csv - name = '{0:04}-{1:02}-{2:02}_{3:02}-{4:02}-{5:02}.csv'.format(*localtime()) - - if not dir: - MessageDialog(self, 'No directory selected.', 'Export path').Show() - return - - if not os.path.isdir(dir): - MessageDialog(self, 'Invalid directory selected', 'Export path').Show() - return - - file_path = os.path.join(dir, name) - if os.path.exists(file_path): - MessageDialog(self, file_path, 'File exists').Show() - return - - # Everything looks alright, so open the file. - export_file = open(file_path, 'w') - export_csv = csv.writer(export_file) - exporting = True - - # Show the path in the GUI. - self.last_file_name.Value = file_path - - # Write the header. - export_csv.writerow(['Time (s)'] + - ['{0.name} ({0.units})'.format(var) if var.units is not None else var.name - for var in flatten(output_variables)] + - ['{0.name} ({1})'.format(var, units) if units is not None else var.name - for var, units in zip(input_variables, measurement_units)]) - - self.capture_dialogs += 1 - - dlg = DataCaptureDialog(self, resources, output_variables, num_items, measurement_resources, - input_variables, condition_resources, condition_variables, pulse_config, continuous=continuous) - dlg.SetMinSize((500, -1)) - - for name in measurement_resource_names: - wx.CallAfter(pub.sendMessage, 'data_capture.start', name=name) - - # Export buffer. - max_buf_size = 10 - buf = [] - buf_lock = Lock() - - def flush(): - export_csv.writerows(buf) - export_file.flush() - - while buf: - buf.pop() - - def data_callback(cur_time, values, measurement_values): - for name, value in zip(measurement_resource_names, measurement_values): - wx.CallAfter(pub.sendMessage, 'data_capture.data', name=name, value=value) - - # Extract values out of quantities, since the units have already been taken care of in the header. - values = [x.original_value if hasattr(x, 'original_value') else x for x in values] - measurement_values = [x.original_value if hasattr(x, 'original_value') else x for x in measurement_values] - - if exporting: - with buf_lock: - buf.append([cur_time] + values + measurement_values) - - if len(buf) >= max_buf_size: - flush() - - def close_callback(): - self.capture_dialogs -= 1 - - if exporting: - with buf_lock: - flush() - export_file.close() - - for name in measurement_resource_names: - wx.CallAfter(pub.sendMessage, 'data_capture.stop', name=name) - - dlg.data_callback = data_callback - dlg.close_callback = close_callback - dlg.Show() - dlg.start() diff --git a/spacq/gui/action/smooth_reset.py b/spacq/gui/action/smooth_reset.py deleted file mode 100644 index 26f2e1c..0000000 --- a/spacq/gui/action/smooth_reset.py +++ /dev/null @@ -1,131 +0,0 @@ -from functools import partial -from threading import Thread -import wx - -from spacq.iteration.variables import OutputVariable -from spacq.tool.box import sift - -from ..tool.box import MessageDialog - - -class SmoothResetPanel(wx.Panel): - """ - A panel to change variables smoothly to and from preset values. - """ - - def __init__(self, parent, global_store, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Reset. - reset_static_box = wx.StaticBox(self, label='Smooth reset') - reset_box = wx.StaticBoxSizer(reset_static_box, wx.VERTICAL) - panel_box.Add(reset_box, flag=wx.CENTER|wx.ALL, border=10) - - ### To zero. - self.to_button = wx.Button(self, label='To Zero') - self.Bind(wx.EVT_BUTTON, self.OnResetToZero, self.to_button) - reset_box.Add(self.to_button, flag=wx.EXPAND) - - ### From zero. - self.from_button = wx.Button(self, label='From Zero') - self.Bind(wx.EVT_BUTTON, self.OnResetFromZero, self.from_button) - reset_box.Add(self.from_button, flag=wx.EXPAND) - - ## To Value - self.toVal_button = wx.Button(self, label='To Value') - self.Bind(wx.EVT_BUTTON, self.OnResetToValue, self.toVal_button) - reset_box.Add(self.toVal_button, flag=wx.EXPAND) - - ### Steps. - steps_static_box = wx.StaticBox(self, label='Steps') - steps_box = wx.StaticBoxSizer(steps_static_box, wx.VERTICAL) - reset_box.Add(steps_box, flag=wx.EXPAND) - - self.reset_steps_input = wx.SpinCtrl(self, min=1, initial=10) - steps_box.Add(self.reset_steps_input) - - self.SetSizer(panel_box) - - def choose_variables(self): - """ - Return all the selected variables, ensuring that their resources are valid. - """ - - all_vars = sift(self.global_store.variables.values(), OutputVariable) - vars = [var for var in all_vars if var.enabled and var.use_const and var.resource_name] - - missing_resources = [] - unwritable_resources = [] - for var in vars: - try: - if not self.global_store.resources[var.resource_name].writable: - unwritable_resources.append(var.resource_name) - except KeyError: - missing_resources.append(var.resource_name) - - if missing_resources: - MessageDialog(self, ', '.join(missing_resources), 'Missing resources').Show() - if unwritable_resources: - MessageDialog(self, ', '.join(unwritable_resources), 'Unwritable resources').Show() - if missing_resources or unwritable_resources: - return None - - return vars - - def reset(self, sweep_setting): - vars = self.choose_variables() - if vars is None: - return - - self.to_button.Disable() - self.from_button.Disable() - self.toVal_button.Disable() - - def exception_callback(e): - MessageDialog(self, str(e), 'Error writing to resource').Show() - - def sweep_all_vars(): - try: - thrs = [] - for var in vars: - resource = self.global_store.resources[var.resource_name] - - if sweep_setting == 1: - value_from, value_to = 0, var.with_type(var.const) - elif sweep_setting == 0: - value_from, value_to = var.with_type(var.const), 0 - else: - value_from, value_to = resource.value , var.with_type(var.const) - - thr = Thread(target=resource.sweep, args=(value_from, value_to, self.reset_steps_input.Value), - kwargs={'exception_callback': partial(wx.CallAfter, exception_callback)}) - thr.daemon = True - thrs.append(thr) - - for thr in thrs: - thr.start() - for thr in thrs: - thr.join() - finally: - if self: - wx.CallAfter(self.to_button.Enable) - wx.CallAfter(self.from_button.Enable) - wx.CallAfter(self.toVal_button.Enable) - - thr = Thread(target=sweep_all_vars) - thr.daemon = True - thr.start() - - def OnResetToZero(self, evt=None): - self.reset(0) - - def OnResetFromZero(self, evt=None): - self.reset(1) - - def OnResetToValue(self, evt=None): - self.reset(2) diff --git a/spacq/gui/config/__init__.py b/spacq/gui/config/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/config/device/__init__.py b/spacq/gui/config/device/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/config/device/device_config.py b/spacq/gui/config/device/device_config.py deleted file mode 100644 index 7407eb2..0000000 --- a/spacq/gui/config/device/device_config.py +++ /dev/null @@ -1,399 +0,0 @@ -import wx -from wx.lib.masked.ipaddrctrl import IpAddrCtrl - -from spacq.devices.config import device_tree, ConnectionError, DeviceConfig - -from ...tool.box import load_pickled, save_pickled, Dialog, MessageDialog -from .resource_tree import DeviceResourcesPanel - -""" -Device configuration for and through the GUI. -""" - - -class DeviceConfigPanel(wx.Panel): - """ - Set up a device for consumption of its resources. - """ - - def __init__(self, parent, connection_callback=None, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.connection_callback = connection_callback - - # Implementation info. - ## Find all the available devices. - self.device_tree = device_tree() - self.manufacturers = [''] + sorted(self.device_tree.keys()) - self.models = [''] - - ## Chosen values. - self.manufacturer = None - self.model = None - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Address. - address_static_box = wx.StaticBox(self, label='Address') - address_box = wx.StaticBoxSizer(address_static_box, wx.VERTICAL) - address_sizer = wx.BoxSizer(wx.HORIZONTAL) - address_box.Add(address_sizer, flag=wx.EXPAND) - panel_box.Add(address_box, flag=wx.EXPAND|wx.ALL, border=5) - - ### Ethernet. - ethernet_static_box = wx.StaticBox(self) - ethernet_box = wx.StaticBoxSizer(ethernet_static_box, wx.VERTICAL) - address_sizer.Add(ethernet_box, proportion=1) - - self.address_mode_eth = wx.RadioButton(self, label='Ethernet', style=wx.RB_GROUP) - ethernet_box.Add(self.address_mode_eth) - - ethernet_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - ethernet_box.Add(ethernet_sizer, flag=wx.EXPAND) - - ethernet_sizer.Add(wx.StaticText(self, label='IP address:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.ip_address_input = IpAddrCtrl(self) - ethernet_sizer.Add(self.ip_address_input, flag=wx.CENTER) - - ### Telnet - telnet_static_box = wx.StaticBox(self) - telnet_box = wx.StaticBoxSizer(telnet_static_box, wx.VERTICAL) - address_sizer.Add(telnet_box, proportion=1) - - self.address_mode_tel = wx.RadioButton(self, label='Telnet') - telnet_box.Add(self.address_mode_tel) - - telnet_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - telnet_box.Add(telnet_sizer, flag=wx.EXPAND) - - telnet_sizer.Add(wx.StaticText(self, label='Host:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.host_address_input = IpAddrCtrl(self) - telnet_sizer.Add(self.host_address_input, flag=wx.CENTER) - - ### GPIB. - self.gpib_static_box = wx.StaticBox(self) - gpib_box = wx.StaticBoxSizer(self.gpib_static_box, wx.VERTICAL) - address_sizer.Add(gpib_box, proportion=1) - - self.address_mode_gpib = wx.RadioButton(self, label='GPIB') - gpib_box.Add(self.address_mode_gpib) - - gpib_sizer = wx.FlexGridSizer(rows=3, cols=2, hgap=5) - gpib_box.Add(gpib_sizer, flag=wx.EXPAND) - - gpib_sizer.Add(wx.StaticText(self, label='Board:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.gpib_board_input = wx.SpinCtrl(self, min=0, max=100, initial=0) - gpib_sizer.Add(self.gpib_board_input, flag=wx.CENTER) - - gpib_sizer.Add(wx.StaticText(self, label='PAD:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.gpib_pad_input = wx.SpinCtrl(self, min=1, max=30, initial=1) - gpib_sizer.Add(self.gpib_pad_input, flag=wx.CENTER) - - gpib_sizer.Add(wx.StaticText(self, label='SAD:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.gpib_sad_input = wx.SpinCtrl(self, min=0, max=30, initial=0) - gpib_sizer.Add(self.gpib_sad_input, flag=wx.CENTER) - - ### USB. - usb_static_box = wx.StaticBox(self) - usb_box = wx.StaticBoxSizer(usb_static_box, wx.VERTICAL) - address_box.Add(usb_box, flag=wx.EXPAND) - - self.address_mode_usb = wx.RadioButton(self, label='USB') - usb_box.Add(self.address_mode_usb) - - usb_sizer = wx.BoxSizer(wx.HORIZONTAL) - usb_box.Add(usb_sizer, flag=wx.EXPAND) - - usb_sizer.Add(wx.StaticText(self, label='USB resource: '), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.usb_resource_input = wx.TextCtrl(self, size=(300, -1)) - usb_sizer.Add(self.usb_resource_input, proportion=1) - - ## Implementation. - implementation_static_box = wx.StaticBox(self, label='Implementation') - implementation_box = wx.StaticBoxSizer(implementation_static_box, wx.HORIZONTAL) - panel_box.Add(implementation_box, flag=wx.EXPAND|wx.ALL, border=5) - - self.manufacturer_input = wx.Choice(self, choices=self.manufacturers) - self.Bind(wx.EVT_CHOICE, self.OnManufacturer, self.manufacturer_input) - implementation_box.Add(self.manufacturer_input, proportion=1) - - self.model_input = wx.Choice(self, choices=self.models) - self.Bind(wx.EVT_CHOICE, self.OnModel, self.model_input) - implementation_box.Add(self.model_input, proportion=1) - - self.mock_input = wx.CheckBox(self, label='Mock') - implementation_box.Add(self.mock_input, flag=wx.CENTER) - - ## Connection buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - panel_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - self.connect_button = wx.Button(self, label='Connect') - self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connect_button) - button_box.Add(self.connect_button) - - self.disconnect_button = wx.Button(self, label='Disconnect') - self.Bind(wx.EVT_BUTTON, self.OnDisconnect, self.disconnect_button) - button_box.Add(self.disconnect_button) - - self.SetSizerAndFit(panel_box) - - def get_address_mode(self): - if self.address_mode_eth.Value: - return DeviceConfig.address_modes.ethernet - elif self.address_mode_tel.Value: - return DeviceConfig.address_modes.telnet - elif self.address_mode_gpib.Value: - return DeviceConfig.address_modes.gpib - elif self.address_mode_usb.Value: - return DeviceConfig.address_modes.usb - - def GetValue(self): - dev_cfg = DeviceConfig(name=self.name) - - # Address mode. - dev_cfg.address_mode = self.get_address_mode() - - ## Ethernet. - possible_address = self.ip_address_input.GetAddress() - if self.ip_address_input.IsValid() and len(possible_address) > 6: - dev_cfg.ip_address = possible_address - else: - dev_cfg.ip_address = None - - ## Telnet - dev_cfg.host_address = self.host_address_input.GetAddress() - - ## GPIB. - dev_cfg.gpib_board = self.gpib_board_input.Value - dev_cfg.gpib_pad = self.gpib_pad_input.Value - dev_cfg.gpib_sad = self.gpib_sad_input.Value - - ## USB. - possible_resource = self.usb_resource_input.Value - if possible_resource: - dev_cfg.usb_resource = possible_resource - else: - dev_cfg.usb_resource = None - - # Implementation. - dev_cfg.manufacturer = self.manufacturer - dev_cfg.model = self.model - dev_cfg.mock = self.mock_input.Value - - # Device. - dev_cfg.device = self.device - - # Resource labels. - dev_cfg.resource_labels = self.resource_labels - - return dev_cfg - - def SetValue(self, dev_cfg): - self.name = dev_cfg.name - - # Address mode. - if dev_cfg.address_mode == DeviceConfig.address_modes.ethernet: - self.address_mode_eth.Value = True - elif dev_cfg.address_mode == DeviceConfig.address_modes.gpib: - self.address_mode_gpib.Value = True - elif dev_cfg.address_mode == DeviceConfig.address_modes.usb: - self.address_mode_usb.Value = True - - ## Ethernet. - if dev_cfg.ip_address: - self.ip_address_input.SetValue(dev_cfg.ip_address) - - ## Telnet - if dev_cfg.host_address: - self.host_address_input.SetValue(dev_cfg.host_address) - - ## GPIB. - self.gpib_board_input.Value = dev_cfg.gpib_board - self.gpib_pad_input.Value = dev_cfg.gpib_pad - self.gpib_sad_input.Value = dev_cfg.gpib_sad - - ## USB. - if dev_cfg.usb_resource: - self.usb_resource_input.Value = dev_cfg.usb_resource - - # Implementation. - if dev_cfg.manufacturer is not None: - self.manufacturer_input.StringSelection = dev_cfg.manufacturer - self.OnManufacturer() - - if dev_cfg.model is not None: - self.model_input.StringSelection = dev_cfg.model - self.OnModel() - - self.mock_input.Value = dev_cfg.mock - - # Device. - self.device = dev_cfg.device - if self.device is not None: - self.connect_button.Disable() - self.disconnect_button.Enable() - else: - self.connect_button.Enable() - self.disconnect_button.Disable() - - # Resource labels. - self.resource_labels = dev_cfg.resource_labels - - if self.device is not None and self.connection_callback is not None: - self.connection_callback(self.device, self.resource_labels) - - def OnManufacturer(self, evt=None): - self.manufacturer = self.manufacturers[self.manufacturer_input.Selection] - - self.models = [''] - if self.manufacturer: - self.models.extend(self.device_tree[self.manufacturer].keys()) - else: - self.manufacturer = None - - self.model_input.SetItems(self.models) - - def OnModel(self, evt=None): - self.model = self.models[self.model_input.Selection] - - if self.model: - model = self.device_tree[self.manufacturer][self.model] - - if 'real' in model and 'mock' not in model: - self.mock_input.Value = False - self.mock_input.Disable() - elif 'real' not in model and 'mock' in model: - self.mock_input.Value = True - self.mock_input.Disable() - else: - self.mock_input.Enable() - else: - self.model = None - - def OnDisconnect(self, evt=None): - self.device = None - - if self.connection_callback is not None: - self.connection_callback(None, {}) - - self.disconnect_button.Disable() - - def OnConnect(self, evt=None): - dev_cfg = self.GetValue() - try: - dev_cfg.connect() - except ConnectionError as e: - MessageDialog(self, str(e), 'Connection error').Show() - return - - self.device = dev_cfg.device - - if self.connection_callback is not None: - self.connection_callback(self.device, self.resource_labels) - - self.connect_button.Disable() - - -class DeviceConfigDialog(Dialog): - """ - A dialog for configuring a device, including connection and resources. - """ - - def __init__(self, parent, ok_callback, *args, **kwargs): - Dialog.__init__(self, parent, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER, - *args, **kwargs) - - self.ok_callback = ok_callback - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Tabs. - self.notebook = wx.Notebook(self) - dialog_box.Add(self.notebook, proportion=1, flag=wx.EXPAND) - - self.resources_panel = DeviceResourcesPanel(self.notebook) - self.connection_panel = DeviceConfigPanel(self.notebook, - connection_callback=self.resources_panel.set_device) - - self.notebook.AddPage(self.connection_panel, 'Connection') - self.notebook.AddPage(self.resources_panel, 'Resources') - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.TOP, border=10) - - ### OK, cancel. - dialog_button_box = wx.BoxSizer(wx.HORIZONTAL) - button_box.Add(dialog_button_box) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - dialog_button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - dialog_button_box.Add(cancel_button) - - ### Save, load. - save_button_box = wx.BoxSizer(wx.HORIZONTAL) - button_box.Add(save_button_box, flag=wx.LEFT, border=20) - - save_button = wx.Button(self, wx.ID_SAVE, label='Save...') - self.Bind(wx.EVT_BUTTON, self.OnSave, save_button) - save_button_box.Add(save_button) - - load_button = wx.Button(self, wx.ID_OPEN, label='Load...') - self.Bind(wx.EVT_BUTTON, self.OnLoad, load_button) - save_button_box.Add(load_button) - - self.SetSizerAndFit(dialog_box) - - def GetValue(self): - dev_cfg = self.connection_panel.GetValue() - labels, resources = self.resources_panel.GetValue() - - dev_cfg.resources = resources - - # Preserve labels between device instances. - if dev_cfg.device is not None: - dev_cfg.resource_labels = labels - - return dev_cfg - - def SetValue(self, dev_cfg): - self.connection_panel.SetValue(dev_cfg) - self.resources_panel.SetValue(dev_cfg.resource_labels, dev_cfg.resources) - - def OnOk(self, evt=None): - if self.ok_callback(self): - self.Destroy() - - def OnSave(self, evt=None): - try: - save_pickled(self, self.GetValue(), extension='dev', - file_type='Device configuration') - except IOError as e: - MessageDialog(self, str(e), 'Save error').Show() - return - - def OnLoad(self, evt=None): - try: - value = load_pickled(self, extension='dev', file_type='Device configuration') - - try: - if value is not None: - value.name = self.connection_panel.name - self.SetValue(value) - except Exception as e: - raise IOError('Could not set values.', e) - except IOError as e: - MessageDialog(self, str(e), 'Load error').Show() - return diff --git a/spacq/gui/config/device/resource_tree.py b/spacq/gui/config/device/resource_tree.py deleted file mode 100644 index 7f515f4..0000000 --- a/spacq/gui/config/device/resource_tree.py +++ /dev/null @@ -1,363 +0,0 @@ -from functools import partial -from threading import Thread -import wx -from wx.gizmos import TreeListCtrl - -from spacq.interface.resources import NotReadable -from spacq.interface.units import IncompatibleDimensions - -from ...tool.box import MessageDialog - -""" -A tree of subdevices and resources. -""" - - -class ResourceFetcher(Thread): - """ - A thread which iterates over a list of items, getting resource values. - """ - - def __init__(self, items, getter, callback, *args, **kwargs): - """ - items: List of TreeListCtrl items. - getter: Given an item, returns a Resource or None. - callback: Is called with the item and the value of the resource. - """ - - Thread.__init__(self, *args, **kwargs) - - self.items = items - self.getter = getter - self.callback = callback - - def run(self): - try: - for item in self.items: - resource = self.getter(item) - - if resource is not None and resource.readable: - if resource.slow: - wx.CallAfter(self.callback, item, '[N/A]') - else: - wx.CallAfter(self.callback, item, resource.value) - except wx.PyDeadObjectError: - # The values are no longer wanted. - return - - -class ItemData(object): - """ - Useful information about a node to be stored in its PyData. - """ - - def __init__(self, path, resource): - self.path = path - self.resource = resource - - self.fetched = False - - -class ResourceTree(TreeListCtrl): - """ - A tree list to display an hierarchy of subdevices and resources. - """ - - def __init__(self, parent, *args, **kwargs): - TreeListCtrl.__init__(self, parent, *args, - style=wx.TR_DEFAULT_STYLE|wx.TR_FULL_ROW_HIGHLIGHT|wx.TR_HIDE_ROOT, - **kwargs) - - self.root = None - self.resource_labels = [] - - self.col_name = 0 - self.AddColumn('Name', 200) - self.col_r = 1 - self.AddColumn('R', 24) - self.col_w = 2 - self.AddColumn('W', 24) - self.col_units = 3 - self.AddColumn('Units', 50) - self.col_label = 4 - self.AddColumn('Label', 200, edit=True) - self.col_value = 5 - self.AddColumn('Value', 400, edit=True) - - # Extra 50 for nesting. - self.SetMinSize((950, -1)) - - self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded) - self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnBeginLabelEdit) - self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnEndLabelEdit) - self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivated) - - def GetChildren(self, item): - """ - Non-recursive generator for the children of an item. - """ - - if self.HasChildren(item): - child, cookie = self.GetFirstChild(item) - - while child: - yield child - - child, cookie = self.GetNextChild(item, cookie) - - def GetLeaves(self, item=None): - """ - Recursively collect the leaves under an item. - """ - - if item is None: - if self.root is None: - return [] - else: - item = self.root - - if not self.HasChildren(item): - return [item] - else: - result = [] - - for child in self.GetChildren(item): - result.extend(self.GetLeaves(child)) - - return result - - def fell(self): - """ - Cut down the tree. - """ - - self.DeleteAllItems() - self.root = None - - def spawn_fetch_thread(self, items): - """ - Create a thread to populate the items. - """ - - def fetch(item): - pydata = self.GetItemPyData(item) - - if pydata is not None: - if not pydata.fetched: - pydata.fetched = True - - return pydata.resource - - def set(item, value): - try: - self.SetItemText(item, str(value), self.col_value) - except wx.PyDeadObjectError: - # The value isn't wanted anymore. - return - - thr = ResourceFetcher(items, fetch, set) - thr.daemon = True - thr.start() - - def build_tree(self, device, resource_labels, root=None, path=None): - """ - Recursively append all subdevices and resources. - """ - - if root is None: - self.fell() - - self.root = self.AddRoot('') - root = self.root - - path = () - - for name, subdev in device.subdevices.items(): - item = self.AppendItem(root, name) - full_path = path + (name,) - - self.build_tree(subdev, resource_labels, item, full_path) - - for name, resource in device.resources.items(): - item = self.AppendItem(root, name) - full_path = path + (name,) - - if resource.getter is not None: - self.SetItemText(item, 'R', self.col_r) - - if resource.setter is not None: - self.SetItemText(item, 'W', self.col_w) - - if resource.display_units is not None: - self.SetItemText(item, resource.display_units, self.col_units) - - self.SetItemPyData(item, ItemData(full_path, resource)) - - if full_path in resource_labels: - self.SetItemText(item, resource_labels[full_path], self.col_label) - - self.SortChildren(root) - - if root == self.root: - self.spawn_fetch_thread(self.GetChildren(self.root)) - - def set_value(self, item, value, error_callback=None): - """ - Set the value of a resource, as well as the displayed value. - """ - - pydata = self.GetItemPyData(item) - resource = pydata.resource - - def update(): - try: - resource.value = resource.convert(value) - except IncompatibleDimensions: - if error_callback is not None: - error_callback(ValueError('Expected dimensions to match "{0}"'.format(resource.units))) - else: - raise - except Exception as e: - if error_callback is not None: - error_callback(e) - else: - raise - - try: - true_value = str(resource.value) - except NotReadable: - pass - else: - wx.CallAfter(self.SetItemText, item, true_value, self.col_value) - - thr = Thread(target=update) - thr.daemon = True - thr.start() - - def OnItemExpanded(self, evt): - """ - Get any resources which may now be visible. - """ - - self.spawn_fetch_thread(self.GetChildren(evt.Item)) - - def OnBeginLabelEdit(self, evt): - # EVT_TREE_END_LABEL_EDIT does not carry this value. - self.editing_col = evt.Int - - if evt.Int == self.col_label: - # Only resources can have labels. - if not (self.GetItemText(evt.Item, self.col_r) or - self.GetItemText(evt.Item, self.col_w)): - evt.Veto() - else: - self.old_label = self.GetItemText(evt.Item, self.col_label) - elif evt.Int == self.col_value: - # Can only write to writable resources. - if not self.GetItemText(evt.Item, self.col_w): - evt.Veto() - - pydata = self.GetItemPyData(evt.Item) - resource = pydata.resource - - if resource.allowed_values is not None: - options = [str(x) for x in sorted(resource.allowed_values)] - - dlg = wx.SingleChoiceDialog(self, '', 'Choose value', options) - # Select the current value if possible. - try: - dlg.SetSelection(options.index(self.GetItemText(evt.Item, self.col_value))) - except ValueError: - pass - - if dlg.ShowModal() == wx.ID_OK: - try: - self.set_value(evt.Item, dlg.GetStringSelection()) - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return - - # No need for the editor. - evt.Veto() - else: - evt.Veto() - - def OnEndLabelEdit(self, evt): - if self.editing_col == self.col_label: - # Prevent duplicates. - value = evt.Label - - # Don't do anything if unchanged. - if value != self.old_label: - if value not in self.resource_labels: - if self.old_label: - self.resource_labels.remove(self.old_label) - if value: - self.resource_labels.append(value) - else: - evt.Veto() - MessageDialog(self, str(value), 'Duplicate label').Show() - return - elif self.editing_col == self.col_value: - # Update the real value. - value = evt.Label - - def error_callback(e): - MessageDialog(self, str(e), 'Invalid value').Show() - - self.set_value(evt.Item, value, error_callback=partial(wx.CallAfter, error_callback)) - - def OnActivated(self, evt): - """ - Double click to edit. - """ - - self.EditLabel(evt.Item, evt.Int) - - -class DeviceResourcesPanel(wx.Panel): - """ - A panel for displaying the subdevices and resources of a device. - """ - - def __init__(self, parent, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Tree. - self.tree = ResourceTree(self) - panel_box.Add(self.tree, proportion=1, flag=wx.EXPAND) - - self.SetSizer(panel_box) - - def set_device(self, device, resource_labels): - if device is None: - self.tree.fell() - else: - self.tree.build_tree(device, resource_labels) - - def GetValue(self): - labels = {} - resources = {} - - for leaf in self.tree.GetLeaves(): - pydata = self.tree.GetItemPyData(leaf) - name = self.tree.GetItemText(leaf, self.tree.col_label) - - if name: - labels[pydata.path] = name - resources[name] = pydata.resource - - return (labels, resources) - - def SetValue(self, resource_labels, resources): - for path, name in resource_labels.items(): - for leaf in self.tree.GetLeaves(): - pydata = self.tree.GetItemPyData(leaf) - - if pydata.path == path: - self.tree.SetItemText(leaf, name, self.tree.col_label) - - self.tree.resource_labels = resource_labels.values() diff --git a/spacq/gui/config/devices.py b/spacq/gui/config/devices.py deleted file mode 100644 index 8e9c7e3..0000000 --- a/spacq/gui/config/devices.py +++ /dev/null @@ -1,259 +0,0 @@ -import ObjectListView -from threading import Thread -from time import sleep -import wx - -from spacq.devices.config import DeviceConfig -from ..tool.box import MessageDialog -from .device.device_config import DeviceConfigDialog - -""" -An interface for creating and editing DeviceConfig objects. -""" - - -class DeviceColumnDefn(ObjectListView.ColumnDefn): - """ - A column with useful defaults. - """ - - def __init__(self, align='left', *args, **kwargs): - ObjectListView.ColumnDefn.__init__(self, align=align, *args, **kwargs) - - # No auto-width if space filling. - if self.isSpaceFilling: - self.width = 0 - - -class DevicesPanel(wx.Panel): - col_name = DeviceColumnDefn(title='Name', valueGetter='name', width=200) - col_connection = DeviceColumnDefn(title='Connection', width=110, - valueGetter=lambda x: '{0}onnected'.format('C' if x.device is not None else 'Disc')) - col_setup = DeviceColumnDefn(title='Setup', width=70, - valueGetter=lambda x: 'Setup...' if x.gui_setup is not None else '') - col_status = DeviceColumnDefn(title='Status', isSpaceFilling=True, isEditable=False, - valueGetter=lambda x: (x.device.status[0] if x.device.status else 'Idle') if x.device is not None else '') - - def __init__(self, parent, global_store, dialog_owner, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self.dialog_owner = dialog_owner - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## OLV. - self.olv = ObjectListView.FastObjectListView(self) - panel_box.Add(self.olv, proportion=1, flag=wx.ALL|wx.EXPAND) - - self.olv.SetColumns([self.col_name, self.col_connection, self.col_setup, self.col_status]) - self.olv.SetSortColumn(self.col_name) - - self.olv.cellEditMode = self.olv.CELLEDIT_DOUBLECLICK - self.olv.Bind(ObjectListView.EVT_CELL_EDIT_STARTING, self.OnCellEditStarting) - self.olv.Bind(ObjectListView.EVT_CELL_EDIT_FINISHING, self.OnCellEditFinishing) - - ## Buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - panel_box.Add(button_box, proportion=0, flag=wx.ALL|wx.CENTER) - - ### Row buttons. - row_box = wx.BoxSizer(wx.HORIZONTAL) - button_box.Add(row_box) - - add_button = wx.Button(self, wx.ID_ADD) - add_button.Bind(wx.EVT_BUTTON, self.OnAddDevice) - row_box.Add(add_button) - - remove_button = wx.Button(self, wx.ID_REMOVE) - remove_button.Bind(wx.EVT_BUTTON, self.OnRemoveDevices) - row_box.Add(remove_button) - - self.SetMinSize((600, 250)) - self.SetSizer(panel_box) - - with self.global_store.devices.lock: - for name, dev in self.global_store.devices.iteritems(): - self.olv.AddObject(dev) - - def update_resources(self, old, new): - """ - Inform everybody of updated resources. - """ - - (appeared, changed, disappeared) = old.diff_resources(new) - - with self.global_store.resources.lock: - # Check for conflicts. - conflicting_resources = [label for label in appeared if label in self.global_store.resources] - if conflicting_resources: - return conflicting_resources - - # Set up the resources. - for label in disappeared.union(changed): - del self.global_store.resources[label] - - for label in appeared.union(changed): - self.global_store.resources[label] = new.resources[label] - - return [] - - def OnCellEditStarting(self, evt): - col = evt.objectListView.columns[evt.subItemIndex] - dev = evt.rowModel - - # Ignore frivolous requests. - if evt.rowIndex < 0: - evt.Veto() - return - - veto = False - - if col == self.col_connection: - def ok_callback(dlg): - dev_new = dlg.GetValue() - # Use the new instance. - with self.global_store.devices.lock: - del self.global_store.devices[dev.name] - self.global_store.devices[dev_new.name] = dev_new - - conflicting_resources = self.update_resources(dev, dev_new) - if conflicting_resources: - MessageDialog(self, ', '.join(conflicting_resources), 'Conflicting resources').Show() - - return False - - # Close the old device as necessary. - if dev.device is not None and dev_new.device != dev.device: - dev.device.close() - - self.olv.RemoveObject(dev) - self.olv.AddObject(dev_new) - - return True - - dlg = DeviceConfigDialog(self, ok_callback, title=dev.name) - dlg.SetValue(dev) - dlg.Show() - - veto = True - elif col == self.col_setup: - if dev.gui_setup is not None: - dev.gui_setup(self.dialog_owner, self.global_store, dev.name).Show() - - veto = True - - if veto: - # No need to use the default editor. - evt.Veto() - - def OnCellEditFinishing(self, evt): - col = evt.objectListView.columns[evt.subItemIndex] - - if col == self.col_name: - dev = evt.rowModel # With old name. - dev_new_name = evt.editor.Value - - if dev_new_name == dev.name: - # Not actually changed. - return - - # Attempt to add a new entry first. - try: - self.global_store.devices[dev_new_name] = dev - except KeyError: - MessageDialog(self, dev_new_name, 'Device name conflicts').Show() - evt.Veto() - return - - # Remove the old entry. - del self.global_store.devices[dev.name] - - def OnAddDevice(self, evt=None): - """ - Add a blank variable to the OLV. - """ - - # Ensure that we get a unique name. - with self.global_store.devices.lock: - num = 1 - done = False - while not done: - name = 'New device {0}'.format(num) - dev_cfg = DeviceConfig(name=name) - - try: - self.global_store.devices[name] = dev_cfg - except KeyError: - num += 1 - else: - done = True - - self.olv.AddObject(dev_cfg) - - # OLV likes to select a random item at this point. - self.olv.DeselectAll() - - def OnRemoveDevices(self, evt=None): - """ - Remove all selected variables from the OLV. - """ - - selected = self.olv.GetSelectedObjects() - - connected_devices = set() - for row in selected: - if row.device is not None: - connected_devices.add(row.name) - - if connected_devices: - MessageDialog(self, ', '.join(sorted(connected_devices)), 'Devices still connected').Show() - return - - if selected: - self.olv.RemoveObjects(selected) - - for row in selected: - del self.global_store.devices[row.name] - - -class DeviceConfigFrame(wx.Frame): - def __init__(self, parent, global_store, close_callback, *args, **kwargs): - wx.Frame.__init__(self, parent, title='Device Configuration', *args, **kwargs) - - self.close_callback = close_callback - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - ## Devices. - self.devices_panel = DevicesPanel(self, global_store, parent) - frame_box.Add(self.devices_panel, proportion=1, flag=wx.EXPAND) - - self.SetSizerAndFit(frame_box) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - thr = Thread(target=self.status_poller) - thr.daemon = True - thr.start() - - def status_poller(self): - """ - Keep updating the status as long as the frame is open. - """ - - while True: - try: - wx.CallAfter(self.devices_panel.olv.RefreshObjects) - except wx.PyDeadObjectError: - # The panel has left the building. - return - - sleep(0.2) - - def OnClose(self, evt): - self.close_callback() - - evt.Skip() diff --git a/spacq/gui/config/measurement.py b/spacq/gui/config/measurement.py deleted file mode 100755 index 2b750d2..0000000 --- a/spacq/gui/config/measurement.py +++ /dev/null @@ -1,213 +0,0 @@ -from pubsub import pub -import wx - -from spacq.iteration.variables import InputVariable - -from ..tool.box import OK_BACKGROUND_COLOR, MessageDialog -from .scaling import ScalingSettings, ScalingSettingsDialog - - -class MeasurementConfigPanel(wx.Panel): - """ - Measurement configuration panel. - """ - - def __init__(self, parent, global_store, scaling=True, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.parent = parent - self.global_store = global_store - self.scaling = scaling - - if self.scaling: - self.scaling_settings = ScalingSettings() - - # Ensure that we get a unique name. - with self.global_store.variables.lock: - num = 1 - done = False - while not done: - name = 'New measurement {0}'.format(num) - self.var = InputVariable(name=name, enabled=True) - - try: - self.global_store.variables[name] = self.var - except KeyError: - num += 1 - else: - done = True - - # Keep track of the scaling wrapper and resource. - if self.scaling: - self.scaling_wrap_token = '{0}.{1}'.format(self.__class__.__name__, self.wrap_with_scaling.__name__) - self.resource = None - self.unwrapping = False - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Configuration. - configuration_box = wx.BoxSizer(wx.HORIZONTAL) - panel_box.Add(configuration_box, flag=wx.EXPAND|wx.ALL, border=5) - - self.enabled_checkbox = wx.CheckBox(self, label='Capture') - self.enabled_checkbox.Value = self.var.enabled - configuration_box.Add(self.enabled_checkbox, flag=wx.CENTER|wx.RIGHT, border=15) - - self.Bind(wx.EVT_CHECKBOX, self.OnCaptureChecked, self.enabled_checkbox) - - ### Names. - names_box = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - names_box.AddGrowableCol(1, 1) - configuration_box.Add(names_box, flag=wx.EXPAND, proportion=1) - - names_box.Add(wx.StaticText(self, label='Resource name:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.resource_name_input = wx.TextCtrl(self, value=self.var.resource_name, style=wx.TE_PROCESS_ENTER) - self.resource_name_input.default_background_color = self.resource_name_input.BackgroundColour - self.resource_name_input.BackgroundColour = OK_BACKGROUND_COLOR - names_box.Add(self.resource_name_input, flag=wx.EXPAND) - - self.Bind(wx.EVT_TEXT, self.OnResourceNameChange, self.resource_name_input) - self.Bind(wx.EVT_TEXT_ENTER, self.OnResourceNameInput, self.resource_name_input) - - names_box.Add(wx.StaticText(self, label='Measurement name:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.measurement_name_input = wx.TextCtrl(self, value=self.var.name, style=wx.TE_PROCESS_ENTER) - self.measurement_name_input.default_background_color = self.measurement_name_input.BackgroundColour - self.measurement_name_input.BackgroundColour = OK_BACKGROUND_COLOR - names_box.Add(self.measurement_name_input, flag=wx.EXPAND) - - self.Bind(wx.EVT_TEXT, self.OnMeasurementNameChange, self.measurement_name_input) - self.Bind(wx.EVT_TEXT_ENTER, self.OnMeasurementNameInput, self.measurement_name_input) - - ### Scaling. - if self.scaling: - scaling_button = wx.Button(self, label='Scaling...', style=wx.BU_EXACTFIT) - self.Bind(wx.EVT_BUTTON, self.OnScaling, scaling_button) - configuration_box.Add(scaling_button, flag=wx.EXPAND|wx.LEFT, border=10) - - self.SetSizerAndFit(panel_box) - - self.set_title() - - # Subscriptions. - if self.scaling: - pub.subscribe(self.msg_resource, 'resource.added') - pub.subscribe(self.msg_resource, 'resource.removed') - - @property - def live_view_panel(self): - return self.parent.live_view_panel - - def wrap_with_scaling(self, name, resource): - if not self.scaling: - return - - # Don't double-wrap. - if resource.is_wrapped_by(self.scaling_wrap_token): - return - - # Modify the resource value by the scaling. - def transform(x): - # Close over self, so that updating scaling settings automatically takes effect. - return self.scaling_settings.transform(x) - wrapped_resource = resource.wrapped(self.scaling_wrap_token, transform) - - with self.global_store.lock: - del self.global_store.resources[name] - self.global_store.resources[name] = wrapped_resource - - def unwrap_with_scaling(self): - if not self.scaling: - return - - if self.resource is None: - return - - # Don't allow immediate re-wrapping. - self.unwrapping = True - - name = self.live_view_panel.measurement_resource_name - unwrapped_resource = self.resource.unwrapped(self.scaling_wrap_token) - - with self.global_store.lock: - del self.global_store.resources[name] - self.global_store.resources[name] = unwrapped_resource - - self.resource = None - self.unwrapping = False - - def set_title(self): - self.parent.Title = '{0} ({1}){2}'.format(self.var.name, self.var.resource_name, - '' if self.var.enabled else ' [Disabled]') - - def close(self): - self.unwrap_with_scaling() - - del self.global_store.variables[self.var.name] - - def OnCaptureChecked(self, evt=None): - self.var.enabled = self.live_view_panel.enabled = self.enabled_checkbox.Value - self.set_title() - - def OnResourceNameChange(self, evt=None): - self.resource_name_input.BackgroundColour = self.resource_name_input.default_background_color - - def OnResourceNameInput(self, evt=None): - if self.var.resource_name != self.resource_name_input.Value: - # Ensure that the resource is unwrapped before releasing it. - self.unwrap_with_scaling() - - self.var.resource_name = name = self.resource_name_input.Value - - # Inform the panel. - self.live_view_panel.measurement_resource_name = name - - # Grab the new resource if it already exists. - try: - self.resource = self.global_store.resources[name] - except KeyError: - pass - else: - self.wrap_with_scaling(name, self.resource) - - self.resource_name_input.BackgroundColour = OK_BACKGROUND_COLOR - self.set_title() - - def OnMeasurementNameChange(self, evt=None): - self.measurement_name_input.BackgroundColour = self.measurement_name_input.default_background_color - - def OnMeasurementNameInput(self, evt=None): - if self.var.name != self.measurement_name_input.Value: - # Attempt to add a new entry first. - var_new_name = self.measurement_name_input.Value - try: - self.global_store.variables[var_new_name] = self.var - except KeyError: - MessageDialog(self, var_new_name, 'Variable name conflicts').Show() - else: - # Remove the old entry. - del self.global_store.variables[self.var.name] - - self.var.name = var_new_name - - self.measurement_name_input.BackgroundColour = OK_BACKGROUND_COLOR - self.set_title() - - def OnScaling(self, evt=None): - def ok_callback(dlg): - self.scaling_settings = dlg.GetValue() - - dlg = ScalingSettingsDialog(self, ok_callback) - dlg.SetValue(self.scaling_settings) - dlg.Show() - - def msg_resource(self, name, value=None): - resource_name = self.var.resource_name - - if name == resource_name: - self.resource = value - - if value is not None and not self.unwrapping: - self.wrap_with_scaling(resource_name, value) diff --git a/spacq/gui/config/pulse.py b/spacq/gui/config/pulse.py deleted file mode 100644 index 02eaecc..0000000 --- a/spacq/gui/config/pulse.py +++ /dev/null @@ -1,768 +0,0 @@ -from functools import partial -from threading import Thread -import wx -from wx.lib import filebrowsebutton -from wx.lib.scrolledpanel import ScrolledPanel - -from spacq.interface.pulse.parser import PulseError, PulseSyntaxError -from spacq.interface.pulse.program import Program -from spacq.interface.resources import Resource -from spacq.interface.units import IncompatibleDimensions, Quantity - -from ..display.waveform import WaveformFrame -from ..tool.box import OK_BACKGROUND_COLOR, determine_wildcard, MessageDialog - - -class FileBrowseButton(filebrowsebutton.FileBrowseButton): - ChangeValue = filebrowsebutton.FileBrowseButton.SetValue - - def SetBackgroundColour(self, colour): - self.textControl.SetBackgroundColour(colour) - - -def pos_int_converter(x): - try: - result = int(x) - - if result <= 0: - raise ValueError() - - return result - except ValueError: - raise ValueError('Expected positive integer') - -def quantity_converter(x, symbols='s', dimensions='time', non_negative=True): - try: - q = Quantity(x) - q.assert_dimensions(symbols) - except (IncompatibleDimensions, ValueError): - raise ValueError('Expected {0} quantity'.format(dimensions)) - - if non_negative and q.value < 0: - raise ValueError('Expected non-negative quantity') - - return q - - -class ParameterPanel(ScrolledPanel): - """ - A generic panel to display parameters of a particular type. - """ - - attributes = False - hide_variables = False - use_resource_labels = False - - spacer_height = 15 - - def extract_variables(self, prog): - """ - By default, extract the variables which pertain to the current type. - """ - - return [k for k, v in prog.variables.items() if v == self.type] - - def extract_parameters(self, prog): - """ - By default, extract the parameters which pertain to the current type. - """ - - variables = self.extract_variables(prog) - - return sorted([item for item in prog.all_values for variable in variables if item[0] == variable]) - - @property - def num_cols(self): - """ - Number of columns per row. - """ - - # Label and input field. - cols = 2 - - # Label includes attribute name. - if self.attributes: - cols += 1 - - # Label excludes variable name. - if self.hide_variables: - cols -= 1 - - # Also resource label input field. - if self.use_resource_labels: - cols += 1 - - return cols - - @property - def input_col(self): - """ - The 0-based position of the growable input column. - """ - - if self.use_resource_labels: - # Second-to-last column. - return self.num_cols - 2 - else: - # Last column. - return self.num_cols - 1 - - def get_value(self, parameter): - """ - Get the value of a parameter as a string, or raise KeyError if not available. - """ - - return str(self.values[parameter]) - - def get_resource_label(self, parameter): - """ - Get the resource label for a parameter, or empty string if not available. - """ - - try: - return self.resource_labels[parameter] - except KeyError: - return '' - - @property - def posn(self): - return (self.cur_row, self.cur_col) - - def add_headings(self): - """ - Add column headings. - """ - - if self.use_resource_labels: - # Default value. - self.parameter_sizer.Add(wx.StaticText(self, label='Default value'), (self.cur_row, self.input_col), - flag=wx.EXPAND) - - # Resource label. - self.parameter_sizer.Add(wx.StaticText(self, label='Resource label'), (self.cur_row, self.input_col + 1), - flag=wx.EXPAND) - else: - self.parameter_sizer.Add(wx.StaticText(self, label=''), (self.cur_row, 0), - flag=wx.EXPAND) - - self.cur_row += 1 - - def add_resource_label(self, parameter): - """ - Add a resource label input. - """ - - resource_input = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER) - self.Bind(wx.EVT_TEXT, partial(self.OnResourceChange, parameter), resource_input) - self.Bind(wx.EVT_TEXT_ENTER, partial(self.OnResourceInput, parameter), resource_input) - - self.parameter_sizer.Add(resource_input, self.posn, flag=wx.EXPAND) - self.cur_col += 1 - - label = self.get_resource_label(parameter) - resource_input.ChangeValue(label) - resource_input.default_background_color = resource_input.BackgroundColour - if label: - resource_input.BackgroundColour = OK_BACKGROUND_COLOR - - def add_row(self, parameter, input_type='text', increment_row=True): - """ - Add a parameter to the sizer and display the value if it is available. - """ - - self.cur_col = 0 - - if not self.hide_variables: - if self.last_variable == parameter[0]: - label = '' - else: - label = parameter[0] - self.last_variable = parameter[0] - - self.parameter_sizer.Add(wx.StaticText(self, label=label), self.posn, - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.cur_col += 1 - - if self.attributes: - self.parameter_sizer.Add(wx.StaticText(self, label=parameter[1]), self.posn, - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT) - self.cur_col += 1 - - if input_type == 'text': - input = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER) - - self.Bind(wx.EVT_TEXT, partial(self.OnChange, parameter), input) - self.Bind(wx.EVT_TEXT_ENTER, partial(self.OnInput, parameter), input) - elif input_type == 'file': - input = FileBrowseButton(self, labelText='File:', changeCallback=partial(self.OnInput, parameter)) - else: - raise ValueError('Unrecognized type "{0}"'.format(input_type)) - - self.parameter_sizer.Add(input, self.posn, flag=wx.EXPAND) - self.cur_col += 1 - - input.default_background_color = input.BackgroundColour - - try: - input.ChangeValue(self.get_value(parameter)) - except KeyError: - # No default value set. - pass - else: - input.SetBackgroundColour(OK_BACKGROUND_COLOR) - - if self.use_resource_labels: - self.add_resource_label(parameter) - - if increment_row: - self.cur_row += 1 - - def converter(self, parameter, x): - """ - Identity. - """ - - return x - - def __init__(self, parent, global_store, prog, *args, **kwargs): - ScrolledPanel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self.prog = prog - - self.values = prog.values - self.resource_labels = prog.resource_labels - self.resources = prog.resources - - self.last_variable = None - self.cur_row, self.cur_col = 0, 0 - - parameters = self.extract_parameters(prog) - - # Panel. - self.parameter_sizer = wx.GridBagSizer(hgap=5) - - self.parameter_sizer.AddGrowableCol(self.input_col, 1) - if self.use_resource_labels: - self.parameter_sizer.AddGrowableCol(self.input_col + 1, 1) - - ## Headings. - self.add_headings() - - ## Parameter inputs. - for parameter in parameters: - self.add_row(parameter) - - self.SetSizer(self.parameter_sizer) - self.SetupScrolling() - - def set_value(self, parameter, value): - self.values[parameter] = value - - def del_value(self, parameter): - try: - del self.values[parameter] - except KeyError: - pass - - def set_resource_label(self, parameter, value, resource): - self.resource_labels[parameter] = value - self.resources[parameter] = resource - - def del_resource_label(self, parameter): - try: - label = self.resource_labels[parameter] - except KeyError: - pass - else: - del self.resource_labels[parameter] - del self.resources[parameter] - del self.global_store.resources[label] - - def OnChange(self, parameter, evt): - # Awaiting validation. - self.del_value(parameter) - - evt.EventObject.BackgroundColour = evt.EventObject.default_background_color - - def OnInput(self, parameter, evt): - try: - value = self.converter(parameter, evt.String) - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - - return - - # Validated. - self.set_value(parameter, value) - - evt.EventObject.BackgroundColour = OK_BACKGROUND_COLOR - - def OnResourceChange(self, parameter, evt): - # Awaiting validation. - self.del_resource_label(parameter) - - evt.EventObject.BackgroundColour = evt.EventObject.default_background_color - - def OnResourceInput(self, parameter, evt): - label = evt.String - - try: - # Do nothing if there has not been a change. - if label == self.resource_labels[parameter]: - return - except KeyError: - pass - - # The actual setter is generated when the program is cloned. - resource = Resource(setter=lambda x: None) - - try: - self.global_store.resources[label] = resource - except KeyError as e: - MessageDialog(self, str(e[0]), 'Resource label conflicts').Show() - - return - - # Validated. - self.set_resource_label(parameter, label, resource) - - evt.EventObject.BackgroundColour = OK_BACKGROUND_COLOR - - -class AcqMarkerPanel(ParameterPanel): - type = 'acq_marker' - name = 'Acquisition' - attributes = True - hide_variables = True - - def converter(self, parameter, x): - x = ParameterPanel.converter(self, parameter, x) - - if parameter[1] == 'marker_num': - return pos_int_converter(x) - elif parameter[1] == 'output': - try: - if self.prog.variables[x] == 'output': - return x - else: - raise KeyError() - except KeyError: - raise ValueError('Expected valid output name') - - def __init__(self, *args, **kwargs): - ParameterPanel.__init__(self, *args, **kwargs) - - # Spacer. - self.parameter_sizer.Add((-1, self.spacer_height), (self.cur_row, 0)) - self.cur_row += 1 - - # Times to average. - self.parameter_sizer.Add(wx.StaticText(self, label='Times to average'), (self.cur_row, 0), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.times_average_input = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER) - self.parameter_sizer.Add(self.times_average_input, (self.cur_row, 1), flag=wx.EXPAND) - self.cur_row += 1 - - self.times_average_input.Value = str(self.prog.times_average) - self.times_average_input.default_background_color = self.times_average_input.BackgroundColour - self.times_average_input.BackgroundColour = OK_BACKGROUND_COLOR - - self.Bind(wx.EVT_TEXT, self.OnTimesAverageChange, self.times_average_input) - self.Bind(wx.EVT_TEXT_ENTER, self.OnTimesAverageInput, self.times_average_input) - - # Post-trigger delay. - self.parameter_sizer.Add(wx.StaticText(self, label='Post-trigger delay'), (self.cur_row, 0), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.delay_input = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER) - self.parameter_sizer.Add(self.delay_input, (self.cur_row, 1), flag=wx.EXPAND) - self.cur_row += 1 - - self.delay_input.Value = str(self.prog.acq_delay) - self.delay_input.default_background_color = self.delay_input.BackgroundColour - self.delay_input.BackgroundColour = OK_BACKGROUND_COLOR - - self.Bind(wx.EVT_TEXT, self.OnDelayChange, self.delay_input) - self.Bind(wx.EVT_TEXT_ENTER, self.OnDelayInput, self.delay_input) - - def OnTimesAverageChange(self, evt=None): - self.times_average_input.BackgroundColour = self.times_average_input.default_background_color - - def OnTimesAverageInput(self, evt=None): - try: - value = pos_int_converter(self.times_average_input.Value) - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - - return - - self.prog.times_average = value - - self.times_average_input.BackgroundColour = OK_BACKGROUND_COLOR - - def OnDelayChange(self, evt=None): - self.delay_input.BackgroundColour = self.delay_input.default_background_color - - def OnDelayInput(self, evt=None): - try: - value = quantity_converter(self.delay_input.Value, 's', 'time') - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - - return - - self.prog.acq_delay = value - - self.delay_input.BackgroundColour = OK_BACKGROUND_COLOR - - -class DelayPanel(ParameterPanel): - type = 'delay' - name = 'Delays' - use_resource_labels = True - - def converter(self, parameter, x): - x = ParameterPanel.converter(self, parameter, x) - - return quantity_converter(x) - - -class IntPanel(ParameterPanel): - type = 'int' - name = 'Integers' - use_resource_labels = True - - def converter(self, parameter, x): - x = ParameterPanel.converter(self, parameter, x) - - try: - return int(x) - except ValueError: - raise ValueError('Expected integer') - - -class OutputPanel(ParameterPanel): - type = 'output' - name = 'Outputs' - - def extract_parameters(self, prog): - return sorted([(x,) for x in self.extract_variables(prog)]) - - @property - def num_cols(self): - return ParameterPanel.num_cols.__get__(self) + 1 - - @property - def input_col(self): - return self.num_cols - 2 - - def get_value(self, parameter): - result = self.prog.output_channels[parameter[0]] - - if result is not None: - return str(result) - else: - raise KeyError(parameter[0]) - - def add_row(self, parameter): - ParameterPanel.add_row(self, parameter, increment_row=False) - - view_button = wx.Button(self, label='View') - self.parameter_sizer.Add(view_button, self.posn) - self.cur_col += 1 - self.Bind(wx.EVT_BUTTON, partial(self.OnView, parameter), view_button) - - self.cur_row += 1 - - def converter(self, parameter, x): - if x == '': - return x - else: - return pos_int_converter(x) - - def __init__(self, *args, **kwargs): - ParameterPanel.__init__(self, *args, **kwargs) - - # Spacer. - self.parameter_sizer.Add((-1, self.spacer_height), (self.cur_row, 0)) - self.cur_row += 1 - - # Frequency input. - self.parameter_sizer.Add(wx.StaticText(self, label='Sampling rate'), (self.cur_row, 0), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.freq_input = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER) - self.parameter_sizer.Add(self.freq_input, (self.cur_row, 1), flag=wx.EXPAND) - self.cur_row += 1 - - self.freq_input.Value = str(self.prog.frequency) - self.freq_input.default_background_color = self.freq_input.BackgroundColour - self.freq_input.BackgroundColour = OK_BACKGROUND_COLOR - - self.Bind(wx.EVT_TEXT, self.OnFrequencyChange, self.freq_input) - self.Bind(wx.EVT_TEXT_ENTER, self.OnFrequencyInput, self.freq_input) - - # Spacer. - self.parameter_sizer.Add((-1, self.spacer_height), (self.cur_row, 0)) - self.cur_row += 1 - - # AWG input. - self.parameter_sizer.Add(wx.StaticText(self, label='AWG'), (self.cur_row, 0), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.awg_input = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER) - self.parameter_sizer.Add(self.awg_input, (self.cur_row, 1), flag=wx.EXPAND) - self.cur_row += 1 - - self.awg_input.Value = self.prog.awg - self.awg_input.default_background_color = self.awg_input.BackgroundColour - self.awg_input.BackgroundColour = OK_BACKGROUND_COLOR - - self.Bind(wx.EVT_TEXT, self.OnAWGChange, self.awg_input) - self.Bind(wx.EVT_TEXT_ENTER, self.OnAWGInput, self.awg_input) - - # Oscilloscope input. - self.parameter_sizer.Add(wx.StaticText(self, label='Oscilloscope'), (self.cur_row, 0), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.oscilloscope_input = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER) - self.parameter_sizer.Add(self.oscilloscope_input, (self.cur_row, 1), flag=wx.EXPAND) - self.cur_row += 1 - - self.oscilloscope_input.Value = self.prog.oscilloscope - self.oscilloscope_input.default_background_color = self.oscilloscope_input.BackgroundColour - self.oscilloscope_input.BackgroundColour = OK_BACKGROUND_COLOR - - self.Bind(wx.EVT_TEXT, self.OnOscilloscopeChange, self.oscilloscope_input) - self.Bind(wx.EVT_TEXT_ENTER, self.OnOscilloscopeInput, self.oscilloscope_input) - - def set_value(self, parameter, value): - if value == '': - value = None - - self.prog.output_channels[parameter[0]] = value - - def OnFrequencyChange(self, evt=None): - self.freq_input.BackgroundColour = self.freq_input.default_background_color - - def OnFrequencyInput(self, evt=None): - try: - value = quantity_converter(self.freq_input.Value, 'Hz', 'frequency') - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - - return - - self.prog.frequency = value - - self.freq_input.BackgroundColour = OK_BACKGROUND_COLOR - - def OnAWGChange(self, evt=None): - self.awg_input.BackgroundColour = self.awg_input.default_background_color - - def OnAWGInput(self, evt=None): - self.prog.awg = self.awg_input.Value - - self.awg_input.BackgroundColour = OK_BACKGROUND_COLOR - - def OnOscilloscopeChange(self, evt=None): - self.oscilloscope_input.BackgroundColour = self.oscilloscope_input.default_background_color - - def OnOscilloscopeInput(self, evt=None): - self.prog.oscilloscope = self.oscilloscope_input.Value - - self.oscilloscope_input.BackgroundColour = OK_BACKGROUND_COLOR - - def OnView(self, parameter, evt=None): - def show_frame(waveform, markers, frequency): - view_frame = WaveformFrame(self, parameter[0]) - view_frame.SetValue(waveform, markers, frequency) - view_frame.Show() - - def show_error(error, monospace=False): - MessageDialog(self, error, 'Waveform generation error', monospace=monospace).Show() - - def show_waveform(): - try: - waveforms = self.prog.generate_waveforms() - except ValueError as e: - wx.CallAfter(show_error, str(e)) - - return - except PulseError as e: - wx.CallAfter(show_error, '\n'.join((e[0])), monospace=True) - - return - - waveform, markers = waveforms[parameter[0]] - - wx.CallAfter(show_frame, waveform, markers, self.prog.frequency) - - thr = Thread(target=show_waveform) - thr.daemon = True - thr.start() - - -class PulsePanel(ParameterPanel): - type = 'pulse' - name = 'Pulses' - attributes = True - use_resource_labels = True - - def add_resource_label(self, parameter): - if parameter[1] == 'shape': - self.cur_col += 1 - else: - ParameterPanel.add_resource_label(self, parameter) - - def add_row(self, parameter): - kwargs = {} - if parameter[1] == 'shape': - kwargs['input_type'] = 'file' - - return ParameterPanel.add_row(self, parameter, **kwargs) - - def converter(self, parameter, x): - x = ParameterPanel.converter(self, parameter, x) - - if parameter[1] == 'amplitude': - return quantity_converter(x, 'V', 'voltage', False) - elif parameter[1] == 'length': - return quantity_converter(x) - elif parameter[1] == 'shape': - return x - - -class PulseProgramPanel(wx.Panel): - """ - A panel to display and change all the parameters of a program. - """ - - panel_types = {'acq_marker': AcqMarkerPanel, 'delay': DelayPanel, 'int': IntPanel, - 'output': OutputPanel, 'pulse': PulsePanel} - - def __init__(self, parent, global_store, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - - self.prog = None - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Notebook. - self.parameter_notebook = wx.Notebook(self) - self.parameter_notebook.SetMinSize((600, 400)) - panel_box.Add(self.parameter_notebook, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) - - self.SetSizerAndFit(panel_box) - - def create_parameter_panels(self, prog): - types = set(prog.variables.values()) - - for type in sorted(types): - try: - result = self.panel_types[type](self.parameter_notebook, self.global_store, prog) - except KeyError: - MessageDialog('Unrecognized variable type "{0}"'.format(type)).Show() - - return - - self.parameter_notebook.AddPage(result, result.name) - - def OnOpen(self, prog): - self.prog = prog - - self.create_parameter_panels(self.prog) - - def OnClose(self): - self.prog = None - - self.parameter_notebook.DeleteAllPages() - - -class PulseProgramFrame(wx.Frame): - default_title = 'Pulse program' - - def __init__(self, parent, global_store, close_callback, *args, **kwargs): - if 'title' not in kwargs: - kwargs['title'] = self.default_title - else: - self.default_title = kwargs['title'] - - wx.Frame.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self.close_callback = close_callback - - # Menu. - menuBar = wx.MenuBar() - - ## File. - menu = wx.Menu() - menuBar.Append(menu, '&File') - - item = menu.Append(wx.ID_OPEN, '&Open...') - self.Bind(wx.EVT_MENU, self.OnMenuFileOpen, item) - - item = menu.Append(wx.ID_CLOSE, '&Close') - self.Bind(wx.EVT_MENU, self.OnMenuFileClose, item) - - self.SetMenuBar(menuBar) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - self.pulse_panel = PulseProgramPanel(self, self.global_store) - frame_box.Add(self.pulse_panel, proportion=1, flag=wx.EXPAND) - - self.SetSizerAndFit(frame_box) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - # Reload existing program. - if self.global_store.pulse_program is not None: - self.load_program(self.global_store.pulse_program) - - def load_program(self, prog): - self.Title = '{0} - {1}'.format(prog.filename, self.default_title) - - self.pulse_panel.OnOpen(prog) - - def OnMenuFileOpen(self, evt=None): - wildcard = determine_wildcard('pulse', 'Pulse program') - dlg = wx.FileDialog(parent=self, message='Load...', wildcard=wildcard, - style=wx.FD_OPEN) - - if dlg.ShowModal() == wx.ID_OK: - path = dlg.GetPath() - - try: - prog = Program.from_file(path) - except PulseSyntaxError as e: - MessageDialog(self, '\n'.join(e[0]), 'Compilation error', monospace=True).Show() - - return - - # Only purge the previous file if this one has been opened successfully. - self.OnMenuFileClose() - - self.load_program(prog) - - self.global_store.pulse_program = prog - - def OnMenuFileClose(self, evt=None): - if self.global_store.pulse_program is None: - return - - self.pulse_panel.OnClose() - - self.Title = self.default_title - - for label in self.global_store.pulse_program.resource_labels.values(): - del self.global_store.resources[label] - - self.global_store.pulse_program = None - - def OnClose(self, evt): - self.close_callback() - - evt.Skip() diff --git a/spacq/gui/config/scaling.py b/spacq/gui/config/scaling.py deleted file mode 100644 index e525256..0000000 --- a/spacq/gui/config/scaling.py +++ /dev/null @@ -1,101 +0,0 @@ -import wx -from wx.lib.agw.floatspin import FloatSpin - -from spacq.interface.units import Quantity - -from ..tool.box import Dialog - -""" -Resource scaling configuration. -""" - - -class ScalingSettings(object): - """ - Set up scaling of the form: - f(x) = a * x * (10 ** b) + c - """ - - def __init__(self): - self.linear_scale = 1 - self.exponential_scale = 0 - self.offset = 0 - - def transform(self, x): - """ - Perform a transform according to the scaling. - """ - - if self.offset == 0: - return self.linear_scale * x * (10 ** self.exponential_scale) - elif isinstance(x, Quantity): - return self.linear_scale * x * (10 ** self.exponential_scale) + Quantity(self.offset, x.original_units) - else: - return self.linear_scale * x * (10 ** self.exponential_scale) + self.offset - - -class ScalingSettingsDialog(Dialog): - def __init__(self, parent, ok_callback, *args, **kwargs): - Dialog.__init__(self, parent, title='Scaling settings') - - self.ok_callback = ok_callback - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Settings. - settings_box = wx.FlexGridSizer(rows=3, cols=2, hgap=5) - dialog_box.Add(settings_box, flag=wx.EXPAND|wx.ALL, border=5) - - ### Linear scale. - settings_box.Add(wx.StaticText(self, label='Linear scale:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.linear_scale_input = FloatSpin(self, value=0, min_val=-1e9, max_val=1e9, - increment=1, digits=5) - settings_box.Add(self.linear_scale_input, flag=wx.EXPAND) - - ### Exponential scale. - settings_box.Add(wx.StaticText(self, label='Exponential scale:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.exponential_scale_input = FloatSpin(self, value=0, min_val=-100, max_val=100, - increment=1, digits=2) - settings_box.Add(self.exponential_scale_input, flag=wx.EXPAND) - - ### Offset. - settings_box.Add(wx.StaticText(self, label='Offset:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.offset_input = FloatSpin(self, value=0, min_val=-1e9, max_val=1e9, - increment=1, digits=5, size=(200, -1)) - settings_box.Add(self.offset_input, flag=wx.EXPAND) - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def GetValue(self): - result = ScalingSettings() - - result.linear_scale = self.linear_scale_input.GetValue() - result.exponential_scale = self.exponential_scale_input.GetValue() - result.offset = self.offset_input.GetValue() - - return result - - def SetValue(self, value): - self.linear_scale_input.SetValue(value.linear_scale) - self.exponential_scale_input.SetValue(value.exponential_scale) - self.offset_input.SetValue(value.offset) - - def OnOk(self, evt=None): - self.ok_callback(self) - - self.Destroy() diff --git a/spacq/gui/config/variables.py b/spacq/gui/config/variables.py deleted file mode 100755 index 7ec2cfc..0000000 --- a/spacq/gui/config/variables.py +++ /dev/null @@ -1,920 +0,0 @@ -import ObjectListView -import wx - -from numpy import array -from spacq.interface.units import Quantity -from spacq.iteration.variables import OutputVariable, LinSpaceConfig, ArbitraryConfig -from spacq.iteration.variables import ConditionVariable, Condition - -from ..tool.box import Dialog, MessageDialog, load_pickled, save_pickled, load_csv -from spacq.gui.display.table.generic import VirtualListCtrl - -""" -An interface for creating and editing Variable objects. -""" - - -class VariableColumnDefn(ObjectListView.ColumnDefn): - """ - A column with useful defaults. - """ - - def __init__(self, width=90, align='centre', groupKeyGetter='order', *args, **kwargs): - ObjectListView.ColumnDefn.__init__(self, width=width, align=align, - groupKeyGetter=groupKeyGetter, *args, **kwargs) - - # No auto-width if space filling. - if self.isSpaceFilling: - self.width = 0 - - -class LinSpaceConfigPanel(wx.Panel): - def __init__(self, parent, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Config. - config_sizer = wx.FlexGridSizer(rows=3, cols=2) - config_sizer.AddGrowableCol(1, 1) - panel_box.Add(config_sizer, proportion=1, flag=wx.EXPAND) - - ### Initial. - config_sizer.Add(wx.StaticText(self, label='Initial:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - self.initial_input = wx.TextCtrl(self) - config_sizer.Add(self.initial_input, flag=wx.EXPAND|wx.ALL, border=5) - - ### Final. - config_sizer.Add(wx.StaticText(self, label='Final:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - self.final_input = wx.TextCtrl(self) - config_sizer.Add(self.final_input, flag=wx.EXPAND|wx.ALL, border=5) - - ### Steps. - config_sizer.Add(wx.StaticText(self, label='Steps:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - self.steps_input = wx.SpinCtrl(self, min=1, initial=1, max=1e9) - config_sizer.Add(self.steps_input, flag=wx.EXPAND|wx.ALL, border=5) - - self.SetSizerAndFit(panel_box) - - def GetValue(self): - # Ensure the values are sane. - try: - initial = float(self.initial_input.Value) - except ValueError: - raise ValueError('Invalid initial value.') - - try: - final = float(self.final_input.Value) - except ValueError: - raise ValueError('Invalid final value.') - - return LinSpaceConfig(initial, final, self.steps_input.Value) - - def SetValue(self, config): - self.initial_input.Value, self.final_input.Value, self.steps_input.Value = (str(config.initial), - str(config.final), config.steps) - - -class ArbitraryConfigPanel(wx.Panel): - def __init__(self, parent, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Config. - config_sizer = wx.FlexGridSizer(rows=1, cols=2) - config_sizer.AddGrowableCol(1, 1) - panel_box.Add(config_sizer, proportion=1, flag=wx.EXPAND) - - ### Values. - config_sizer.Add(wx.StaticText(self, label='Values:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - self.values_input = wx.TextCtrl(self) - config_sizer.Add(self.values_input, flag=wx.EXPAND|wx.ALL, border=5) - - self.SetSizerAndFit(panel_box) - - def GetValue(self): - raw_values = self.values_input.Value.split(',') - - # Ensure the values are sane. - try: - values = [float(x) for x in raw_values] - except ValueError as e: - raise ValueError('Invalid value: {0}'.format(str(e))) - - return ArbitraryConfig(values) - - def SetValue(self, config): - self.values_input.Value = ', '.join('{0:n}'.format(x) for x in config.values) - -# New config panel for getting arbitary variable values from CSV. -class FileConfigPanel(wx.Panel): - def __init__(self, parent, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - # Make parent attribute of FileConfigPanel to play nice with MessageDialog - self.parent = parent - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Table for csv data from spacq.gui.display.table.generic - self.table = VirtualListCtrl(self) - self.table.Hide() - - self.column_names = [] - - ## Config. - config_sizer = wx.GridBagSizer(3,2) - panel_box.Add(config_sizer, proportion=1, flag=wx.EXPAND) - - ### Load button - load_button = wx.Button(self, wx.ID_OPEN, label='Load CSV') - ## # TODO: write method for file selection - load_button.Bind(wx.EVT_BUTTON, self.OnMenuFileOpen ) - config_sizer.Add(load_button, pos=(0,0), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - ### File name display - self.csv_address = '' - self.csv_filename = wx.StaticText(self, label=self.csv_address) - config_sizer.Add(self.csv_filename,pos=(0,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL, border=5) - - ### Variable select - config_sizer.Add(wx.StaticText(self, label='Select Column:'), pos=(1,0), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - ### Display column names - self.variable_list = wx.ListBox(self, size=wx.Size(200,100), choices=self.column_names) - self.Bind(wx.EVT_LISTBOX, self.OnAxisSelection, self.variable_list) - config_sizer.Add(self.variable_list, pos=(1,1), flag=wx.ALL, border=5) - - ### Values. - # TODO: add enable/disable for needing csv selected first - config_sizer.Add(wx.StaticText(self, label='Values:'), pos=(2,0), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - # TODO: find wx box that allows overflow - self.value_bar = wx.StaticText(self, label='No CSV column selected.', - style=wx.ST_NO_AUTORESIZE) - config_sizer.Add(self.value_bar,pos=(2,1), flag=wx.ST_NO_AUTORESIZE|wx.ALL, border=5) - - - self.SetSizerAndFit(panel_box) - - # TODO: return to finishing this up or what not - # From math_setup for data explorer function for callback - def OnAxisSelection(self, evt=None): - # Get what list index was selected - if self.variable_list.Selection == wx.NOT_FOUND: - resultIndex = None - else: - resultIndex = self.variable_list.Selection - - # Retrieve table data, and select chosen column - headings, rows, types = self.table.GetValue(types=['scalar']) - - # Ensure the values are sane, because types is only determined off first entries. - try: - self.values_input = [float(x) for x in rows[:,resultIndex]] - except ValueError as e: - # not really working - MessageDialog(self.parent, str(e), 'Invalid value: {0}'.format(str(e))) - return - - # Formatting to remove newlines that numpy will add into array for printing - valuesLabel = str(self.values_input)[1:-1].replace('\n', ' ') - - # Display string of list - self.value_bar.SetLabel(valuesLabel) - - # loads csv and adds headers and data to self.object - def OnMenuFileOpen(self, evt=None): - try: - result = load_csv(self.parent) - except IOError as e: - # May need to be self.parent if this error ever gets called - MessageDialog(self.panel_box, str(e), 'Could not load data').Show() - return - - has_header, values, filename = result - - # TODO: load_csv will return has_header for first line being data - # not what wanted. Come back later - # If has_header is True, the first row becomes column names - - if has_header: - headers, rows = values[0], array(values[1:]) - else: - headers, rows = [''] * len(values[0]), array(values) - # Ensure that all columns have a header. - for i, header in enumerate(headers): - if not header: - headers[i] = 'Column {0}'.format(i + 1) - - # put filename and column variables into update display - self.csv_address = filename - self.column_names = headers - self.csv_filename.SetLabel(self.csv_address) - self.variable_list.Set(self.column_names) - - # put header names and data in the self.table for VirtualListCtrl - self.table.SetValue(headers, rows) - ############################################### - - # Regular Get and Set that finally set variables - def GetValue(self): - raw_values = self.values_input - - # Ensure the values are sane. - try: - values = [float(x) for x in raw_values] - except ValueError as e: - raise ValueError('Invalid value: {0}'.format(str(e))) - - return ArbitraryConfig(values) - - def SetValue(self, config): - self.values_input.Value = ', '.join('{0:n}'.format(x) for x in config.values) - -class ConditionEditor(Dialog): - def __init__(self, parent, ok_callback, *args, **kwargs): - kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER - - Dialog.__init__(self, parent, *args, **kwargs) - - self.ok_callback = ok_callback - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Condition Editor - - condition_static_box = wx.StaticBox(self, label = 'Condition') - condition_box = wx.StaticBoxSizer(condition_static_box, wx.HORIZONTAL) - dialog_box.Add(condition_box) - - ### Left Argument - - left_arg_box = wx.BoxSizer(wx.VERTICAL) - condition_box.Add(left_arg_box) - left_arg_input = wx.TextCtrl(self, size=(150, -1)) - left_arg_box.Add(left_arg_input) - - ### Operator - - operators = ['<','==', '!=', '>'] - self.op_menu = wx.ComboBox(self,choices = operators, style=wx.CB_READONLY, size =(70,-1)) - self.op_menu.SetStringSelection(operators[0]) - condition_box.Add(self.op_menu) - - ### Right Argument - - right_arg_box = wx.BoxSizer(wx.VERTICAL) - condition_box.Add(right_arg_box) - right_arg_input = wx.TextCtrl(self, size=(150, -1)) - right_arg_box.Add(right_arg_input) - - self.args = [left_arg_input, right_arg_input] - arg_boxes = [left_arg_box, right_arg_box] - - ## Type. - - #This loop setup is a bit messy coding. - self.arg_type_setters = [] - self.units_input = [] - for i in xrange(0,2): - type_static_box = wx.StaticBox(self, label='Argument {0}'.format(i+1)) - type_box = wx.StaticBoxSizer(type_static_box, wx.VERTICAL) - arg_boxes[i].Add(type_box, flag=wx.CENTER|wx.ALL, border=5) - - types = {} - - types['float'] = wx.RadioButton(self, label='Float', style=wx.RB_GROUP) - type_box.Add(types['float'], flag=wx.ALIGN_LEFT|wx.ALL, border=5) - - types['integer'] = wx.RadioButton(self, label='Integer') - type_box.Add(types['integer'], flag=wx.ALIGN_LEFT|wx.ALL, border=5) - - types['string'] = wx.RadioButton(self, label='String') - type_box.Add(types['string'], flag=wx.ALIGN_LEFT|wx.ALL, border=5) - - types['resource name'] = wx.RadioButton(self, label='Resource') - type_box.Add(types['resource name'], flag=wx.ALIGN_LEFT|wx.ALL, border=5) - - ### Units. - types['quantity'] = wx.RadioButton(self, label='Quantity') - type_box.Add(types['quantity'], flag=wx.CENTER) - - self.arg_type_setters.append(types) - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def OnOk(self, evt=None): - if self.ok_callback(self): - self.Destroy() - - def SetValue(self, condition): - self.arg_type_setters[0][condition.type1].Value = True - self.arg_type_setters[1][condition.type2].Value = True - self.args[0].Value = str(condition.arg1) - self.args[1].Value = str(condition.arg2) - self.op_menu.SetStringSelection(condition.op_symbol) - - - def GetValue(self): - cond_args = [] - arg_types = [] - for i, type in enumerate(self.arg_type_setters): - # We ensure values are sane along the way. - if type['float'].Value: - arg_types.append('float') - cond_args.append(float(self.args[i].Value)) - elif type['integer'].Value: - arg_types.append('integer') - cond_args.append(int(self.args[i].Value)) - elif type['resource name'].Value: - arg_types.append('resource name') - cond_args.append(self.args[i].Value) - elif type['quantity'].Value: - arg_types.append('quantity') - cond_args.append(Quantity(self.args[i].Value)) - elif type['string'].Value: - arg_types.append('string') - cond_args.append(self.args[i].Value) - - condition = Condition(arg_types[0], arg_types[1], cond_args[0], self.op_menu.Value, cond_args[1]) - - return condition - - -class ConditionVariableEditor(Dialog): - col_conditions = VariableColumnDefn(title='Condition', valueGetter=lambda x: str(x), - isSpaceFilling=True, align='left') - - - def __init__(self, parent, ok_callback, *args, **kwargs): - kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER - - Dialog.__init__(self, parent, *args, **kwargs) - - self.ok_callback = ok_callback - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - dialog_box.SetMinSize((350,200)) - - ## List (left op, op, right op). - - ## OLV. - self.condition_olv = ObjectListView.FastObjectListView(self) - dialog_box.Add(self.condition_olv, proportion=1, flag=wx.ALL|wx.EXPAND) - - self.condition_olv.SetColumns([self.col_conditions]) - - self.condition_olv.cellEditMode = self.condition_olv.CELLEDIT_DOUBLECLICK - self.condition_olv.Bind(ObjectListView.EVT_CELL_EDIT_STARTING, self.OnCellEditStarting) - - row_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(row_box, proportion=0, flag=wx.ALL|wx.CENTER) - - ## Add Condition. - add_button = wx.Button(self, wx.ID_ADD) - self.Bind(wx.EVT_BUTTON, self.OnAddCondition, add_button) - row_box.Add(add_button) - - ## Remove Condition. - remove_button = wx.Button(self, wx.ID_REMOVE) - remove_button.Bind(wx.EVT_BUTTON, self.OnRemoveConditions) - row_box.Add(remove_button) - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def GetValue(self): - conditions = self.condition_olv.GetObjects() - - # Get the resource_names from the conditions. - resource_names = [] - for condition in conditions: - if condition.type1 == 'resource name': - resource_names.append(condition.arg1) - if condition.type2 == 'resource name': - resource_names.append(condition.arg2) - - return resource_names, conditions - - def SetValue(self, resource_names, conditions): - - self.condition_olv.SetObjects(conditions) - - def OnOk(self, evt=None): - if self.ok_callback(self): - self.Destroy() - - def OnAddCondition(self, evt=None): - """ - Add a condition to the listctrl - """ - - def ok_callback(dlg): - try: - self.condition_olv.AddObject(dlg.GetValue()) - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return False - - # OLV likes to select a random item at this point. - self.condition_olv.DeselectAll() - return True - - #Start up the new dialog. - dlg = ConditionEditor(self, ok_callback, title='Condition Editor') - dlg.Show() - - def OnRemoveConditions(self, evt=None): - """ - Remove all selected conditions from the OLV. - """ - - selected = self.condition_olv.GetSelectedObjects() - - if selected: - self.condition_olv.RemoveObjects(selected) - - def OnCellEditStarting(self, evt): - condition = evt.rowModel - - # Ignore frivolous requests. - if evt.rowIndex < 0: - evt.Veto() - return - - def ok_callback(dlg): - try: - value = dlg.GetValue() - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return False - - condition.type1 = value.type1 - condition.type2 = value.type2 - condition.arg1 = value.arg1 - condition.arg2 = value.arg2 - condition.op_symbol = value.op_symbol - return True - - dlg = ConditionEditor(self, ok_callback, title='Condition Editor') - dlg.SetValue(condition) - dlg.Show() - - # No need to use the default editor. - evt.Veto() - - -class OutputVariableEditor(Dialog): - def __init__(self, parent, ok_callback, *args, **kwargs): - kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER - - Dialog.__init__(self, parent, *args, **kwargs) - - self.ok_callback = ok_callback - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Config. - self.config_notebook = wx.Notebook(self) - dialog_box.Add(self.config_notebook, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) - - self.config_panel_types = [] - - ### Linear. - linspace_config_panel = LinSpaceConfigPanel(self.config_notebook) - self.config_panel_types.append(LinSpaceConfig) - self.config_notebook.AddPage(linspace_config_panel, 'Linear') - - ### Arbitrary. - arbitrary_config_panel = ArbitraryConfigPanel(self.config_notebook) - self.config_panel_types.append(ArbitraryConfig) - self.config_notebook.AddPage(arbitrary_config_panel, 'Arbitrary') - - ### File load - file_config_panel = FileConfigPanel(self.config_notebook) - self.config_panel_types.append(ArbitraryConfig) - self.config_notebook.AddPage(file_config_panel, 'From File') - - ## Smooth set. - smooth_static_box = wx.StaticBox(self, label='Smooth set') - smooth_box = wx.StaticBoxSizer(smooth_static_box, wx.HORIZONTAL) - dialog_box.Add(smooth_box, flag=wx.CENTER|wx.ALL, border=5) - - smooth_box.Add(wx.StaticText(self, label='Steps:'), flag=wx.CENTER) - - self.smooth_steps_input = wx.SpinCtrl(self, min=1, initial=10) - smooth_box.Add(self.smooth_steps_input, flag=wx.CENTER|wx.ALL, border=5) - - self.smooth_from_checkbox = wx.CheckBox(self, label='From const') - smooth_box.Add(self.smooth_from_checkbox, flag=wx.CENTER|wx.ALL, border=5) - - self.smooth_to_checkbox = wx.CheckBox(self, label='To const') - smooth_box.Add(self.smooth_to_checkbox, flag=wx.CENTER|wx.ALL, border=5) - - self.smooth_transition_checkbox = wx.CheckBox(self, label='Transition') - smooth_box.Add(self.smooth_transition_checkbox, flag=wx.CENTER|wx.ALL, border=5) - - ## Type. - type_static_box = wx.StaticBox(self, label='Type') - type_box = wx.StaticBoxSizer(type_static_box, wx.HORIZONTAL) - dialog_box.Add(type_box, flag=wx.CENTER|wx.ALL, border=5) - - self.type_float = wx.RadioButton(self, label='Float', style=wx.RB_GROUP) - type_box.Add(self.type_float, flag=wx.CENTER|wx.ALL, border=5) - - self.type_integer = wx.RadioButton(self, label='Integer') - type_box.Add(self.type_integer, flag=wx.CENTER|wx.ALL, border=5) - - ### Units. - quantity_static_box = wx.StaticBox(self, label='Quantity') - quantity_box = wx.StaticBoxSizer(quantity_static_box, wx.HORIZONTAL) - type_box.Add(quantity_box) - - self.type_quantity = wx.RadioButton(self) - quantity_box.Add(self.type_quantity, flag=wx.CENTER) - - self.units_input = wx.TextCtrl(self) - quantity_box.Add(self.units_input) - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def GetValue(self): - if self.type_float.Value: - type = 'float' - units = None - elif self.type_integer.Value: - type = 'integer' - units = None - else: - type = 'quantity' - units = self.units_input.Value - - # Ensure that the units are valid. - Quantity(1, units) - - return (self.config_notebook.CurrentPage.GetValue(), self.smooth_steps_input.Value, - self.smooth_from_checkbox.Value, self.smooth_to_checkbox.Value, - self.smooth_transition_checkbox.Value, type, units) - - def SetValue(self, config, smooth_steps, smooth_from, smooth_to, smooth_transition, type, units): - config_type = self.config_panel_types.index(config.__class__) - self.config_notebook.ChangeSelection(config_type) - self.config_notebook.CurrentPage.SetValue(config) - - (self.smooth_steps_input.Value, self.smooth_from_checkbox.Value, - self.smooth_to_checkbox.Value, - self.smooth_transition_checkbox.Value) = smooth_steps, smooth_from, smooth_to, smooth_transition - - if type == 'float': - self.type_float.Value = True - elif type == 'integer': - self.type_integer.Value = True - else: - self.type_quantity.Value = True - self.units_input.Value = units if units is not None else '' - - def OnOk(self, evt=None): - if self.ok_callback(self): - self.Destroy() - - -class VariablesPanel(wx.Panel): - col_name = VariableColumnDefn(checkStateGetter='enabled', title='Name', valueGetter='name', - width=150, align='left') - col_order = VariableColumnDefn(title='#', valueGetter='order', width=40) - col_resource = VariableColumnDefn(title='Resource', valueGetter='resource_name', - width=150, align='left') - col_values = VariableColumnDefn(title='Values', valueGetter=lambda x: str(x), - isSpaceFilling=True, align='left') - col_wait = VariableColumnDefn(title='Wait time', valueGetter='wait') - col_const = VariableColumnDefn(checkStateGetter='use_const', title='Const. value', - valueGetter='const') - - def __init__(self, parent, global_store, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## OLV. - self.olv = ObjectListView.GroupListView(self) - panel_box.Add(self.olv, proportion=1, flag=wx.ALL|wx.EXPAND) - - self.olv.SetColumns([self.col_name, self.col_order, self.col_resource, self.col_values, - self.col_wait, self.col_const]) - self.olv.SetSortColumn(self.col_order) - - self.olv.cellEditMode = self.olv.CELLEDIT_DOUBLECLICK - self.olv.Bind(ObjectListView.EVT_CELL_EDIT_STARTING, self.OnCellEditStarting) - self.olv.Bind(ObjectListView.EVT_CELL_EDIT_FINISHING, self.OnCellEditFinishing) - self.olv.Bind(ObjectListView.EVT_CELL_EDIT_FINISHED, self.OnCellEditFinished) - - ## Buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - panel_box.Add(button_box, proportion=0, flag=wx.ALL|wx.CENTER) - - ### Row buttons. - row_box = wx.BoxSizer(wx.HORIZONTAL) - button_box.Add(row_box, flag=wx.LEFT, border=20) - - add_button = wx.Button(self, wx.ID_ADD, label='Add Output') - add_button.Bind(wx.EVT_BUTTON, self.OnAddVariable) - row_box.Add(add_button) - - add_cond_button = wx.Button(self, wx.ID_ADD, label='Add Condition') - add_cond_button.Bind(wx.EVT_BUTTON, self.OnAddConditionVariable) - row_box.Add(add_cond_button) - - remove_button = wx.Button(self, wx.ID_REMOVE) - remove_button.Bind(wx.EVT_BUTTON, self.OnRemoveVariables) - row_box.Add(remove_button) - - ### Export buttons. - export_box = wx.BoxSizer(wx.HORIZONTAL) - button_box.Add(export_box, flag=wx.LEFT, border=20) - - save_button = wx.Button(self, wx.ID_SAVE, label='Save...') - save_button.Bind(wx.EVT_BUTTON, self.OnSave) - export_box.Add(save_button) - - load_button = wx.Button(self, wx.ID_OPEN, label='Load...') - load_button.Bind(wx.EVT_BUTTON, self.OnLoad) - export_box.Add(load_button) - - self.SetSizer(panel_box) - - def max_order(self): - """ - Find the highest-used order in the OLV. - """ - - try: - return max(x.order for x in self.olv.GetObjects()) - except ValueError: - return 0 - - def OnCellEditStarting(self, evt): - col = evt.objectListView.columns[evt.subItemIndex] - var = evt.rowModel - - # Ignore frivolous requests. - if evt.rowIndex < 0: - evt.Veto() - return - - if col == self.col_values: - def ok_callback(dlg): - try: - values = dlg.GetValue() - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return False - - for i, name in enumerate(var.editor_parameters): - setattr(var, name, values[i]) - - return True - - editor = var.editor - dlg = editor(self, ok_callback, title=var.name) - dlg.SetValue(*[getattr(var,attr) for attr in var.editor_parameters]) - - dlg.Show() - - # No need to use the default editor. - evt.Veto() - - - elif col == self.col_const: - # We replace the editor with float editor, as ObjectListView picks the first entry by default - # in the column as what defines the editor. - evt.editor = ObjectListView.CellEditor.FloatEditor(self.olv, evt.subItemIndex) - - - # if there is something non-editable, we cancel the edit. - if col.valueGetter in var.edit_restrictions: - evt.Veto() - - def OnCellEditFinishing(self, evt): - col = evt.objectListView.columns[evt.subItemIndex] - - if col == self.col_name: - var = evt.rowModel # With old name. - var_new_name = evt.editor.Value - - if var_new_name == var.name: - # Not actually changed. - return - - # Attempt to add a new entry first. - try: - self.global_store.variables[var_new_name] = var.variable - except KeyError: - MessageDialog(self, var_new_name, 'Variable name conflicts').Show() - evt.Veto() - return - - # Remove the old entry. - del self.global_store.variables[var.name] - - def OnCellEditFinished(self, evt): - col = evt.objectListView.columns[evt.subItemIndex] - - if col == self.col_order: - self.olv.RebuildGroups() - - def OnSave(self, evt=None): - """ - Save all the rows in the OLV. - """ - - try: - save_pickled(self, [var.variable for var in self.olv.GetObjects()], extension='var', file_type='Variables') - except IOError as e: - MessageDialog(self, str(e), 'Save error').Show() - return - - def OnLoad(self, evt=None): - """ - Load some rows to the OLV. - """ - - try: - values = load_pickled(self, extension='var', file_type='Variables') - except IOError as e: - MessageDialog(self, str(e), 'Load error').Show() - return - - if values is not None: - # Clear the OLV. - for var in self.olv.GetObjects(): - del self.global_store.variables[var.name] - self.olv.RemoveObject(var) - - conflicting_names = [] - for var in values: - try: - self.global_store.variables[var.name] = var.variable - except KeyError: - conflicting_names.append(var.name) - continue - - self.olv.AddObject(var) - - if conflicting_names: - MessageDialog(self, ', '.join(conflicting_names), 'Variable names conflict').Show() - - def OnAddVariable(self, evt=None): - """ - Add a blank variable to the OLV. - """ - - # Ensure that we get a unique name. - with self.global_store.variables.lock: - num = 1 - done = False - while not done: - name = 'New variable {0}'.format(num) - var = OutputVariable(name=name, order=self.max_order()+1) - - try: - self.global_store.variables[name] = var - except KeyError: - num += 1 - else: - done = True - - guivar = GuiVariable(var,'output') - self.olv.AddObject(guivar) - - # OLV likes to select a random item at this point. - self.olv.DeselectAll() - - def OnAddConditionVariable(self, evt=None): - """ - Add a blank conditional variable to the OLV. - """ - - with self.global_store.variables.lock: - num = 1 - done = False - while not done: - name = 'New condition {0}'.format(num) - var = ConditionVariable(name=name, order=self.max_order()+1) - - try: - self.global_store.variables[name] = var - except KeyError: - num += 1 - else: - done = True - - guivar = GuiVariable(var,'condition') - self.olv.AddObject(guivar) - - # OLV likes to select a random item at this point. - self.olv.DeselectAll() - - def OnRemoveVariables(self, evt=None): - """ - Remove all selected variables from the OLV. - """ - - selected = self.olv.GetSelectedObjects() - - if selected: - self.olv.RemoveObjects(selected) - - for row in selected: - del self.global_store.variables[row.name] - - -class GuiVariable(object): - """ - Wraps variables for displaying in the ObjectListView. - This variable will act like the wrapped variable, but will also contain attributes - describing how the values should be edited. - If the variable doesn't have the attribute, then this class creates that attribute - and assigns it a value of 'N/A'. - """ - def __init__(self, var, vartype): - - # Make the gui variable quack like the wrapped variable. - self.__class__ = type(var.__class__.__name__, - (self.__class__, var.__class__), - {}) - self.__dict__ = var.__dict__ - - self.variable = var - self._allowed_types = set(['condition','output']) - self.edit_restrictions = [] - - if vartype not in self._allowed_types: - raise ValueError('Invalid variable type: {0}'.format(type)) - - # Depending on the type of variable, how the variable is displayed and edited is defined below. - if vartype == 'condition': - self.editor = ConditionVariableEditor - self.editor_parameters = ('resource_names', 'conditions') - - #columns that condition variables DONT have. - self.edit_restrictions.append('const') - self.edit_restrictions.append('resource_name') - - elif vartype == 'output': - self.editor = OutputVariableEditor - self.editor_parameters = ('config', 'smooth_steps', 'smooth_from', - 'smooth_to', 'smooth_transition', - 'type', 'units') - - def __getattr__(self, name): - #If the gui variable doesn't have the attribute, then create the attribute - #with a value of 'N/A', and return its value. - setattr(self,name,'N/A') - return getattr(self, name) diff --git a/spacq/gui/config/virtual_variables.py b/spacq/gui/config/virtual_variables.py deleted file mode 100644 index 048e3b2..0000000 --- a/spacq/gui/config/virtual_variables.py +++ /dev/null @@ -1,267 +0,0 @@ -import wx -import wx.lib.scrolledpanel as scrolled - -""" -An interface for defining virtual swept variables and writing to -real resources. -""" -class MultipleVariableConfigPanel(scrolled.ScrolledPanel): - def __init__(self, parent, *args, **kwargs): - scrolled.ScrolledPanel.__init__(self, parent, -1, style=wx.HSCROLL, *args, **kwargs) - - self.initial_var_count = 4 - - # Panel - - self.scrolled_panel = scrolled.ScrolledPanel(self,-1, - style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER, name='scroll panel') - self.scrolled_panel.SetAutoLayout(1) - self.scrolled_panel.SetupScrolling() - - multiple_static_box = wx.StaticBox(self, label= 'Virtual Variable Setup') - - self.panel_box = wx.StaticBoxSizer(multiple_static_box,wx.VERTICAL) - - count_setup = wx.BoxSizer(wx.HORIZONTAL) - self.panel_box.Add(count_setup, flag=wx.ALL, border=5) - - label = wx.StaticText(self, label='Number of Virtual Variables') - self.var_count = wx.SpinCtrl(self, min=1, initial=2, max=100) - button = wx.Button(self, label='Update') - self.Bind(wx.EVT_BUTTON, self.OnUpdate, button) - - count_setup.Add(label,flag=wx.ALL, border=5) - count_setup.Add(self.var_count, flag=wx.ALL, border=5) - count_setup.Add(button, flag=wx.ALL, border=5) - - # Class attribute so can add to in OnUpdate - self.value_setup = wx.BoxSizer(wx.HORIZONTAL) - self.panel_box.Add(self.value_setup, flag=wx.ALL, border=5) - - # Labels for virtual variable inputs - label_setup = wx.BoxSizer(wx.VERTICAL) - label_setup.AddSpacer(5) - label_setup.Add(wx.StaticText(self, label='Name'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - label_setup.AddSpacer(8) - label_setup.Add(wx.StaticText(self, label='Initial:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - label_setup.AddSpacer(8) - label_setup.Add(wx.StaticText(self, label='Final:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - label_setup.AddSpacer(8) - label_setup.Add(wx.StaticText(self, label='Steps:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - label_setup.AddSpacer(8) - label_setup.Add(wx.StaticText(self, label='Order:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) - self.value_setup.Add(label_setup, flag=wx.EXPAND|wx.ALL, border=5) - - # for i in range(0,self.variable_count.GetValue()): - self.name_value = [None]*self.initial_var_count - self.start_value = [None]*self.initial_var_count - self.end_value = [None]*self.initial_var_count - self.step_value = [None]*self.initial_var_count - self.order_value = [None]*self.initial_var_count - - for i in range(0,self.initial_var_count): - mini_setup = wx.BoxSizer(wx.VERTICAL) - self.name_value[i] = wx.TextCtrl(self, value="VirtVar{0}".format(i)) - mini_setup.Add(self.name_value[i], flag=wx.EXPAND|wx.ALL, border=5) - self.start_value[i] = wx.TextCtrl(self, value="1") - mini_setup.Add(self.start_value[i], flag=wx.EXPAND|wx.ALL, border=5) - self.end_value[i] = wx.TextCtrl(self, value="2") - mini_setup.Add(self.end_value[i], flag=wx.EXPAND|wx.ALL, border=5) - self.step_value[i] = wx.SpinCtrl(self, min=1, initial=3, max=1e9) - mini_setup.Add(self.step_value[i], flag=wx.EXPAND|wx.ALL, border=5) - self.order_value[i] = wx.SpinCtrl(self, min=1, initial=1, max=1e9) - mini_setup.Add(self.order_value[i], flag=wx.EXPAND|wx.ALL, border=5) - - self.value_setup.Add(mini_setup, flag=wx.EXPAND|wx.ALL, border=5) - - self.SetSizerAndFit(self.panel_box) - self.SetupScrolling(scroll_y = False) - - def OnUpdate(self, evt=None): - EnableCount = self.var_count.Value - - # Adding more than initially setup with - if EnableCount > self.initial_var_count: - for i in range(self.initial_var_count,EnableCount): - self.name_value.append(wx.TextCtrl(self, value="VirtVar{0}".format(i))) - self.start_value.append(wx.TextCtrl(self, value="1")) - self.end_value.append(wx.TextCtrl(self, value="2")) - self.step_value.append(wx.SpinCtrl(self, min=1, initial=3, max=1e9)) - self.order_value.append(wx.SpinCtrl(self, min=1, initial=1, max=1e9)) - - mini_setup = wx.BoxSizer(wx.VERTICAL) - mini_setup.Add(self.name_value[i], flag=wx.EXPAND|wx.ALL, border=5) - mini_setup.Add(self.start_value[i], flag=wx.EXPAND|wx.ALL, border=5) - mini_setup.Add(self.end_value[i], flag=wx.EXPAND|wx.ALL, border=5) - mini_setup.Add(self.step_value[i], flag=wx.EXPAND|wx.ALL, border=5) - mini_setup.Add(self.order_value[i], flag=wx.EXPAND|wx.ALL, border=5) - - self.value_setup.Add(mini_setup, flag=wx.EXPAND|wx.ALL, border=5) - - # self.SetSizerAnd(self.panel_box) - # self.SetSizerAndFit(self.panel_box) - self.Layout() - self.SetupScrolling(scroll_y = False) - - self.initial_var_count = EnableCount - - for i in range(0,EnableCount): - self.name_value[i].Show() - self.start_value[i].Show() - self.end_value[i].Show() - self.step_value[i].Show() - self.order_value[i].Show() - for i in range(EnableCount,self.initial_var_count): - self.name_value[i].Hide() - self.start_value[i].Hide() - self.end_value[i].Hide() - self.step_value[i].Hide() - self.order_value[i].Hide() - - # self.SetSizerAndFit(self.panel_box) - - def GetValue(self): - try: - starts = [float(x.Value) for x in self.start_value] - except ValueError: - raise ValueError('Invalid initial value.') - try: - ends = [float(x.Value) for x in self.end_value] - except ValueError: - raise ValueError('Invalid initial value.') - - names = [x.Value for x in self.name_value] - steps = [x.Value for x in self.step_value] - orders = [x.Value for x in self.order_value] - - return names, starts, ends, steps, orders - - - def GetValue(self): - try: - starts = [float(x.Value) for x in self.start_value] - except ValueError: - raise ValueError('Invalid initial value.') - try: - ends = [float(x.Value) for x in self.end_value] - except ValueError: - raise ValueError('Invalid initial value.') - - names = [x.Value for x in self.name_value] - steps = [x.Value for x in self.step_value] - orders = [x.Value for x in self.order_value] - - return names, starts, ends, steps, orders - - -class DependentVariableConfigPanel(scrolled.ScrolledPanel): - def __init__(self, parent, *args, **kwargs): - scrolled.ScrolledPanel.__init__(self, parent, -1, style=wx.VSCROLL, *args, **kwargs) - - self.scrolled_panel = scrolled.ScrolledPanel(self,-1, - style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER, name='scroll panel') - self.scrolled_panel.SetAutoLayout(1) - self.scrolled_panel.SetupScrolling() - - - static_box = wx.StaticBox(self, label='Dependent Variable Setup') - static_panel_box = wx.StaticBoxSizer(static_box, wx.VERTICAL) - - count_setup = wx.BoxSizer(wx.HORIZONTAL) - static_panel_box.Add(count_setup, flag=wx.ALL, border=5) - - label = wx.StaticText(self, label='Number of Dependent Variables') - self.dependent_count = wx.SpinCtrl(self, min=1, initial=2, max=100) - button = wx.Button(self, label='Update') - self.Bind(wx.EVT_BUTTON, self.OnUpdate, button) - - count_setup.Add(label, flag=wx.ALL, border = 5) - count_setup.Add(self.dependent_count, flag=wx.ALL, border = 5) - count_setup.Add(button, flag=wx.ALL, border = 5) - - self.initial_var_count = 6 - - # panel_box = wx.FlexGridSizer(3,2) - panel_box = wx.BoxSizer(wx.HORIZONTAL) - self.left_box = wx.BoxSizer(wx.VERTICAL) - self.right_box = wx.BoxSizer(wx.VERTICAL) - - self.name_value = [None]*self.initial_var_count - self.expression_value = [None]*self.initial_var_count - self.equal_bar = [None]*self.initial_var_count - self.enable = [True]*self.initial_var_count - - for i in range(0,self.initial_var_count): - unit_box = wx.BoxSizer(wx.HORIZONTAL) - self.name_value[i] = wx.TextCtrl(self, value="RealVar{0}".format(i)) - unit_box.Add(self.name_value[i], flag=wx.EXPAND|wx.ALL, border=5) - self.equal_bar[i] = wx.StaticText(self, label=' = ') - unit_box.Add(self.equal_bar[i], flag=wx.ALL) - self.expression_value[i] = wx.TextCtrl(self) - unit_box.Add(self.expression_value[i], flag=wx.EXPAND|wx.ALL, border=5) - - # Alternate left and right boxs - if i % 2 == 0: - self.left_box.Add(unit_box, flag=wx.EXPAND|wx.ALL, border=5) - else: - self.right_box.Add(unit_box, flag=wx.EXPAND|wx.ALL, border=5) - - panel_box.Add(self.left_box, flag=wx.EXPAND|wx.ALL, border=5) - panel_box.Add(self.right_box, flag=wx.EXPAND|wx.ALL, border=5) - - static_panel_box.Add(panel_box, flag=wx.EXPAND|wx.ALL, border=5) - self.SetSizerAndFit(static_panel_box) - self.SetupScrolling(scroll_x = False) - - def OnUpdate(self, evt=None): - EnableCount = self.dependent_count.Value - - # Add entries to store - if EnableCount > self.initial_var_count: - for i in range(self.initial_var_count, EnableCount): - # Add to the lists - self.name_value.append(wx.TextCtrl(self, value="RealVar{0}".format(i))) - self.equal_bar.append(wx.StaticText(self, label=' = ')) - self.expression_value.append(wx.TextCtrl(self)) - self.enable.append(True) - - unit_box = wx.BoxSizer(wx.HORIZONTAL) - unit_box.Add(self.name_value[i], flag=wx.EXPAND|wx.ALL, border=5) - unit_box.Add(self.equal_bar[i], flag=wx.ALL) - unit_box.Add(self.expression_value[i], flag=wx.EXPAND|wx.ALL, border=5) - - # panel_box.Add(unit_box, flag=wx.EXPAND|wx.ALL, border=5) - if i % 2 == 0: - self.left_box.Add(unit_box, flag=wx.EXPAND|wx.ALL, border=5) - else: - self.right_box.Add(unit_box, flag=wx.EXPAND|wx.ALL, border=5) - - self.Layout() - self.SetupScrolling(scroll_x = False) - - self.initial_var_count = EnableCount - - - # Toggle those enabled and not - for i in range(0,EnableCount): - self.name_value[i].Show() - self.expression_value[i].Show() - self.equal_bar[i].Show() - self.enable[i] = True - - for i in range(EnableCount,self.initial_var_count): - self.name_value[i].Hide() - self.expression_value[i].Hide() - self.equal_bar[i].Hide() - self.enable[i] = False - - def GetValue(self): - names = [x.Value for x in self.name_value] - expressions = [x.Value for x in self.expression_value] - - return names, expressions diff --git a/spacq/gui/display/__init__.py b/spacq/gui/display/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/display/plot/__init__.py b/spacq/gui/display/plot/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/display/plot/colormapped.py b/spacq/gui/display/plot/colormapped.py deleted file mode 100644 index 84b7651..0000000 --- a/spacq/gui/display/plot/colormapped.py +++ /dev/null @@ -1,100 +0,0 @@ -from chaco.api import ArrayPlotData, ColorBar, HPlotContainer, jet, LinearMapper -from chaco.tools.api import RangeSelection, RangeSelectionOverlay -from enable.api import Window - -from .common.chaco_plot import ChacoPlot - -""" -An embeddable colormapped plot. -""" - - -class ColormappedPlot(ChacoPlot): - """ - A colormapped plot. - """ - - def __init__(self, parent, x_bounds, y_bounds, *args, **kwargs): - self.parent = parent - - self.data = ArrayPlotData() - self.data.set_data('color', [[0]]) - - ChacoPlot.__init__(self, self.data, *args, **kwargs) - - self.img_plot('color', colormap=jet, xbounds=x_bounds, ybounds=y_bounds) - - self.configure() - - @property - def plot_obj(self): - """ - The actual plot object. - """ - - return self.plots.values()[0][0] - - @property - def control(self): - """ - A drawable control with a color bar. - """ - - color_map = self.plot_obj.color_mapper - linear_mapper = LinearMapper(range=color_map.range) - color_bar = ColorBar(index_mapper=linear_mapper, color_mapper=color_map, plot=self.plot_obj, - orientation='v', resizable='v', width=30) - color_bar._axis.tick_label_formatter = self.sci_formatter - color_bar.padding_top = self.padding_top - color_bar.padding_bottom = self.padding_bottom - color_bar.padding_left = 50 # Room for labels. - color_bar.padding_right = 10 - - range_selection = RangeSelection(component=color_bar) - range_selection.listeners.append(self.plot_obj) - color_bar.tools.append(range_selection) - - range_selection_overlay = RangeSelectionOverlay(component=color_bar) - color_bar.overlays.append(range_selection_overlay) - - container = HPlotContainer(use_backbuffer=True) - container.add(self) - container.add(color_bar) - - return Window(self.parent, component=container).control - - @property - def color_data(self): - """ - Plotted values. - """ - - return self.data.get_data('color') - - @color_data.setter - def color_data(self, values): - self.data.set_data('color', values) - - @property - def low_setting(self): - """ - Lowest color value. - """ - - return self.plot_obj.color_mapper.range.low - - @low_setting.setter - def low_setting(self, value): - self.plot_obj.color_mapper.range.low_setting = value - - @property - def high_setting(self): - """ - Highest color value. - """ - - return self.plot_obj.color_mapper.range.high - - @high_setting.setter - def high_setting(self, value): - self.plot_obj.color_mapper.range.high_setting = value diff --git a/spacq/gui/display/plot/common/__init__.py b/spacq/gui/display/plot/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/display/plot/common/chaco_plot.py b/spacq/gui/display/plot/common/chaco_plot.py deleted file mode 100644 index 25a6293..0000000 --- a/spacq/gui/display/plot/common/chaco_plot.py +++ /dev/null @@ -1,72 +0,0 @@ -from chaco.api import Plot -from chaco.tools.api import BetterSelectingZoom, PanTool - -""" -Chaco wrapper. -""" - - -class ChacoPlot(Plot): - """ - A 2D Chaco plot wrapped with useful common functionality. - """ - - @staticmethod - def sci_formatter(value): - """ - Convert a value to a scientific notation string as applicable. - """ - - # Subtly different from g or n presentation types. - if value != 0 and (abs(value) < 1e-3 or abs(value) > 1e3): - parts = '{0:e}'.format(value).split('e') - result = parts[0].rstrip('0').rstrip('.') + 'e' + parts[1] - else: - result = '{0:f}'.format(value).rstrip('0').rstrip('.') - - return result - - def configure(self): - """ - Configure padding, tools, etc. - """ - - # Padding. - self.padding = 20 - self.padding_left = 120 - self.padding_bottom = 55 - - # Axes. - self.index_axis.tick_label_formatter = self.sci_formatter - self.value_axis.tick_label_formatter = self.sci_formatter - - # Tools. - self.tools.append(PanTool(self)) - - zoom = BetterSelectingZoom(self) - self.tools.append(zoom) - self.overlays.append(zoom) - - @property - def x_label(self): - """ - The x axis label. - """ - - return self.index_axis.title - - @x_label.setter - def x_label(self, value): - self.index_axis.title = value - - @property - def y_label(self): - """ - The y axis label. - """ - - return self.value_axis.title - - @y_label.setter - def y_label(self, value): - self.value_axis.title = value diff --git a/spacq/gui/display/plot/live/__init__.py b/spacq/gui/display/plot/live/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/display/plot/live/list.py b/spacq/gui/display/plot/live/list.py deleted file mode 100644 index 574f598..0000000 --- a/spacq/gui/display/plot/live/list.py +++ /dev/null @@ -1,430 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -import functools -import numpy -from pubsub import pub -from threading import Lock -from time import localtime -import wx -from wx.lib.agw import floatspin - -from spacq.interface.resources import AcquisitionThread -from spacq.interface.units import Quantity - -from ....config.measurement import MeasurementConfigPanel -from ....tool.box import Dialog, MessageDialog - -try: - from ..two_dimensional import TwoDimensionalPlot -except ImportError as e: - plot_available = False - log.debug('Could not import TwoDimensionalPlot: {0}'.format(str(e))) -else: - plot_available = True - -""" -A historical live view plot for list values. -""" - - -class PlotSettings(object): - """ - Wrapper for all the settings configured via dialog. - """ - - def __init__(self): - self.enabled = plot_available - self.update_x = True - self.update_y = True - self.delay = Quantity(0.2, 's') - - -class PlotSettingsDialog(Dialog): - """ - Set up the live view plot. - """ - - def __init__(self, parent, ok_callback, *args, **kwargs): - Dialog.__init__(self, parent=parent, title='Plot settings') - - self.ok_callback = ok_callback - - dialog_box = wx.BoxSizer(wx.VERTICAL) - - # Enabled. - self.enabled_checkbox = wx.CheckBox(self, label='Enabled') - if not plot_available: - self.enabled_checkbox.Disable() - dialog_box.Add(self.enabled_checkbox, flag=wx.ALL, border=5) - - # Capture. - capture_static_box = wx.StaticBox(self, label='Capture') - capture_box = wx.StaticBoxSizer(capture_static_box, wx.VERTICAL) - capture_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - capture_box.Add(capture_sizer, flag=wx.CENTER) - dialog_box.Add(capture_box, flag=wx.EXPAND|wx.ALL, border=5) - - ## Delay. - capture_sizer.Add(wx.StaticText(self, label='Delay (s):'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.delay_input = floatspin.FloatSpin(self, min_val=0.2, max_val=1e4, increment=0.1, digits=2) - capture_sizer.Add(self.delay_input, flag=wx.CENTER) - - # End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def OnOk(self, evt=None): - self.ok_callback(self) - - self.Destroy() - - def GetValue(self): - plot_settings = PlotSettings() - plot_settings.enabled = self.enabled_checkbox.Value - plot_settings.delay = Quantity(self.delay_input.GetValue(), 's') - - return plot_settings - - def SetValue(self, plot_settings): - self.enabled_checkbox.Value = plot_settings.enabled - self.delay_input.SetValue(plot_settings.delay.value) - - -class ListLiveViewPanel(wx.Panel): - """ - A panel to display a live view plot of a list resource. - """ - - def __init__(self, parent, global_store, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self._measurement_resource_name = None - - # Defaults. - self.plot_settings = PlotSettings() - self.unit_conversion = 0 - - self.enabled = True - self.capturing_data = False - self.restart_live_view = False - self.resource_backup = None - - # csv save file information - self.save_path = '' - self.defaultName='' - - # Initialize recorded values. - self.init_values() - - # This lock blocks the acquisition thread from acquiring. - self.running_lock = Lock() - - # Plot and toolbar. - display_box = wx.BoxSizer(wx.VERTICAL) - - ## Plot. - if plot_available: - self.plot = TwoDimensionalPlot(self, color='blue') - display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) - -# self.plot.x_label = 'Waveform time (s)' -# self.plot.y_label = 'History' - else: - display_box.Add((500, -1), proportion=1, flag=wx.EXPAND) - - ## Controls. - if plot_available: - controls_box = wx.BoxSizer(wx.HORIZONTAL) - display_box.Add(controls_box, flag=wx.CENTER|wx.ALL, border=5) - - ### Manual data export - saveData_static_box = wx.StaticBox(self, label='Last trace') - saveData_box = wx.StaticBoxSizer(saveData_static_box, wx.HORIZONTAL) - controls_box.Add(saveData_box, flag=wx.CENTER) - - self.csv_button = wx.Button(self, label='Save to .csv') - self.Bind(wx.EVT_BUTTON, self.onSave, self.csv_button) - saveData_box.Add(self.csv_button, flag=wx.CENTER) - - ### Capture. - capture_static_box = wx.StaticBox(self, label='Control') - capture_box = wx.StaticBoxSizer(capture_static_box) - controls_box.Add(capture_box, flag=wx.CENTER) - - self.run_button = wx.Button(self, label='Run') - self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button) - capture_box.Add(self.run_button, flag=wx.CENTER) - - self.pause_button = wx.Button(self, label='Pause') - self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button) - capture_box.Add(self.pause_button, flag=wx.CENTER) - - self.reset_button = wx.Button(self, label='Reset') - self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button) - capture_box.Add(self.reset_button, flag=wx.CENTER) - - ### Settings. - settings_static_box = wx.StaticBox(self, label='Settings') - settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL) - controls_box.Add(settings_box, flag=wx.CENTER|wx.LEFT, border=10) - - self.plot_settings_button = wx.Button(self, label='Plot...') - self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button) - settings_box.Add(self.plot_settings_button, flag=wx.CENTER) - - self.SetSizer(display_box) - - # Acquisition thread. - callback = functools.partial(wx.CallAfter, self.add_values) - self.acq_thread = AcquisitionThread(self.plot_settings.delay, callback, - running_lock=self.running_lock) - self.acq_thread.daemon = True - self.acq_thread.start() - - # Wait for a resource to begin capturing. - self.OnPause() - self.run_button.Disable() - - # Subscriptions. - pub.subscribe(self.msg_resource, 'resource.added') - pub.subscribe(self.msg_resource, 'resource.removed') - pub.subscribe(self.msg_data_capture_start, 'data_capture.start') - pub.subscribe(self.msg_data_capture_data, 'data_capture.data') - pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop') - - @property - def running(self): - return self.pause_button.Enabled - - @property - def resource(self): - return self.acq_thread.resource - - @resource.setter - def resource(self, value): - # Ignore unreadable resources. - if value is not None and not value.readable: - value = None - - if self.running: - # Currently running. - running = True - self.OnPause() - else: - running = False - - self.acq_thread.resource = value - - self.run_button.Enable(value is not None) - - # Resume if applicable. - if running: - self.OnRun() - - @property - def measurement_resource_name(self): - if self._measurement_resource_name is None: - return '' - else: - return self._measurement_resource_name - - @measurement_resource_name.setter - def measurement_resource_name(self, value): - if value: - self._measurement_resource_name = value - try: - self.resource = self.global_store.resources[self._measurement_resource_name] - except KeyError: - self.resource = None - else: - self._measurement_resource_name = None - self.resource = None - - def init_values(self): - """ - Clear captured values. - """ - - self._times = numpy.array([]) - self._values = numpy.array([]) - - def update_plot(self): - """ - Redraw the plot. - """ - - # Wait for at least one line. - if not len(self._times) > 0: - display_time = [0] - display_values = [0] - else: - display_time = self._times - display_values = self._values - - if self.plot_settings.update_x: - self.plot.x_autoscale() - if self.plot_settings.update_y: - self.plot.y_autoscale() - - self.plot.x_data, self.plot.y_data = display_time, display_values - - def add_values(self, values): - """ - Update the plot with a new list of values. - """ - - if not self.plot_settings.enabled: - return - - # Extract the times and the data values. - times, values = zip(*values) - - # Update values. - self._values = numpy.append(numpy.array([]), values) - self._times = numpy.append(numpy.array([]),times) - - # Plot. - self.update_plot() - - def close(self): - """ - Perform cleanup. - """ - - # Unsubscriptions. - pub.unsubscribe(self.msg_resource, 'resource.added') - pub.unsubscribe(self.msg_resource, 'resource.removed') - pub.unsubscribe(self.msg_data_capture_start, 'data_capture.start') - pub.unsubscribe(self.msg_data_capture_data, 'data_capture.data') - pub.unsubscribe(self.msg_data_capture_stop, 'data_capture.stop') - - # Ensure the thread exits. - self.acq_thread.resource = None - self.acq_thread.done = True - if not self.running: - self.running_lock.release() - self.acq_thread.join() - del self.acq_thread - - def onSave(self, evt=None): - """ - save the latest trace to a manually named csv - """ - outArray = numpy.column_stack((self._times,self._values)) - - self.defaultName = 'listTrace_{0:04}-{1:02}-{2:02}_{3:02}-{4:02}-{5:02}.csv'.format(*localtime()) - fdlg = wx.FileDialog(self, "Save last trace", "", self.defaultName, "CSV files(*.csv)|*.*", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) - if fdlg.ShowModal() == wx.ID_OK: - self.save_path = fdlg.GetPath() - if not(self.save_path.endswith(".csv")): - self.save_path = self.save_path + ".csv" - numpy.savetxt(self.save_path,outArray,delimiter=',') - - def OnRun(self, evt=None): - """ - Let the acquisition thread run. - """ - - self.run_button.Disable() - - if self.acq_thread.resource is None: - return - - self.running_lock.release() - - self.pause_button.Enable() - - def OnPause(self, evt=None): - """ - Block the acquisition thread. - """ - - if not self.running: - return - - self.running_lock.acquire() - - if self.acq_thread.resource is not None: - self.run_button.Enable() - self.pause_button.Disable() - - def OnReset(self, evt=None): - self.init_values() - self.update_plot() - - def OnPlotSettings(self, evt=None): - """ - Open the plot settings dialog. - """ - - def ok_callback(dlg): - self.plot_settings = dlg.GetValue() - - dlg = PlotSettingsDialog(self, ok_callback) - dlg.SetValue(self.plot_settings) - dlg.Show() - - def msg_resource(self, name, value=None): - if self.measurement_resource_name is not None and name == self.measurement_resource_name: - self.resource = value - - def msg_data_capture_start(self, name): - if name == self.measurement_resource_name: - if self.enabled: - self.capturing_data = True - - def msg_data_capture_data(self, name, value): - if name == self.measurement_resource_name: - if self.capturing_data: - self.add_values(value) - - def msg_data_capture_stop(self, name): - if name == self.measurement_resource_name: - if self.capturing_data: - self.capturing_data = False - - -class ListMeasurementFrame(wx.Frame): - def __init__(self, parent, global_store, *args, **kwargs): - wx.Frame.__init__(self, parent, *args, **kwargs) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - ## Measurement setup. - self.measurement_config_panel = MeasurementConfigPanel(self, global_store, scaling=False) - frame_box.Add(self.measurement_config_panel, flag=wx.EXPAND) - - ## Live view. - self.live_view_panel = ListLiveViewPanel(self, global_store) - self.live_view_panel.SetMinSize((-1, 400)) - frame_box.Add(self.live_view_panel, proportion=1, flag=wx.EXPAND) - - self.SetSizerAndFit(frame_box) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - def OnClose(self, evt): - if self.live_view_panel.capturing_data: - msg = 'Cannot close, as a sweep is currently in progress.' - MessageDialog(self, msg, 'Sweep in progress').Show() - - evt.Veto() - return - - self.measurement_config_panel.close() - self.live_view_panel.close() - - evt.Skip() diff --git a/spacq/gui/display/plot/live/list_3d.py b/spacq/gui/display/plot/live/list_3d.py deleted file mode 100644 index bf89525..0000000 --- a/spacq/gui/display/plot/live/list_3d.py +++ /dev/null @@ -1,306 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -import numpy -from pubsub import pub -import wx - -from ....config.measurement import MeasurementConfigPanel -from ....tool.box import Dialog, MessageDialog - -try: - from ..surface import SurfacePlot -except ImportError as e: - plot_available = False - log.debug('Could not import SurfacePlot: {0}'.format(str(e))) -else: - plot_available = True - -""" -A historical live view plot for list values. -""" - - -class PlotSettings(object): - """ - Wrapper for all the settings configured via dialog. - """ - - def __init__(self): - self.enabled = plot_available - self.num_lines = 100 - - -class PlotSettingsDialog(Dialog): - """ - Set up the live view plot. - """ - - def __init__(self, parent, ok_callback, *args, **kwargs): - Dialog.__init__(self, parent=parent, title='Plot settings') - - self.ok_callback = ok_callback - - dialog_box = wx.BoxSizer(wx.VERTICAL) - - # Enabled. - self.enabled_checkbox = wx.CheckBox(self, label='Enabled') - if not plot_available: - self.enabled_checkbox.Disable() - dialog_box.Add(self.enabled_checkbox, flag=wx.ALL, border=5) - - # Capture. - capture_static_box = wx.StaticBox(self, label='Capture') - capture_box = wx.StaticBoxSizer(capture_static_box, wx.VERTICAL) - capture_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - capture_box.Add(capture_sizer, flag=wx.CENTER) - dialog_box.Add(capture_box, flag=wx.EXPAND|wx.ALL, border=5) - - ## Number of lines. - capture_sizer.Add(wx.StaticText(self, label='Lines:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.lines_input = wx.SpinCtrl(self, min=2, max=1e4, initial=100) - capture_sizer.Add(self.lines_input, flag=wx.CENTER) - - # End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def OnOk(self, evt=None): - self.ok_callback(self) - - self.Destroy() - - def GetValue(self): - plot_settings = PlotSettings() - plot_settings.enabled = self.enabled_checkbox.Value - plot_settings.num_lines = self.lines_input.Value - - return plot_settings - - def SetValue(self, plot_settings): - self.enabled_checkbox.Value = plot_settings.enabled - self.lines_input.Value = plot_settings.num_lines - - -class ListLiveViewPanel(wx.Panel): - """ - A panel to display a live view plot of a list resource. - """ - - def __init__(self, parent, global_store, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self._measurement_resource_name = None - - # Defaults. - self.plot_settings = PlotSettings() - - self.enabled = True - self.capturing_data = False - self.resource_backup = None - - # Initialize recorded values. - self.init_values() - - # Plot and toolbar. - display_box = wx.BoxSizer(wx.VERTICAL) - - ## Plot. - if plot_available: - self.plot = SurfacePlot(self, style='waveform') - display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) - - self.plot.x_label = 'Waveform time (s)' - self.plot.y_label = 'History' - else: - display_box.Add((500, -1), proportion=1, flag=wx.EXPAND) - - ## Controls. - if plot_available: - controls_box = wx.BoxSizer(wx.HORIZONTAL) - display_box.Add(controls_box, flag=wx.CENTER|wx.ALL, border=5) - - ### Capture. - capture_static_box = wx.StaticBox(self, label='Control') - capture_box = wx.StaticBoxSizer(capture_static_box) - controls_box.Add(capture_box, flag=wx.CENTER) - - self.reset_button = wx.Button(self, label='Reset') - self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button) - capture_box.Add(self.reset_button, flag=wx.CENTER) - - ### Settings. - settings_static_box = wx.StaticBox(self, label='Settings') - settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL) - controls_box.Add(settings_box, flag=wx.CENTER|wx.LEFT, border=10) - - self.plot_settings_button = wx.Button(self, label='Plot...') - self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button) - settings_box.Add(self.plot_settings_button, flag=wx.CENTER) - - self.SetSizer(display_box) - - # Subscriptions. - pub.subscribe(self.msg_data_capture_start, 'data_capture.start') - pub.subscribe(self.msg_data_capture_data, 'data_capture.data') - pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop') - - @property - def measurement_resource_name(self): - if self._measurement_resource_name is None: - return '' - else: - return self._measurement_resource_name - - @measurement_resource_name.setter - def measurement_resource_name(self, value): - if value: - self._measurement_resource_name = value - try: - self.resource = self.global_store.resources[self._measurement_resource_name] - except KeyError: - self.resource = None - else: - self._measurement_resource_name = None - self.resource = None - - def init_values(self): - """ - Clear captured values. - """ - - self._lines = None - self.time_range = (0.0, 0.0) - - def update_plot(self): - """ - Redraw the plot. - """ - - # Wait for at least one line. - if self._lines is None: - self.plot.surface_data = None - else: - self.plot.surface_data = (self._lines, self.time_range, (1, len(self._lines))) - - wx.CallAfter(self.plot.redraw) - - def add_values(self, values): - """ - Update the plot with a new list of values. - """ - - if not self.plot_settings.enabled: - return - - # Extract the times and the data values. - times, values = zip(*values) - time_range = min(times), max(times) - - # Sanity check, since the new values must match existing ones. - if self._lines is not None: - if len(self._lines[-1]) != len(values): - log.warning('Data length mismatch: was {0}, became {1}'.format(len(self._lines[-1]), len(values))) - self.init_values() - elif self.time_range != time_range: - log.warning('Time range mismatch: was {0}, became {1}'.format(self.time_range, time_range)) - self.init_values() - - # Update values. - if self._lines is None: - self._lines = numpy.array([values]) - self.time_range = time_range - else: - self._lines = numpy.append(self._lines, [values], 0) - - cut_idx = len(self._lines) - self.plot_settings.num_lines - if cut_idx > 0: - self._lines = self._lines[cut_idx:] - - # Plot. - self.update_plot() - - def close(self): - """ - Perform cleanup. - """ - - # Unsubscriptions. - pub.unsubscribe(self.msg_data_capture_start, 'data_capture.start') - pub.unsubscribe(self.msg_data_capture_data, 'data_capture.data') - pub.unsubscribe(self.msg_data_capture_stop, 'data_capture.stop') - - def OnReset(self, evt=None): - self.init_values() - self.update_plot() - - def OnPlotSettings(self, evt=None): - """ - Open the plot settings dialog. - """ - - def ok_callback(dlg): - self.plot_settings = dlg.GetValue() - - dlg = PlotSettingsDialog(self, ok_callback) - dlg.SetValue(self.plot_settings) - dlg.Show() - - def msg_data_capture_start(self, name): - if name == self.measurement_resource_name: - if self.enabled: - self.capturing_data = True - - def msg_data_capture_data(self, name, value): - if name == self.measurement_resource_name: - if self.capturing_data: - self.add_values(value) - - def msg_data_capture_stop(self, name): - if name == self.measurement_resource_name: - if self.capturing_data: - self.capturing_data = False - - -class ListMeasurementFrame(wx.Frame): - def __init__(self, parent, global_store, *args, **kwargs): - wx.Frame.__init__(self, parent, *args, **kwargs) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - ## Measurement setup. - self.measurement_config_panel = MeasurementConfigPanel(self, global_store, scaling=False) - frame_box.Add(self.measurement_config_panel, flag=wx.EXPAND) - - ## Live view. - self.live_view_panel = ListLiveViewPanel(self, global_store) - frame_box.Add(self.live_view_panel, proportion=1, flag=wx.EXPAND) - - self.SetSizerAndFit(frame_box) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - def OnClose(self, evt): - if self.live_view_panel.capturing_data: - msg = 'Cannot close, as a sweep is currently in progress.' - MessageDialog(self, msg, 'Sweep in progress').Show() - - evt.Veto() - return - - self.measurement_config_panel.close() - self.live_view_panel.close() - - evt.Skip() diff --git a/spacq/gui/display/plot/live/scalar.py b/spacq/gui/display/plot/live/scalar.py deleted file mode 100755 index 084bea4..0000000 --- a/spacq/gui/display/plot/live/scalar.py +++ /dev/null @@ -1,588 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -import functools -import math -import numpy -from pubsub import pub -from threading import Lock -import time -import wx -from wx.lib.agw import floatspin - -from spacq.interface.resources import AcquisitionThread -from spacq.interface.units import Quantity - -from ....config.measurement import MeasurementConfigPanel -from ....tool.box import Dialog, MessageDialog - -try: - from ..two_dimensional import TwoDimensionalPlot -except ImportError as e: - plot_available = False - log.debug('Could not import TwoDimensionalPlot: {0}'.format(str(e))) -else: - plot_available = True - -""" -A historical live view plot for scalar values. -""" - - -class PlotSettings(object): - """ - Wrapper for all the settings configured via dialog. - """ - - def __init__(self): - self.enabled = plot_available - self.num_points = 500 - self.delay = Quantity(0.2, 's') - self.update_x = True - self.time_value = 0 - self.time_mode = 0 - self.update_y = True - self.y_scale = 0 - self.units_from = '' - self.units_to = '' - - -class PlotSettingsDialog(Dialog): - """ - Set up the live view plot. - """ - - def __init__(self, parent, ok_callback, *args, **kwargs): - Dialog.__init__(self, parent=parent, title='Plot settings') - - self.ok_callback = ok_callback - - dialog_box = wx.BoxSizer(wx.VERTICAL) - - # Enabled. - self.enabled_checkbox = wx.CheckBox(self, label='Enabled') - if not plot_available: - self.enabled_checkbox.Disable() - dialog_box.Add(self.enabled_checkbox, flag=wx.ALL, border=5) - - # Capture. - capture_static_box = wx.StaticBox(self, label='Capture') - capture_box = wx.StaticBoxSizer(capture_static_box, wx.VERTICAL) - capture_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - capture_box.Add(capture_sizer, flag=wx.CENTER) - dialog_box.Add(capture_box, flag=wx.EXPAND|wx.ALL, border=5) - - ## Number of points. - capture_sizer.Add(wx.StaticText(self, label='Points:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.points_input = wx.SpinCtrl(self, min=2, max=1e4, initial=100) - capture_sizer.Add(self.points_input, flag=wx.CENTER) - - ## Delay. - capture_sizer.Add(wx.StaticText(self, label='Delay (s):'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - # TODO: Input should be a time amount directly (eg. '200 ms'). - self.delay_input = floatspin.FloatSpin(self, min_val=0.2, max_val=1e4, increment=0.1, digits=2) - capture_sizer.Add(self.delay_input, flag=wx.CENTER) - - # Axes. - axes_static_box = wx.StaticBox(self, label='Axes') - axes_box = wx.StaticBoxSizer(axes_static_box, wx.HORIZONTAL) - dialog_box.Add(axes_box, flag=wx.EXPAND|wx.ALL, border=5) - - ## x - x_static_box = wx.StaticBox(self, label='x') - x_box = wx.StaticBoxSizer(x_static_box, wx.VERTICAL) - axes_box.Add(x_box, flag=wx.EXPAND) - - self.update_x_axis = wx.CheckBox(self, label='Autofit') - x_box.Add(self.update_x_axis) - - ### Value. - self.time_value = wx.RadioBox(self, label='Value', choices=['Time', 'Points']) - x_box.Add(self.time_value, flag=wx.EXPAND) - - ### Mode. - self.time_mode = wx.RadioBox(self, label='Mode', choices=['Relative', 'Absolute']) - x_box.Add(self.time_mode, flag=wx.EXPAND) - - ## y - y_static_box = wx.StaticBox(self, label='y') - y_box = wx.StaticBoxSizer(y_static_box, wx.VERTICAL) - axes_box.Add(y_box, flag=wx.EXPAND) - - self.update_y_axis = wx.CheckBox(self, label='Autofit') - y_box.Add(self.update_y_axis) - - ### Conversion. - conversion_static_box = wx.StaticBox(self, label='Conversion') - conversion_box = wx.StaticBoxSizer(conversion_static_box, wx.VERTICAL) - conversion_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5) - conversion_box.Add(conversion_sizer) - y_box.Add(conversion_box) - - conversion_sizer.Add(wx.StaticText(self, label='Exp. scale:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.y_scale = floatspin.FloatSpin(self, min_val=-100, max_val=100, increment=1, digits=2) - conversion_sizer.Add(self.y_scale) - - #### Units. - units_static_box = wx.StaticBox(self, label='Units') - units_box = wx.StaticBoxSizer(units_static_box, wx.VERTICAL) - units_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - units_box.Add(units_sizer) - conversion_box.Add(units_box, flag=wx.EXPAND) - - units_sizer.Add(wx.StaticText(self, label='From:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.units_from_input = wx.TextCtrl(self) - units_sizer.Add(self.units_from_input) - - units_sizer.Add(wx.StaticText(self, label='To:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.units_to_input = wx.TextCtrl(self) - units_sizer.Add(self.units_to_input) - - # End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def OnOk(self, evt=None): - self.ok_callback(self) - - self.Destroy() - - def GetValue(self): - plot_settings = PlotSettings() - plot_settings.enabled = self.enabled_checkbox.Value - plot_settings.num_points = self.points_input.Value - plot_settings.delay = Quantity(self.delay_input.GetValue(), 's') - plot_settings.update_x = self.update_x_axis.Value - plot_settings.time_value = self.time_value.Selection - plot_settings.time_mode = self.time_mode.Selection - plot_settings.update_y = self.update_y_axis.Value - plot_settings.y_scale = self.y_scale.GetValue() - plot_settings.units_from = self.units_from_input.Value - plot_settings.units_to = self.units_to_input.Value - - return plot_settings - - def SetValue(self, plot_settings): - self.enabled_checkbox.Value = plot_settings.enabled - self.points_input.Value = plot_settings.num_points - self.delay_input.SetValue(plot_settings.delay.value) - self.update_x_axis.Value = plot_settings.update_x - self.time_value.Selection = plot_settings.time_value - self.time_mode.Selection = plot_settings.time_mode - self.update_y_axis.Value = plot_settings.update_y - self.y_scale.SetValue(plot_settings.y_scale) - self.units_from_input.Value = plot_settings.units_from - self.units_to_input.Value = plot_settings.units_to - - -class ScalarLiveViewPanel(wx.Panel): - """ - A panel to display a live view plot of a scalar resource. - """ - - def __init__(self, parent, global_store, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.global_store = global_store - self._measurement_resource_name = None - - # Defaults. - self.plot_settings = PlotSettings() - self.unit_conversion = 0 - - self.enabled = False - self.capturing_data = False - self.restart_live_view = False - self.resource_backup = None - - # This lock blocks the acquisition thread from acquiring. - self.running_lock = Lock() - - # Initialize recorded values. - self.init_values() - - # Plot and toolbar. - display_box = wx.BoxSizer(wx.VERTICAL) - - ## Plot. - if plot_available: - self.plot = TwoDimensionalPlot(self, color='blue') - display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) - - self.plot.x_label = 'Time (s)' - else: - display_box.Add((500, -1), proportion=1, flag=wx.EXPAND) - - ## Controls. - if plot_available: - controls_box = wx.BoxSizer(wx.HORIZONTAL) - display_box.Add(controls_box, flag=wx.CENTER|wx.ALL, border=5) - - ### Numeric display. - numeric_display_static_box = wx.StaticBox(self, label='Reading') - numeric_display_box = wx.StaticBoxSizer(numeric_display_static_box, wx.HORIZONTAL) - controls_box.Add(numeric_display_box, flag=wx.CENTER) - - self.numeric_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) - self.numeric_display.BackgroundColour = wx.LIGHT_GREY - numeric_display_box.Add(self.numeric_display) - - ### Capture. - capture_static_box = wx.StaticBox(self, label='Control') - capture_box = wx.StaticBoxSizer(capture_static_box) - controls_box.Add(capture_box, flag=wx.CENTER|wx.LEFT, border=10) - - self.run_button = wx.Button(self, label='Run') - self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button) - capture_box.Add(self.run_button, flag=wx.CENTER) - - self.pause_button = wx.Button(self, label='Pause') - self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button) - capture_box.Add(self.pause_button, flag=wx.CENTER) - - self.reset_button = wx.Button(self, label='Reset') - self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button) - capture_box.Add(self.reset_button, flag=wx.CENTER|wx.LEFT, border=10) - - ### Settings. - settings_static_box = wx.StaticBox(self, label='Settings') - settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL) - controls_box.Add(settings_box, flag=wx.CENTER|wx.LEFT, border=10) - - self.plot_settings_button = wx.Button(self, label='Plot...') - self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button) - settings_box.Add(self.plot_settings_button, flag=wx.CENTER) - - self.SetSizer(display_box) - - # Acquisition thread. - callback = functools.partial(wx.CallAfter, self.add_value) - self.acq_thread = AcquisitionThread(self.plot_settings.delay, callback, - running_lock=self.running_lock) - self.acq_thread.daemon = True - self.acq_thread.start() - - # Wait for a resource to begin capturing. - self.OnPause() - self.run_button.Disable() - - # Subscriptions. - pub.subscribe(self.msg_resource, 'resource.added') - pub.subscribe(self.msg_resource, 'resource.removed') - pub.subscribe(self.msg_data_capture_start, 'data_capture.start') - pub.subscribe(self.msg_data_capture_data, 'data_capture.data') - pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop') - - @property - def running(self): - return self.pause_button.Enabled - - @property - def resource(self): - return self.acq_thread.resource - - @resource.setter - def resource(self, value): - # Ignore unreadable resources. - if value is not None and not value.readable: - value = None - - if self.running: - # Currently running. - running = True - self.OnPause() - else: - running = False - - self.acq_thread.resource = value - - self.run_button.Enable(value is not None) - - # Resume if applicable. - if running: - self.OnRun() - - @property - def measurement_resource_name(self): - if self._measurement_resource_name is None: - return '' - else: - return self._measurement_resource_name - - @measurement_resource_name.setter - def measurement_resource_name(self, value): - if value: - self._measurement_resource_name = value - try: - self.resource = self.global_store.resources[self._measurement_resource_name] - except KeyError: - self.resource = None - else: - self._measurement_resource_name = None - self.resource = None - - def init_values(self): - """ - Clear captured values. - """ - - self._points = numpy.array([]) - self._times = numpy.array([]) - self._values = numpy.array([]) - - self.current_value = None - - self.start_time = None - - def update_plot(self): - """ - Redraw the plot. - """ - - if not len(self._points) > 0: - display_time = [0] - display_values = [0] - else: - if self.plot_settings.time_value == 0: # Time. - display_time = self._times - - if self.plot_settings.time_mode == 0: # Relative. - # Calculate the number of seconds passed since each point. - max_time = self._times[-1] - display_time = [x - max_time for x in display_time] - elif self.plot_settings.time_mode == 1: # Absolute. - display_time = [x - self.start_time for x in display_time] - elif self.plot_settings.time_value == 1: # Points. - display_time = self._points - - if self.plot_settings.time_mode == 0: # Relative. - # Calculate the number of seconds passed since each point. - max_point = self._points[-1] - display_time = [x - max_point for x in display_time] - - display_values = [x * 10 ** (self.plot_settings.y_scale + self.unit_conversion) for - x in self._values] - - if self.plot_settings.update_x: - self.plot.x_autoscale() - if self.plot_settings.update_y: - self.plot.y_autoscale() - - self.plot.x_data, self.plot.y_data = display_time, display_values - - def add_value(self, value): - """ - Update the plot with a new value. - """ - - if not self.plot_settings.enabled: - return - - # Extract the value of a Quantity. - try: - # Label with the base dimensions. - if self.unit_conversion == 0: - self.plot.y_label = '({0})'.format(value.original_units) - value = value.original_value - except AttributeError: - pass - - # Update values. - try: - self._points = numpy.append(self._points, self._points[-1] + 1) - except IndexError: - self._points = numpy.append(self._points, 0) - cur_time = time.time() - self._times = numpy.append(self._times, cur_time) - self._values = numpy.append(self._values, value) - - if self.start_time is None: - self.start_time = cur_time - - cut_idx = len(self._points) - int(self.plot_settings.num_points) - if cut_idx > 0: - self._points = self._points[cut_idx:] - self._times = self._times[cut_idx:] - self._values = self._values[cut_idx:] - - # Set number display. - self.current_value = value * 10 ** (self.plot_settings.y_scale + self.unit_conversion) - self.numeric_display.Value = '{0:.6g}'.format(self.current_value) - - # Plot. - self.update_plot() - - def close(self): - """ - Perform cleanup. - """ - - # Unsubscriptions. - pub.unsubscribe(self.msg_resource, 'resource.added') - pub.unsubscribe(self.msg_resource, 'resource.removed') - pub.unsubscribe(self.msg_data_capture_start, 'data_capture.start') - pub.unsubscribe(self.msg_data_capture_data, 'data_capture.data') - pub.unsubscribe(self.msg_data_capture_stop, 'data_capture.stop') - - # Ensure the thread exits. - self.acq_thread.resource = None - self.acq_thread.done = True - if not self.running: - self.running_lock.release() - self.acq_thread.join() - del self.acq_thread - - def OnRun(self, evt=None): - """ - Let the acquisition thread run. - """ - - self.run_button.Disable() - - if self.acq_thread.resource is None: - return - - self.running_lock.release() - - self.pause_button.Enable() - - def OnPause(self, evt=None): - """ - Block the acquisition thread. - """ - - if not self.running: - return - - self.running_lock.acquire() - - if self.acq_thread.resource is not None: - self.run_button.Enable() - self.pause_button.Disable() - - def OnReset(self, evt=None): - self.init_values() - self.update_plot() - - def OnPlotSettings(self, evt=None): - """ - Open the plot settings dialog. - """ - - def ok_callback(dlg): - self.plot_settings = dlg.GetValue() - - if self.plot_settings.units_from and self.plot_settings.units_to: - try: - quantity_from = Quantity(1, self.plot_settings.units_from) - quantity_to = Quantity(1, self.plot_settings.units_to) - except ValueError as e: - self.unit_conversion = 0 - MessageDialog(self, str(e), 'Invalid unit').Show() - else: - # We don't actually care about the units; just the prefix values. - self.unit_conversion = math.log(quantity_from.value, 10) - math.log(quantity_to.value, 10) - else: - self.unit_conversion = 0 - - self.acq_thread.delay = self.plot_settings.delay - - if self.plot_settings.time_value == 0: - self.plot.x_label = 'Time (s)' - elif self.plot_settings.time_value == 1: - self.plot.x_label = 'Points' - - if self.plot_settings.y_scale != 0: - self.plot.y_label = '/ 10 ^ {0}'.format(self.plot_settings.y_scale) - else: - self.plot.y_label = '' - - if self.plot_settings.units_to: - self.plot.y_label += ' ({0})'.format(self.plot_settings.units_to) - - self.update_plot() - - dlg = PlotSettingsDialog(self, ok_callback) - dlg.SetValue(self.plot_settings) - dlg.Show() - - def msg_resource(self, name, value=None): - if self.measurement_resource_name is not None and name == self.measurement_resource_name: - self.resource = value - - def msg_data_capture_start(self, name): - if name == self.measurement_resource_name: - if self.enabled: - self.capturing_data = True - - # Keep track of whether to restart the capture afterwards. - self.restart_live_view = self.running - - # Disable live view. - self.resource_backup = self.resource - self.resource = None - - def msg_data_capture_data(self, name, value): - if name == self.measurement_resource_name: - if self.capturing_data: - self.add_value(value) - - def msg_data_capture_stop(self, name): - if name == self.measurement_resource_name: - if self.capturing_data: - self.capturing_data = False - - # Re-enable live view. - self.resource = self.resource_backup - self.resource_backup = None - - if self.restart_live_view: - self.OnRun() - - -class ScalarMeasurementFrame(wx.Frame): - def __init__(self, parent, global_store, *args, **kwargs): - wx.Frame.__init__(self, parent, *args, **kwargs) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - ## Measurement setup. - self.measurement_config_panel = MeasurementConfigPanel(self, global_store) - frame_box.Add(self.measurement_config_panel, flag=wx.EXPAND) - - ## Live view. - self.live_view_panel = ScalarLiveViewPanel(self, global_store) - self.live_view_panel.SetMinSize((-1, 400)) - frame_box.Add(self.live_view_panel, proportion=1, flag=wx.EXPAND) - - self.SetSizerAndFit(frame_box) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - def OnClose(self, evt): - if self.live_view_panel.capturing_data: - msg = 'Cannot close, as a sweep is currently in progress.' - MessageDialog(self, msg, 'Sweep in progress').Show() - - evt.Veto() - return - - self.live_view_panel.close() - self.measurement_config_panel.close() - - evt.Skip() diff --git a/spacq/gui/display/plot/plotmath/__init__.py b/spacq/gui/display/plot/plotmath/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/display/plot/plotmath/common/__init__.py b/spacq/gui/display/plot/plotmath/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/display/plot/plotmath/common/math_setup.py b/spacq/gui/display/plot/plotmath/common/math_setup.py deleted file mode 100755 index 6bac71a..0000000 --- a/spacq/gui/display/plot/plotmath/common/math_setup.py +++ /dev/null @@ -1,159 +0,0 @@ -import wx - -from .....tool.box import Dialog - -class AxisSelectionPanel(wx.Panel): - """ - A panel for choosing the headings to be used for the axes. - """ - - def __init__(self, parent, axes, headings, selection_callback, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.selection_callback = selection_callback - - # Panel. - panel_box = wx.BoxSizer(wx.HORIZONTAL) - - ## Axes. - self.axis_lists = [] - - for axis in axes: - axis_static_box = wx.StaticBox(self, label=axis) - axis_box = wx.StaticBoxSizer(axis_static_box, wx.VERTICAL) - panel_box.Add(axis_box, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) - - axis_list = wx.ListBox(self, choices=headings) - axis_list.SetMinSize((-1, 300)) - self.Bind(wx.EVT_LISTBOX, self.OnAxisSelection, axis_list) - axis_box.Add(axis_list, proportion=1, flag=wx.EXPAND) - - self.axis_lists.append(axis_list) - - self.SetSizer(panel_box) - - def OnAxisSelection(self, evt=None): - """ - Announce the latest selection. - """ - - result = [None if list.Selection == wx.NOT_FOUND else list.Selection for - list in self.axis_lists] - - self.selection_callback(result) - -class MathSetupDialog_Derivative(Dialog): - """ - Math configuration dialog (for derivative). - """ - - def __init__(self, parent, headings, axis_names, *args, **kwargs): - Dialog.__init__(self, parent, *args, **kwargs) - - self.axes = [None for _ in axis_names] - - self.step_size = 1 - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Axis setup. - axis_panel = AxisSelectionPanel(self, axis_names, headings, self.OnAxisSelection) - dialog_box.Add(axis_panel) - - # Derivative Slider - slider_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(slider_box, flag=wx.CENTER) - - self.slider_title = wx.StaticText(self, label='Step Size:') - slider_box.Add(self.slider_title) - - self.reading = wx.StaticText(self, label='01') - slider_box.Add(self.reading) - - self.step_slider = wx.Slider(self, value=1, minValue=1, maxValue= 20, size=(200,-1), style=wx.SL_HORIZONTAL) - self.Bind(wx.EVT_SCROLL, self.OnSliderScroll, self.step_slider) - slider_box.Add(self.step_slider) - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER) - - self.ok_button = wx.Button(self, wx.ID_OK) - self.ok_button.Disable() - self.Bind(wx.EVT_BUTTON, self.OnOk, self.ok_button) - button_box.Add(self.ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def OnAxisSelection(self, values): - self.axes = values - - self.ok_button.Enable(all(axis is not None for axis in self.axes)) - - def OnSliderScroll(self, evt=None): - self.step_size = self.step_slider.GetValue() - - self.reading.SetLabel(str(self.step_size)) - - def OnOk(self, evt=None): - title, d_data = self.calculate() - self.dheading = title - self.ddata = d_data - self.Destroy() - -class MathSetupDialog_Function(Dialog): - """ - Math configuration dialog(for functions). - """ - - def __init__(self, parent, headings, axis_names, *args, **kwargs): - Dialog.__init__(self, parent, *args, **kwargs) - - self.axes = [None for _ in axis_names] - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Axis setup. - axis_panel = AxisSelectionPanel(self, axis_names, headings, self.OnAxisSelection) - dialog_box.Add(axis_panel) - - ## Inputs. - input_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5) - input_sizer.AddGrowableCol(1, 1) - dialog_box.Add(input_sizer, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) - - input_sizer.Add(wx.StaticText(self, label='Scalar function of '+', '.join(axis_names)+':'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.function_input = wx.TextCtrl(self) - self.function_input.SetMinSize((300, -1)) - input_sizer.Add(self.function_input, flag=wx.EXPAND) - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER) - - self.ok_button = wx.Button(self, wx.ID_OK) - self.ok_button.Disable() - self.Bind(wx.EVT_BUTTON, self.OnOk, self.ok_button) - button_box.Add(self.ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def OnAxisSelection(self, values): - self.axes = values - - self.ok_button.Enable(all(axis is not None for axis in self.axes)) - - def OnOk(self, evt=None): - title, d_data = self.calculate() - self.dheading = title - self.ddata = d_data - self.Destroy() diff --git a/spacq/gui/display/plot/plotmath/derivative.py b/spacq/gui/display/plot/plotmath/derivative.py deleted file mode 100644 index 84880eb..0000000 --- a/spacq/gui/display/plot/plotmath/derivative.py +++ /dev/null @@ -1,74 +0,0 @@ -from ....tool.box import MessageDialog -from .common.math_setup import MathSetupDialog_Derivative -from numpy import concatenate - -class DerivativeMathSetupDialog(MathSetupDialog_Derivative): - - dheading = [] - ddata = [] - - def __init__(self, parent, headings, data, *args, **kwargs): - MathSetupDialog_Derivative.__init__(self, parent, headings, ['d', '/d'], *args, **kwargs) - - self.parent = parent - self.headings = headings - self.data = data - - - def calculate(self): - try: - y_data, x_data = [self.data[:,axis].astype(float) for axis in self.axes] - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return - - y_label, x_label = [self.headings[x] for x in self.axes] - title = 'd{0}/d{1}'.format(y_label, x_label) - - derivative_loop = len(x_data) - - if x_data[0] == x_data[1]: - for i,x in enumerate(x_data[1:]): - if x != x_data[0]: - derivative_loop = i+1 - break - h = x_data[derivative_loop]-x_data[0] - d_data = [ [-9999] ]*len(y_data) - y_small = [ -9999 ] * (len(y_data)/derivative_loop) - for k in range(0,derivative_loop): - for j in range(0,len(y_data)/derivative_loop): - y_small[j] = y_data[j*derivative_loop+k] - if self.step_size >= len(y_small)/2: - self.step_size = 1 - for i,y in enumerate(y_small): - if i - self.step_size < 0: - d_data[i*derivative_loop+k] = [ (y_small[i+self.step_size]-y_small[0])/((i+self.step_size)*h) ] - elif len(y_small) - i - self.step_size < 1: - d_data[i*derivative_loop+k] = [ (y_small[len(y_small)-1]-y_small[i-self.step_size])/((len(y_small)-1-i+self.step_size)*h) ] - else: - d_data[i*derivative_loop+k] = [ (y_small[i+self.step_size]-y_small[i-self.step_size])/(2*self.step_size*h) ] - - - else: - for i,x in enumerate(x_data[1:]): - if x == x_data[0]: - derivative_loop = i+1 - break - - h = x_data[1]-x_data[0] - d_data = [ [-9999] ]*len(y_data) - for j in range(0,len(y_data)/derivative_loop): - y_small = y_data[derivative_loop*j:(j+1)*derivative_loop] - if self.step_size >= len(y_small)/2: - self.step_size = 1 - for i,y in enumerate(y_small): - if i - self.step_size < 0: - d_data[i+j*derivative_loop] = [ (y_small[i+self.step_size]-y_small[0])/((i+self.step_size)*h) ] - elif len(y_small) - i - self.step_size < 1: - d_data[i+j*derivative_loop] = [ (y_small[len(y_small)-1]-y_small[i-self.step_size])/((len(y_small)-1-i+self.step_size)*h) ] - else: - d_data[i+j*derivative_loop] = [ (y_small[i+self.step_size]-y_small[i-self.step_size])/(2*self.step_size*h) ] - - return(title,d_data) - - diff --git a/spacq/gui/display/plot/plotmath/function.py b/spacq/gui/display/plot/plotmath/function.py deleted file mode 100644 index 2f96955..0000000 --- a/spacq/gui/display/plot/plotmath/function.py +++ /dev/null @@ -1,62 +0,0 @@ -import wx - -from ....tool.box import MessageDialog -from .common.math_setup import MathSetupDialog_Function -from numpy import * - -class FunctionMathSetupDialog(MathSetupDialog_Function): - - dheading = [] - ddata = [] - - def __init__(self, parent, headings, data, *args, **kwargs): - MathSetupDialog_Function.__init__(self, parent, headings, ['X'], *args, **kwargs) - - self.parent = parent - self.headings = headings - self.data = data - - - def calculate(self): - try: - y_data = [self.data[:,x].astype(float) for x in self.axes] - y_name = [self.headings[x] for x in self.axes] - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return - - title = 'y = {0}'.format(self.function_input.Value.replace('X',y_name[0])) - y_data = y_data[0] - d_data = eval(self.function_input.Value.replace('X','y_data')) - d_data = d_data.reshape(d_data.size,1) - return(title,d_data) - - class FunctionMathSetupDialog2arg(MathSetupDialog_Function): - - dheading = [] - ddata = [] - - def __init__(self, parent, headings, data, *args, **kwargs): - MathSetupDialog_Function.__init__(self, parent, headings, ['X','Y'], *args, **kwargs) - - self.parent = parent - self.headings = headings - self.data = data - - - def calculate(self): - try: - f_data = [self.data[:,x].astype(float) for x in self.axes] - y_name = [self.headings[x] for x in self.axes] - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return - - title = 'z = {0}'.format(self.function_input.Value.replace('X',y_name[0]).replace('Y',y_name[1])) - x_data = f_data[0] - y_data = f_data[1] - d_data = eval(self.function_input.Value.replace('X','x_data').replace('Y','y_data')) - d_data = d_data.reshape(d_data.size,1) - return(title,d_data) - - diff --git a/spacq/gui/display/plot/static/__init__.py b/spacq/gui/display/plot/static/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/display/plot/static/colormapped.py b/spacq/gui/display/plot/static/colormapped.py deleted file mode 100644 index bfac12c..0000000 --- a/spacq/gui/display/plot/static/colormapped.py +++ /dev/null @@ -1,131 +0,0 @@ -import wx - -from spacq.tool.box import triples_to_mesh, triples_to_mesh_y - -from ....tool.box import MessageDialog -from ..colormapped import ColormappedPlot -from .common.plot_setup import PlotSetupDialog - -class ColormappedPlotPanel(wx.Panel): - def __init__(self, parent, color_data, x_bounds, y_bounds, x_label, y_label, - *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Plot. - self.plot = ColormappedPlot(self, x_bounds, y_bounds) - panel_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) - - self.SetSizer(panel_box) - - self.plot.x_label, self.plot.y_label = x_label, y_label - self.plot.color_data = color_data - - -class ColormappedPlotFrame(wx.Frame): - bounds_format = '{0:.4e}' - - def __init__(self, parent, color_data, x_bounds, y_bounds, x_label, y_label, - *args, **kwargs): - wx.Frame.__init__(self, parent, *args, **kwargs) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - ## Plot panel. - self.panel = ColormappedPlotPanel(self, color_data, x_bounds, y_bounds, - x_label, y_label) - self.panel.SetMinSize((400, 300)) - frame_box.Add(self.panel, proportion=1, flag=wx.EXPAND) - ## Settings. - settings_box = wx.BoxSizer(wx.HORIZONTAL) - frame_box.Add(settings_box, flag=wx.CENTER) - - ### Minimum value. - settings_box.Add(wx.StaticText(self, label='Min: '), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.minimum_value_input = wx.TextCtrl(self, - value=self.bounds_format.format(self.panel.plot.low_setting), - size=(100, -1), style=wx.TE_PROCESS_ENTER) - self.Bind(wx.EVT_TEXT_ENTER, self.OnMinValue, self.minimum_value_input) - settings_box.Add(self.minimum_value_input, flag=wx.RIGHT, border=20) - - ### Maximum value. - settings_box.Add(wx.StaticText(self, label='Max: '), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.maximum_value_input = wx.TextCtrl(self, - value=self.bounds_format.format(self.panel.plot.high_setting), - size=(100, -1), style=wx.TE_PROCESS_ENTER) - self.Bind(wx.EVT_TEXT_ENTER, self.OnMaxValue, self.maximum_value_input) - settings_box.Add(self.maximum_value_input) - - self.SetSizerAndFit(frame_box) - - def OnMinValue(self, evt=None): - value = self.minimum_value_input.Value - try: - value = float(value) - except ValueError: - value = 'auto' - - if value <= self.panel.plot.high_setting: - self.panel.plot.low_setting = value - - # Update the text box. - self.minimum_value_input.Value = self.bounds_format.format(self.panel.plot.low_setting) - - def OnMaxValue(self, evt=None): - value = self.maximum_value_input.Value - try: - value = float(value) - except ValueError: - value = 'auto' - - if value >= self.panel.plot.low_setting: - self.panel.plot.high_setting = value - - # Update the text box. - self.maximum_value_input.Value = self.bounds_format.format(self.panel.plot.high_setting) - - -class ColormappedPlotSetupDialog(PlotSetupDialog): - def __init__(self, parent, headings, data, *args, **kwargs): - #Limit the number of grid points on each axis: - self.max_mesh = [401, 401] #default value passed to parent constructor. Pass [-1,-1] to remove feature from all colorplots - PlotSetupDialog.__init__(self, parent, headings, ['x', 'y', 'color'], self.max_mesh, '_2d_no_mask', - *args, **kwargs) - - self.parent = parent - self.headings = headings - self.data = data - - def make_plot(self): - try: - x_data, y_data, z_data = [self.data[:,axis].astype(float) for axis in self.axes] - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return - - try: - if self.interp_mode == '_2d_no_mask': - color_data, x_bounds, y_bounds, _ = triples_to_mesh(x_data, y_data, z_data, self.max_mesh, has_mask=False) - elif self.interp_mode == '_2d_full_mask': - color_data, x_bounds, y_bounds, _ = triples_to_mesh(x_data, y_data, z_data, self.max_mesh, has_mask=True) - elif self.interp_mode == '_y': - color_data, x_bounds, y_bounds, _ = triples_to_mesh_y(x_data, y_data, z_data, self.max_mesh) - else: - MessageDialog(self, 'This feature is not yet impelemted. Sorry!', 'Not immplemented!').Show() - except Exception as e: - MessageDialog(self, str(e), 'Conversion failure').Show() - return - - x_label, y_label, z_label = [self.headings[x] for x in self.axes] - title = '{0} vs ({1}, {2})'.format(z_label, x_label, y_label) - - frame = ColormappedPlotFrame(self.parent, color_data, x_bounds, y_bounds, - x_label, y_label, title=title) - frame.Show() - - return True diff --git a/spacq/gui/display/plot/static/common/__init__.py b/spacq/gui/display/plot/static/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/display/plot/static/common/plot_setup.py b/spacq/gui/display/plot/static/common/plot_setup.py deleted file mode 100644 index 3375d53..0000000 --- a/spacq/gui/display/plot/static/common/plot_setup.py +++ /dev/null @@ -1,210 +0,0 @@ -import wx - -from .....tool.box import Dialog, MessageDialog -from spacq.tool.box import Enum - -""" -Plot configuration. -""" - -class AxisSelectionPanel(wx.Panel): - """ - A panel for choosing the headings to be used for the axes. - """ - - def __init__(self, parent, axes, headings, selection_callback, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - self.selection_callback = selection_callback - - # Panel. - panel_box = wx.BoxSizer(wx.HORIZONTAL) - - ## Axes. - self.axis_lists = [] - - for axis in axes: - axis_static_box = wx.StaticBox(self, label=axis) - axis_box = wx.StaticBoxSizer(axis_static_box, wx.VERTICAL) - panel_box.Add(axis_box, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) - - axis_list = wx.ListBox(self, choices=headings) - axis_list.SetMinSize((-1, 300)) - self.Bind(wx.EVT_LISTBOX, self.OnAxisSelection, axis_list) - axis_box.Add(axis_list, proportion=1, flag=wx.EXPAND) - - self.axis_lists.append(axis_list) - - self.SetSizer(panel_box) - - def OnAxisSelection(self, evt=None): - """ - Announce the latest selection. - """ - - result = [None if list.Selection == wx.NOT_FOUND else list.Selection for - list in self.axis_lists] - - self.selection_callback(result) - - -class PlotSetupDialog(Dialog): - - bounds_format = '{0:.4e}' - """ - Plot configuration dialog. - """ - - def __init__(self, parent, headings, axis_names, max_mesh = [-1, -1], interp_mode = '_remove', *args, **kwargs): - Dialog.__init__(self, parent, *args, **kwargs) - - self.max_mesh = max_mesh #derivative classes can choose to call this constructor with or wihtout the max_mesh argument; - #defaults to [-1, -1] meaning 'skip' - self.interp_mode = interp_mode #derivative classes can call this constructor with or without the interp_mode argument; - #defualt is -1 meaning 'skip' -- show no radio buttons and do no interpolation (for 2D plots) - self.axes = [None for _ in axis_names] - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Axis setup. - axis_panel = AxisSelectionPanel(self, axis_names, headings, self.OnAxisSelection) - dialog_box.Add(axis_panel) - - ## Input: Interpolation Mode: Radio Buttons - InterpolationModes = Enum(['_none','_x','_y','_2d_no_mask', '_2d_full_mask']) - self.InterpolationModes = InterpolationModes - if (not interp_mode == '_remove'): - try: - if not any( interp_mode == x for x in InterpolationModes ): - raise ValueError(interp_mode) - except ValueError as e: - MessageDialog(self, 'Bad interpolation mode '+str(e)+'. No interplolation assumed.', 'ValueError').Show() - self.interp_mode = '_remove' - - if (not self.interp_mode == '_remove'): - radio_box = wx.BoxSizer(wx.HORIZONTAL) - radio_static_box = wx.StaticBox(self, label='Interpolation Mode:') - radio_settings_box = wx.StaticBoxSizer(radio_static_box, wx.HORIZONTAL) - radio_box.Add(radio_settings_box, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) - dialog_box.Add(radio_box, flag=wx.CENTER) - - self.rb1 = wx.RadioButton(self, -1, 'None\n(Not Implemented Yet)', (10, 10), style=wx.RB_GROUP) - self.rb2 = wx.RadioButton(self, -1, 'x-axis only\n(Not Implemented Yet)', (10, 10)) - self.rb3 = wx.RadioButton(self, -1, 'y-axis only\n(SLOW!)', (10, 10)) - self.rb4 = wx.RadioButton(self, -1, '2D (no mask)', (10, 10)) - self.rb5 = wx.RadioButton(self, -1, '2D with mask\n(warning: very high memory usage)', (10, 10)) - - radio_settings_box.Add(self.rb1, flag=wx.RIGHT, border=10) - radio_settings_box.Add(self.rb2, flag=wx.RIGHT, border=10) - radio_settings_box.Add(self.rb3, flag=wx.RIGHT, border=10) - radio_settings_box.Add(self.rb4, flag=wx.RIGHT, border=10) - radio_settings_box.Add(self.rb5, flag=wx.RIGHT, border=10) - - #Boy I miss C-style swith-case - if self.interp_mode == InterpolationModes._none: - self.rb1.SetValue(True) - elif self.interp_mode == InterpolationModes._x: - self.rb2.SetValue(True) - elif self.interp_mode == InterpolationModes._y: - self.rb3.SetValue(True) - elif self.interp_mode == InterpolationModes._2d_no_mask: - self.rb4.SetValue(True) - else: - self.rb5.SetValue(True) - ## Input: Max Grid Points - if (all (item > 0 for item in max_mesh)): - self.has_max_mesh_value = True - - grid_box = wx.BoxSizer(wx.HORIZONTAL) - button_static_box = wx.StaticBox(self, label='Max Grid Points') - settings_box = wx.StaticBoxSizer(button_static_box, wx.HORIZONTAL) - grid_box.Add(settings_box, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) - - dialog_box.Add(grid_box, flag=wx.CENTER) - - ### Max X mesh size. - settings_box.Add(wx.StaticText(self, label='x: '), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.mesh_x_value = wx.TextCtrl(self, - value=str(max_mesh[0]), - size=(100, -1), style=wx.TE_PROCESS_ENTER) - self.Bind(wx.EVT_TEXT_ENTER, self.OnXValue, self.mesh_x_value) - settings_box.Add(self.mesh_x_value, flag=wx.RIGHT, border=20) - - ### Max Y mesh size. - settings_box.Add(wx.StaticText(self, label='y: '), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.mesh_y_value = wx.TextCtrl(self, - value=str(max_mesh[1]), - size=(100, -1), style=wx.TE_PROCESS_ENTER) - self.Bind(wx.EVT_TEXT_ENTER, self.OnYValue, self.mesh_y_value) - settings_box.Add(self.mesh_y_value, flag=wx.RIGHT, border=20) - else: - self.has_max_mesh_value = False - - ## End buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER) - - self.ok_button = wx.Button(self, wx.ID_OK) - self.ok_button.Disable() - self.Bind(wx.EVT_BUTTON, self.OnOk, self.ok_button) - button_box.Add(self.ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def OnAxisSelection(self, values): - self.axes = values - - self.ok_button.Enable(all(axis is not None for axis in self.axes)) - - def OnOk(self, evt=None): - if not self.interp_mode == '_remove': - if self.rb1.GetValue(): - self.interp_mode = self.InterpolationModes._none - if self.rb2.GetValue(): - self.interp_mode = self.InterpolationModes._x - if self.rb3.GetValue(): - self.interp_mode = self.InterpolationModes._y - if self.rb4.GetValue(): - self.interp_mode = self.InterpolationModes._2d_no_mask - if self.rb5.GetValue(): - self.interp_mode = self.InterpolationModes._2d_full_mask - - if self.has_max_mesh_value: # update the values typed in (but ENTER not pressed) for max_x, max_y - self.OnXValue() - self.OnYValue() - if self.make_plot(): - self.Destroy() - - return True - - def OnXValue(self, evt=None): - value = self.mesh_x_value.Value - try: - value = int(value) - except ValueError: - value = self.max_mesh[0] - - if (value > 0): - self.max_mesh[0] = value - - # Update the text box. - self.mesh_x_value.Value = str(self.max_mesh[0]) - - def OnYValue(self, evt=None): - value = self.mesh_y_value.Value - try: - value = int(value) - except ValueError: - value = self.max_mesh[1] - - if (value > 0): - self.max_mesh[1] = value - - # Update the text box. - self.mesh_y_value.Value = str(self.max_mesh[1]) diff --git a/spacq/gui/display/plot/static/delegator.py b/spacq/gui/display/plot/static/delegator.py deleted file mode 100644 index 2ab46ff..0000000 --- a/spacq/gui/display/plot/static/delegator.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from spacq.tool.box import Enum - -""" -Figure out which plot formats can be used on this system. -""" - - -# 2D curve, 3D colormapped, 3D surface, 3D waveforms -formats = Enum(['two_dimensional', 'colormapped', 'surface', 'waveforms']) - - -# Try to import all available formats. -available_formats = {} - -try: - from .colormapped import ColormappedPlotSetupDialog -except ImportError as e: - log.debug('Could not import from .colormapped: {0}'.format(str(e))) -else: - available_formats[formats.colormapped] = ColormappedPlotSetupDialog - -try: - from .surface import SurfacePlotSetupDialog, WaveformsPlotSetupDialog -except ImportError as e: - log.debug('Could not import from .surface: {0}'.format(str(e))) -else: - available_formats[formats.surface] = SurfacePlotSetupDialog - available_formats[formats.waveforms] = WaveformsPlotSetupDialog - -try: - from .two_dimensional import TwoDimensionalPlotSetupDialog -except ImportError as e: - log.debug('Could not import from .two_dimensional: {0}'.format(str(e))) -else: - available_formats[formats.two_dimensional] = TwoDimensionalPlotSetupDialog - - -log.debug('Available plot formats: {0}'.format(', '.join(available_formats.keys()))) diff --git a/spacq/gui/display/plot/static/surface.py b/spacq/gui/display/plot/static/surface.py deleted file mode 100644 index 03847f2..0000000 --- a/spacq/gui/display/plot/static/surface.py +++ /dev/null @@ -1,114 +0,0 @@ -from numpy import array -import wx - -from spacq.interface.list_columns import ListParser -from spacq.tool.box import triples_to_mesh - -from ....tool.box import MessageDialog -from ..surface import SurfacePlot -from .common.plot_setup import PlotSetupDialog - - -class SurfacePlotPanel(wx.Panel): - def __init__(self, parent, surface_data, x_bounds, y_bounds, x_label, y_label, z_label, - style, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Plot. - self.plot = SurfacePlot(self, style=style) - panel_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) - - self.SetSizer(panel_box) - - self.plot.x_label, self.plot.y_label, self.plot.z_label = x_label, y_label, z_label - self.plot.surface_data = (surface_data, x_bounds, y_bounds) - - -class SurfacePlotFrame(wx.Frame): - def __init__(self, parent, surface_data, x_bounds, y_bounds, x_label, y_label, z_label, - style, *args, **kwargs): - wx.Frame.__init__(self, parent, *args, **kwargs) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - ## Plot panel. - self.panel = SurfacePlotPanel(self, surface_data, x_bounds, y_bounds, x_label, - y_label, z_label, style) - frame_box.Add(self.panel, proportion=1, flag=wx.EXPAND) - - self.SetSizerAndFit(frame_box) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - def OnClose(self, evt): - self.panel.plot.close() - - evt.Skip() - - -class SurfacePlotSetupDialog(PlotSetupDialog): - def __init__(self, parent, headings, data, *args, **kwargs): - PlotSetupDialog.__init__(self, parent, headings, ['x', 'y', 'z'], - *args, **kwargs) - - self.parent = parent - self.headings = headings - self.data = data - - def make_plot(self): - try: - x_data, y_data, z_data = [self.data[:,axis].astype(float) for axis in self.axes] - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return - - try: - surface_data, x_bounds, y_bounds, _ = triples_to_mesh(x_data, y_data, z_data) - except Exception as e: - MessageDialog(self, str(e), 'Conversion failure').Show() - return - - x_label, y_label, z_label = [self.headings[x] for x in self.axes] - title = '{0} vs ({1}, {2})'.format(z_label, x_label, y_label) - - frame = SurfacePlotFrame(self.parent, surface_data, x_bounds, y_bounds, - x_label, y_label, z_label, title=title, style='surface') - frame.Show() - - return True - - -class WaveformsPlotSetupDialog(PlotSetupDialog): - def __init__(self, parent, headings, data, *args, **kwargs): - PlotSetupDialog.__init__(self, parent, headings, ['z'], - *args, **kwargs) - - self.parent = parent - self.headings = headings - self.data = data - - def make_plot(self): - axis = self.axes[0] - lp = ListParser() - - try: - surface_data = array([[x[1] for x in lp(row)] for row in self.data[:,axis]]) - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return - - x_axis = [x[0] for x in lp(self.data[:,axis][0])] - x_bounds = (x_axis[0], x_axis[-1]) - - x_label, y_label, z_label = 'Waveform (s)', 'History', self.headings[axis] - title = '{0} vs ({1}, {2})'.format(z_label, x_label, y_label) - - frame = SurfacePlotFrame(self.parent, surface_data, x_bounds, (1, len(surface_data)), - x_label, y_label, z_label, title=title, style='waveform') - frame.Show() - - return True diff --git a/spacq/gui/display/plot/static/two_dimensional.py b/spacq/gui/display/plot/static/two_dimensional.py deleted file mode 100644 index a662953..0000000 --- a/spacq/gui/display/plot/static/two_dimensional.py +++ /dev/null @@ -1,63 +0,0 @@ -import wx - -from ....tool.box import MessageDialog -from ..two_dimensional import TwoDimensionalPlot -from .common.plot_setup import PlotSetupDialog - - -class TwoDimensionalPlotPanel(wx.Panel): - def __init__(self, parent, x_data, y_data, x_label, y_label, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Plot. - self.plot = TwoDimensionalPlot(self) - panel_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) - - self.SetSizer(panel_box) - - self.plot.x_label, self.plot.y_label = x_label, y_label - self.plot.x_data, self.plot.y_data = x_data, y_data - - -class TwoDimensionalPlotFrame(wx.Frame): - def __init__(self, parent, x_data, y_data, x_label, y_label, *args, **kwargs): - wx.Frame.__init__(self, parent, *args, **kwargs) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - ## Plot panel. - panel = TwoDimensionalPlotPanel(self, x_data, y_data, x_label, y_label) - panel.SetMinSize((400, 300)) - frame_box.Add(panel, proportion=1, flag=wx.EXPAND) - - self.SetSizerAndFit(frame_box) - - -class TwoDimensionalPlotSetupDialog(PlotSetupDialog): - def __init__(self, parent, headings, data, *args, **kwargs): - PlotSetupDialog.__init__(self, parent, headings, ['x', 'y'], - *args, **kwargs) - - self.parent = parent - self.headings = headings - self.data = data - - def make_plot(self): - try: - x_data, y_data = [self.data[:,axis].astype(float) for axis in self.axes] - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return - - x_label, y_label = [self.headings[x] for x in self.axes] - title = '{0} vs {1}'.format(y_label, x_label) - - frame = TwoDimensionalPlotFrame(self.parent, x_data, y_data, - x_label, y_label, title=title) - frame.Show() - - return True diff --git a/spacq/gui/display/plot/surface.py b/spacq/gui/display/plot/surface.py deleted file mode 100644 index 95ab462..0000000 --- a/spacq/gui/display/plot/surface.py +++ /dev/null @@ -1,114 +0,0 @@ -from matplotlib import pyplot -from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas -from mpl_toolkits.mplot3d import axes3d -import numpy -import wx - -""" -An embeddable three-dimensional surface plot. -""" - - -class SurfacePlot(object): - """ - A surface plot. - """ - - alpha = 0.8 - - def __init__(self, parent, style='surface'): - self.style = style - - self.figure = pyplot.figure() - self.canvas = FigureCanvas(parent, wx.ID_ANY, self.figure) - - self.axes = axes3d.Axes3D(self.figure) - self.surface = None - - def __del__(self): - try: - self.close() - except Exception: - pass - - @property - def control(self): - """ - A drawable control. - """ - - return self.canvas - - def close(self): - """ - Inform pyplot that this figure is no longer required. - """ - - pyplot.close(self.figure.number) - - def set_surface_data(self, data): - """ - Set the surface data based on the data tuple. - """ - - if self.surface is not None: - self.axes.collections.remove(self.surface) - self.surface = None - - if data is None: - return - - surface_data, x_bounds, y_bounds = data - - # Number of values along each axis. - y_num, x_num = surface_data.shape - # The equally-spaced values along each axis. - x_values = numpy.linspace(*x_bounds, num=x_num) - y_values = numpy.linspace(*y_bounds, num=y_num) - # The meshgrid of values. - x, y = numpy.meshgrid(x_values, y_values) - - if self.style == 'surface': - # Just a regular surface. - self.surface = self.axes.plot_surface(x, y, surface_data, alpha=self.alpha) - elif self.style == 'waveform': - # Waveform style shows individual waveforms nicely. - self.surface = self.axes.plot_wireframe(x, y, surface_data, cstride=100000) - - surface_data = property(fset=set_surface_data) - - @property - def x_label(self): - """ - The x axis label. - """ - return self.axes.get_xlabel() - - @x_label.setter - def x_label(self, value): - self.axes.set_xlabel(value) - - @property - def y_label(self): - """ - The y axis label. - """ - return self.axes.get_ylabel() - - @y_label.setter - def y_label(self, value): - self.axes.set_ylabel(value) - - @property - def z_label(self): - """ - The z axis label. - """ - return self.axes.get_zlabel() - - @z_label.setter - def z_label(self, value): - self.axes.set_zlabel(value) - - def redraw(self): - self.canvas.draw() diff --git a/spacq/gui/display/plot/two_dimensional.py b/spacq/gui/display/plot/two_dimensional.py deleted file mode 100644 index 6c92635..0000000 --- a/spacq/gui/display/plot/two_dimensional.py +++ /dev/null @@ -1,82 +0,0 @@ -from chaco.api import ArrayPlotData -from enable.api import Window -from functools import partial - -from .common.chaco_plot import ChacoPlot - -""" -An embeddable two-dimensional plot. -""" - - -class TwoDimensionalPlot(ChacoPlot): - """ - A 2D plot. - """ - - auto_color_idx = 0 - auto_color_list = ['green', 'brown', 'blue', 'red', 'black'] - - @classmethod - def auto_color(cls): - """ - Choose the next color. - """ - - color = cls.auto_color_list[cls.auto_color_idx] - cls.auto_color_idx = (cls.auto_color_idx + 1) % len(cls.auto_color_list) - - return color - - def __init__(self, parent, color=None, *args, **kwargs): - self.parent = parent - - if color is None: - color = self.auto_color() - - self.data = ArrayPlotData() - self.data.set_data('x', [0]) - self.data.set_data('y', [0]) - - ChacoPlot.__init__(self, self.data, *args, **kwargs) - - self.plot(('x', 'y'), color=color) - - self.configure() - - @property - def control(self): - """ - A drawable control. - """ - - return Window(self.parent, component=self).control - - def get_data(self, axis): - """ - Values for an axis. - """ - - return self.data.get_data(axis) - - def set_data(self, values, axis): - self.data.set_data(axis, values) - - x_data = property(partial(get_data, axis='x'), partial(set_data, axis='x')) - y_data = property(partial(get_data, axis='y'), partial(set_data, axis='y')) - - def x_autoscale(self): - """ - Enable autoscaling for the x axis. - """ - - x_range = self.plots.values()[0][0].index_mapper.range - x_range.low = x_range.high = 'auto' - - def y_autoscale(self): - """ - Enable autoscaling for the y axis. - """ - - y_range = self.plots.values()[0][0].value_mapper.range - y_range.low = y_range.high = 'auto' diff --git a/spacq/gui/display/table/__init__.py b/spacq/gui/display/table/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/display/table/filter.py b/spacq/gui/display/table/filter.py deleted file mode 100644 index e994aa3..0000000 --- a/spacq/gui/display/table/filter.py +++ /dev/null @@ -1,196 +0,0 @@ -from functools import partial -import wx - -from ...tool.box import Dialog, MessageDialog - -""" -Graphical interface for data filters. -""" - - -class FilterEditDialog(Dialog): - def __init__(self, parent, headings, ok_callback, *args, **kwargs): - kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER - kwargs['title'] = kwargs.get('title', 'Add filter') - - Dialog.__init__(self, parent, *args, **kwargs) - - self.ok_callback = ok_callback - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Inputs. - input_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) - input_sizer.AddGrowableCol(1, 1) - dialog_box.Add(input_sizer, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) - - input_sizer.Add(wx.StaticText(self, label='Column:'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.column_input = wx.Choice(self, choices=headings) - input_sizer.Add(self.column_input, flag=wx.EXPAND) - - input_sizer.Add(wx.StaticText(self, label='Function (# is data):'), - flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) - self.function_input = wx.TextCtrl(self) - self.function_input.SetMinSize((300, -1)) - input_sizer.Add(self.function_input, flag=wx.EXPAND) - - ## Buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - ok_button = wx.Button(self, wx.ID_OK) - self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) - button_box.Add(ok_button) - - cancel_button = wx.Button(self, wx.ID_CANCEL) - button_box.Add(cancel_button) - - self.SetSizerAndFit(dialog_box) - - def GetValue(self): - return (self.column_input.StringSelection, self.function_input.Value) - - def SetValue(self, values): - (self.column_input.StringSelection, self.function_input.Value) = values - - def OnOk(self, evt=None): - try: - self.ok_callback(self) - except ValueError as e: - MessageDialog(self, str(e), 'Invalid value').Show() - return - - self.Destroy() - - -class FilterListDialog(Dialog): - def __init__(self, parent, table, close_callback, filters=None, filter_columns=None, - *args, **kwargs): - kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER - kwargs['title'] = kwargs.get('title', 'Filters') - - Dialog.__init__(self, parent, *args, **kwargs) - - self.table = table - self.close_callback = close_callback - - if filters is None or filter_columns is None: - self.filters, self.filter_columns = {}, {} - else: - self.filters, self.filter_columns = filters, filter_columns - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Filter list. - self.filter_list = wx.ListBox(self, choices=self.filters.keys()) - self.filter_list.SetMinSize((100, 200)) - self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnEditFilter, self.filter_list) - dialog_box.Add(self.filter_list, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) - - ## Buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) - - add_button = wx.Button(self, wx.ID_ADD) - add_button.Bind(wx.EVT_BUTTON, self.OnAddFilter) - button_box.Add(add_button, flag=wx.CENTER) - - remove_button = wx.Button(self, wx.ID_REMOVE) - remove_button.Bind(wx.EVT_BUTTON, self.OnRemoveFilter) - button_box.Add(remove_button, flag=wx.CENTER) - - self.SetSizerAndFit(dialog_box) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - def create_filter(self, f_text, col): - """ - Create a filter out of text. - """ - - col_idx = self.table.headings.index(col) - - return lambda i, x: eval(f_text.replace('#', 'float(x[{0}])'.format(col_idx))) - - @property - def meta_filter(self): - """ - Create a meta-filter out of all the filters. - """ - - filters = [self.create_filter(f, col) for f, col in - zip(self.filters.values(), self.filter_columns.values())] - - return lambda i, x: all([f(i, x) for f in filters]) - - def edit_ok_callback(self, dlg, selection=None): - col, f = dlg.GetValue() - - name = '{0}: {1}'.format(col, f) - - if selection is not None and name == selection: - return - - if name in self.filters: - raise ValueError('Filter "{0}" already exists'.format(name)) - - if not f: - raise ValueError('No function provided') - - f_function = self.create_filter(f, col) - - try: - self.table.apply_filter(f_function) - except Exception as e: - raise ValueError(e) - - if selection is not None: - self.OnRemoveFilter(selection=selection) - self.table.apply_filter(f_function) - - self.filters[name] = f - self.filter_columns[name] = col - self.filter_list.Items += [name] - - def OnAddFilter(self, evt=None): - FilterEditDialog(self, self.table.headings, self.edit_ok_callback).Show() - - def OnEditFilter(self, evt=None): - selection = self.filter_list.StringSelection - - if not selection: - return - - dlg = FilterEditDialog(self, self.table.headings, partial(self.edit_ok_callback, selection=selection), - title='Edit filter') - dlg.SetValue((self.filter_columns[selection], self.filters[selection])) - dlg.Show() - - def OnRemoveFilter(self, evt=None, selection=None): - if selection is None: - selection = self.filter_list.StringSelection - - if not selection: - return - - try: - del self.filters[selection] - except KeyError: - pass - - try: - del self.filter_columns[selection] - except KeyError: - pass - - self.filter_list.Items = [x for x in self.filter_list.Items if x != selection] - - self.table.apply_filter(self.meta_filter, afresh=True) - - def OnClose(self, evt): - self.close_callback(self) - - evt.Skip() diff --git a/spacq/gui/display/table/generic.py b/spacq/gui/display/table/generic.py deleted file mode 100644 index 7a2e3ac..0000000 --- a/spacq/gui/display/table/generic.py +++ /dev/null @@ -1,204 +0,0 @@ -from numpy import array, compress, zeros -import wx -from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin - -from spacq.interface.list_columns import ListParser - - -""" -Embeddable, generic, virtual, tabular display. -""" - - -class VirtualListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): - """ - A generic virtual list. - """ - - max_value_len = 10 # Characters. - - @staticmethod - def find_type(value): - """ - Determine the type of a column based on a single value. - - The type is one of: scalar, list, string. - """ - - try: - float(value) - except ValueError: - pass - else: - return 'scalar' - - try: - ListParser()(value) - except ValueError: - pass - else: - return 'list' - - return 'string' - - def __init__(self, parent, *args, **kwargs): - wx.ListCtrl.__init__(self, parent, - style=wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES, - *args, **kwargs) - - ListCtrlAutoWidthMixin.__init__(self) - - self.reset() - - def reset(self): - self.headings = [] - self.data = array([]) - self.filtered_data = None - self.display_data = array([]) - - self.types = [] - - def refresh_with_values(self, data): - self.ItemCount = len(data) - - if self.ItemCount > 0: - self.display_data = zeros(data.shape, dtype='|S{0}'.format(self.max_value_len)) - - for i, _ in enumerate(self.headings): - # Truncate for display. - self.display_data[:,i] = [str(x)[:self.max_value_len] for x in data[:,i]] - - self.Refresh() - - def apply_filter(self, f, afresh=False): - """ - Set the data to be the old data, along with the application of a filter. - - f is a function of two parameters: the index of the row and the row itself. - f must return True if the row is to be kept and False otherwise. - - If afresh is True, all old filtered data is discarded. - Otherwise, a new filter can be quickly applied. - """ - - if afresh: - self.filtered_data = None - - if self.filtered_data is not None: - original_set = self.filtered_data - else: - original_set = self.data - - self.filtered_data = compress([f(i, x) for i, x in enumerate(original_set)], original_set, axis=0) - - self.refresh_with_values(self.filtered_data) - - def GetValue(self, types=None): - # Get all types by default. - if types is None: - types = set(self.types) - else: - types = set(types) - - # Find column indices of the correct type. - idxs = [i for i, t in enumerate(self.types) if t in types] - - if self.filtered_data is not None: - data = self.filtered_data - else: - data = self.data - - return ([self.headings[i] for i in idxs], data[:,idxs], [self.types[i] for i in idxs]) - - def SetValue(self, headings, data): - """ - headings: A list of strings. - data: A 2D NumPy array. - """ - - self.ClearAll() - self.reset() - - self.headings = headings - self.data = data - - self.refresh_with_values(self.data) - - if self.ItemCount > 0: - width, height = self.GetSize() - # Give some room for the scrollbar. - col_width = (width - 50) / len(self.headings) - - for i, heading in enumerate(self.headings): - self.InsertColumn(i, heading, width=col_width) - - type = self.find_type(data[0,i]) - self.types.append(type) - - def OnGetItemText(self, item, col): - """ - Return cell value for LC_VIRTUAL. - """ - - return self.display_data[item,col] - - -class TabularDisplayPanel(wx.Panel): - """ - A panel to display arbitrary tabular data. - """ - - def __init__(self, parent, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Table. - self.table = VirtualListCtrl(self) - panel_box.Add(self.table, proportion=1, flag=wx.EXPAND) - - self.SetSizer(panel_box) - - def __len__(self): - return self.table.ItemCount - - # TODO: has headers does not function as intended, never will reach code to give header names - def from_csv_data(self, has_header, values): - """ - Import the given CSV data into the table. - - If has_header is True, the first row is treated specially. - """ - - if has_header: - headers, rows = values[0], array(values[1:]) - else: - headers, rows = [''] * len(values[0]), array(values) - - # Ensure that all columns have a header. - for i, header in enumerate(headers): - if not header: - headers[i] = 'Column {0}'.format(i + 1) - - self.SetValue(headers, rows) - - def GetValue(self, *args, **kwargs): - return self.table.GetValue(*args, **kwargs) - - def SetValue(self, headings, values): - self.table.SetValue(headings, values) - - -class TabularDisplayFrame(wx.Frame): - def __init__(self, parent, *args, **kwargs): - wx.Frame.__init__(self, parent, *args, **kwargs) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - ## Display panel. - self.display_panel = TabularDisplayPanel(self) - frame_box.Add(self.display_panel, proportion=1, flag=wx.EXPAND) - - self.SetSizer(frame_box) diff --git a/spacq/gui/display/waveform.py b/spacq/gui/display/waveform.py deleted file mode 100644 index 41a398e..0000000 --- a/spacq/gui/display/waveform.py +++ /dev/null @@ -1,88 +0,0 @@ -from chaco.api import VPlotContainer -from enable.api import Window -from math import floor, log10 -from numpy import linspace -import wx - -from spacq.interface.units import SIValues - -from .plot.two_dimensional import TwoDimensionalPlot - - -class WaveformDisplay(TwoDimensionalPlot): - marker_height = 50 - - def __init__(self, parent, *args, **kwargs): - TwoDimensionalPlot.__init__(self, parent, *args, **kwargs) - - self.padding_left = 50 - - self.title = 'Waveform' - - self.vplot_container = VPlotContainer(use_backbuffer=True) - self.vplot_container.stack_order = 'top_to_bottom' - self.vplot_container.add(self) - - @property - def control(self): - return Window(self.parent, component=self.vplot_container).control - - def add_marker(self, num, data): - marker_plot = TwoDimensionalPlot(self, height=self.marker_height, resizable='h') - marker_plot.padding_left = self.padding_left - - marker_plot.x_data = self.x_data - marker_plot.y_data = data - marker_plot.title = 'Marker {0}'.format(num) - - # Synchronize with waveform plot. - marker_plot.index_range = self.index_range - - self.vplot_container.add(marker_plot) - - -class WaveformPanel(wx.Panel): - def __init__(self, parent, *args, **kwargs): - wx.Panel.__init__(self, parent, *args, **kwargs) - - # Panel. - panel_box = wx.BoxSizer(wx.VERTICAL) - - ## Waveform plot. - self.waveform_plot = WaveformDisplay(self) - panel_box.Add(self.waveform_plot.control, proportion=1, flag=wx.EXPAND) - - self.SetSizer(panel_box) - - def SetValue(self, waveform, marker_data, frequency): - max_time = len(waveform) / frequency.value - # Find the order of magnitude (to within 3 orders, to keep it at n, u, m, etc). - magnitude = 3 * floor(floor(log10(max_time)) / 3) - - self.waveform_plot.x_data = linspace(0, max_time / (10 ** magnitude), len(waveform)) - self.waveform_plot.y_data = waveform - - self.waveform_plot.x_label = '{0}s'.format(SIValues.prefixes_[magnitude]) - - for num, data in marker_data.items(): - self.waveform_plot.add_marker(num, data) - - self.waveform_plot.x_autoscale() - self.waveform_plot.y_autoscale() - - -class WaveformFrame(wx.Frame): - def __init__(self, parent, output_name, *args, **kwargs): - wx.Frame.__init__(self, parent, title=output_name, *args, **kwargs) - - # Frame. - frame_box = wx.BoxSizer(wx.VERTICAL) - - self.panel = WaveformPanel(self) - self.panel.SetMinSize((600, 400)) - frame_box.Add(self.panel, proportion=1, flag=wx.EXPAND) - - self.SetSizerAndFit(frame_box) - - def SetValue(self, *args): - self.panel.SetValue(*args) diff --git a/spacq/gui/global_store.py b/spacq/gui/global_store.py deleted file mode 100644 index 59d6325..0000000 --- a/spacq/gui/global_store.py +++ /dev/null @@ -1,27 +0,0 @@ -from functools import partial -from threading import RLock -from pubsub import pub -import wx - -from spacq.tool.box import PubDict - -""" -A single storage location for everything globally-unique. -""" - - -class GlobalStore(object): - """ - A global value store for an entire application. - """ - - def __init__(self): - self.lock = RLock() - - send = partial(wx.CallAfter, pub.sendMessage) - - self.devices = PubDict(self.lock, send, 'device') - self.resources = PubDict(self.lock, send, 'resource') - self.variables = PubDict(self.lock, send, 'variable') - - self.pulse_program = None diff --git a/spacq/gui/tool/__init__.py b/spacq/gui/tool/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/tool/box.py b/spacq/gui/tool/box.py deleted file mode 100644 index 22074ba..0000000 --- a/spacq/gui/tool/box.py +++ /dev/null @@ -1,245 +0,0 @@ -import csv -from os.path import basename -import pickle -import wx - - -OK_BACKGROUND_COLOR = 'PALE GREEN' - - -def determine_wildcard(extension=None, file_type=None): - """ - Assemble a wildcard string of the form: - [[file_type ](*.extension)|*.extension|] - """ - - all_files = 'All files|*' - - if extension is not None: - if '|' in extension: - raise ValueError(extension) - - if file_type is not None: - if '|' in file_type: - raise ValueError(file_type) - - wildcard = '{0} (*.{1})|*.{1}|{2}'.format(file_type, extension, all_files) - else: - wildcard = '(*.{0})|*.{0}|{1}'.format(extension, all_files) - else: - wildcard = all_files - - return wildcard - - -def load_pickled(parent, extension=None, file_type=None): - """ - Unpickle data from a file based on a file dialog. - """ - - wildcard = determine_wildcard(extension, file_type) - dlg = wx.FileDialog(parent=parent, message='Load...', wildcard=wildcard, - style=wx.FD_OPEN) - - if dlg.ShowModal() == wx.ID_OK: - path = dlg.GetPath() - - with open(path, 'rb') as f: - try: - return pickle.load(f) - except Exception as e: - # Wrap all problems. - raise IOError('Could not load data.', e) - -def save_pickled(parent, values, extension=None, file_type=None): - """ - Pickle data to a file based on a file dialog. - """ - - wildcard = determine_wildcard(extension, file_type) - dlg = wx.FileDialog(parent=parent, message='Save...', - wildcard=wildcard, style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) - - if dlg.ShowModal() == wx.ID_OK: - path = dlg.GetPath() - - # Automatically append extension if none given. - if extension is not None and '.' not in path: - path = '{0}.{1}'.format(path, extension) - - with open(path, 'wb') as f: - try: - pickle.dump(values, f, protocol=pickle.HIGHEST_PROTOCOL) - except Exception as e: - # Wrap all problems: - raise IOError('Could not save data.', e) - - -def load_csv(parent, extension='csv', file_type='CSV'): - """ - Load data from a CSV file based on a file dialog. - - ZParrott: has_header functions partially in that it removes a blank first - row, but hte has_header boolean then fails in subsequent dependicies where - it has meaning of having a column title or not. - """ - - wildcard = determine_wildcard(extension, file_type) - dlg = wx.FileDialog(parent=parent, message='Load...', wildcard=wildcard, - style=wx.FD_OPEN) - - if dlg.ShowModal() == wx.ID_OK: - path = dlg.GetPath() - - filename = basename(path) - - with open(path, 'rb') as f: - try: - result = list(csv.reader(f)) - try: - has_header = len(result[0]) > 0 - except IndexError: - has_header = False - else: - # Remove empty row. - if not has_header: - result = result[1:] - - return (has_header, result, filename) - except Exception as e: - # Wrap all problems. - raise IOError('Could not load data.', e) - -def save_csv(parent, values, headers=None, extension='csv', file_type='CSV'): - """ - Save data to a CSV file based on a file dialog. - """ - - wildcard = determine_wildcard(extension, file_type) - dlg = wx.FileDialog(parent=parent, message='Save...', - wildcard=wildcard, style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) - - if dlg.ShowModal() == wx.ID_OK: - path = dlg.GetPath() - - # Automatically append extension if none given. - if extension is not None and '.' not in path: - path = '{0}.{1}'.format(path, extension) - - with open(path, 'wb') as f: - try: - w = csv.writer(f) - - if headers is not None: - w.writerow(headers) - else: - w.writerow([]) - - w.writerows(values) - except Exception as e: - # Wrap all problems: - raise IOError('Could not save data.', e) - - -class Dialog(wx.Dialog): - """ - Auto-destroying dialog. - """ - - def __init__(self, parent, auto_destroy=True, *args, **kwargs): - wx.Dialog.__init__(self, parent, *args, **kwargs) - - self.auto_destroy = auto_destroy - - self.Bind(wx.EVT_SHOW, self.OnShow) - - def OnShow(self, evt): - """ - Destroy the dialog when it disappears. - """ - - if self.auto_destroy and not evt.Show: - if not self.IsBeingDeleted(): - self.Destroy() - - -class MessageDialog(Dialog): - """ - A simple error message dialog. - """ - - def __init__(self, parent, message, title='', unclosable=False, monospace=False, - *args, **kwargs): - kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER - if unclosable: - kwargs['style'] &= ~wx.CLOSE_BOX - - Dialog.__init__(self, parent=parent, title=title, *args, **kwargs) - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Message. - message_text = wx.StaticText(self, label=message) - if monospace: - font = message_text.Font - message_text.Font = wx.Font(font.PointSize, wx.MODERN, font.Style, font.Weight) - message_text.SetMinSize((450, 100)) - dialog_box.Add(message_text, proportion=1, flag=wx.EXPAND|wx.ALL, border=20) - - ## OK button. - if not unclosable: - ok_button = wx.Button(self, wx.ID_OK) - dialog_box.Add(ok_button, flag=wx.EXPAND) - - self.SetSizerAndFit(dialog_box) - - -class YesNoQuestionDialog(Dialog): - """ - A yes/no question dialog. - """ - - def __init__(self, parent, prompt, yes_callback=None, no_callback=None, title='', - *args, **kwargs): - Dialog.__init__(self, parent=parent, title=title, - style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER, - *args, **kwargs) - - self.yes_callback = yes_callback - self.no_callback = no_callback - - # Dialog. - dialog_box = wx.BoxSizer(wx.VERTICAL) - - ## Prompt. - prompt_text = wx.StaticText(self, label=prompt) - dialog_box.Add(prompt_text, proportion=1, flag=wx.EXPAND|wx.ALL, border=20) - - ## Buttons. - button_box = wx.BoxSizer(wx.HORIZONTAL) - dialog_box.Add(button_box, flag=wx.CENTER) - - yes_button = wx.Button(self, wx.ID_YES) - self.Bind(wx.EVT_BUTTON, self.OnYes, yes_button) - button_box.Add(yes_button) - - no_button = wx.Button(self, wx.ID_NO) - self.Bind(wx.EVT_BUTTON, self.OnNo, no_button) - button_box.Add(no_button) - - self.SetSizerAndFit(dialog_box) - - self.Bind(wx.EVT_CLOSE, self.OnNo) - - def OnYes(self, evt=None): - if self.yes_callback is not None: - self.yes_callback() - - self.Destroy() - - def OnNo(self, evt=None): - if self.no_callback is not None: - self.no_callback() - - self.Destroy() diff --git a/spacq/gui/tool/tests/__init__.py b/spacq/gui/tool/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/gui/tool/tests/test_box.py b/spacq/gui/tool/tests/test_box.py deleted file mode 100644 index 2cad588..0000000 --- a/spacq/gui/tool/tests/test_box.py +++ /dev/null @@ -1,58 +0,0 @@ -from nose.tools import eq_ -from unittest import main, TestCase - -from .. import box - - -class DetermineWildcardTest(TestCase): - def testDefault(self): - """ - Simply all files. - """ - - w = box.determine_wildcard() - eq_(w, 'All files|*') - - def testExtension(self): - """ - With a nameless extension. - """ - - w = box.determine_wildcard('test') - eq_(w, '(*.test)|*.test|All files|*') - - def testFileType(self): - """ - With a named extension. - """ - - w = box.determine_wildcard('test', 'Test') - eq_(w, 'Test (*.test)|*.test|All files|*') - - def testInvalid(self): - """ - Try some invalid combinations. - """ - - # Unused argument. - w = box.determine_wildcard(file_type='Test') - eq_(w, 'All files|*') - - # Invalid characters. - try: - box.determine_wildcard('test|test', 'Test') - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - try: - box.determine_wildcard('test', 'Test|Test') - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - -if __name__ == '__main__': - main() diff --git a/spacq/interface/__init__.py b/spacq/interface/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/interface/list_columns.py b/spacq/interface/list_columns.py deleted file mode 100644 index 4f6f549..0000000 --- a/spacq/interface/list_columns.py +++ /dev/null @@ -1,29 +0,0 @@ -from pyparsing import (delimitedList, ParseBaseException, Regex, Suppress) -from re import IGNORECASE - -""" -Tools for parsing list columns on import. -""" - - -def ListParser(): - """ - A parser for list columns, where each list is composed of pairs of values. - """ - - value = Regex(r'[-+]?[0-9]+(?:\.[0-9]*)?(?:e[-+]?[0-9]+)?', IGNORECASE) - value.setParseAction(lambda toks: float(toks[0])) - - item = Suppress('(') + value + Suppress(',') + value + Suppress(')') - item.setParseAction(tuple) - - lst = Suppress('[') + delimitedList(item) + Suppress(']') - lst.setParseAction(list) - - def parse(s): - try: - return lst.parseString(s).asList() - except ParseBaseException as e: - raise ValueError(e) - - return parse diff --git a/spacq/interface/pulse/__init__.py b/spacq/interface/pulse/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/interface/pulse/parser.py b/spacq/interface/pulse/parser.py deleted file mode 100644 index dbea705..0000000 --- a/spacq/interface/pulse/parser.py +++ /dev/null @@ -1,165 +0,0 @@ -from pyparsing import (alphanums, alphas, delimitedList, nums, CaselessLiteral, Combine, - Forward, Keyword, LineEnd, Literal, OneOrMore, Optional, ParseBaseException, ParseException, - ParserElement, QuotedString, SkipTo, StringEnd, Suppress, Word, ZeroOrMore) - -from ..units import Quantity -from .tool.box import find_location, format_error -from .tree import (Acquire, Assignment, Attribute, Block, Declaration, Delay, Dictionary, - DictionaryItem, Loop, ParallelPulses, Pulse, PulseSequence, Variable) - -""" -A parser for pulse programs. -""" - - -class PulseError(Exception): - """ - A problem with the pulse program. - """ - - pass - -class PulseSyntaxError(PulseError): - """ - Could not parse pulse program. - """ - - pass - - -def read_quantity(s, loc, toks): - """ - Attempt to create a Quantity object. - """ - - try: - return Quantity(toks[0], toks[1]) - except ValueError as e: - raise ParseException(s, loc, e) - - -def Parser(raw=False): - """ - Create a pulse program parser. - - If raw is True, returns the pyparsing parser object. - Otherwise, returns a function which takes a string and returns an AST. - """ - - old_whitespace = ParserElement.DEFAULT_WHITE_CHARS - - try: - # Handle line breaks manually. - ParserElement.setDefaultWhitespaceChars(ParserElement.DEFAULT_WHITE_CHARS.replace('\n', '')) - - # Keywords. - ## Types. - DELAY, INT, OUTPUT, PULSE = Keyword('delay'), Keyword('int'), Keyword('output'), Keyword('pulse') - - ## Syntax. - ACQUIRE = Keyword('acquire').suppress().setName('acquire') - TERMINATOR = (LineEnd() | ';').suppress().setName('terminator') - TIMES = Keyword('times').suppress().setName('times') - - # Values. - identifier = Word(alphas + '_', alphanums + '_') - identifier.setName('identifier') - - ## Numbers. - ### Integer. - unparsed_inum = Word('+-' + nums, nums) - inum = unparsed_inum.copy().setParseAction(lambda x: int(x[0])) - inum.setName('inum') - - ### Floating point. - dot = Literal('.') - e = CaselessLiteral('e') - - fractional_part = dot + Word(nums, nums) - exponent_part = e + inum - - fnum = Combine(unparsed_inum + (fractional_part + Optional(exponent_part) | exponent_part)) - fnum.setParseAction(lambda x: float(x[0])) - fnum.setName('fnum') - - number = fnum | inum - - ## Quantities. - # Unit symbol cannot start with "e". - unit_symbol = Combine(Word(alphas.replace('E', '').replace('e', ''), alphas) + Optional(Word(nums))) - unit_symbols = delimitedList(unit_symbol, delim='.', combine=True) - quantity = (number + unit_symbols).setParseAction(read_quantity) - quantity.setName('quantity') - - ## Strings. - string = QuotedString(r'"', escChar=r'\\') | QuotedString(r"'", escChar=r'\\') - - value = quantity | number | string - - ## Dictionaries. - dictionary_item = (identifier('key') + Suppress(':') + value('value')).setParseAction(DictionaryItem) - dictionary = Suppress('{') + Optional(delimitedList(dictionary_item)) + Suppress('}') - dictionary.setParseAction(Dictionary) - - # Variables. - type = DELAY | INT | OUTPUT | PULSE - - attribute = (identifier('variable') + Suppress('.') - identifier('name')).setParseAction(Attribute) - - identifier_assignment = identifier('target') + Suppress('=') - (dictionary | value)('value') - identifier_assignment.setName('identifier_assignment') - - attribute_assignment = attribute('target') + Suppress('=') - (value)('value') - attribute_assignment.setName('attribute_assignment') - - assignment = (identifier_assignment | attribute_assignment).setParseAction(Assignment) - - declaration_list = delimitedList(assignment | identifier('name').setParseAction(Variable)) - declaration = (type('type') + declaration_list('variables')).setParseAction(Declaration) - - # Commands. - ## Acquire. - acquire = ACQUIRE.setParseAction(Acquire) - - ## Delay. - delay = (quantity | identifier)('length').setParseAction(Delay) - - ## Pulse. - pulse_sequence = (Suppress('(') + OneOrMore(identifier | delay) + Suppress(')')) | identifier - pulse_sequence.setParseAction(PulseSequence) - pulse = (pulse_sequence('sequence') + Suppress(':') + identifier('target')).setParseAction(Pulse) - pulses = OneOrMore(pulse).setParseAction(ParallelPulses) - - command = acquire | pulses | delay - - # Blocks. - block = Forward().setParseAction(Block) - loop_block = (TIMES + (identifier | inum)('times') + block('block')).setParseAction(Loop) - - # Statements. - statement = Optional(assignment | declaration | command) - # The last statement does not require a terminator. - statements = ZeroOrMore(loop_block | statement + TERMINATOR) + statement - - block << (Suppress('{') + statements + Suppress('}')) - - parser = (statements + StringEnd().suppress()).setParseAction(Block) - - # Comments. - comment = Literal('#') + SkipTo(LineEnd()) - parser.ignore(comment) - - if raw: - return parser - else: - def parseString(s): - s = s.expandtabs() - - try: - return parser.parseString(s)[0] - except ParseBaseException as e: - raise PulseSyntaxError([format_error(e.msg, *find_location(s, e.loc))]) - - return parseString - finally: - ParserElement.setDefaultWhitespaceChars(old_whitespace) diff --git a/spacq/interface/pulse/program.py b/spacq/interface/pulse/program.py deleted file mode 100644 index ec4df8f..0000000 --- a/spacq/interface/pulse/program.py +++ /dev/null @@ -1,155 +0,0 @@ -from copy import copy, deepcopy -from os.path import basename, dirname - -from ..units import Quantity -from .parser import Parser, PulseError, PulseSyntaxError -from .tree import Environment - -""" -Pulse programs as compiled entities. -""" - - -class Program(object): - """ - A prepared, compiled pulse program. - """ - - filename = '' - - @classmethod - def from_string(cls, s): - """ - Create a program from a string. - """ - - env = Environment() - ast = Parser()(s) - - return Program(env, ast) - - @classmethod - def from_file(cls, path): - """ - Load a program from a file. - """ - - with open(path) as f: - prog_lines = f.readlines() - - p = cls.from_string(''.join(prog_lines)) - p._env.cwd = dirname(path) - p.filename = basename(path) - - return p - - def __init__(self, env, ast): - """ - Given a blank Environment and AST, prepare a program. - """ - - self._env = env - self._ast = ast - - for stage in self._env.prep_stages: - self._env.stage = stage - self._env.traverse_tree(self._ast) - - if self._env.errors: - raise PulseSyntaxError(self._env.format_errors()) - - # Output channel numbers. - self.output_channels = dict((name, None) for name, type in self._env.variables.items() if type == 'output') - - # Output device labels. - self.awg = '' - self.oscilloscope = '' - - # Same structure as values, but optional string-only resource labels and matching Resource objects. - self.resource_labels = {} - self.resources = {} - - # Program averaging. - self.times_average = 1 - self.acq_delay = Quantity(0, 's') - - @property - def all_values(self): - return self._env.all_values - - @property - def frequency(self): - return self._env.frequency - - @frequency.setter - def frequency(self, value): - self._env.frequency = value - - @property - def missing_values(self): - return self._env.missing_values - - @property - def values(self): - return self._env.values - - @property - def variables(self): - return self._env.variables - - def set_value(self, parameter, value): - """ - Set a value, even if it has already been set previously. - """ - - self._env.values[parameter] = value - - def generate_waveforms(self, dry_run=False): - """ - Generate the waveforms, given that the values are all filled in. - """ - - self._env.stage = self._env.stages.waveforms - self._env.dry_run = dry_run - self._env.missing_shapes = set() - self._env.errors = [] - self._env.traverse_tree(self._ast) - - if self._env.errors: - raise PulseError(self._env.format_errors()) - - return self._env.waveforms - - @property - def with_resources(self): - """ - Produce a copy object, with a cloned Environment, and with all resources which exist mutated to point to the corresponding values. - - Note: Because the Resource objects must be shared between copies, there may only ever exist one copy at a time. - """ - - result = copy(self) - - # We plan to modify the values in the Environment. - result._env = deepcopy(self._env) - - for parameter, label in self.resource_labels.items(): - def setter(x, parameter=parameter): - result._env.values[parameter] = x - - res = result.resources[parameter] - res.setter = setter - - # Type checking. - var_type = result._env.variables[parameter[0]] - if var_type == 'delay': - res.units = 's' - elif var_type == 'int': - pass - elif var_type == 'pulse': - if parameter[1] == 'amplitude': - res.units = 'V' - elif parameter[1] == 'length': - res.units = 's' - - return result diff --git a/spacq/interface/pulse/tests/__init__.py b/spacq/interface/pulse/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/interface/pulse/tests/resources/01.pulse b/spacq/interface/pulse/tests/resources/01.pulse deleted file mode 100644 index f8e7c59..0000000 --- a/spacq/interface/pulse/tests/resources/01.pulse +++ /dev/null @@ -1,35 +0,0 @@ -## Declarations. -int bumps = 2 - -delay bump_spacing = 10 ns, settle, end_delay = 15 ns - -pulse first_square, wobble -pulse last_square = {shape: "square"} - -pulse manipulator = {shape: 'non-square'} - -output f1, f2 - -## Initializations of known values. -first_square = {shape: 'square', length: 1 ns} - -wobble.length = 8 ns -wobble.amplitude = -1.0 mV - -## The commands themselves: -10 ns -first_square:f1 wobble:f2 - -times bumps { - bump_spacing - - times 2 { - first_square:f1 wobble:f2 - } -} - -settle -acquire - -last_square:f1 (manipulator end_delay manipulator):f2 -end_delay diff --git a/spacq/interface/pulse/tests/resources/02.pulse b/spacq/interface/pulse/tests/resources/02.pulse deleted file mode 100644 index 587be6b..0000000 --- a/spacq/interface/pulse/tests/resources/02.pulse +++ /dev/null @@ -1 +0,0 @@ -x diff --git a/spacq/interface/pulse/tests/resources/non-square b/spacq/interface/pulse/tests/resources/non-square deleted file mode 100644 index fe5f636..0000000 --- a/spacq/interface/pulse/tests/resources/non-square +++ /dev/null @@ -1,3 +0,0 @@ -0.1, 0.5, 0.7, 1.0, 4.2 -4.2, 4.2, 3.6 -9.9 diff --git a/spacq/interface/pulse/tests/test_parser.py b/spacq/interface/pulse/tests/test_parser.py deleted file mode 100644 index b5c44a4..0000000 --- a/spacq/interface/pulse/tests/test_parser.py +++ /dev/null @@ -1,301 +0,0 @@ -from nose.tools import eq_ -from os import path -from unittest import main, TestCase - -from ...units import Quantity -from ..parser import (Acquire, Assignment, Attribute, Block, Declaration, - Delay, Dictionary, DictionaryItem, Loop, ParallelPulses, Pulse, - PulseSequence, Variable) - -from .. import parser - - -resource_dir = path.join(path.dirname(__file__), 'resources') - - -class ParserTest(TestCase): - def testValid(self): - """ - Run down the happy path, line by line. - """ - - prog = [ - # Lone declaration. - ('int test', - Declaration({'type': 'int', 'variables': [ - Variable({'name': 'test'})]})), - # Declaration with some initialization. - ('delay d1=+5ms,d2,\td3 = -7e2 ns2.J', - Declaration({'type': 'delay', 'variables': [ - Assignment({'target': 'd1', 'value': Quantity(5, 'ms')}), - Variable({'name': 'd2'}), - Assignment({'target': 'd3', 'value': Quantity(-7e2, 'ns2.J')})]})), - # Assignment of value. - ('test = -99', - Assignment({'target': 'test', 'value': -99})), - # Assignment of multiple values. - ('long_pulse = {shape: \'filename\', length: 1 Ps}', - Assignment({'target': 'long_pulse', 'value': Dictionary([ - DictionaryItem({'key': 'shape', 'value': 'filename'}), - DictionaryItem({'key': 'length', 'value': Quantity(1, 'Ps')})])})), - # Comments. - (' output f1 ,f2\t # outputs', - Declaration({'type': 'output', 'variables': [ - Variable({'name': 'f1'}), - Variable({'name': 'f2'})]})), - # Delay. - ('10 us', - Delay({'length': Quantity(10, 'us')})), - # Variable delay. - ('d5 #Comment: a = 5', - Delay({'length': 'd5'})), - # Attribute assignment. - ('p1.length = 50.0 as', - Assignment({ - 'target': Attribute({'variable': 'p1', 'name': 'length'}), - 'value': Quantity(50.0, 'as')})), - # Pulse command. - ('some_pulse:f1', - ParallelPulses([ - Pulse({'sequence': PulseSequence(['some_pulse']), 'target': 'f1'})])), - # Parallel pulses. - ('a_pulse:outputA another_pulse:outputB', - ParallelPulses([ - Pulse({'sequence': PulseSequence(['a_pulse']), 'target': 'outputA'}), - Pulse({'sequence': PulseSequence(['another_pulse']), 'target': 'outputB'})])), - # Multiple pulse commands. - ('(several commands with a 99 fs delay):formula1 lone_command:indy500', - ParallelPulses([ - Pulse({'sequence': PulseSequence([ - 'several', 'commands', 'with', 'a', - Delay({'length': Quantity(99, 'fs')}), - 'delay']), 'target': 'formula1'}), - Pulse({'sequence': PulseSequence(['lone_command']), 'target': 'indy500'})])), - # Acquire. - ('\tacquire #', - Acquire()) - ] - - pp = parser.Parser() - - for line, ast in prog: - try: - result = pp(line) - except Exception: - print line - raise - - eq_(result, Block([ast])) - - def testMultiple(self): - """ - Several lines at once. - """ - - prog = """ - # Declare: - int placeholder = 0 - delay settle, end_delay = 10 ns - pulse first_square, wobble - output f1, f2 - - # Configure: - first_square = {shape: 'square', length: 1 ms} - wobble.length = 50 us - wobble.amplitude = -0.5 mV - - # Execute: - 10 us - first_square:f1 (wobble +1e-3 us wobble):f2 - settle # Wait before the acquisition. - acquire - end_delay - """ - - expected = [ - Declaration({'type': 'int', 'variables': [ - Assignment({'target': 'placeholder', 'value': 0})]}), - Declaration({'type': 'delay', 'variables': [ - Variable({'name': 'settle'}), - Assignment({'target': 'end_delay', 'value': Quantity(10, 'ns')})]}), - Declaration({'type': 'pulse', 'variables': [ - Variable({'name': 'first_square'}), - Variable({'name': 'wobble'})]}), - Declaration({'type': 'output', 'variables': [ - Variable({'name': 'f1'}), - Variable({'name': 'f2'})]}), - Assignment({'target': 'first_square', - 'value': Dictionary([ - DictionaryItem({'key': 'shape', 'value': 'square'}), - DictionaryItem({'key': 'length', 'value': Quantity(1, 'ms')})])}), - Assignment({ - 'target': Attribute({'variable': 'wobble', 'name': 'length'}), - 'value': Quantity(50, 'us')}), - Assignment({ - 'target': Attribute({'variable': 'wobble', 'name': 'amplitude'}), - 'value': Quantity(-0.5, 'mV')}), - Delay({'length': Quantity(10, 'us')}), - ParallelPulses([ - Pulse({'sequence': PulseSequence(['first_square']), 'target': 'f1'}), - Pulse({'sequence': PulseSequence([ - 'wobble', - Delay({'length': Quantity(+1e-3, 'us')}), - 'wobble']), 'target': 'f2'})]), - Delay({'length': 'settle'}), - Acquire(), - Delay({'length': 'end_delay'}), - ] - - pp = parser.Parser() - result = pp(prog) - - eq_(result, Block(expected)) - - def testLoop(self): - """ - Repeat! - """ - - prog = """ - times 5 { - d1 - p1:f1 p2:f2 - } - - times M { - d2 - - times N { - p2:f1 p1:f2 - } - } - """ - - expected = [ - Loop({'times': 5, 'block': Block([ - Delay({'length': 'd1'}), - ParallelPulses([ - Pulse({'sequence': PulseSequence(['p1']), 'target': 'f1'}), - Pulse({'sequence': PulseSequence(['p2']), 'target': 'f2'})])])}), - Loop({'times': 'M', 'block': Block([ - Delay({'length': 'd2'}), - Loop({'times': 'N', 'block': Block([ - ParallelPulses([ - Pulse({'sequence': PulseSequence(['p2']), 'target': 'f1'}), - Pulse({'sequence': PulseSequence(['p1']), 'target': 'f2'})])])})])}), - ] - - pp = parser.Parser() - result = pp(prog) - - eq_(result, Block(expected)) - - def testOneLine(self): - """ - Line breaks are optional. - """ - - prog = 'int x=5;delay d1;;d1;d1;times x{d1;d1;};times x{d1}d1' - - expected = [ - Declaration({'type': 'int', 'variables': [ - Assignment({'target': 'x', 'value': 5})]}), - Declaration({'type': 'delay', 'variables': [ - Variable({'name': 'd1'})]}), - Delay({'length': 'd1'}), - Delay({'length': 'd1'}), - Loop({'times': 'x', 'block': Block([ - Delay({'length': 'd1'}), - Delay({'length': 'd1'})])}), - Loop({'times': 'x', 'block': Block([ - Delay({'length': 'd1'})])}), - Delay({'length': 'd1'}), - ] - - pp = parser.Parser() - result = pp(prog) - - eq_(result, Block(expected)) - - def testFromFile(self): - """ - Parse an entire file. - """ - - expected = [ - Declaration({'type': 'int', 'variables': [ - Assignment({'target': 'bumps', 'value': 2})]}), - Declaration({'type': 'delay', 'variables': [ - Assignment({'target': 'bump_spacing', 'value': Quantity(10, 'ns')}), - Variable({'name': 'settle'}), - Assignment({'target': 'end_delay', 'value': Quantity(15, 'ns')})]}), - Declaration({'type': 'pulse', 'variables': [ - Variable({'name': 'first_square'}), - Variable({'name': 'wobble'})]}), - Declaration({'type': 'pulse', 'variables': [ - Assignment({'target': 'last_square', 'value': Dictionary([ - DictionaryItem({'key': 'shape', 'value': 'square'})])})]}), - Declaration({'type': 'pulse', 'variables': [ - Assignment({'target': 'manipulator', 'value': Dictionary([ - DictionaryItem({'key': 'shape', 'value': 'non-square'})])})]}), - Declaration({'type': 'output', 'variables': [ - Variable({'name': 'f1'}), - Variable({'name': 'f2'})]}), - Assignment({'target': 'first_square', 'value': Dictionary([ - DictionaryItem({'key': 'shape', 'value': 'square'}), - DictionaryItem({'key': 'length', 'value': Quantity(1, 'ns')})])}), - Assignment({ - 'target': Attribute({'variable': 'wobble', 'name': 'length'}), - 'value': Quantity(8, 'ns')}), - Assignment({ - 'target': Attribute({'variable': 'wobble', 'name': 'amplitude'}), - 'value': Quantity(-1, 'mV')}), - Delay({'length': Quantity(10, 'ns')}), - ParallelPulses([ - Pulse({'sequence': PulseSequence(['first_square']), 'target': 'f1'}), - Pulse({'sequence': PulseSequence(['wobble']), 'target': 'f2'})]), - Loop({'times': 'bumps', 'block': Block([ - Delay({'length': 'bump_spacing'}), - Loop({'times': 2, 'block': Block([ - ParallelPulses([ - Pulse({'sequence': PulseSequence(['first_square']), 'target': 'f1'}), - Pulse({'sequence': PulseSequence(['wobble']), 'target': 'f2'})])])})])}), - Delay({'length': 'settle'}), - Acquire(), - ParallelPulses([ - Pulse({'sequence': PulseSequence(['last_square']), 'target': 'f1'}), - Pulse({'sequence': PulseSequence(['manipulator', 'end_delay', 'manipulator']), 'target': 'f2'})]), - Delay({'length': 'end_delay'}), - ] - - pp = parser.Parser() - - with open(path.join(resource_dir, '01.pulse')) as f: - result = pp(''.join(f.readlines())) - - eq_(result, Block(expected)) - - def testInvalid(self): - """ - Try the error detection. - """ - - prog = [ - 'int a b', 'int a = b = c', '5:f1', 'times {d1;d2}', 'd1;{d2}d3', - 'a = b', 'c.d = e.f', 'g.h', 'd1 (d2 d3):f1', 'refresh = d1:f5', - 'times 5 V {}', '100 units', - ] - - pp = parser.Parser() - - for line in prog: - try: - pp(line) - except parser.PulseSyntaxError: - pass - else: - assert False, 'Expected ParseException' - - -if __name__ == '__main__': - main() diff --git a/spacq/interface/pulse/tests/test_program.py b/spacq/interface/pulse/tests/test_program.py deleted file mode 100644 index eeef165..0000000 --- a/spacq/interface/pulse/tests/test_program.py +++ /dev/null @@ -1,152 +0,0 @@ -from os import path -from nose.tools import assert_raises, eq_ -from numpy.testing import assert_array_almost_equal, assert_array_equal -from unittest import main, TestCase - -from ...units import Quantity -from ..parser import PulseSyntaxError - -from .. import program - - -resource_dir = path.join(path.dirname(__file__), 'resources') - - -class ProgramTest(TestCase): - missing = [ - (('_acq_marker', 'marker_num'), 1), - (('_acq_marker', 'output'), 'f1'), - (('first_square', 'amplitude'), Quantity(0.5, 'V')), - (('last_square', 'amplitude'), Quantity(-0.5, 'V')), - (('last_square', 'length'), Quantity(5, 'ns')), - (('manipulator', 'amplitude'), Quantity(1, 'V')), - (('manipulator', 'length'), Quantity(12, 'ns')), - (('settle',), Quantity(20, 'ns')), - ] - - def testFromFile(self): - """ - Grab a file and load the program in it. - """ - - p = program.Program.from_file(path.join(resource_dir, '01.pulse')) - - p.set_value(('wobble', 'shape'), 'non-square') - - eq_(p.missing_values, set([name for name, value in self.missing])) - eq_(set(p.variables), set(['bumps', 'bump_spacing', 'settle', 'end_delay', 'first_square', 'wobble', 'last_square', 'manipulator', 'f1', 'f2', '_acq_marker'])) - - def testInvalid(self): - """ - This program isn't syntactically correct. - """ - - assert_raises(PulseSyntaxError, program.Program.from_file, path.join(resource_dir, '02.pulse')) - - def testInvalidShapePath(self): - """ - A shape file which isn't there. - """ - - p = program.Program.from_file(path.join(resource_dir, '01.pulse')) - - for name, value in self.missing: - p.set_value(name, value) - - p.set_value(('wobble', 'shape'), 'this-shape-doesn\'t-exist') - - try: - p.generate_waveforms() - except program.PulseError as e: - eq_('\n'.join(e[0]), """\ -error: File "this-shape-doesn't-exist" (due to "wobble") not found at column 17 on line 21: - first_square:f1 wobble:f2 - ^\ -""") - else: - assert False, 'Expected PulseError' - - def testInvalidShapeFile(self): - """ - A shape file which isn't a shape file. - """ - - p = program.Program.from_file(path.join(resource_dir, '01.pulse')) - - for name, value in self.missing: - p.set_value(name, value) - - assert ('wobble', 'shape') not in p.values - p.set_value(('wobble', 'shape'), '01.pulse') - assert ('wobble', 'shape') in p.values - - assert_raises(ValueError, p.generate_waveforms) - - def testGenerateWaveforms(self): - """ - Finally generate some waveforms. - """ - - p = program.Program.from_file(path.join(resource_dir, '01.pulse')) - - # Missing values. - assert_raises(ValueError, p.generate_waveforms) - - for name, value in self.missing: - p.set_value(name, value) - - p.set_value(('wobble', 'shape'), 'non-square') - - p.frequency = Quantity(1, 'GHz') - eq_(p.frequency, Quantity(1, 'GHz')) - waveforms = p.generate_waveforms() - - eq_(set(waveforms.keys()), set(['f1', 'f2'])) - - f1 = waveforms['f1'] - loop = [0.0] * 10 + [0.5] + [0.0] * 7 + [0.5] + [0.0] * 7 - assert_array_equal(f1.data, [0.0] * 10 + [0.5] * 1 + [0.0] * 7 + loop * 2 + [0.0] * 20 + [-0.5] * 5 + [0.0] * 49) - eq_(f1.markers[1], [False] * 90 + [True] * 54) - - f2 = waveforms['f2'] - f2_gen = p._env.generators['f2'] - non_square = [0.1, 0.5, 0.7, 1.0] + [4.2] * 3 + [3.6, 9.9] - wobble = f2_gen._scale_waveform(non_square, Quantity(-1, 'mV').value, Quantity(8, 'ns')) - manipulator = f2_gen._scale_waveform(non_square, Quantity(1, 'V').value, Quantity(12, 'ns')) - loop = [0.0] * 10 + wobble * 2 - end = manipulator + [0.0] * 15 - assert_array_almost_equal(f2.data, [0.0] * 10 + wobble + loop * 2 + [0.0] * 20 + end * 2, 2) - - def testWaveformsDryRun(self): - """ - Run through waveform generation, but don't actually generate anything. - """ - - p = program.Program.from_file(path.join(resource_dir, '01.pulse')) - - # Missing values. - assert_raises(ValueError, p.generate_waveforms, dry_run=True) - - for name, value in self.missing: - p.set_value(name, value) - - p.set_value(('wobble', 'shape'), 'non-square') - - # Too long. - p.frequency = Quantity(1, 'YHz') - assert_raises(ValueError, p.generate_waveforms, dry_run=True) - - # Just right. - p.frequency = Quantity(1, 'GHz') - waveforms = p.generate_waveforms(dry_run=True) - - # But nothing there. - eq_(set(waveforms.keys()), set(['f1', 'f2'])) - eq_(list(waveforms['f1'].data), []) - eq_(waveforms['f1'].markers, {}) - eq_(list(waveforms['f2'].data), []) - eq_(waveforms['f2'].markers, {}) - - -if __name__ == '__main__': - main() diff --git a/spacq/interface/pulse/tests/test_tree.py b/spacq/interface/pulse/tests/test_tree.py deleted file mode 100644 index a0c5b73..0000000 --- a/spacq/interface/pulse/tests/test_tree.py +++ /dev/null @@ -1,353 +0,0 @@ -from nose.tools import assert_raises, eq_ -from numpy.testing import assert_array_almost_equal -from os import path -from unittest import main, TestCase - -from ...units import Quantity -from ..parser import Parser - -from .. import tree - - -resource_dir = path.join(path.dirname(__file__), 'resources') - - -class ValidTreeTest(TestCase): - prog = Parser()(""" - int repeat = 2 - delay abc1 = 100 ns, def2 - pulse ghi1, jkl2 = {{shape: 'square'}} - output mno1, pqr2 - - ghi1.shape = '{0}' - ghi1.length = 8 ns - ghi1.amplitude = -1.0 mV - - abc1 - (10 ns):pqr2 - - times repeat {{ - def2 - ghi1:mno1 - (ghi1 abc1 10 ns jkl2):mno1 (def2 jkl2 def2):pqr2 - }} - - acquire - - 5 ns - """.format(path.join(resource_dir, 'non-square'))) - - def testDeclarations(self): - env = tree.Environment() - - env.stage = env.stages.declarations - env.traverse_tree(self.prog) - - expected = { - '_acq_marker': 'acq_marker', - 'abc1': 'delay', - 'def2': 'delay', - 'ghi1': 'pulse', - 'jkl2': 'pulse', - 'mno1': 'output', - 'pqr2': 'output', - 'repeat': 'int', - } - - eq_(env.errors, []) - eq_(env.variables, expected) - - def testValues(self): - env = tree.Environment() - - env.stage = env.stages.declarations - env.traverse_tree(self.prog) - - env.stage = env.stages.values - env.traverse_tree(self.prog) - - expected = { - ('abc1',): Quantity('100 ns'), - ('ghi1', 'amplitude'): Quantity(-1, 'mV'), - ('ghi1', 'length'): Quantity(8, 'ns'), - ('ghi1', 'shape'): path.join(resource_dir, 'non-square'), - ('jkl2', 'shape'): 'square', - ('repeat',): 2, - } - - missing = set([('_acq_marker', 'marker_num'), ('_acq_marker', 'output'), ('def2',), - ('jkl2', 'amplitude'), ('jkl2', 'length')]) - - eq_(env.errors, []) - eq_(env.values, expected) - eq_(env.missing_values, missing) - - # Later additions (perhaps from the UI). - updates = { - ('_acq_marker', 'marker_num'): 1, - ('_acq_marker', 'output'): 'mno1', - ('def2',): Quantity('7 ns'), - ('jkl2', 'amplitude'): Quantity(1, 'V'), - ('jkl2', 'length'): Quantity(50, 'ns'), - } - - for name, value in updates.items(): - env.set_value(name, value) - - expected.update(updates) - - eq_(env.errors, []) - eq_(env.values, expected) - - def testCommands(self): - env = tree.Environment() - - env.stage = env.stages.declarations - env.traverse_tree(self.prog) - - env.stage = env.stages.commands - env.traverse_tree(self.prog) - - eq_(env.errors, []) - assert env.acquisition - - def testWaveforms(self): - env = tree.Environment() - - env.stage = env.stages.declarations - env.traverse_tree(self.prog) - - env.stage = env.stages.values - env.traverse_tree(self.prog) - - env.stage = env.stages.commands - env.traverse_tree(self.prog) - - updates = { - ('_acq_marker', 'marker_num'): 5, - ('_acq_marker', 'output'): 'pqr2', - ('def2',): Quantity(7, 'ns'), - ('jkl2', 'amplitude'): Quantity(1, 'V'), - ('jkl2', 'length'): Quantity(50, 'ns'), - } - - for name, value in updates.items(): - env.set_value(name, value) - - env.frequency = Quantity(1, 'GHz') - env.stage = env.stages.waveforms - env.traverse_tree(self.prog) - - eq_(env.errors, []) - - mno1 = env.waveforms['mno1'] - mno1_gen = env.generators['mno1'] - non_square = mno1_gen._scale_waveform([0.1, 0.5, 0.7, 1.0] + [4.2] * 3 + [3.6, 9.9], - Quantity(-1, 'mV').value, Quantity(8, 'ns')) - loop = [0.0] * 7 + non_square * 2 + [0.0] * 110 + [1.0] * 50 + [0.0] - assert_array_almost_equal(mno1.data, [0.0] * 110 + loop * 2 + [0.0] * 5) - - pqr2 = env.waveforms['pqr2'] - loop = [0.0] * 22 + [1.0] * 50 + [0.0] * 112 - assert_array_almost_equal(pqr2.data, [0.0] * 110 + loop * 2 + [0.0] * 5) - - assert 1 not in pqr2.markers - eq_(pqr2.markers[5], [False] * 478 + [True] * 5) - - -class InvalidTreeTest(TestCase): - def testDeclarations(self): - prog = Parser()(""" - int repeat = 2 - delay abc1 = 100 ns, def2 - pulse ghi1, jkl2 = {shape: 'square'} - output mno1, pqr2 - - delay abc1, def2 = 10 ms, stu3 - - vwx4 - - times 5 { - int x = 9 - } - """) - - env = tree.Environment() - - env.stage = env.stages.declarations - env.traverse_tree(prog) - - expected_errors = ['Re-decl'] * 2 + ['Declara'] - - eq_(len(env.errors), len(expected_errors)) - - for error, expected_error in zip(env.errors, expected_errors): - assert error[0].startswith(expected_error), error - - def testValues(self): - prog = Parser()(""" - int repeat = 2 - delay abc1 = 100 ns, def2 - pulse ghi1, jkl2 = {shape: 'square'} - output mno1, pqr2 - - delay abc1 = 50 ms, def2 - mno1 = "test" - def2 = 6 ; def2 = 6 Hz - ghi1 = 0 - ghi1.shape = 50 ms - jkl2.amplitude = 8 - jkl2.length = 1234 A - repeat = 6 s - repeat = 2.0 - zzz1 = 5 ms - zzz1.foo = 5 ms - ghi1.something_else = 5 - - times 5 { - int x = 9 - y = 10 - } - """) - - env = tree.Environment() - - env.stage = env.stages.declarations - env.traverse_tree(prog) - - env.errors = [] - env.stage = env.stages.values - env.traverse_tree(prog) - - expected_errors = (['Re-assi', 'Cannot a'] + ['Must assi'] * 8 + ['Undecla', 'Unrec'] * 2 + - ['Assig'] + ['Undecl']) - - eq_(len(env.errors), len(expected_errors)) - - for error, expected_error in zip(env.errors, expected_errors): - assert error[0].startswith(expected_error), (error, expected_error) - - assert_raises(KeyError, env.set_value, ('xyz',), 'zyx') - - def testCommands(self): - prog = Parser()(""" - int repeat = 2 - delay abc1 = 100 ns, def2 - pulse ghi1, jkl2 = {shape: 'square'} - output mno1, pqr2 - - abc1 - (10 ns):pqr2 - - times repeat { - def2 - ghi1:mno1 - (ghi1 abc1 10 ns jkl2):mno1 (10 V def2 jkl2 def2 pqr2):pqr2 - - acquire - mno1 - } - - acquire - - times ghi1 {} - - acquire - - 5 ns - 1 A - """) - env = tree.Environment() - - env.stage = env.stages.declarations - env.traverse_tree(prog) - - env.errors = [] - env.stage = env.stages.commands - env.traverse_tree(prog) - - expected_errors = ['Delay mu', 'Invali', 'Not a d', 'Repeate', 'Repeti', 'Repeate', 'Delay mu'] - - eq_(len(env.errors), len(expected_errors)) - - for error, expected_error in zip(env.errors, expected_errors): - assert error[0].startswith(expected_error), (error, expected_error) - - def testWaveforms(self): - prog = Parser()(""" - delay d1 - - d1 - """) - env = tree.Environment() - - env.stage = env.stages.declarations - env.traverse_tree(prog) - - env.stage = env.stages.values - env.traverse_tree(prog) - - env.stage = env.stages.commands - env.traverse_tree(prog) - - env.errors = [] - env.stage = env.stages.waveforms - - assert_raises(ValueError, env.traverse_tree, prog) - - -class DrawThingTest(TestCase): - def testDrawTree(self): - """ - Try our hand at drawing. - """ - - prog = Parser()(""" - pulse p1 = {shape: 'something'} - output f1 - - p1.length = 5 ns - - times 5 { - 10 ns - p1:f1 - } - - acquire - """) - - drawing = prog.draw() - - eq_(drawing, """\ -Block - Declaration - pulse - Assignment - p1 - Dictionary - 'shape': 'something' - Declaration - output - Variable - f1 - Assignment - Attribute - p1 - length - 5 ns - Loop - 5 - Block - Delay - 10 ns - ParallelPulses - Pulse - PulseSequence - p1 - f1 - Acquire -""") - - -if __name__ == '__main__': - main() diff --git a/spacq/interface/pulse/tool/__init__.py b/spacq/interface/pulse/tool/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/interface/pulse/tool/box.py b/spacq/interface/pulse/tool/box.py deleted file mode 100644 index 83d2dc4..0000000 --- a/spacq/interface/pulse/tool/box.py +++ /dev/null @@ -1,65 +0,0 @@ -import csv - -from spacq.tool.box import flatten - -""" -Pulse program helpers. -""" - - -def find_location(s, loc): - """ - Find where loc (linear index) in s. - - Returns: - line number - column number - line itself - - Note: Tabs are not handled specially. - """ - - if loc < 0 or loc > len(s): - raise ValueError('Location given ({0}) is outside string'.format(loc)) - - count = 0 - lines = s.splitlines() - with_ends = s.splitlines(True) - - for i, (line, with_end) in enumerate(zip(lines, with_ends), 1): - if count + len(line) < loc: - count += len(with_end) - else: - col = loc - count + 1 - - return i, col, line - - -def format_error(msg, row=None, col=None, line=None): - """ - Format the error for human consumption. - """ - - if row is None or col is None or line is None: - return 'error: {0}'.format(msg) - else: - return 'error: {0} at column {1} on line {2}:\n{3}{4}\n{5}^'.format(msg, - col, row, ' ' * 2, line, ' ' * (col + 1)) - - -def load_values(f): - """ - Load data points from a file. - - The values in the file must either be comma separated, line-wise, or a combination of the two. - For example: - 1.0,2.0,3.0 - 4.0,5.0 - 6.0 - would be interpreted as [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - """ - - reader = csv.reader(f) - - # Ignore blank lines. - return [float(x) for x in flatten(reader) if not x.isspace()] diff --git a/spacq/interface/pulse/tool/tests/__init__.py b/spacq/interface/pulse/tool/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/interface/pulse/tool/tests/test_box.py b/spacq/interface/pulse/tool/tests/test_box.py deleted file mode 100644 index f522fae..0000000 --- a/spacq/interface/pulse/tool/tests/test_box.py +++ /dev/null @@ -1,98 +0,0 @@ -from nose.tools import assert_raises, eq_ -from StringIO import StringIO -from unittest import main, TestCase - -from .. import box - - -class FindLocationTest(TestCase): - multiline = [ - '', - 'This is some text.', - '', - 'It has blank lines, and no tabs.\r', - 'It also has Windows-style line endings.', - 'Sometimes.' - ] - - def testOneLine(self): - s = 'Test string. Do not read.' - - data = [ - (0, 1, 1), - (5, 1, 6), - (25, 1, 26), - ] - - for loc, row, col in data: - eq_(box.find_location(s, loc), (row, col, s)) - - def testMultiline(self): - s = '\n'.join(self.multiline) - - data = [ - (0, 1, 1), - (40, 4, 20), - (105, 6, 11), - ] - - for loc, row, col in data: - eq_(box.find_location(s, loc), (row, col, self.multiline[row - 1].rstrip())) - - def testInvalid(self): - s = '\n'.join(self.multiline) - - data = [-10, -1, 106] - - for loc in data: - assert_raises(ValueError, box.find_location, s, loc) - - -class FormatErrorTest(TestCase): - def testWithoutPosition(self): - data = ['', 'test', 'This is a message!'] - - for msg in data: - eq_(box.format_error(msg), 'error: ' + msg) - - def testWithPosition(self): - result = box.format_error('Test', 1, 2, 'line') - - eq_(result, 'error: Test at column 2 on line 1:\n line\n ^') - - -class LoadValuesTest(TestCase): - def testEmpty(self): - f = StringIO() - result = box.load_values(f) - - eq_(result, []) - - def testOneLine(self): - f = StringIO('1.0, 2.0, 3.0,4.0') - result = box.load_values(f) - - eq_(result, [1.0, 2.0, 3.0, 4.0]) - - def testOneColumn(self): - f = StringIO('1.0\n2.0\n3.0\n4.0') - result = box.load_values(f) - - eq_(result, [1.0, 2.0, 3.0, 4.0]) - - def testShaped(self): - f = StringIO(""" - 1.0 ,2.0,3.0 - - 4.0, 5.0 - 6.0 - - """) - - result = box.load_values(f) - - eq_(result, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) - - -if __name__ == '__main__': - main() diff --git a/spacq/interface/pulse/tree.py b/spacq/interface/pulse/tree.py deleted file mode 100644 index c898dee..0000000 --- a/spacq/interface/pulse/tree.py +++ /dev/null @@ -1,602 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from os import path - -from spacq.tool.box import Enum - -from ..units import IncompatibleDimensions, Quantity -from ..waveform import Generator -from .tool.box import find_location, format_error, load_values - -""" -Abstract syntax tree bits for pulse programs. -""" - - -def draw_thing(thing, depth): - """ - Draws something at a given depth. - """ - - if isinstance(thing, ASTNode): - if thing.is_list: - result = ('{0}\n'.format(thing.__class__.__name__) + - ''.join(draw_thing(item, depth + 1) for item in thing.items)) - else: - result = thing.draw(depth) - else: - result = str(thing) + '\n' - - return ' ' * depth + result - - -class Environment(object): - """ - An AST-traversal environment. - """ - - # Traversal stages: - # declarations: collect variable names and types - # values: collect variable values - # commands: verify commands for outputs - # waveforms: generate waveforms - stages = Enum(['declarations', 'values', 'commands', 'waveforms']) - - # The stages that must happen (in this order) before waveform generation. - # These only require the data which is contained in the pulse program itself. - prep_stages = [stages.declarations, stages.values, stages.commands] - - def __init__(self): - self.stage = None - self.stack = [] - - # Declared variable names (with types as values). - self.variables = {} - - # Variable and attribute values. - # Keys are tuples of strings: - # ('x',): x is a variable - # ('x', 'y'): y is an attribute - self.values = {} - - # Whether acquisition has already been requested. - self.acquisition = False - - # Any errors; each error is of the form - # (error string, (row, col, code line)) - self.errors = [] - - # Whether to actually generate waveforms. - self.dry_run = False - - # Waveform generators for the output channels. - # Keys are output names. - self.generators = {} - - # Generated waveforms. - self.waveforms = {} - - # Where to look for shapes. - self.cwd = None - - # Shapes that could not be found. - self.missing_shapes = set() - - # Default frequency. - self.frequency = Quantity(1, 'Hz') - - @property - def missing_values(self): - existing_values = set(self.values.keys()) - - return self.all_values - existing_values - - def add_error(self, msg, loc=None): - """ - Add an error. - """ - - self.errors.append((msg, loc)) - - def pre_stage(self): - """ - Set up for a stage. - """ - - if self.stage == self.stages.waveforms: - if self.missing_values: - values = ', '.join('.'.join(x) for x in sorted(self.missing_values)) - - raise ValueError('Cannot generate waveforms while values are missing: {0}'.format(values)) - - # Set up output waveform generators. - for output in self.waveforms: - self.generators[output] = Generator(frequency=self.frequency, dry_run=self.dry_run) - - def post_stage(self): - """ - Clean up after a stage has completed. - """ - - if self.stage == self.stages.declarations: - # Prepare for output waveform generators. - for output in [var for var, type in self.variables.items() if type == 'output']: - self.generators[output] = None - self.waveforms[output] = None - - # Generate labels for all necessary values. - self.all_values = set() - for name, type in self.variables.items(): - if type == 'pulse': - for attr in ['amplitude', 'length', 'shape']: - self.all_values.add((name, attr)) - elif type == 'acq_marker': - for attr in ['marker_num', 'output']: - self.all_values.add((name, attr)) - elif type != 'output': - self.all_values.add((name,)) - elif self.stage == self.stages.waveforms: - # Finalize waveform creation. - for output in self.generators: - self.waveforms[output] = self.generators[output].waveform - - def set_value(self, target, value): - """ - Set a value if the types work out. TypeError otherwise. - """ - - # Type checking. - var_type = self.variables[target[0]] - if var_type == 'acq_marker': - if len(target) == 1: - raise TypeError('Must assign dictionary to acq_marker') - else: - if target[1] == 'marker_num': - if not isinstance(value, int): - raise TypeError('Must assign int to acq_marker num') - elif target[1] == 'output': - if not isinstance(value, basestring): - raise TypeError('Must assign string to acq_marker num') - elif var_type == 'delay': - try: - value.assert_dimensions('s') - except (AttributeError, IncompatibleDimensions): - raise TypeError('Must assign time quantity to delay') - elif var_type == 'int': - if not isinstance(value, int): - raise TypeError('Must assign integer to int') - elif var_type == 'pulse': - if len(target) == 1: - raise TypeError('Must assign dictionary to pulse') - else: - if target[1] == 'amplitude': - try: - value.assert_dimensions('V') - except (AttributeError, IncompatibleDimensions): - raise TypeError('Must assign voltage quantity to pulse amplitude') - elif target[1] == 'length': - try: - value.assert_dimensions('s') - except (AttributeError, IncompatibleDimensions): - raise TypeError('Must assign time quantity to pulse length') - elif target[1] == 'shape': - if not isinstance(value, basestring): - raise TypeError('Must assign string to pulse shape') - else: - raise TypeError('Cannot assign to variable of type "{0}"'.format(var_type)) - - if target in self.values and self.values[target] is not None: - raise TypeError('Re-assignment of {0}'.format(target)) - else: - self.values[target] = value - - def traverse_tree(self, root): - """ - Modify the Environment, given a pulse program AST. - """ - - self.pre_stage() - root.visit(self) - self.post_stage() - - def format_errors(self): - result = [] - - # Sort by line number, then column number, and ignore duplicates. - for error in sorted(set(self.errors), - key=(lambda x: (x[1][0], x[1][1]) if x[1] is not None else (-1, -1))): - if error[1] is not None: - result.append(format_error(error[0], *error[1])) - else: - result.append(format_error(error[0])) - - return result - - -class ASTNode(object): - names = [] - is_list = False - - def __init__(self, *args): - log.debug('Creating node of type "{0}".'.format(self.__class__.__name__)) - - if len(args) == 3: - self.s = args[0] - self.loc = args[1] - - tok = args[2] - else: - self.s = None - self.loc = None - - if len(args) == 1: - tok = args[0] - else: - tok = None - - log.debug('Received tokens: {0!r}'.format(tok)) - - if self.is_list: - self.items = list(tok) - else: - for name in self.names: - setattr(self, name, tok[name]) - - def __eq__(self, other): - return repr(self) == repr(other) - - def draw(self, depth): - return ' ' * depth + self.__class__.__name__ + '\n' - - @property - def location(self): - return find_location(self.s, self.loc) - - -class Acquire(ASTNode): - def __repr__(self): - return 'acquire' - - def visit(self, env): - if env.stage == env.stages.declarations: - if len(env.stack) != 1: - env.add_error('Acquisition not at top level', self.location) - - env.variables['_acq_marker'] = 'acq_marker' - if env.stage == env.stages.commands: - if env.acquisition: - env.add_error('Repeated acquisition', self.location) - else: - env.acquisition = True - elif env.stage == env.stages.waveforms: - acq_marker = env.values[('_acq_marker', 'marker_num')] - acq_output = env.values[('_acq_marker', 'output')] - - env.generators[acq_output].marker(acq_marker, True) - - -class Assignment(ASTNode): - names = ['target', 'value'] - - def __repr__(self): - return '{0!r} = {1!r}'.format(self.target, self.value) - - def draw(self, depth): - return 'Assignment\n' + draw_thing(self.target, depth + 1) + draw_thing(self.value, depth + 1) - - def assign_value(self, env, target, value): - try: - env.set_value(target, value) - except KeyError as e: - env.add_error('Undeclared variable "{0}"'.format(e), self.location) - except TypeError as e: - env.add_error(str(e), self.location) - - def visit(self, env): - if env.stage == env.stages.declarations: - if isinstance(self.target, ASTNode): - return self.target.visit(env) - else: - return (self.target,) - elif env.stage == env.stages.values: - if len(env.stack) != 1 and env.stack[-1].__class__ != Declaration: - env.add_error('Assignment neither at top level nor inside declaration', self.location) - - if isinstance(self.target, ASTNode): - env.stack.append(self) - target = self.target.visit(env) - env.stack.pop() - else: - target = (self.target,) - - if target[0] not in env.variables: - env.add_error('Undeclared variable "{0}"'.format(target[0]), self.location) - else: - if isinstance(self.value, Dictionary): - for k, v in self.value.visit(env).items(): - self.assign_value(env, target + (k,), v) - else: - self.assign_value(env, target, self.value) - - -class Attribute(ASTNode): - names = ['variable', 'name'] - - def __repr__(self): - return '{0!r}.{1!r}'.format(self.variable, self.name) - - def draw(self, depth): - return 'Attribute\n' + draw_thing(self.variable, depth + 1) + draw_thing(self.name, depth + 1) - - def visit(self, env): - result = (self.variable, self.name) - - if env.stage == env.stages.values: - if result not in env.all_values: - env.add_error('Unrecognized attribute "{0}"'.format('.'.join(result)), self.location) - - return result - - -class Block(ASTNode): - is_list = True - - def __repr__(self): - return '{{{0}}}'.format(', '.join(repr(item) for item in self.items)) - - def draw(self, depth=0): - # A default of 0 allows this to be called on the AST as a whole. - - return draw_thing(self, depth) - - def visit(self, env): - env.stack.append(self) - for item in self.items: - item.visit(env) - env.stack.pop() - - -class Declaration(ASTNode): - names = ['type', 'variables'] - - def __repr__(self): - return '{0!r} {1}'.format(self.type, ', '.join(repr(variable) for variable in self.variables)) - - def draw(self, depth): - result = ['Declaration\n' + draw_thing(self.type, depth + 1)] - - for variable in self.variables: - result.append(draw_thing(variable, depth + 1)) - - return ''.join(result) - - def visit(self, env): - if env.stage == env.stages.declarations: - if len(env.stack) != 1: - env.add_error('Declaration not at top level', self.location) - - for variable in self.variables: - env.stack.append(self) - visited = variable.visit(env) - env.stack.pop() - - if env.stage == env.stages.declarations: - new_var_name = visited[0] - - if new_var_name in env.variables: - env.add_error('Re-declaration of "{0}"'.format(new_var_name), variable.location) - else: - env.variables[new_var_name] = self.type - else: - for variable in self.variables: - env.stack.append(self) - visited = variable.visit(env) - env.stack.pop() - - -class Delay(ASTNode): - names = ['length'] - - def __repr__(self): - return '{0!r}'.format(self.length) - - def draw(self, depth): - return 'Delay\n' + draw_thing(self.length, depth + 1) - - def visit(self, env): - if env.stage == env.stages.commands: - if isinstance(self.length, basestring): - try: - if env.variables[self.length] != 'delay': - env.add_error('Not a delay', self.location) - except KeyError: - env.add_error('Undeclared variable "{0}"'.format(self.length), self.location) - else: - if not self.length.assert_dimensions('s', exception=False): - env.add_error('Delay must be a time value', self.location) - if env.stage == env.stages.waveforms: - if isinstance(self.length, basestring): - length = env.values[(self.length,)] - else: - length = self.length - - for waveform in env.generators.values(): - waveform.set_next(0.0) - waveform.delay(length) - - -class Dictionary(ASTNode): - is_list = True - - def __repr__(self): - return '{{{0}}}'.format(', '.join(repr(item) for item in self.items)) - - def visit(self, env): - return dict(x.visit(env) for x in self.items) - - -class DictionaryItem(ASTNode): - names = ['key', 'value'] - - def __repr__(self): - return '{0!r}: {1!r}'.format(self.key, self.value) - - def draw(self, depth): - return repr(self) + '\n' - - def visit(self, env): - return (self.key, self.value) - - -class Loop(ASTNode): - names = ['times', 'block'] - - def __repr__(self): - return 'times {0!r} {1!r}'.format(self.times, self.block) - - def draw(self, depth): - return 'Loop\n' + draw_thing(self.times, depth + 1) + draw_thing(self.block, depth + 1) - - def visit(self, env): - if env.stage == env.stages.commands: - if isinstance(self.times, basestring): - try: - if env.variables[self.times] != 'int': - env.add_error('Repetition count must be int', self.location) - except KeyError: - env.add_error('Undeclared variable "{0}"'.format(self.times), self.location) - - if env.stage == env.stages.waveforms: - if isinstance(self.times, basestring): - times = env.values[(self.times,)] - else: - times = self.times - - for _ in xrange(times): - env.stack.append(self) - self.block.visit(env) - env.stack.pop() - else: - env.stack.append(self) - self.block.visit(env) - env.stack.pop() - - -class ParallelPulses(ASTNode): - is_list = True - - def __repr__(self): - return '{0}'.format(' '.join(repr(item) for item in self.items)) - - def visit(self, env): - env.stack.append(self) - for item in self.items: - item.visit(env) - env.stack.pop() - - if env.stage == env.stages.waveforms: - max_length = max(len(waveform._wave) for waveform in env.generators.values()) - - for waveform in env.generators.values(): - if len(waveform._wave) < max_length: - waveform.append([0.0] * (max_length - len(waveform._wave))) - - -class Pulse(ASTNode): - names = ['sequence', 'target'] - - def __repr__(self): - return '{0!r}:{1!r}'.format(self.sequence, self.target) - - def draw(self, depth): - return 'Pulse\n' + draw_thing(self.sequence, depth + 1) + draw_thing(self.target, depth + 1) - - def visit(self, env): - if env.stage == env.stages.commands: - if self.target not in env.generators: - env.add_error('Undefined output "{0}"'.format(self.target), self.location) - - env.stack.append(self) - self.sequence.visit(env) - env.stack.pop() - - -class PulseSequence(ASTNode): - is_list = True - - def __repr__(self): - return '({0})'.format(', '.join(repr(item) for item in self.items)) - - def visit(self, env): - if env.stage == env.stages.commands: - for item in self.items: - if isinstance(item, Delay): - if not item.length.assert_dimensions('s', exception=False): - env.add_error('Delay must be a time value', self.location) - - continue - - try: - type = env.variables[item] - except KeyError: - env.add_error('Undeclared variable "{0}"'.format(item), self.location) - else: - if type not in ['delay', 'pulse']: - env.add_error('Invalid command "{0}"'.format(item), self.location) - elif env.stage == env.stages.waveforms: - target = env.generators[env.stack[-1].target] - - for item in self.items: - if isinstance(item, basestring): - type = env.variables[item] - - if type == 'delay': - target.set_next(0.0) - target.delay(env.values[(item,)]) - elif type == 'pulse': - amplitude = float(env.values[(item, 'amplitude')].value) - length = env.values[(item, 'length')] - shape = env.values[(item, 'shape')] - - if shape not in env.missing_shapes: - if shape == 'square': - target.square(amplitude, length) - else: - # Figure out all the locations where the file can be. - paths = [shape] - if env.cwd is not None: - paths.append(path.join(env.cwd, shape)) - - data = None - for p in paths: - try: - with open(p) as f: - data = load_values(f) - except IOError: - continue - except ValueError: - raise ValueError('Not a shape file: {0}'.format(p)) - - if data is None: - env.add_error('File "{0}" (due to "{1}") not found'.format(shape, item), - self.location) - env.missing_shapes.add(shape) - else: - target.pulse(data, amplitude, length) - else: - target.set_next(0.0) - target.delay(item.length) - - -class Variable(ASTNode): - names = ['name'] - - def __repr__(self): - return '{0!r}'.format(self.name) - - def draw(self, depth): - return 'Variable\n' + draw_thing(self.name, depth + 1) - - def visit(self, env): - return (self.name,) diff --git a/spacq/interface/resources.py b/spacq/interface/resources.py deleted file mode 100755 index 9798430..0000000 --- a/spacq/interface/resources.py +++ /dev/null @@ -1,315 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from copy import copy -from numpy import linspace -from threading import Thread -import time - -from .units import IncompatibleDimensions, Quantity - -from spacq.tool.box import Without - -""" -Tools for working with generic resources. -""" - - -class NotReadable(Exception): - """ - Resource cannot be read from. - """ - - pass - - -class NotWritable(Exception): - """ - Resource cannot be written to. - """ - - pass - - -class Resource(object): - """ - A generic resource which can potentially be read from or written to. - """ - - def __init__(self, obj=None, getter=None, setter=None, converter=None, allowed_values=None): - """ - obj: The device to which the resource belongs. - getter: The method used to get the value. - setter: The method used to set the value. - converter: A function which returns a valid value for the resource, given a string. - allowed_values: A set of all values which are valid for the resource. - - The getter and setter can be actual methods, or just attribute/property name strings. - """ - - if getter is not None and not callable(getter) and obj is None: - raise ValueError('Cannot call getter with no object.') - - if setter is not None and not callable(setter) and obj is None: - raise ValueError('Cannot call setter with no object.') - - self.obj = obj - self.getter = getter - self.setter = setter - self.converter = converter - if allowed_values is not None: - self.allowed_values = set(allowed_values) - else: - self.allowed_values = None - - self.wrappers = [] - - # Dimensions (specified as unit symbol strings) which values for this resource must match. - self._units = None - # String used for display purposes. Typically the same as self.units. - self.display_units = None - - # Resources marked slow should not be fetched implicitly. - self.slow = False - - @property - def units(self): - return self._units - - @units.setter - def units(self, value): - self._units = value - - self.display_units = self.units - - def verify_dimensions(self, value, exception=True, from_string=False): - """ - Ensure that the type and dimensions of the value are as expected. - - If from_string is True, the value is expected to be a unit symbol string. - """ - - if from_string: - value = Quantity(1, value) - - try: - if self.units is not None: - try: - value.assert_dimensions(self.units) - except AttributeError: - raise TypeError('Expected a Quantity, not "{0}"'.format(value)) - except IncompatibleDimensions: - raise TypeError('Expected dimensions matching "{0}", not "{1}"'.format(self.units, value)) - else: - if isinstance(value, Quantity): - raise TypeError('Unexpected Quantity "{0}"'.format(value)) - except Exception: - if exception: - raise - else: - return False - - return True - - @property - def value(self): - """ - The value of the resource. - """ - - if self.getter is None: - raise NotReadable('Resource not readable.') - - if callable(self.getter): - result = self.getter() - elif self.obj is not None: - result = getattr(self.obj, self.getter) - else: - raise NotReadable('Cannot read from resource.') - - self.verify_dimensions(result) - - # Apply the wrappers. - for _, getter_filter, _ in self.wrappers: - if getter_filter is None: - continue - - result = getter_filter(result) - self.verify_dimensions(result) - - return result - - @value.setter - def value(self, v): - if self.setter is None: - raise NotWritable('Resource not writable.') - - self.verify_dimensions(v) - - # Apply the wrappers. - for _, _, setter_filter in self.wrappers: - if setter_filter is None: - continue - - v = setter_filter(v) - self.verify_dimensions(v) - - if self.allowed_values is not None and v not in self.allowed_values: - raise ValueError('Given disallowed value: {0}. Allowed values are: {1}'.format(v,self.allowed_values)) - - if callable(self.setter): - self.setter(v) - elif self.obj is not None: - setattr(self.obj, self.setter, v) - else: - raise NotWritable('Cannot write to resource.') - - def convert(self, value): - """ - Either use the specified converter, treat as a quantity, or do nothing. - """ - - if self.converter is not None: - return self.converter(value) - elif self.units is not None: - q = Quantity(value) - q.assert_dimensions(self.units) - - return q - else: - return value - - @property - def readable(self): - return self.getter is not None - - @property - def writable(self): - return self.setter is not None - - def _find_wrapper_by_name(self, name): - """ - Return the last index of the given wrapper. - """ - - for i, (n, _, _) in reversed(list(enumerate(self.wrappers))): - if n == name: - return i - - raise ValueError('Wrapper not found: {0}'.format(name)) - - def is_wrapped_by(self, name): - """ - Determine whether the given wrapper already wraps this Resource. - """ - - try: - self._find_wrapper_by_name(name) - except ValueError: - return False - else: - return True - - def wrapped(self, name, getter_filter=None, setter_filter=None): - """ - Produce a Resource which is a wrapper around this Resource. - - name: The name of the wrapper to add. - getter_filter: Function of one argument through which to pass any obtained values. - setter_filter: Function of one argument through which to pass values when setting. - """ - - result = copy(self) - - result.wrappers = self.wrappers + [(name, getter_filter, setter_filter)] - - return result - - def unwrapped(self, name): - """ - Produce a Resource with the last instance of the given wrapper removed. - - name: The name of the wrapper to remove. - """ - - result = copy(self) - - idx = self._find_wrapper_by_name(name) - result.wrappers = self.wrappers[:idx] + self.wrappers[idx+1:] - - return result - - def sweep(self, value_from, value_to, steps, delay=0.1, exception_callback=None): - """ - Sweep the Resource slowly over a linear space. - """ - - # Check for dimension mismatches. - if isinstance(value_from, Quantity) and not isinstance(value_to, Quantity) and value_to == 0: - value_to = Quantity(0, value_from.original_units) - elif isinstance(value_to, Quantity) and not isinstance(value_from, Quantity) and value_from == 0: - value_from = Quantity(0, value_to.original_units) - elif isinstance(value_from, Quantity) and isinstance(value_to, Quantity): - value_from.assert_dimensions(value_to) - - for value in linspace(value_from, value_to, steps): - try: - self.value = value - except Exception as e: - if exception_callback is not None: - exception_callback(e) - return - - time.sleep(delay) - - -class AcquisitionThread(Thread): - """ - Once every delay, call the callback with a fresh value from the resource. - - An optional running lock can block execution until it is released elsewhere. - """ - - def __init__(self, delay, callback, resource=None, running_lock=None): - Thread.__init__(self) - - delay.assert_dimensions('s') - - self.resource = resource - self.delay = delay - self.callback = callback - if running_lock is None: - self.running_lock = Without() - else: - self.running_lock = running_lock - - # Allow the thread to be stopped prematurely. - self.done = False - - def run(self): - while not self.done: - # If something goes wrong, sleep the maximum. - delay = self.delay.value - - with self.running_lock: - if time: - next_run = time.time() + self.delay.value - else: - # Weird things happen at shutdown. - return - - if self.resource is not None: - try: - value = self.resource.value - except Exception as e: - log.error('Could not obtain resource value: {0!r}'.format(e)) - else: - self.callback(value) - - if time: - delay = next_run - time.time() - else: - return - - if not self.done and delay > 0: - time.sleep(delay) diff --git a/spacq/interface/tests/__init__.py b/spacq/interface/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/interface/tests/test_list_columns.py b/spacq/interface/tests/test_list_columns.py deleted file mode 100644 index fe57ee0..0000000 --- a/spacq/interface/tests/test_list_columns.py +++ /dev/null @@ -1,48 +0,0 @@ -from nose.tools import assert_raises, eq_ -from unittest import main, TestCase - -from .. import list_columns - - -class ListParserTest(TestCase): - def testSmall(self): - """ - Try a small list. - """ - - lp = list_columns.ListParser() - - eq_(lp('[(1.2, 3.4)]'), [(1.2, 3.4)]) - - def testLarger(self): - """ - Try a larger (Python-generated) list. - """ - - lst = [(float(x) / 3, float(y) / 5) for x in xrange(55) for y in xrange(25)] - - lp = list_columns.ListParser() - - eq_(lp(str(lst)), lst) - - def testInvalid(self): - """ - These aren't lists! - """ - - lsts = [ - '', - '[]', - '(1,2),(3,4)', - '[(1,2),', - '[(1,2,3),(4,5,6)]', - ] - - lp = list_columns.ListParser() - - for lst in lsts: - assert_raises(ValueError, lp, lst) - - -if __name__ == '__main__': - main() diff --git a/spacq/interface/tests/test_resources.py b/spacq/interface/tests/test_resources.py deleted file mode 100644 index 3ad7009..0000000 --- a/spacq/interface/tests/test_resources.py +++ /dev/null @@ -1,457 +0,0 @@ -from nose.tools import eq_ -from numpy import linspace -from threading import Lock -import time -from unittest import main, TestCase - -from spacq.tests.tool.box import AssertHandler - -from ..units import Quantity - -from .. import resources - - -class WithAttribute(object): - x = 5 - - -class WithMethods(object): - _x = 5 - - def get_x(self): - return self._x - - def set_x(self, value): - self._x = value - - -class WithProperty(object): - _x = 5 - - @property - def x(self): - return self._x - - @x.setter - def x(self, value): - self._x = value - - -class ResourceTest(TestCase): - def testUseless(self): - """ - A resource with neither a getter nor a setter. - """ - - res = resources.Resource() - - try: - res.value - except resources.NotReadable: - pass - else: - assert False, 'Expected NotReadable.' - - assert not res.readable - - try: - res.value = 5 - except resources.NotWritable: - pass - else: - assert False, 'Expected NotWritable.' - - assert not res.writable - - def testMoreThanUseless(self): - """ - A resource with a lookup getter or setter, but no object. - """ - - # Broken from the start. - try: - res = resources.Resource(getter='x') - except: - pass - else: - assert False, 'Expected ValueError.' - - try: - res = resources.Resource(setter='x') - except: - pass - else: - assert False, 'Expected ValueError.' - - # Broken later on. - res = resources.Resource() - res.getter = 'x' - res.setter = 'x' - - try: - res.value - except resources.NotReadable: - pass - else: - assert False, 'Expected NotReadable.' - - try: - res.value = 5 - except resources.NotWritable: - pass - else: - assert False, 'Expected NotWritable.' - - def testReadonly(self): - """ - A read-only resource using an attribute. - """ - - dev = WithAttribute() - res = resources.Resource(dev, 'x') - - eq_(res.value, 5) - - try: - res.value = 55 - except resources.NotWritable: - pass - else: - assert False, 'Expected NotWritable.' - - eq_(res.value, 5) - - def testWriteonly(self): - """ - A write-only resource using an attribute. - """ - - dev = WithAttribute() - res = resources.Resource(dev, None, 'x') - - eq_(dev.x, 5) - - try: - res.value - except resources.NotReadable: - pass - else: - assert False, 'Expected NotReadable.' - - res.value = 55 - - eq_(dev.x, 55) - - def testWithMethods(self): - """ - A read-write resource using methods. - """ - - dev = WithMethods() - res = resources.Resource(None, dev.get_x, dev.set_x) - - eq_(res.value, 5) - - res.value = 1234 - - eq_(res.value, 1234) - - def testWithProperty(self): - """ - A read-write resource using a property. - """ - - dev = WithProperty() - res = resources.Resource(dev, 'x', 'x') - - eq_(res.value, 5) - - res.value = 5678 - - eq_(res.value, 5678) - - def testConverter(self): - """ - Conversion with and without a converter. - """ - - dev = WithAttribute() - res1 = resources.Resource(dev) - res2 = resources.Resource(dev, converter=int) - res3 = resources.Resource(dev, converter=float) - - eq_(res1.convert('5'), '5') - eq_(res2.convert('5') / 2, 2) - eq_(res3.convert('5') / 2, 2.5) - - def testAllowedValues(self): - """ - Verify that only allowed values are allowed. - """ - - allowed = set([5, 10, 15]) - - dev = WithAttribute() - res = resources.Resource(dev, 'x', 'x', allowed_values=allowed) - - for value in allowed: - res.value = value - eq_(res.value, value) - - try: - res.value = -5 - except ValueError: - pass - else: - assert False, 'Expected ValueError' - - eq_(res.allowed_values, allowed) - - def testWrapping(self): - """ - Wrap resources in other resources, and then undo. - """ - - dev = WithAttribute() - res1 = resources.Resource(dev, 'x', 'x') - - res1.value = 5 - eq_(res1.value, 5) - - # Wrap once. - res2 = res1.wrapped('wrapper1', setter_filter=lambda x: 3 * x) - - eq_(res2.value, 5) - res2.value = 5 - eq_(res1.value, 15) - eq_(res2.value, 15) - - # Wrap again. - res3 = res2.wrapped('wrapper2', lambda x: 5 * x) - - eq_(res3.value, 75) - res3.value = 10 - eq_(res1.value, 30) - eq_(res2.value, 30) - eq_(res3.value, 150) - - # Unwrap. - res4 = res3.unwrapped('wrapper1') - - eq_(res4.value, 150) - res4.value = 7 - eq_(res1.value, 7) - eq_(res2.value, 7) - eq_(res3.value, 35) - eq_(res4.value, 35) - - # Modify the original Resource. - res1.value = 1 - eq_(res1.value, 1) - eq_(res2.value, 1) - eq_(res3.value, 5) - eq_(res4.value, 5) - - assert not res1.is_wrapped_by('wrapper1') - assert not res1.is_wrapped_by('wrapper2') - assert res2.is_wrapped_by('wrapper1') - assert not res2.is_wrapped_by('wrapper2') - assert res3.is_wrapped_by('wrapper1') - assert res3.is_wrapped_by('wrapper2') - assert not res4.is_wrapped_by('wrapper1') - assert res4.is_wrapped_by('wrapper2') - - def testSweep(self): - """ - Ramp up and down. - """ - - buf = None - def setter(value): - if abs(value) > 10: - raise ValueError(value) - - buf.append(value) - - exceptions = None - def exception_callback(e): - exceptions.append(tuple(e)) - - res = resources.Resource(setter=setter) - - # Check the values. - buf = [] - res.sweep(1.0, 5.0, 5) - eq_(buf, list(linspace(1.0, 5.0, 5))) - - # Check the time. - buf = [] - start_time = time.time() - res.sweep(-5.0, 5.0, 11, delay=0.05) - time_diff = time.time() - start_time - assert time_diff > 0.55 - assert time_diff < 0.75 - eq_(buf, list(linspace(-5.0, 5.0, 11))) - - # Check the exceptions. - buf = [] - exceptions = [] - res.sweep(9.0, 12.0, 4, delay=0.05, exception_callback=exception_callback) - res.sweep(-15.0, 0.0, 3, delay=0.05, exception_callback=exception_callback) - eq_(buf, list(linspace(9.0, 10.0, 2))) - eq_(exceptions, [(11.0,), (-15.0,)]) - - def testDimensions(self): - """ - Ensure that dimensions for values are verified in both directions. - """ - - value = [] - - res1 = resources.Resource(getter=lambda: value[-1], setter=lambda x: value.append(x)) - res1.units = 'ns.V2.pJ-1' - - # Valid. - ## Without wrapping. - res1.value = Quantity(12.34, 'kg.m2.s-3.A-2') - eq_(res1.value, Quantity(12340, 'g.m2.s-3.A-2')) - - ## With wrapping. - res2 = res1.wrapped('w1', getter_filter=lambda x: 2 * x, setter_filter=lambda x: 3 * x) - res2.value = Quantity(12.34, 'kg.m2.s-3.A-2') - eq_(res2.value, Quantity(74040, 'g.m2.s-3.A-2')) - - # Invalid. - ## Dimensions. - try: - res1.value = Quantity(12.34, 's') - except TypeError: - pass - else: - assert False, 'Expected TypeError' - - ## Not even a quantity. - try: - res1.value = 12.34 - except TypeError: - pass - else: - assert False, 'Expected TypeError' - - ## Also on read. - value.append(12.34) - - try: - res1.value - except TypeError: - pass - else: - assert False, 'Expected TypeError' - - ## And with wrapping. - res2 = res1.wrapped('w2', getter_filter=lambda x: Quantity(-9.1, 'A'), setter_filter=lambda x: 6.3) - - try: - res2.value - except TypeError: - pass - else: - assert False, 'Expected TypeError' - - try: - res2.value = Quantity(12.34, 'ns.V2.pJ-1') - except TypeError: - pass - else: - assert False, 'Expected TypeError' - - -class AcquisitionThreadTest(TestCase): - def testWithoutResource(self): - """ - Let the thread run without a resource. - """ - - buf = [] - delay = Quantity(30, 'ms') - - thr = resources.AcquisitionThread(delay, buf.append) - - thr.start() - time.sleep(delay.value * 4.5) - thr.done = True - - eq_(buf, []) - - def testWithResource(self): - """ - Let the thread run with a resource. - """ - - dev = WithMethods() - res = resources.Resource(dev, dev.get_x) - - expected = [res.value] * 5 - - buf = [] - delay = Quantity(30, 'ms') - - thr = resources.AcquisitionThread(delay, buf.append, res) - - thr.start() - time.sleep(delay.value * 4.5) - thr.done = True - - eq_(buf, expected) - - def testWithLock(self): - """ - Pause the thread with a lock. - """ - - dev = WithMethods() - res = resources.Resource(dev, dev.get_x) - lock = Lock() - - expected = [res.value] * 4 - - buf = [] - delay = Quantity(30, 'ms') - - thr = resources.AcquisitionThread(delay, buf.append, res, running_lock=lock) - - thr.start() - time.sleep(delay.value * 1.5) - lock.acquire() - time.sleep(delay.value * 5) - lock.release() - time.sleep(delay.value * 1.5) - thr.done = True - - eq_(buf, expected) - - def testWithUnreadableResource(self): - """ - Watch the errors come rolling in. - """ - - res = resources.Resource() - - buf = [] - delay = Quantity(30, 'ms') - - thr = resources.AcquisitionThread(delay, buf.append, res) - - log = AssertHandler() - - thr.start() - time.sleep(delay.value * 4.5) - thr.done = True - - log.assert_logged('error', 'not readable') - - eq_(buf, []) - - -if __name__ == '__main__': - main() diff --git a/spacq/interface/tests/test_units.py b/spacq/interface/tests/test_units.py deleted file mode 100644 index 757a65a..0000000 --- a/spacq/interface/tests/test_units.py +++ /dev/null @@ -1,251 +0,0 @@ -from copy import deepcopy -from nose.tools import assert_raises, eq_ -from unittest import main, TestCase - -from .. import units - - -class QuantityTest(TestCase): - # String value, value, units, multiplier, proper string value. - data = [ - # Straightforward. - ('-1 s', -1, 's', 0, None), - ('0 s', 0, 's', 0, None), - ('1e0 s', 1, 's', 0, '1 s'), - # Multiple units. - ('10 A.m', 10, 'A.m', 0, None), - ('0.5 GJ.Hz.cd', 0.5, 'GJ.Hz.cd', 9, None), - ('500 kg.ms-1', 500, 'kg.ms-1', -3, None), - ('-1234567890123 m.s-2', -1234567890123, 'm.s-2', 0, '-1.23456789e+12 m.s-2'), - # Large & small. - ('3e40 nA', 3e40, 'nA', -9, '3e+40 nA'), - ('-3e40 mA', -3e40, 'mA', -3, '-3e+40 mA'), - ('7e-40 GA', 7e-40, 'GA', 9, None), - ('-7e-40 uA', -7e-40, 'uA', -6, None), - # Various whitespace. - ('5s', 5, 's', 0, '5 s'), - ('5s . Hz\t2 ', 5, 's.Hz2', 0, '5 s.Hz2'), - (' \t 123454321 \t uHz \t ', 123454321, 'uHz', -6, '123454321 uHz'), - ] - - def testSimple(self): - """ - Some simple quantities. - """ - - for string, value, orig_units, multiplier, _ in self.data: - q = units.Quantity(string) - - eq_(q, units.Quantity(value, orig_units)) - eq_(q.value, value * 10 ** multiplier) - eq_(q.original_value, value) - - def testBadStrings(self): - """ - Invalid strings pretending to be quantities. - """ - - data = [ - '', '0', 's', '0seconds', '0 something', '1234 anything', 's 0', - ] - - for string in data: - assert_raises(ValueError, units.Quantity, string) - - def testMismatchedUnits(self): - """ - Values should be comparable as long as they have the same dimensions. - """ - - qs = [ - units.Quantity(1, 'GJ.s'), - units.Quantity(1e9, 'J.Hz-1'), - units.Quantity(1, 'Tg.m2.s-1'), - units.Quantity(1, 'kg.Gm.m.Hz'), - units.Quantity(1000, 'Gg.m2.s.Hz2'), - ] - - for q in qs: - eq_(q, qs[0]) - - def testAmbiguousUnit(self): - """ - This shouldn't happen, but let's make sure it works. - """ - - # Ensure it works normally. - units.Quantity(5, 'ps') - - # Insert fake unit. - assert 'ps' not in units.SIValues.units - units.SIValues.units.add('ps') - - try: - assert_raises(ValueError, units.Quantity, 5, 'ps') - finally: - units.SIValues.units.remove('ps') - - def testAssertDimensions(self): - """ - Ensure that dimensions are asserted correctly. - """ - - q = units.Quantity(5, 's') - - # No exception. - assert q.assert_dimensions('s', exception=False) - assert not q.assert_dimensions('N.m', exception=False) - assert q.assert_dimensions(q, exception=False) - - # Exception. - assert q.assert_dimensions('s') - assert_raises(units.IncompatibleDimensions, q.assert_dimensions, 'A') - assert_raises(units.IncompatibleDimensions, eq_, units.Quantity(1, 's'), units.Quantity(1, 'm')) - - def testComparison(self): - """ - Check that comparison works. - """ - - assert units.Quantity(1, 's.m') == units.Quantity(1, 'm.s') - assert units.Quantity(1, 'm.s-1') < units.Quantity(2, 's-1.m') - assert units.Quantity(5.5, 's') >= units.Quantity(5.5, 's') - assert units.Quantity(5.5, 's') != units.Quantity(5.4, 's') - - try: - units.Quantity(1, 's') > units.Quantity(1, 's2') - except units.IncompatibleDimensions: - pass - else: - assert False, 'Expected IncompatibleDimensions.' - - try: - units.Quantity(1, 's') <= units.Quantity(1, 'Hz') - except units.IncompatibleDimensions: - pass - else: - assert False, 'Expected IncompatibleDimensions.' - - try: - 1 == units.Quantity(1, 's') - except TypeError: - pass - else: - assert False, 'Expected TypeError' - - try: - 1 <= units.Quantity(1, 's') - except TypeError: - pass - else: - assert False, 'Expected TypeError' - - def testArithmetic(self): - """ - Perform arithmetic on quantities. - """ - - # Absolute value. - q1 = units.Quantity(123, 'T') - q2 = units.Quantity(0, 'T') - q3 = units.Quantity(-123, 'T') - - eq_(abs(q1), q1) - eq_(abs(q2), q2) - eq_(abs(q3), q1) - - # Addition & subtraction. - ## Matching units. - q1 = units.Quantity(5, 's') - q2 = units.Quantity(-4, 'ms') - q3 = units.Quantity(4.996, 's') - q4 = units.Quantity(5.004, 's') - - eq_(q1 + q2, q3) - eq_(q1 - q2, q4) - - ## Non-matching units. - q2 = units.Quantity(-4, 'mHz') - - try: - q1 + q2 - except units.IncompatibleDimensions: - pass - else: - assert False, 'Expected IncompatibleDimensions' - - try: - q1 - q2 - except units.IncompatibleDimensions: - pass - else: - assert False, 'Expected IncompatibleDimensions' - - try: - units.Quantity(1, 's') + 1 - except TypeError: - pass - else: - assert False, 'Expected TypeError' - - try: - units.Quantity(1, 's') - 1 - except TypeError: - pass - else: - assert False, 'Expected TypeError' - - # Multiplication by real. - q = units.Quantity(1.5, 'N.m') - - q_mul = 2 * q - q_mul = q_mul * 3 - - eq_(q, units.Quantity(1.5, 'J')) - eq_(q_mul, units.Quantity(9, 'J')) - - # Division by real. - q = units.Quantity(-1.5, 'N.m') - - q_mul = q / -3 - - eq_(q, units.Quantity(-1.5, 'J')) - eq_(q_mul, units.Quantity(0.5, 'J')) - - def testRepr(self): - """ - Ensure that repr() gives a useful value. - """ - - # For eval(). - Quantity = units.Quantity - - for _, value, orig_units, _, _ in self.data: - q = units.Quantity(value, orig_units) - - eq_(eval(repr(q)), q) - - def testStr(self): - """ - Ensure that str() gives a meaningful value. - """ - - for string, value, orig_units, _, proper_string in self.data: - if proper_string is None: - proper_string = string - - q = units.Quantity(value, orig_units) - - eq_(str(q), proper_string) - - def testDeepCopy(self): - """ - """ - - q = units.Quantity('100 ns.V2') - - eq_(deepcopy(q), units.Quantity('100 ns.V2')) - - -if __name__ == '__main__': - main() diff --git a/spacq/interface/tests/test_waveform.py b/spacq/interface/tests/test_waveform.py deleted file mode 100644 index 3485783..0000000 --- a/spacq/interface/tests/test_waveform.py +++ /dev/null @@ -1,79 +0,0 @@ -from nose.tools import assert_raises, eq_ -from numpy.testing import assert_array_almost_equal -from unittest import main, TestCase - -from ..units import Quantity - -from .. import waveform - - -class GeneratorTest(TestCase): - def testEmpty(self): - """ - Generate an empty waveform. - """ - - wg = waveform.Generator(frequency=Quantity(1, 'Hz')) - - eq_(list(wg.waveform.data), []) - eq_(wg.waveform.markers, {}) - - def testWaveform(self): - """ - Generate a non-empty waveform. - """ - - wg = waveform.Generator(frequency=Quantity(2, 'Hz')) - - wg.delay(Quantity(2, 's')) - wg.marker(1, True) - wg.marker(2, True) - wg.pulse([], 0.5, Quantity(1, 's')) - wg.pulse([1.0, 0.0, -1.0], 1.0, Quantity(3, 's')) - wg.marker(1, False) - wg.square(-0.5, Quantity(2, 's')) - - expected = [0.0, 0.0, 0.0, 1.0, 0.6, 0.2, -0.2, -0.6, -1.0, -0.5, -0.5, -0.5, -0.5, -1.0] - - wave, markers = wg.waveform - assert_array_almost_equal(wave, expected, 4) - eq_(markers[1], [False] * 3 + [True] * 6 + [False] * 5) - eq_(markers[2], [False] * 3 + [True] * 11) - assert 3 not in markers - - def testEndWithMarker(self): - """ - Marker data is longer than waveform data. - """ - - wg = waveform.Generator(frequency=Quantity(1, 'mHz')) - - wg.marker(1, True) - wg.marker(2, False) - - wave, markers = wg.waveform - eq_(wave, [0.0]) - eq_(markers, {1: [True], 2: [False]}) - - def testTooLong(self, dry_run=False): - """ - Try to create a waveform that is far too long. - """ - - wg = waveform.Generator(frequency=Quantity(1, 'GHz'), dry_run=dry_run) - - wg.delay(Quantity(1, 'ns')) - wg.delay(Quantity(1, 'us')) - wg.delay(Quantity(1, 'ms')) - assert_raises(ValueError, wg.delay, Quantity(0.01, 's')) - - def testDryRun(self): - """ - testTooLong, but as a dry run. - """ - - self.testTooLong(dry_run=True) - - -if __name__ == '__main__': - main() diff --git a/spacq/interface/units.py b/spacq/interface/units.py deleted file mode 100755 index 44f9810..0000000 --- a/spacq/interface/units.py +++ /dev/null @@ -1,320 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from copy import deepcopy -from math import log10 -from numpy import allclose -import quantities as pq - -""" -Tools for working with quantities and units. -""" - - -class IncompatibleDimensions(TypeError): - """ - Operation on quantities with different dimensions. - """ - - pass - - -class SIValues(object): - """ - Values for SI units. - """ - - prefixes = { - 'y': -24, - 'z': -21, - 'a': -18, - 'f': -15, - 'p': -12, - 'n': -9, - 'u': -6, - 'm': -3, - 'c': -2, - 'd': -1, - '': 0, - 'da': 1, - 'h': 2, - 'k': 3, - 'M': 6, - 'G': 9, - 'T': 12, - 'P': 15, - 'E': 18, - 'Z': 21, - 'Y': 24, - } - prefixes_ = dict([(v, k) for (k, v) in prefixes.items()]) - - # SI base units. Note the g instead of kg. - units = set(['A', 'cd', 'g', 'K', 'm', 'mol', 's']) - # SI derived units. Add more as necessary. - units.update(['Hz', 'J', 'N', 'T', 'V', 'G']) - - -class Quantity(object): - """ - A quantity with a value and dimensions. - """ - - @staticmethod - def parse_units(string): - """ - Convert group of unit symbols to pq-acceptable notation: - Determine the multipliers and strip the prefixes. - Use "*" and "**" to combine units. - - eg. ' mN.m.ks-2' -> 'N*m*s**-2', 3 - """ - - symbols = [x.strip() for x in string.split('.')] - - result_units = [] - result_multiplier = 0 - - for symbol in symbols: - symbol_unit, symbol_multiplier = None, None - for prefix, multiplier in SIValues.prefixes.items(): - if not symbol.startswith(prefix): - continue - - unit = symbol[len(prefix):] - - exponent = None - for i in xrange(len(unit)): - try: - exponent = float(unit[i:]) - break - except ValueError: - pass - - if exponent is not None: - unit = unit[:i] - - if unit not in SIValues.units: - continue - - if symbol_unit is None: - if exponent is not None: - unit += '**{0}'.format(exponent) - symbol_unit, symbol_multiplier = unit, multiplier - else: - # Already found a match. - raise ValueError('Ambiguous unit symbol: "{0}"'.format(symbol)) - - if symbol_unit is not None: - result_units.append(symbol_unit) - result_multiplier += symbol_multiplier - else: - # Did not find anything. - raise ValueError('Unrecognized unit symbol: "{0}"'.format(symbol)) - - return ('*'.join(result_units), result_multiplier) - - @staticmethod - def from_string(string): - """ - Separate the value from the units. - """ - - for i in xrange(len(string), 0, -1): - try: - value = float(string[:i]) - except ValueError: - continue - - return value, string[i:] - - raise ValueError(string) - - def __init__(self, value, units=None): - """ - Both ('100 ms') and (100, 'ms') are acceptable. - """ - - if isinstance(value, basestring): - value, units = self.from_string(value) - - # Always work with single floats. - value = float(value) - - log.debug('Creating Quantity: {0}, {1}'.format(value, units)) - - # Remove unit prefixes. - new_units, multiplier = self.parse_units(units) - new_value = value * (10 ** multiplier) - - # Normalize to SI base units. - original_quantity = pq.Quantity(new_value, new_units) - self._q = original_quantity.simplified - - # Information to restore original representation. - self.original_units = units - self.original_multiplier = multiplier - - # Find the normalization factor. - q, orig = self._q.magnitude, original_quantity.magnitude - if q != orig: - self.original_multiplier += log10(abs(q)) - log10(abs(orig)) - - @property - def dimensions(self): - """ - The set of simplified units and their exponents. - """ - - return set(self._q.dimensionality.items()) - - @property - def dimensions_string(self): - """ - Returns the simplified units and their exponents in string form. - """ - - return self._q.dimensionality - - @property - def value(self): - """ - The magnitude of the quantity, normalized to the base units. - """ - - result = self._q.magnitude - - return result.tolist() - - @property - def original_value(self): - """ - The magnitude of the quantity that matches the units. - """ - - return self._q.magnitude / (10 ** self.original_multiplier) - - def assert_dimensions(self, other, exception=True): - """ - Whether the dimensions match. - - If exception is True and we would have returned False, raise an exception. - """ - - if isinstance(other, basestring): - # Given a units string. - other = Quantity(1, other).dimensions - elif isinstance(other, Quantity): - # Given a Quantity. - other = other.dimensions - - if self.dimensions == other: - return True - elif exception: - raise IncompatibleDimensions(self.dimensions, other) - else: - return False - - # FIXME: Python 2.7 provides functools.total_ordering() - def __eq__(self, other): - try: - self.assert_dimensions(other.dimensions) - except AttributeError: - raise TypeError('Expected dimensions for "{0!r}"'.format(other)) - - return allclose(self.value, other.value) - - def __lt__(self, other): - try: - self.assert_dimensions(other.dimensions) - except AttributeError: - raise TypeError('Expected dimensions for "{0!r}"'.format(other)) - - return self.value < other.value - - def __ne__(self, other): - return not self == other - - def __le__(self, other): - return self == other or self < other - - def __ge__(self, other): - return not self < other - - def __gt__(self, other): - return not self <= other - - def __abs__(self): - if self.value < 0: - return Quantity(abs(self.original_value), self.original_units) - else: - return self - - def __add__(self, other): - """ - Addition with matching dimensions. - """ - - try: - self.assert_dimensions(other.dimensions) - except AttributeError: - raise TypeError('Expected dimensions for "{0!r}"'.format(other)) - - result = deepcopy(self) - result._q += other._q - - return result - - def __sub__(self, other): - """ - Subtraction with matching dimensions. - """ - - try: - self.assert_dimensions(other.dimensions) - except AttributeError: - raise TypeError('Expected dimensions for "{0!r}"'.format(other)) - - result = deepcopy(self) - result._q -= other._q - - return result - - def __mul__(self, other): - """ - Multiplication by reals. - """ - - result = deepcopy(self) - result._q *= other - - return result - - def __rmul__(self, other): - return self * other - - def __div__(self, other): - """ - Division by reals. - """ - - result = deepcopy(self) - result._q /= other - - return result - - def __repr__(self): - return '{0}(\'{1}\')'.format(self.__class__.__name__, str(self)) - - def __str__(self): - value = self.original_value - symbol = self.original_units - - return '{0:.10g} {1}'.format(value, symbol) - - def __deepcopy__(self, memo): - """ - Rather than copying anything, simply create a new instance. - """ - - return Quantity(str(self)) diff --git a/spacq/interface/waveform.py b/spacq/interface/waveform.py deleted file mode 100644 index 2a8f7a5..0000000 --- a/spacq/interface/waveform.py +++ /dev/null @@ -1,194 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from collections import namedtuple -from numpy import append, array, interp, linspace - -""" -A waveform generator. -""" - - -Waveform = namedtuple('Waveform', 'data, markers') - - -class Generator(object): - """ - A generator for arbitrary waveforms. - """ - - # Generation should fail if the number of points exceeds this value. - max_length = 10000000 # 1e7 (0.01 s @ 1 GHz) - - length = 0 - - def __init__(self, frequency, dry_run=False): - # The sampling frequency. - self.frequency = frequency - - # If True, do not generate a waveform. Useful for verifying the generating code. - self.dry_run = dry_run - - # The resulting wave, with each data point on the interval [-1.0, 1.0]. - self._wave = array([]) - - # The resulting marker channels, with each channel being a sparse list represented as a dictionary. - self._markers = {} - - @property - def waveform(self): - """ - The waveform and marker data for the generated waveform. - """ - - try: - last_marker_point = max(pos for data in self._markers.values() for pos in data.keys()) - except ValueError: - last_marker_point = -1 - - extra_points = last_marker_point + 1 - len(self._wave) - if extra_points > 0: - resulting_wave = append(self._wave, [0.0] * extra_points) - else: - resulting_wave = self._wave - - marker_data = dict((num, self._get_marker(num, len(resulting_wave))) for num in self._markers) - - return Waveform(resulting_wave, marker_data) - - def check_length(self, additional): - resulting_length = self.length + additional - - if resulting_length > self.max_length: - raise ValueError('Waveform is too long; stopping at {0:n} points'.format(resulting_length)) - - def append(self, values): - self.length += len(values) - - if not self.dry_run: - self._wave = append(self._wave, values) - - def _get_marker(self, num, length): - """ - Get the marker values for all data points in the waveform. - """ - - result = [] - - def last_value(): - try: - return result[-1] - except IndexError: - return False - - for idx, value in sorted(self._markers[num].items()): - if idx >= len(result): - result.extend([last_value()] * (idx - len(result) + 1)) - - result[idx] = value - - if len(result) < length: - result.extend([last_value()] * (length - len(result))) - - return result - - def _parse_time(self, value): - """ - Convert a time value to a number of samples based on the frequency. - """ - - log.debug('Parsing time "{0!r}" with frequency {1!r}'.format(value, self.frequency)) - result = int(value.value * self.frequency.value) - log.debug('Parsed time "{0!r}" as {1} samples'.format(value, result)) - - return result - - def _scale_waveform(self, data, amplitude=None, duration=None): - """ - Shorten or elongate a waveform in both axes. - - Due to the discrete nature of these waveforms, interpolation is used when changing duration. - """ - - if not data: - return data - - new_data = data[:] - - # Change amplitude. - if amplitude is not None: - new_data = [amplitude * x for x in new_data] - - # Change duration. - if duration is not None: - duration = self._parse_time(duration) - actual_duration = len(new_data) - - points = linspace(0, 1, duration) - actual_points = linspace(0, 1, actual_duration) - - new_data = interp(points, actual_points, new_data) - new_data = [round(x, 5) for x in new_data] - - return new_data - - def set_next(self, value): - """ - Set the next point to have the given amplitude. - """ - - self.check_length(1) - self.append([value]) - - def delay(self, value, less_points=1): - """ - Extend the last value of the waveform to last the length of the delay. - """ - - delay_length = self._parse_time(value) - less_points - - self.check_length(delay_length) - - try: - last_value = self._wave[-1] - except IndexError: - last_value = 0.0 - - self.append([last_value] * delay_length) - - def square(self, amplitude, length): - """ - Generate a square pulse. - """ - - try: - return_to = self._wave[-1] - except IndexError: - return_to = 0.0 - - self.set_next(amplitude) - self.delay(length, less_points=1) - self.set_next(return_to) - - def pulse(self, values, amplitude, duration): - """ - Literal amplitude values. - """ - - data = self._scale_waveform(values, amplitude, duration) - - self.check_length(len(data)) - self.append(data) - - def marker(self, num, value): - """ - Set the value of a marker starting from the current position. - """ - - if self.dry_run: - return - - if num not in self._markers: - self._markers[num] = {} - - self._markers[num][len(self._wave)] = value diff --git a/spacq/iteration/__init__.py b/spacq/iteration/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/iteration/sweep.py b/spacq/iteration/sweep.py deleted file mode 100755 index decc825..0000000 --- a/spacq/iteration/sweep.py +++ /dev/null @@ -1,642 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from functools import partial, wraps -from itertools import izip, repeat -from threading import Condition, Thread -from time import sleep, time - -from spacq.tool.box import flatten - - -def update_current_f(f): - @wraps(f) - def wrapped(self): - self.current_f = f.__name__ - - log.debug('Entering function: {0}'.format(self.current_f)) - - return f(self) - - return wrapped - - -class PulseConfiguration(object): - """ - The configuration necessary to execute a pulse program with a device. - """ - - # All the directly-used attributes. - awg_attrs = ['channels', 'clear_channels', 'enabled', 'run_mode', 'sampling_rate', 'trigger'] - oscilloscope_attrs = ['acquiring', 'fastframe', 'fastframe_count', 'fastframe_sum', 'stopafter'] - - @staticmethod - def verify_device(name, device, attributes): - if device is None: - raise TypeError('No "{0}" device configured'.format(name)) - - d = dir(device) - - for attribute in attributes: - if attribute not in d: - raise TypeError('Given "{0}" device lacks "{1}"'.format(name, attribute)) - - def __init__(self, program, channels, awg, oscilloscope): - self.verify_device('AWG', awg, self.awg_attrs) - self.verify_device('Oscilloscope', oscilloscope, self.oscilloscope_attrs) - - self.program = program - self.channels = channels - self.awg = awg - self.oscilloscope = oscilloscope - - -class SweepController(object): - """ - A simple controller for a sweep of several variables. - - conditional_dwell - v ^ - init -> next -> transition -> write -> dwell -> pulse -> read -> condition -> ramp_down -> end - ^ ^ |_____________^ | | - | |____________________________________________________________| | - |__________________________________________________________________________________| - - """ - - def __init__(self, resources, variables, num_items, measurement_resources, measurement_variables, - condition_resources=[], condition_variables=[], pulse_config=None, continuous=False): - self.resources = resources - self.variables = variables - self.num_items = num_items - self.measurement_resources = measurement_resources - self.measurement_variables = measurement_variables - #TODO: Instead of flattening, make use of the group ordering for quicker access - self.condition_resources = [cond_tuple for cond_tuple in flatten(condition_resources)] - self.condition_variables = condition_variables - self.pulse_config = pulse_config - self.continuous = continuous - - # The callbacks should be set before calling run(), if necessary. - self.data_callback, self.close_callback, self.write_callback, self.read_callback = [None] * 4 - self.general_exception_handler = None - self.resource_exception_handler = None - - self.devices_configured = False - - self.current_f = None - - self.item = -1 - - self.paused = False - self.pause_lock = Condition() - - self.last_continuous = False - self.done = False - self.aborting = False - self.abort_fatal = False - - self.sweep_start_time = time() - self.first_time_point = None - - self.orders = [vars[0].order for vars in self.variables] - self.orders.reverse() - self.condition_orders = [group[0].order for group in self.condition_variables] - self.conditional_wait = 0 - self.order_periods = None - - def compute_order_periods(self): - """ - This function computes the number of elements iterated before each order changes. - """ - periods = [] - orders = [] - - - - for group in reversed(self.variables): - #skip if vars in group are consts. - if group[0].use_const != 1: - num_in_group = None - - #get the length of the group - for var in group: - num_in_var = len(var) - - if num_in_group is None or num_in_var < num_in_group: - num_in_group = num_in_var - - #append the period of this group and its order. - if not periods: - periods.append(num_in_group) - else: - periods.append(periods[-1]*num_in_group) - - orders.append(group[0].order) - - - for order in self.condition_orders: - if order not in orders: - orders.append(order) - orders.sort() - new_index = orders.index(order) - if new_index > 0: - periods.insert(new_index, periods[new_index - 1]) - else: #if the condition is the smallest order, it has a period of 1 - periods.insert(new_index, 1) - - # create a dict. - self.order_periods = dict(zip(orders, periods)) - - def create_iterator(self, pos): - """ - Create an iterator for an order of variables. - """ - - return izip(*(iter(var) for var in self.variables[pos])) - - def ramp(self, resources, values_from, values_to, steps): - """ - Slowly sweep the resources. - """ - - thrs = [] - for (name, resource), value_from, value_to, resource_steps in zip(resources, - values_from, values_to, steps): - if resource is None: - continue - - kwargs = {} - if self.resource_exception_handler is not None: - kwargs['exception_callback'] = partial(self.resource_exception_handler, name, write=True) - - thr = Thread(target=resource.sweep, args=(value_from, value_to, resource_steps), kwargs=kwargs) - thrs.append(thr) - thr.daemon = True - thr.start() - - for thr in thrs: - thr.join() - - def write_resource(self, name, resource, value): - """ - Write a value to a resource and handle exceptions. - """ - - try: - resource.value = value - except Exception as e: - if self.resource_exception_handler is not None: - self.resource_exception_handler(name, e, write=True) - return - - def read_resource(self, name, resource, save_callback): - """ - Read a value from a resource and handle exceptions. - """ - - try: - value = resource.value - except Exception as e: - if self.resource_exception_handler is not None: - self.resource_exception_handler(name, e, write=False) - return - - save_callback(value) - - - def run(self, next_f=None): - """ - Run the sweep. - """ - - try: - if next_f is None: - next_f = self.init - - # Trampoline. - while next_f is not None: - f_name = next_f.__name__ - - if self.paused: - log.debug('Paused before function: {0}'.format(f_name)) - - with self.pause_lock: - self.pause_lock.wait() - - if self.aborting: - log.debug('Aborting before function: {0}'.format(f_name)) - - if not self.abort_fatal: - self.continuous = False - self.ramp_down() - - return - - log.debug('Starting function: {0}'.format(f_name)) - - try: - next_f = next_f() - except Exception as e: - if self.general_exception_handler is not None: - self.general_exception_handler(f_name, e) - else: - log.exception('Caught exception in function: {0}'.format(f_name)) - - # Attempt to exit normally at this point. - next_f = None - finally: - self.end() - - @update_current_f - def init(self): - """ - Initialize values and possibly devices. - """ - - self.iterators = None - self.current_values = None - self.last_values = None - - self.item = -1 - - self.compute_order_periods() - - if not self.devices_configured: - log.debug('Configuring devices') - - if self.pulse_config is not None: - # AWG - awg = self.pulse_config.awg - - awg.enabled = False - awg.sampling_rate = self.pulse_config.program.frequency - awg.run_mode = 'triggered' - - self.devices_configured = True - - return self.next - - @update_current_f - def next(self): - """ - Get the next set of values from the iterators. - """ - - self.item += 1 - if self.current_values is not None: - self.last_values = self.current_values[:] - - if self.iterators is None: - # First time around. - self.iterators = [] - for pos in xrange(len(self.variables)): - self.iterators.append(self.create_iterator(pos)) - - self.current_values = [it.next() for it in self.iterators] - self.changed_indices = range(len(self.variables)) - - # Calculate where the end of each order is. - else: - pos = len(self.variables) - 1 - while pos >= 0: - try: - self.current_values[pos] = self.iterators[pos].next() - break - except StopIteration: - self.iterators[pos] = self.create_iterator(pos) - self.current_values[pos] = self.iterators[pos].next() - - - pos -= 1 - - self.changed_indices = range(pos, len(self.variables)) - - return self.transition - - @update_current_f - def transition(self): - """ - Perform a transition for variables, as required. - """ - - if self.last_values is None: - # Smooth set from const. - steps, resources, from_values, to_values = [], [], [], [] - - for pos in xrange(len(self.variables)): - # Extract values for this group. - group_vars, group_resources, current_values = (self.variables[pos], - self.resources[pos], self.current_values[pos]) - - for var, resource, current_value in zip(group_vars, group_resources, - current_values): - if var.use_const or not var.smooth_from: - continue - - steps.append(var.smooth_steps) - resources.append(resource) - from_values.append(var.with_type(var.const)) - to_values.append(current_value) - - self.ramp(resources, from_values, to_values, steps) - else: - # The first changed group is simply stepping; all others rolled over. - affected_groups = self.changed_indices[1:] - - steps, resources, from_values, to_values = [], [], [], [] - - for pos in affected_groups: - # Extract values for this group. - group_vars, group_resources, current_values, last_values = (self.variables[pos], - self.resources[pos], self.current_values[pos], self.last_values[pos]) - - for var, resource, current_value, last_value in zip(group_vars, group_resources, - current_values, last_values): - if var.use_const or not var.smooth_transition: - continue - - steps.append(var.smooth_steps) - resources.append(resource) - from_values.append(last_value) - to_values.append(current_value) - - self.ramp(resources, from_values, to_values, steps) - - return self.write - - @update_current_f - def write(self): - """ - Write the next values to their resources. - """ - - thrs = [] - for pos in self.changed_indices: - for i, ((name, resource), value) in enumerate(zip(self.resources[pos], self.current_values[pos])): - if resource is not None: - thr = Thread(target=self.write_resource, args=(name, resource, value)) - thrs.append(thr) - thr.daemon = True - thr.start() - - if self.write_callback is not None: - self.write_callback(pos, i, value) - - for thr in thrs: - thr.join() - - return self.dwell - - @update_current_f - def dwell(self): - """ - Wait for all changed variables. - """ - - delay = max(var._wait.value for pos in self.changed_indices for var in self.variables[pos]) - sleep(delay) - - if self.pulse_config is not None: - return self.pulse - else: - return self.read - - @update_current_f - def pulse(self): - """ - Run through the pulse program. - """ - - if self.pulse_config.channels: - waveforms = self.pulse_config.program.generate_waveforms() - times = self.pulse_config.program.times_average - - # AWG - awg = self.pulse_config.awg - awg.enabled = False - - awg.clear_channels() - - channels = [] - for output, number in self.pulse_config.channels.items(): - channel = awg.channels[number] - - waveform, markers = waveforms[output] - channel.set_waveform(waveform, markers, name=output) - - channels.append(channel) - - for channel in channels: - channel.enabled = True - - awg.enabled = True - - # Oscilloscope - osc = self.pulse_config.oscilloscope - osc.acquiring = False - - if times > 1: - osc.fastframe = True - osc.fastframe_sum = 'average' - osc.fastframe_count = times + 1 - - if osc.fastframe_count != times + 1: - raise ValueError('Cannot average {0} times; check the oscilloscope'.format(times)) - else: - osc.fastframe = False - - osc.stopafter = 'sequence' - - awg.opc - osc.opc - - osc.acquiring = True - # Wait for the oscilloscope to ready the trigger. - sleep(1) - - # All together now! - trigger = awg.trigger - delay = self.pulse_config.program.acq_delay.value - - for _ in repeat(None, times): - trigger() - awg.opc - - end_time = time() + delay - time_diff = end_time - time() - while time_diff > 0: - sleep(time_diff) - time_diff = end_time - time() - - osc.opc - - acqs = osc.acquisitions - if acqs != times: - raise ValueError('Incorrect number of acquisitions made: {0}'.format(acqs)) - - return self.read - - @update_current_f - def read(self): - """ - Take measurements. - """ - measurements = [None] * len(self.measurement_resources) - - thrs = [] - for i, (name, resource) in enumerate(self.measurement_resources): - if resource is not None: - def save_callback(value, i=i): - measurements[i] = value - if self.read_callback is not None: - self.read_callback(i, value) - - thr = Thread(target=self.read_resource, args=(name, resource, save_callback)) - thrs.append(thr) - thr.daemon = True - thr.start() - - for thr in thrs: - thr.join() - - if self.data_callback is not None: - if self.first_time_point is None: - cur_time = 0 - self.first_time_point = time() - else: - cur_time = time() - self.first_time_point - - self.data_callback(cur_time, tuple(flatten(self.current_values)), tuple(measurements)) - - - return self.condition - - - @update_current_f - def condition(self): - """ - Stalls an order's loop (after the order's variables have been exhausted) - with repeated measurements until conditions are true. - Once conditions are true, then the sweep controller moves on to the next order - and continues as usual. - """ - boolean = True - - if self.condition_variables: - - # Find the orders that have changed - orders_changed = [] - for order in self.order_periods.keys(): - if (self.item + 1) % self.order_periods[order] == 0: - orders_changed.append(order) - orders_changed.sort() - - # The wait time is defined by the max of the wait times of the lowest triggered order of condition variables - self.conditional_wait = 0 - for order in orders_changed: - if order in self.condition_orders: - corresponding_index = self.condition_orders.index(order) - for var in self.condition_variables[corresponding_index]: - if var._wait.value > self.conditional_wait: - self.conditional_wait = var._wait.value - break - - # Check the conditions for the changed orders, starting from the lowest order. - for order in orders_changed: - if order in self.condition_orders: - for var in self.condition_variables[self.condition_orders.index(order)]: - boolean_to_compound = var.evaluate_conditions(self.condition_resources) - boolean = boolean and boolean_to_compound - - if boolean == True: - if self.item == self.num_items - 1: - self.item += 1 - return self.ramp_down - else: - return self.next - if boolean == False: - return self.conditional_dwell - - def conditional_dwell(self): - """ - Dwell for the max time defined by the conditions in the current order. - """ - sleep(self.conditional_wait) - return self.read - - - @update_current_f - def ramp_down(self): - """ - Sweep from the last values to const. - """ - - if not self.current_values: - return - - # Smooth set to const. - steps, resources, from_values, to_values = [], [], [], [] - - for pos in xrange(len(self.variables)): - # Extract values for this group. - group_vars, group_resources, current_values = (self.variables[pos], - self.resources[pos], self.current_values[pos]) - - for var, resource, current_value in zip(group_vars, group_resources, - current_values): - if var.use_const or not var.smooth_to: - continue - - steps.append(var.smooth_steps) - resources.append(resource) - from_values.append(current_value) - to_values.append(var.with_type(var.const)) - - self.ramp(resources, from_values, to_values, steps) - - if self.continuous and not self.last_continuous: - return self.init - - @update_current_f - def end(self): - """ - The sweep is over. - """ - - assert not self.done - self.done = True - - if self.close_callback is not None: - self.close_callback() - - def pause(self): - log.debug('Pausing.') - - self.paused = True - - log.debug('Paused.') - - def unpause(self): - log.debug('Unpausing.') - - with self.pause_lock: - self.paused = False - self.pause_lock.notify() - - log.debug('Unpaused.') - - def abort(self, fatal=False): - """ - Ending abruptly for any reason. - """ - - log.debug('Aborting.') - - self.aborting = True - self.abort_fatal = fatal - - if self.abort_fatal: - log.warning('Aborting fatally.') - - self.unpause() diff --git a/spacq/iteration/tests/__init__.py b/spacq/iteration/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/iteration/tests/resources/01.pulse b/spacq/iteration/tests/resources/01.pulse deleted file mode 100644 index 40beb66..0000000 --- a/spacq/iteration/tests/resources/01.pulse +++ /dev/null @@ -1,11 +0,0 @@ -int i = 1 -delay d = 1 ns -pulse p = {shape: 'square', length: 1 ns, amplitude: 1 mV} - -output f1 - -times i { - (123 ns):f1 -} - -acquire diff --git a/spacq/iteration/tests/test_sweep.py b/spacq/iteration/tests/test_sweep.py deleted file mode 100755 index 2f3ce87..0000000 --- a/spacq/iteration/tests/test_sweep.py +++ /dev/null @@ -1,512 +0,0 @@ -from functools import partial -from nose.tools import eq_ -from os import path -from threading import Thread -from time import sleep, time -from unittest import main, TestCase -from itertools import cycle - -from spacq.devices.config import DeviceConfig -from spacq.interface.pulse.program import Program -from spacq.interface.resources import Resource -from spacq.interface.units import Quantity -from spacq.tool.box import flatten - -from ..variables import sort_condition_variables, sort_output_variables, InputVariable, OutputVariable -from ..variables import ConditionVariable, LinSpaceConfig, Condition - -from .. import sweep - -resource_dir = path.join(path.dirname(__file__), 'resources') - -class SweepControllerTest(TestCase): - def testSingle(self): - """ - Iterate over a single thing without any measurements. - """ - - res_buf = [] - - def setter(value): - res_buf.append(value) - - res = Resource(setter=setter) - var = OutputVariable(name='Var', order=1, enabled=True, const=-1.0) - var.config = LinSpaceConfig(1.0, 4.0, 4) - var.smooth_steps = 3 - var.smooth_from, var.smooth_to = [True] * 2 - - vars, num_items = sort_output_variables([var]) - ctrl = sweep.SweepController([(('Res', res),)], vars, num_items, [], []) - - # Callback verification buffers. - actual_values = [] - actual_measurement_values = [] - actual_writes = [] - actual_reads = [] - closed = [0] - - # Callbacks. - def data_callback(cur_time, values, measurement_values): - actual_values.append(values) - actual_measurement_values.append(measurement_values) - ctrl.data_callback = data_callback - - def close_callback(): - closed[0] += 1 - ctrl.close_callback = close_callback - - def write_callback(pos, i, value): - actual_writes.append((pos, i, value)) - ctrl.write_callback = write_callback - - def read_callback(i, value): - actual_reads.append((i, value)) - ctrl.read_callback = read_callback - - # Let it run. - ctrl.run() - - eq_(res_buf, [-1.0, 0.0, 1.0, 1.0, 2.0, 3.0, 4.0, 4.0, 1.5, -1.0]) - eq_(actual_values, [(1.0,), (2.0,), (3.0,), (4.0,)]) - eq_(actual_measurement_values, [()] * 4) - eq_(actual_writes, [(0, 0, x) for x in [1.0, 2.0, 3.0, 4.0]]) - eq_(actual_reads, []) - eq_(closed, [1]) - - def testProper(self): - """ - Testing everything that there is to test along the happy path: - nested and parallel variables - measurements - dwell time - """ - - res_bufs = [[], [], [], []] - measurement_counts = [0] * 2 - - def setter(i, value): - res_bufs[i].append(value) - - def getter(i): - measurement_counts[i] += (-1) ** i - - return measurement_counts[i] - - dwell_time = Quantity(50, 'ms') - - # Output. - res0 = Resource(setter=partial(setter, 0)) - res0.units = 'cm-1' - res1 = Resource(setter=partial(setter, 1)) - res2 = Resource(setter=partial(setter, 2)) - res3 = Resource(setter=partial(setter, 3)) - - var0 = OutputVariable(name='Var 0', order=2, enabled=True, const=0.0) - var0.config = LinSpaceConfig(-1.0, -2.0, 2) - var0.smooth_steps = 2 - var0.smooth_from, var0.smooth_to, var0.smooth_transition = [True] * 3 - var0.type = 'quantity' - var0.units = 'cm-1' - - var1 = OutputVariable(name='Var 1', order=1, enabled=True, const=-1.0) - var1.config = LinSpaceConfig(1.0, 4.0, 4) - var1.smooth_steps = 3 - var1.smooth_from, var1.smooth_to, var1.smooth_transition = [True] * 3 - - var2 = OutputVariable(name='Var 2', order=1, enabled=True, const=1.23, use_const=True) - - var3 = OutputVariable(name='Var 3', order=1, enabled=True, const=-9.0, wait=str(dwell_time)) - var3.config = LinSpaceConfig(-1.0, 2.0, 4) - var3.smooth_steps = 2 - var3.smooth_from, var3.smooth_to, var3.smooth_transition = True, True, False - - var4 = OutputVariable(name='Var 4', order=3, enabled=True, const=-20.0) - var4.config = LinSpaceConfig(-10.0, 20, 1) - var4.smooth_steps = 2 - var4.smooth_from = True - - # Input. - meas_res0 = Resource(getter=partial(getter, 0)) - meas_res1 = Resource(getter=partial(getter, 1)) - - meas0 = InputVariable(name='Meas 0') - meas1 = InputVariable(name='Meas 1') - - vars, num_items = sort_output_variables([var0, var1, var2, var3, var4]) - ctrl = sweep.SweepController([(('Res 2', res2),), (('Something', None),), (('Res 0', res0),), - (('Res 1', res1), ('Res 3', res3))], vars, num_items, - [('Meas res 0', meas_res0), ('Meas res 1', meas_res1)], [meas0, meas1]) - - # Callback verification buffers. - actual_values = [] - actual_measurement_values = [] - actual_writes = [] - actual_reads = [] - closed = [0] - - # Callbacks. - def data_callback(cur_time, values, measurement_values): - actual_values.append(values) - actual_measurement_values.append(measurement_values) - ctrl.data_callback = data_callback - - def close_callback(): - closed[0] += 1 - ctrl.close_callback = close_callback - - def write_callback(pos, i, value): - actual_writes.append((pos, i, value)) - ctrl.write_callback = write_callback - - def read_callback(i, value): - actual_reads.append((i, value)) - ctrl.read_callback = read_callback - - # Let it run. - start_time = time() - ctrl.run() - elapsed_time = time() - start_time - - expected_time = num_items * dwell_time.value - assert expected_time < elapsed_time, 'Took {0} s, expected at least {1} s.'.format(elapsed_time, expected_time) - - expected_res1 = [1.0, 2.0, 3.0, 4.0] - expected_res2 = [-1.0, 0.0, 1.0, 2.0] - - expected_inner_writes = list(flatten(((3, 0, x), (3, 1, x - 2.0)) for x in [1.0, 2.0, 3.0, 4.0])) - expected_writes = [(0, 0, 1.23), (1, 0, -10.0)] + list(flatten([(2, 0, x)] + expected_inner_writes - for x in [Quantity(x, 'cm-1') for x in [-1.0, -2.0]])) - - eq_(res_bufs, [ - [Quantity(x, 'cm-1') for x in [0.0, -1.0, -1.0, -2.0, -2.0, 0.0]], - [-1.0, 0.0, 1.0] + expected_res1 + [4.0, 2.5, 1.0] + expected_res1 + [4.0, 1.5, -1.0], - [1.23], - [-9.0, -1.0] + expected_res2 + expected_res2 + [2.0, -9.0], - ]) - eq_(measurement_counts, [8, -8]) - eq_(actual_values, [(1.23, -10.0, x, y, y - 2.0) - for x in [Quantity(x, 'cm-1') for x in [-1.0, -2.0]] - for y in [1.0, 2.0, 3.0, 4.0]]) - eq_(actual_measurement_values, [(x, -x) for x in xrange(1, 9)]) - eq_(actual_writes, expected_writes) - eq_(actual_reads, list(flatten(((0, x), (1, -x)) for x in xrange(1, 9)))) - eq_(closed, [1]) - - def testContinuous(self): - """ - Keep going, and then eventually stop. - """ - - res_buf = [] - - def setter(value): - res_buf.append(value) - - res = Resource(setter=setter) - var = OutputVariable(name='Var', order=1, wait='0 ms', enabled=True) - var.config = LinSpaceConfig(1.0, 4.0, 4) - - vars, num_items = sort_output_variables([var]) - ctrl = sweep.SweepController([(('Res', res),)], vars, num_items, [], [],[],[], continuous=True) - - thr = Thread(target=ctrl.run) - thr.daemon = True - thr.start() - - sleep(0.5) - ctrl.pause() - sleep(0.5) - ctrl.unpause() - sleep(0.5) - - ctrl.last_continuous = True - thr.join() - - expected_buf = [1.0, 2.0, 3.0, 4.0] - - eq_(res_buf[:len(expected_buf) * 50], expected_buf * 50) - - def testWriteException(self): - """ - Fail to read. - """ - - exceptions = [] - e = ValueError() - - def setter(value): - raise e - - res = Resource(setter=setter) - var = OutputVariable(name='Var', order=1, enabled=True, const=0.0) - var.config = LinSpaceConfig(1.0, 4.0, 4) - - vars, num_items = sort_output_variables([var]) - ctrl = sweep.SweepController([(('Res', res),)], vars, num_items, [], []) - - def resource_exception_handler(name, e, write): - exceptions.append((name, e)) - ctrl.abort(fatal=True) - assert write - ctrl.resource_exception_handler = resource_exception_handler - - ctrl.run() - - eq_(exceptions, [('Res', e)]) - - def testReadException(self): - """ - Fail to write. - """ - - exceptions = [] - e = ValueError() - - def getter(): - raise e - - res = Resource(setter=lambda x: x) - var = OutputVariable(name='Var', order=1, enabled=True) - var.config = LinSpaceConfig(1.0, 4.0, 4) - - meas_res = Resource(getter=getter) - meas_var = InputVariable(name='Meas var') - - vars, num_items = sort_output_variables([var]) - ctrl = sweep.SweepController([(('Res', res),)], vars, num_items, [('Meas res', meas_res)], [meas_var]) - - def resource_exception_handler(name, e, write): - exceptions.append((name, e)) - assert not write - ctrl.resource_exception_handler = resource_exception_handler - - ctrl.run() - - eq_(exceptions, [('Meas res', e)] * 4) - - def testPulseProgram(self): - """ - Iterate with a pulse program. - """ - - res_buf = [] - - def setter(value): - res_buf.append(value) - - res = Resource(setter=setter) - var1 = OutputVariable(name='Var 1', order=1, enabled=True) - var1.config = LinSpaceConfig(1.0, 4.0, 4) - - p = Program.from_file(path.join(resource_dir, '01.pulse')) - p.frequency = Quantity(1, 'GHz') - p.set_value(('_acq_marker', 'marker_num'), 1) - p.set_value(('_acq_marker', 'output'), 'f1') - - eq_(p.all_values, set([('_acq_marker', 'marker_num'), ('_acq_marker', 'output'), ('d',), ('i',), - ('p', 'amplitude'), ('p', 'length'), ('p', 'shape')])) - - parameters = [('i',), ('d',), ('p', 'amplitude'), ('p', 'length')] - for parameter in parameters: - p.resource_labels[parameter] = 'res_' + '.'.join(parameter) - p.resources[parameter] = Resource() - - var2 = OutputVariable(name='Var 2', order=1, enabled=True) - var2.config = LinSpaceConfig(1, 4, 4) - var2.type = 'integer' - - awg_cfg = DeviceConfig('awg') - awg_cfg.address_mode = awg_cfg.address_modes.gpib - awg_cfg.manufacturer, awg_cfg.model = 'Tektronix', 'AWG5014B' - awg_cfg.mock = True - awg_cfg.connect() - - osc_cfg = DeviceConfig('osc') - osc_cfg.address_mode = awg_cfg.address_modes.gpib - osc_cfg.manufacturer, osc_cfg.model = 'Tektronix', 'DPO7104' - osc_cfg.mock = True - osc_cfg.connect() - - pulse_config = sweep.PulseConfiguration(p.with_resources, {'f1': 1}, awg_cfg.device, osc_cfg.device) - - vars, num_items = sort_output_variables([var1, var2]) - ress = [(('Res 1', res), ('Res 2', p.resources[('i',)]))] - ctrl = sweep.SweepController(ress, vars, num_items, [], [],[],[],pulse_config) - - ctrl.run() - - eq_(res_buf, [1.0, 2.0, 3.0, 4.0]) - - def testConditionsSweep(self): - """ - Tests a setup with condition variables and output variables. Similar to testProper, but with - conditions mixed in. - """ - - res_bufs = [[], [], [], []] - measurement_counts = [0] * 2 - - def setter(i, value): - res_bufs[i].append(value) - - def getter(i): - measurement_counts[i] += (-1) ** i - - return measurement_counts[i] - - dwell_time = Quantity(50, 'ms') - - # Output. - res0 = Resource(setter=partial(setter, 0)) - res0.units = 'cm-1' - res1 = Resource(setter=partial(setter, 1)) - res2 = Resource(setter=partial(setter, 2)) - res3 = Resource(setter=partial(setter, 3)) - - var0 = OutputVariable(name='Var 0', order=2, enabled=True, const=0.0) - var0.config = LinSpaceConfig(-1.0, -2.0, 2) - var0.smooth_steps = 2 - var0.smooth_from, var0.smooth_to, var0.smooth_transition = [True] * 3 - var0.type = 'quantity' - var0.units = 'cm-1' - - var1 = OutputVariable(name='Var 1', order=1, enabled=True, const=-1.0) - var1.config = LinSpaceConfig(1.0, 4.0, 4) - var1.smooth_steps = 3 - var1.smooth_from, var1.smooth_to, var1.smooth_transition = [True] * 3 - - var2 = OutputVariable(name='Var 2', order=1, enabled=True, const=1.23, use_const=True) - - var3 = OutputVariable(name='Var 3', order=1, enabled=True, const=-9.0, wait=str(dwell_time)) - var3.config = LinSpaceConfig(-1.0, 2.0, 4) - var3.smooth_steps = 2 - var3.smooth_from, var3.smooth_to, var3.smooth_transition = True, True, False - - var4 = OutputVariable(name='Var 4', order=3, enabled=True, const=-20.0) - var4.config = LinSpaceConfig(-10.0, 20, 1) - var4.smooth_steps = 2 - var4.smooth_from = True - - # Condition variables. - - ## Resources used for checking the conditions. - - iters = [cycle([0,1]), cycle([0,1,2]), cycle([0,-1,-2])] - def cres_getter(i): - return iters[i].next() - - cres0 = Resource('cres0', getter=partial(cres_getter,0)) - cres1 = Resource('cres1', getter=partial(cres_getter,1)) - cres2 = Resource('cres2', getter=partial(cres_getter,2)) - - condition_resources = [(('cres0',cres0),),(('cres1',cres1),('cres2',cres2),)] - - - cond0 = Condition('resource name','integer','cres0','==',1) - cond1 = Condition('resource name','integer','cres1','==',2) - cond2 = Condition('resource name','integer','cres2','==',-2) - - cvar0 = ConditionVariable(name='cvar0',order=0,enabled=True,wait=str(dwell_time), - resource_names=['cres0',],conditions=[cond0]) - cvar1 = ConditionVariable(name='cvar1',order=1,enabled=True,wait=str(dwell_time), - resource_names=['cres1',],conditions=[cond1]) - cvar2 = ConditionVariable(name='cvar2',order=1,enabled=True,wait=str(dwell_time), - resource_names=['cres2',],conditions=[cond2]) - - # Input. - meas_res0 = Resource(getter=partial(getter, 0)) - meas_res1 = Resource(getter=partial(getter, 1)) - - meas0 = InputVariable(name='Meas 0') - meas1 = InputVariable(name='Meas 1') - - vars, num_items = sort_output_variables([var0, var1, var2, var3, var4]) - cvars = sort_condition_variables([cvar0,cvar1,cvar2]) - ctrl = sweep.SweepController([(('Res 2', res2),), (('Something', None),), (('Res 0', res0),), - (('Res 1', res1), ('Res 3', res3))], vars, num_items, - [('Meas res 0', meas_res0), ('Meas res 1', meas_res1)], [meas0, meas1], - condition_resources, cvars) - - # Callback verification buffers. - actual_values = [] - actual_measurement_values = [] - actual_writes = [] - actual_reads = [] - closed = [0] - - # Callbacks. - def data_callback(cur_time, values, measurement_values): - actual_values.append(values) - actual_measurement_values.append(measurement_values) - ctrl.data_callback = data_callback - - def close_callback(): - closed[0] += 1 - ctrl.close_callback = close_callback - - def write_callback(pos, i, value): - actual_writes.append((pos, i, value)) - ctrl.write_callback = write_callback - - def read_callback(i, value): - actual_reads.append((i, value)) - ctrl.read_callback = read_callback - - # Let it run. - start_time = time() - ctrl.run() - elapsed_time = time() - start_time - - expected_time = num_items * dwell_time.value - assert expected_time < elapsed_time, 'Took {0} s, expected at least {1} s.'.format(elapsed_time, expected_time) - - expected_res1 = [1.0, 2.0, 3.0, 4.0] - expected_res2 = [-1.0, 0.0, 1.0, 2.0] - - expected_inner_writes = list(flatten(((3, 0, x), (3, 1, x - 2.0)) for x in [1.0, 2.0, 3.0, 4.0])) - expected_writes = [(0, 0, 1.23), (1, 0, -10.0)] + list(flatten([(2, 0, x)] + expected_inner_writes - for x in [Quantity(x, 'cm-1') for x in [-1.0, -2.0]])) - - eq_(res_bufs, [ - [Quantity(x, 'cm-1') for x in [0.0, -1.0, -1.0, -2.0, -2.0, 0.0]], - [-1.0, 0.0, 1.0] + expected_res1 + [4.0, 2.5, 1.0] + expected_res1 + [4.0, 1.5, -1.0], - [1.23], - [-9.0, -1.0] + expected_res2 + expected_res2 + [2.0, -9.0], - ]) - - eq_(measurement_counts, [24, -24]) - - - # Construct predicted actual values. - expected_actual_values = [] - for x in [Quantity(x, 'cm-1') for x in [-1.0, -2.0]]: - for y in [1.0, 2.0, 3.0, 4.0]: - - # We append twice since the 0th order condition will create an extra measurement - # for every non_conditionally based measurement - expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) - expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) - - # If were at the end of order 1, we append 4 more times to have a total of 6 - # This is because in the condition checking stage, we require the 0th order condition - # and the 1st order conditions to all be true. So, cycling through entries in [0,1] - # and [0,1,2], it will take a total of 6 condition checks to get 1 in [0,1], and 2 in - # [0,1,2]. - if y == 4: - expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) - expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) - expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) - expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) - - - eq_(actual_values, expected_actual_values) - - eq_(actual_measurement_values, [(x, -x) for x in xrange(1, 25)]) - eq_(actual_writes, expected_writes) - eq_(actual_reads, list(flatten(((0, x), (1, -x)) for x in xrange(1, 25)))) - eq_(closed, [1]) - - -if __name__ == '__main__': - main() diff --git a/spacq/iteration/tests/test_variables.py b/spacq/iteration/tests/test_variables.py deleted file mode 100755 index efb9712..0000000 --- a/spacq/iteration/tests/test_variables.py +++ /dev/null @@ -1,286 +0,0 @@ -from nose.tools import assert_raises, eq_ -from unittest import main, TestCase - -from spacq.interface.units import IncompatibleDimensions, Quantity -from spacq.interface.resources import Resource -from functools import partial - -from .. import variables - -class SortOutputVariablesTest(TestCase): - def testEmpty(self): - """ - Use no variables. - """ - - sorted_variables, num_items = variables.sort_output_variables([]) - - eq_(sorted_variables, []) - eq_(num_items, 0) - - def testSingle(self): - """ - Use a single variable. - """ - - var = variables.OutputVariable(config=variables.LinSpaceConfig(-5.0, 5.0, 11), - name='Name', order=0, enabled=True, const=60.0) - - sorted_variables, num_items = variables.sort_output_variables([var]) - - eq_(sorted_variables, [(var,)]) - eq_(num_items, 11) - - def testMultiple(self): - """ - Use many variables. - """ - - vars = [ - variables.OutputVariable(config=variables.LinSpaceConfig(1.0, 5.0, 3), - name='A', order=3, enabled=True), - variables.OutputVariable(config=variables.LinSpaceConfig(11.0, 12.0, 2), - name='B', order=2, enabled=True, const=10.0), - variables.OutputVariable(config=variables.LinSpaceConfig(-99.0, 0.0), - name='D', order=1, enabled=True, const=9.0, use_const=True), - variables.OutputVariable(config=variables.LinSpaceConfig(21.0, 25.0, 20), - name='C', order=2, enabled=True), - variables.OutputVariable(config=variables.LinSpaceConfig(0.0, 0.0, 1), - name='E', order=4), - variables.OutputVariable( - name='F', order=5, enabled=True, const=5.5, use_const=True), - ] - - sorted_variables, num_items = variables.sort_output_variables(vars) - - eq_(sorted_variables, [(vars[2], vars[5]), (vars[0],), (vars[1], vars[3])]) - eq_(num_items, 6) - -class ConditionVariableTest(TestCase): - - # It goes without saying that if condition variables are upgraded - # with a general boolean parser that these tests will have to be changed. - - def testEvaluateConditions(self): - """ - See if a condition variable evaluates its conditions - properly. - """ - - # Define conditions. - c1 = variables.Condition('integer','integer',1,'<',3) #True - c2 = variables.Condition('integer','integer',1,'>',3) #False - - # Define condition variables. - cv0 = variables.ConditionVariable(1,conditions=[c1], name='cv0') - cv1 = variables.ConditionVariable(1,conditions=[c1,c1], name='cv1') - cv2 = variables.ConditionVariable(1,conditions=[c1,c2], name='cv2') - cv3 = variables.ConditionVariable(1,conditions=[c2,c2,c2,c1], name='cv3') - cv4 = variables.ConditionVariable(1,conditions=[c2,c2], name='cv4') - - # Evaluate a simple case. - eq_(cv0.evaluate_conditions(),True) - - # Check if it is ORing properly. - eq_(cv1.evaluate_conditions(),True) - eq_(cv2.evaluate_conditions(),True) - eq_(cv3.evaluate_conditions(),True) - eq_(cv4.evaluate_conditions(),False) - - def testEvaluateCondition(self): - """ - Test if evaluating conditions works properly - for different combinations of types. - """ - # Some variables to use for tests. - - res_bufs = [[], [], [], []] - - def setter(i, value): - res_bufs[i].append(value) - - def getter(i): - return res_bufs[i][0] - - - a = Quantity('5 T') - b = Quantity('50 kG') - c = 'on' - - res0 = Resource(getter=partial(getter,0), setter=partial(setter, 0)) - res0.units = 'T' - res1 = Resource(getter=partial(getter,1), setter=partial(setter, 1)) - res1.units = 'kG' - res2 = Resource(getter=partial(getter,2), setter=partial(setter, 2)) - res2.allowed_values = ['on','off'] - - res0.value = a - res1.value = b - res2.value = c - - # Check 2 quantities of equivalent units. - c1 = variables.Condition('quantity','quantity',a,'==',b) - eq_(c1.evaluate(), True) - - # Check a resource against a quantity. - c2 = variables.Condition('resource','quantity',res0,'==',a) - eq_(c2.evaluate(), True) - - # Check a resource with a resource. - c3 = variables.Condition('resource','resource',res0,'==',res1) - eq_(c3.evaluate(), True) - - # Check a resource that has an allowed value with a string. - c4 = variables.Condition('resource','string',res2,'==',c) - eq_(c4.evaluate(), True) - - # Test evaluating resource names. - resources = [('res0',res0),('res1',res1),('res2',res2)] - c3 = variables.Condition('resource name','resource name','res0','==','res1') - eq_(c3.evaluate(resources), True) - - # Check some things that should mess up. - - ## string instead of quantity. - try: - c5 = variables.Condition('resource','string',res0,'==','5 T') - eq_(c5.evaluate(), True) - except TypeError: - pass - else: - assert False, 'Expected TypeError.' - - ## not matching units. - try: - c6 = variables.Condition('quantity','quantity',Quantity('5 A'),'==',Quantity('5 T')) - eq_(c6.evaluate(), True) - except IncompatibleDimensions: - pass - else: - assert False, 'Expected IncompatibleDimensions error.' - - -class OutputVariableTest(TestCase): - def testAdjust(self): - """ - Try to adjust the values after initialization. - """ - - var = variables.OutputVariable(name='Name', order=1) - - var.config = variables.LinSpaceConfig() - - var.config.steps = 1000 - eq_(var.config.steps, 1000) - - try: - var.config.steps = -1 - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - var.wait = '1e2 ms' - eq_(var.wait, '100 ms') - - try: - var.wait = '100 Hz' - except IncompatibleDimensions: - pass - else: - assert False, 'Expected IncompatibleDimensions.' - - def testStr(self): - """ - Ensure the variable looks right. - """ - - var = variables.OutputVariable(name='Name', order=1) - - # Very short. - var.config = variables.LinSpaceConfig(0.0, 5.0, 3) - var.type = 'integer' - eq_(str(var), '[0, 2, 5]') - - # Borderline. - var.config = variables.LinSpaceConfig(1.0, 5.0, 5) - var.type = 'float' - eq_(str(var), '[1, 2, 3, 4, 5]') - - # Short enough. - var.config = variables.LinSpaceConfig(-200.0, 200.0, 401) - eq_(str(var), '[-200, -199, -198, -197, ..., 200]') - - # Far too long. - var.config = variables.LinSpaceConfig(0.0, 100000.0, 100001) - eq_(str(var), '[0, 1, 2, 3, ...]') - - # Smooth from constant. - var.smooth_from = True - eq_(str(var), '(0, 1, 2, 3, ...]') - - # And to. - var.smooth_to = True - eq_(str(var), '(0, 1, 2, 3, ...)') - - def testUnits(self): - """ - Ensure that values are wrapped with units. - """ - - var = variables.OutputVariable(name='Name', order=1) - var.type = 'quantity' - var.units = 'g.m.s-1' - var.config = variables.LinSpaceConfig(0.0, -5.0, 3) - - eq_(list(var), [Quantity(x, 'g.m.s-1') for x in [0, -2.5, -5]]) - eq_(str(var), '[0, -2.5, -5] g.m.s-1') - - # Bad combination. - var.units = None - assert_raises(ValueError, list, var) - - -class LinSpaceConfigTest(TestCase): - def testIterator(self): - """ - Create an iterator from a linear space variable. - """ - - var = variables.OutputVariable(config=variables.LinSpaceConfig(-1.0, -3.0, 5), - name='Name', order=1, enabled=True, const=10.0) - - # Non-const. - eq_(len(var), 5) - - it1 = iter(var) - eq_(list(it1), [-1.0, -1.5, -2.0, -2.5, -3.0]) - - # Const. - var.use_const = True - - eq_(len(var), 1) - - it2 = iter(var) - eq_(list(it2), [10.0]) - - -class ArbitraryConfigTest(TestCase): - def testIterator(self): - """ - Create an iterator from an arbitrary variable. - """ - - values = [8, -5, 6.6, 3, 0, 0] - - var = variables.OutputVariable(config=variables.ArbitraryConfig(values), - name='Name', order=1, enabled=True) - - eq_(len(var), len(values)) - - it = iter(var) - eq_(list(it), values) - - -if __name__ == '__main__': - main() diff --git a/spacq/iteration/variables.py b/spacq/iteration/variables.py deleted file mode 100755 index 9a2ad3c..0000000 --- a/spacq/iteration/variables.py +++ /dev/null @@ -1,327 +0,0 @@ -from itertools import groupby, islice -import numpy -import operator - -from spacq.interface.units import Quantity - - -def sort_output_variables(variables): - """ - Sort and group the variables based on their order. - - The returned values are: - variables sorted and grouped by their order - number of items in the Cartesian product of the orders - """ - - # Ignore disabled variables entirely! - variables = [var for var in variables if var.enabled] - - if not variables: - return [], 0 - - const_vars = tuple([var for var in variables if var.use_const]) - - order_attr = operator.attrgetter('order') - ordered = sorted((var for var in variables if not var.use_const), key=order_attr, reverse=True) - grouped = [tuple(vars) for order, vars in groupby(ordered, order_attr)] - - num_items = 1 - for group in grouped: - num_in_group = None - - for var in group: - num_in_var = len(var) - - if num_in_group is None or num_in_var < num_in_group: - num_in_group = num_in_var - - num_items *= num_in_group - - if const_vars: - grouped.insert(0, const_vars) - - return grouped, num_items - -def sort_condition_variables(variables): - """ - Sort and group condition variables based on their order. - This function is similar to sort_output_variables. - - The returned value is: - variables sorted and grouped by their order - - """ - - # Ignore disabled variables entirely! - variables = [var for var in variables if var.enabled] - - if not variables: - return [] - - order_attr = operator.attrgetter('order') - ordered = sorted(variables, key=order_attr, reverse=True) - grouped = [tuple(vars) for order, vars in groupby(ordered, order_attr)] - - return grouped - -class Variable(object): - """ - An abstract superclass for all variables. - """ - - def __init__(self, name, enabled=False): - self.name = name - self.enabled = enabled - - -class ConditionVariable(Variable): - """ - A condition variable. Used to define conditions to make loops in the sweep controller indefinite. - """ - - def __init__(self, order, resource_names=None, conditions=[], wait = '100 ms', *args, **kwargs): - Variable.__init__(self, *args,**kwargs) - - self.order = order - self.conditions = conditions - self._wait = Quantity(wait) - self.resource_names = resource_names - - def evaluate_conditions(self, condition_resources=None): - """ - Checks the conditions where condition_resources contains the resources required to evaluate the - conditions. - """ - # We take OR of all the conditions. - boolean = False - for condition in self.conditions: - boolean = boolean or condition.evaluate(condition_resources) - - if not self.conditions: - boolean = True - - return boolean - - @property - def wait(self): - return str(self._wait) - - @wait.setter - def wait(self, value): - wait = Quantity(value) - wait.assert_dimensions('s') - - self._wait = wait - - def __str__(self): - return '['+', '.join(map(str,self.conditions))+']' - -class Condition(object): - """ - A class used to represent a condition. - """ - - allowed_types = set(['string', 'float', 'integer', 'quantity', 'resource name','resource']) - - def __init__(self, type1, type2, arg1, op_symbol, arg2): - for type in [type1, type2]: - if type not in self.allowed_types: - raise ValueError('Condition cannot be of type {0}.'.format(type)) - - self.type1 = type1 - self.type2 = type2 - self.arg1 = arg1 - self.arg2 = arg2 - self.op_symbol = op_symbol - - def evaluate(self, resources=None): - """ - Evaluate a condition. 'resources' comes as a list of 2-tuples (name, resource obj). - """ - op = {'>':operator.gt, '==':operator.eq, '!=':operator.ne, '<':operator.lt} - - arg1_to_evaluate = self.arg1 - arg2_to_evaluate = self.arg2 - - # We retrieve the values from the resources if the arguments are resource names - if resources: - for name, resource in resources: - # Note that the ordering of the 'and' statements here makes use of python's shortcircuiting. - # Upon reversing boolean arguments, there could be a case that throws an exception if a string - # is being compared against a quantity. This is because of the overloading of __eq__ for Quantity. - if self.type1 == 'resource name' and name == self.arg1: - arg1_to_evaluate = resource.value - if self.type2 == 'resource name' and name == self.arg2: - arg2_to_evaluate = resource.value - - if self.type1 == 'resource': - arg1_to_evaluate = self.arg1.value - if self.type2 == 'resource': - arg2_to_evaluate = self.arg2.value - - boolean = op[self.op_symbol](arg1_to_evaluate, arg2_to_evaluate) - - return boolean - - - - def __str__(self): - return '{0} {1} {2}'.format(self.arg1,self.op_symbol,self.arg2) - - - - -class InputVariable(Variable): - """ - An input (measurement) variable. - """ - def __init__(self, resource_name='', *args, **kwargs): - Variable.__init__(self, *args, **kwargs) - - self.resource_name = resource_name - - -class OutputVariable(Variable): - """ - An abstract superclass for output variables. - """ - - # Maximum number of initial values to display in string form. - display_values = 4 - - # Maximum number of values to search through for the end. - search_values = 1000 - - def __init__(self, order, config=None, wait='100 ms', const=0.0, use_const=False, resource_name='', *args, **kwargs): - Variable.__init__(self, *args, **kwargs) - - self.resource_name = resource_name - - self.order = order - - if config is not None: - self.config = config - else: - self.config = LinSpaceConfig(0.0, 0.0, 1) - - # Iteration parameters. - self._wait = Quantity(wait) - self.const = const - self.use_const = use_const - - # Smooth set. - self.smooth_steps = 10 - self.smooth_from = False - self.smooth_to = False - self.smooth_transition = False - - self.type = 'float' - self.units = None - - @property - def wait(self): - return str(self._wait) - - @wait.setter - def wait(self, value): - wait = Quantity(value) - wait.assert_dimensions('s') - - self._wait = wait - - - def with_type(self, value): - """ - Set to the correct type, and wrap with the correct units. - """ - - if self.type == 'integer': - return int(value) - elif self.type == 'float': - return value - elif self.type == 'quantity' and self.units is not None: - return Quantity(value, self.units) - else: - raise ValueError('Invalid variable setup; type: {0}, units: {1}'.format(self.type, self.units)) - - @property - def raw_iter(self): - if self.use_const: - return [self.const] - else: - return iter(self.config) - - def __iter__(self): - return (self.with_type(x) for x in self.raw_iter) - - def __len__(self): - if self.use_const: - return 1 - else: - return len(self.config) - - def __str__(self): - found_values = islice(self.raw_iter, 0, self.search_values + 1) - - if self.type == 'integer': - found_values = [int(x) for x in found_values] - else: - found_values = list(found_values) - - shown_values = ', '.join('{0:g}'.format(x) for x in found_values[:self.display_values]) - - if len(found_values) > self.display_values: - if len(found_values) > self.display_values + 1: - shown_values += ', ...' - - if len(found_values) <= self.search_values: - shown_values += ', {0:g}'.format(found_values[-1]) - - smooth_from = '(' if not self.use_const and self.smooth_from else '[' - smooth_to = ')' if not self.use_const and self.smooth_to else ']' - units = ' {0}'.format(self.units) if self.units is not None else '' - return '{0}{1}{2}{3}'.format(smooth_from, shown_values, smooth_to, units) - - -class LinSpaceConfig(object): - """ - Linear space variable configuration. - """ - - def __init__(self, initial=0.0, final=0.0, steps=1): - self.initial = initial - self.final = final - self.steps = steps - - @property - def steps(self): - return self._steps - - @steps.setter - def steps(self, value): - if value <= 0: - raise ValueError('Number of steps must be positive, not "{0}".'.format(value)) - - self._steps = value - - def __iter__(self): - return iter(numpy.linspace(self.initial, self.final, self.steps)) - - def __len__(self): - return self.steps - - -class ArbitraryConfig(object): - """ - Variable configuration for arbitrary values. - """ - - def __init__(self, values): - self.values = values - - def __iter__(self): - return iter(self.values) - - def __len__(self): - return len(self.values) diff --git a/spacq/iteration/virtual_variables.py b/spacq/iteration/virtual_variables.py deleted file mode 100644 index c715277..0000000 --- a/spacq/iteration/virtual_variables.py +++ /dev/null @@ -1,260 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -import numpy -from itertools import izip, groupby -import operator -from spacq.gui.tool.box import MessageDialog -from functools import partial, wraps - -# TODO: consider need for constant variable type in virtual? -# TODO: fix message dialog - -# This is needed so don't get recursion depth errors. Decorators -def update_current_f(f): - @wraps(f) - def wrapped(self): - self.current_f = f.__name__ - - log.debug('Entering function: {0}'.format(self.current_f)) - - return f(self) - - return wrapped - - -# currently in config/variables.py like linspaceConfig -class virtLinSpaceConfig(object): - """ - Like LinSpaceConfig but with order... - """ - def __init__(self, name='var', initial=0.0, final=0.0, steps=1, order=1): - self.name = name - self.initial = initial - self.final = final - self.steps = steps - self._order = order - - self.close_callback= None - - @property - def steps(self): - return self._steps - - @property - def order(self): - return self._order - - @steps.setter - def steps(self, value): - if value <= 0: - raise ValueError('Number of steps must be positive, not "{0}".'.format(value)) - - self._steps = value - - def __iter__(self): - return iter(numpy.linspace(self.initial, self.final, self.steps)) - - def __len__(self): - return self.steps - -class DependentConfig(object): - """ - Like LinSpaceConfig but with order... - """ - def __init__(self, name='var', expression='1'): - self.name = name - self.expression = expression - - - def DependentFunctionMath(self, virt_headings, virt_values): - - editExpression = self.expression - - for i,heading in enumerate(virt_headings): - # shouldnt be issue because disabled are always on tail? - editExpression = editExpression.replace(heading,'virt_values[:,{0}]'.format(i)) - - # if nothing gets entered for a enabled variable - if editExpression is None or editExpression == '': - result = numpy.zeros((1,len(virt_values)))[0] - else: - try: - # Allows for constant input - result = numpy.ones((1,len(virt_values)))[0]*float(editExpression) - except ValueError as e: - try: - result = eval(editExpression) - except NameError as e: - # MessageDialog(self, str(e), 'Could not evaluate').Show() - print('Could not evaluate.') - - tempEval = eval(editExpression) - - return result - -# something like SweepController: -class virtSweepController(object): - def __init__(self, variables, num_items): - - # Sorted by order at this point - self.variables = variables - self.num_items = num_items - - self.names = [] - - # Number of variables - self.var_count = 0 - for order in self.variables: - self.var_count += len(order) - for var in order: - self.names.append(var.name) - - # for writing to csv - self.value_history = numpy.zeros([self.num_items,self.var_count]) - - # Count for iterations of needed outputs - self.item = -1 - - # definitely needed - self.orders = [vars[0].order for vars in self.variables] - self.orders.reverse() - - - def compute_order_periods(self): - """ - This function computes the number of elements iterated before each order changes. - """ - periods = [] - orders = [] - - for group in reversed(self.variables): - #skip if vars in group are consts. - # no such const, or maybe we should? - # if group[0].use_const != 1: - num_in_group = None - - #get the length of the group - for var in group: - num_in_var = len(var) - - if num_in_group is None or num_in_var < num_in_group: - num_in_group = num_in_var - - #append the period of this group and its order. - if not periods: - periods.append(num_in_group) - else: - periods.append(periods[-1]*num_in_group) - - orders.append(group[0].order) - - # no conditions - # create a dict. - self.order_periods = dict(zip(orders, periods)) - - def create_iterator(self, pos): - """ - Create an iterator for an order of variables. - """ - return izip(*(iter(var) for var in self.variables[pos])) - - # maybe some renaming - def sweepTable(self): - - """ - Initialize values and possibly devices. - """ - - self.iterators = None - self.current_values = None - self.last_values = None - - self.compute_order_periods() - - # TODO: fill in - while self.item < self.num_items - 1: - - """ - Get the next set of values from the iterators. - """ - - self.item += 1 - if self.current_values is not None: - self.last_values = self.current_values[:] - - if self.iterators is None: - # First time around. - self.iterators = [] - for pos in xrange(len(self.variables)): - self.iterators.append(self.create_iterator(pos)) - - self.current_values = [it.next() for it in self.iterators] - self.changed_indices = range(len(self.variables)) - # create an other copy that we will not change - self.full_indices = range(len(self.variables)) - - # Calculate where the end of each order is. - else: - pos = len(self.variables) - 1 - while pos >= 0: - try: - # Gets new values here - self.current_values[pos] = self.iterators[pos].next() - break - except StopIteration: - self.iterators[pos] = self.create_iterator(pos) - self.current_values[pos] = self.iterators[pos].next() - - pos -= 1 - - self.changed_indices = range(pos, len(self.variables)) - - - """ - Write the next values to the table - """ - - # fills in value_history - counter = 0 - for pos in self.full_indices[0:]: - for i, (var,value) in enumerate(zip(self.variables[pos],self.current_values[pos])): - self.value_history[self.item][counter] = value - counter += 1 - - - -# copeid from iteration/variables.py -def sort_output_variables(variables): - """ - Sort and group the variables based on their order. - - The returned values are: - variables sorted and grouped by their order - number of items in the Cartesian product of the orders - """ - - # Ignore disabled variables entirely! - # no enabled used right now - # variables = [var for var in variables if var.enabled] - - if not variables: - return [], 0 - - order_attr = operator.attrgetter('order') - ordered = sorted((var for var in variables), key=order_attr, reverse=True) - grouped = [tuple(vars) for order, vars in groupby(ordered, order_attr)] - - num_items = 1 - for group in grouped: - num_in_group = None - - for var in group: - num_in_var = len(var) - - if num_in_group is None or num_in_var < num_in_group: - num_in_group = num_in_var - - num_items *= num_in_group - - return grouped, num_items diff --git a/spacq/tests/__init__.py b/spacq/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/tests/tool/__init__.py b/spacq/tests/tool/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/tests/tool/box.py b/spacq/tests/tool/box.py deleted file mode 100644 index dceb633..0000000 --- a/spacq/tests/tool/box.py +++ /dev/null @@ -1,80 +0,0 @@ -import logging -log = logging.getLogger(__name__) - -from nose.plugins.skip import SkipTest -import re -from unittest import TestCase - -from testconfig import config as tc - - -class AssertHandler(logging.handlers.BufferingHandler): - """ - A logging handler that allows making assertions based on its contents. - """ - - def __init__(self, capacity=100, *args, **kwargs): - """ - Add ourselves to the main logger. - """ - - logging.handlers.BufferingHandler.__init__(self, capacity, *args, **kwargs) - - logging.getLogger().addHandler(self) - - def assert_logged(self, level, msg, ignore_case=True, literal=False): - """ - Assert that a message matching the level and regular expression has been logged. - """ - - level = level.lower() - - re_flags = 0 - if ignore_case: - re_flags |= re.IGNORECASE - - for record in self.buffer: - if record.levelname.lower() == level: - if (literal and msg == record.msg or - not literal and re.search(msg, record.msg, re_flags)): - return - - assert False, 'Log message not found at level "{0}": {1}'.format(level, msg) - - -class DeviceServerTestCase(TestCase): - """ - Class for a device server test. - """ - - mock = False - - def obtain_device(self, impl=None, manufacturer=None, model=None): - """ - Try to get a handle for a physical device. - """ - - if self.mock: - return impl() - - all_devices = tc['devices'].values() - - if manufacturer is None or model is None: - if impl is not None: - return impl(**all_devices[-1]['address']) - else: - return all_devices[-1] - - potential_devices = [dev for dev in all_devices if 'address' in dev and - dev['manufacturer'] == manufacturer and dev['model'] == model] - - for device in potential_devices: - try: - if impl is not None: - return impl(**device['address']) - else: - return device - except Exception as e: - log.info('Could not connect to device at "{0}": {1}'.format(device['address'], e)) - - raise SkipTest('Could not connect to device.') diff --git a/spacq/tool/__init__.py b/spacq/tool/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/tool/box.py b/spacq/tool/box.py deleted file mode 100644 index fff3d2b..0000000 --- a/spacq/tool/box.py +++ /dev/null @@ -1,211 +0,0 @@ -from functools import wraps -from itertools import chain -from numpy import linspace, meshgrid, sort, unique, where, nan, zeros, ones, arange, fliplr -from numpy import min as npmin -from scipy.interpolate import griddata, interp1d - -""" -Generic tools. -""" - - -def flatten(iterable): - """ - Flatten an iterable by one level. - """ - - return chain.from_iterable(iterable) - - -def sift(items, cls): - """ - Filter out items which are not instances of cls. - """ - - return [item for item in items if isinstance(item, cls)] - -def get_mask(x,y, tx, ty): - dx = (tx[-1] - tx[0])/(tx.size -1) - dy = (ty[-1] - ty[0])/(ty.size -1) - - d2 = dx**2 + dy**2 - - xgrid = meshgrid(x,tx) - ygrid = meshgrid(y,ty) - - xdist = (xgrid[0] - xgrid[1])**2 - ydist = (ygrid[0] - ygrid[1])**2 - - mask = ones((tx.shape[0], ty.shape[0])) - - for i in range (mask.shape[0]): - for j in range (mask.shape[1]): - mask[i,j] = npmin(xdist[i] + ydist[j]) - - mask = (mask < d2)*1 - mask = where(mask, mask, nan) - - return mask.T - - - -def triples_to_mesh(x, y, z, max_mesh=[-1,-1], has_mask=False): - """ - Convert 3 equal-sized lists of co-ordinates into an interpolated 2D mesh of z-values. - - Returns a tuple of: - the mesh - the x bounds - the y bounds - the z bounds - """ - - x_values, y_values = sort(unique(x)), sort(unique(y)) - - if (all (item > 0 for item in max_mesh)): - display_len_x = min (len(x_values), max_mesh[0]) - display_len_y = min (len(y_values), max_mesh[1]) - else: - display_len_x = len(x_values) - display_len_y = len(y_values) - - x_space = linspace(x_values[0], x_values[-1], display_len_x) - y_space = linspace(y_values[0], y_values[-1], display_len_y) - - target_x, target_y = meshgrid(x_space, y_space) - - target_z = griddata((x, y), z, (target_x, target_y), method='cubic') - - if (has_mask): - mask = get_mask (x, y, x_space, y_space) - target_z = target_z * mask - - return (target_z, (x_values[0], x_values[-1]), (y_values[0], y_values[-1]), - (min(z), max(z))) - -def triples_to_mesh_y(x, y, z, max_mesh=[-1,-1]): - """ - Convert 3 equal-sized lists of co-ordinates into an interpolated mesh of z-values; with - interpolation along the y-axis only. The x-data must be of the form - [x0,x0,x0...x1,x1,x1...,xn,xn,xn...xn] with each value xi repeaded the same number of times. - Otherwiese unexpected behaviour follows. - - Returns a tuple of: - the mesh - the x bounds - the y bounds - the z bounds - """ - x_values, y_values = sort(unique(x)), sort(unique(y)) - - display_len_x = len(x_values) - if (max_mesh[1]>0): - display_len_y = min (len(y_values), max_mesh[1]) - else: - display_len_y = len(y_values) - - x_space = x_values - y_space = linspace(y_values[0], y_values[-1], display_len_y) - xperiod = float(len(x)) / len(x_values) - - target_z = zeros([display_len_x, display_len_y]) - - for i, xi in enumerate(x_space): - yrange = arange(i*xperiod, (i+1)*xperiod-1).tolist() - fy = interp1d (y[yrange], z[yrange], kind='cubic', bounds_error=False) - tempy = fy(y_space) - target_z[i] = tempy - - target_z = target_z.T - if(x[0] - x[-1])>0: - target_z = fliplr(target_z) - - return (target_z, (x_values[0], x_values[-1]), (y_values[0], y_values[-1]), - (min(z), max(z))) - - -class Enum(set): - """ - An enumerated type. - - >>> e = Enum(['a', 'b', 'c']) - >>> e.a - 'a' - >>> e.d - ... - AttributeError: 'Enum' object has no attribute 'd' - """ - - def __getattribute__(self, name): - if name in self: - return name - else: - return set.__getattribute__(self, name) - - -class PubDict(dict): - """ - A locking, publishing dictionary. - """ - - def __init__(self, lock, send, topic, *args, **kwargs): - """ - lock: A re-entrant lock which supports context management. - send: Message-sending method of a PubSub publisher. - topic: The topic on which to send messages. - """ - - dict.__init__(self, *args, **kwargs) - - self.lock = lock - self.send = send - self.topic = topic - - def __setitem__(self, k, v): - """ - Note: Values cannot be overwritten, to ensure that removal is always handled explicitly. - """ - - with self.lock: - if k in self: - raise KeyError(k) - - if v is None: - raise ValueError('No value given.') - - dict.__setitem__(self, k, v) - - self.send('{0}.added'.format(self.topic), name=k, value=v) - - def __delitem__(self, k): - with self.lock: - dict.__delitem__(self, k) - - self.send('{0}.removed'.format(self.topic), name=k) - - -class Synchronized(object): - """ - A decorator for methods which must be synchronized within an object instance. - """ - - @staticmethod - def __call__(f): - @wraps(f) - def decorated(self, *args, **kwargs): - with self.lock: - return f(self, *args, **kwargs) - - return decorated - - -class Without(object): - """ - A no-op object for use with "with". - """ - - def __enter__(self, *args, **kwargs): - return None - - def __exit__(self, *args, **kwargs): - return False diff --git a/spacq/tool/tests/__init__.py b/spacq/tool/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/spacq/tool/tests/test_box.py b/spacq/tool/tests/test_box.py deleted file mode 100644 index 454638e..0000000 --- a/spacq/tool/tests/test_box.py +++ /dev/null @@ -1,324 +0,0 @@ -from nose.tools import eq_ -from numpy import arange, linspace, repeat -from numpy.testing import assert_array_equal, assert_array_almost_equal -from pubsub import pub -from threading import RLock, Thread -import time -from unittest import main, TestCase - -from .. import box - - -class FlattenTest(TestCase): - def testEmpty(self): - """ - Flatten nothing. - """ - - eq_(list(box.flatten([])), []) - - def testSingle(self): - """ - Flatten one thing. - """ - - eq_(list(box.flatten([(1, 2, 3, 4, 5, 6, 7)])), [1, 2, 3, 4, 5, 6, 7]) - - def testMany(self): - """ - Flatten all the things. - """ - - eq_(list(box.flatten([(1, 2, 3), [4, 5, 6], {7: 8}])), [1, 2, 3, 4, 5, 6, 7]) - - -class SiftTest(TestCase): - def testEmpty(self): - """ - Sift nothing. - """ - - eq_(box.sift([], object), []) - - def testAllSame(self): - """ - Either keep all or remove all. - """ - - items = [object() for _ in xrange(5)] - - eq_(box.sift(items, object), items) - eq_(box.sift(items, Exception), []) - - def testVaried(self): - """ - All sorts of objects. - """ - - items = [ValueError(), TypeError(), KeyError(), 5] - - eq_(box.sift(items, object), items) - eq_(box.sift(items, int), [items[3]]) - eq_(box.sift(items, Exception), items[0:3]) - eq_(box.sift(items, ValueError), [items[0]]) - - -class TriplesToMeshTest(TestCase): - def testSimple(self): - """ - No interpolation required. - """ - - x = [1, 2, 3, 4] * 3 # [1, 2, 3, 4, 1, ...] - y = repeat([5, 6, 7], 4) # [5, 5, 5, 5, 6, ...] - z = linspace(0, -11, 12) # [0, -1, -2, -3, ...] - - result, x_bounds, y_bounds, z_bounds = box.triples_to_mesh(x, y, z) - - assert_array_equal(result, z.reshape(3, 4)) - eq_(x_bounds, (1, 4)) - eq_(y_bounds, (5, 7)) - eq_(z_bounds, (-11, 0)) - - def testInterpolated(self): - """ - Some interpolation required. - """ - - x = [0, 0, 0.25, 1, 1] - y = [0, 1, 0, 0, 1] - z = [1, 2, 1.5, 3, 4] - - result, x_bounds, y_bounds, z_bounds = box.triples_to_mesh(x, y, z) - - expected = [ - [1, 2, 3], - [2, 3, 4], - ] - - assert_array_almost_equal(result, expected) - eq_(x_bounds, (0, 1)) - eq_(y_bounds, (0, 1)) - eq_(z_bounds, (1, 4)) - - def testBigDataSet(self): - """ - One hundred thousand evenly-spaced data points. - """ - - x = range(1000) * 100 - y = repeat(range(100), 1000) - z = arange(0, 100000) - - result, x_bounds, y_bounds, z_bounds = box.triples_to_mesh(x, y, z) - - assert_array_equal(result, z.reshape(100, 1000)) - eq_(x_bounds, (0, 999)) - eq_(y_bounds, (0, 99)) - eq_(z_bounds, (0, 99999)) - - -class EnumTest(TestCase): - def testEmpty(self): - """ - A useless object. - """ - - e = box.Enum() - - try: - e.anything - except AttributeError: - pass - else: - assert False, 'Expected AttributeError.' - - eq_(len(e), 0) - - def testNotEmpty(self): - """ - A regular enum. - """ - - values = ['cow', 'moon', 'dish', 'spoon'] - - e = box.Enum(values) - - eq_(len(e), len(values)) - - for v in values: - eq_(getattr(e, v), v) - - e.cow, e.moon, e.dish, e.spoon - - try: - e.diddle - except AttributeError: - pass - else: - assert False, 'Expected AttributeError.' - - def testDuplicates(self): - """ - Check uniqueness and equality testing. - """ - - e = box.Enum(['a'] * 100 + ['b'] * 50) - f = box.Enum(['b', 'a']) - - eq_(e, f) - - -class PubDictTest(TestCase): - def testSimple(self): - """ - Run through some valid cases. - """ - - data = [] - - def msg_added(name, value): - data.append((name, value)) - - def msg_removed(name): - data.append((name,)) - - p = pub.Publisher() - - p.subscribe(msg_added, 'test.added') - p.subscribe(msg_removed, 'test.removed') - - pd = box.PubDict(RLock(), p.sendMessage, 'test') - pd['a'] = 'abc' - pd['b'] = 'def' - pd['c'] = 'ghi' - del pd['a'] - pd['a'] = 'jkl' - del pd['c'] - - expected = {'a': 'jkl', 'b': 'def'} - expected_data = [('a', 'abc'), ('b', 'def'), ('c', 'ghi'), ('a',), ('a', 'jkl'), ('c',)] - - eq_(pd, expected) - eq_(data, expected_data) - - def testLocked(self): - """ - Attempt a compound operation. - """ - - pd = box.PubDict(RLock(), pub.Publisher().sendMessage, 'test') - - pd['a'] = 'abc' - - with pd.lock: - del pd['a'] - pd['a'] = 'def' - - expected = {'a': 'def'} - - eq_(pd, expected) - - def testInvalid(self): - """ - Try some bad scenarios. - """ - - pd = box.PubDict(RLock(), pub.Publisher().sendMessage, 'test') - - # Setting None. - try: - pd['a'] = None - except ValueError: - pass - else: - assert False, 'Expected ValueError.' - - # Overwriting. - pd['a'] = 'abc' - try: - pd['a'] = 'def' - except KeyError: - pass - else: - assert False, 'Expected KeyError.' - - -class SynchronizedTest(TestCase): - class SynchronizedObject(object): - def __init__(self): - self.buf = [] - - self.lock = RLock() - - @box.Synchronized() - def do(self, values): - for i in xrange(values): - self.buf.append(i) - time.sleep(0.001) - - - class SynchronizedThread(Thread): - def __init__(self, obj, times, values): - Thread.__init__(self) - - self.obj = obj - self.times = times - self.values = values - - def run(self): - for i in xrange(self.times): - self.obj.do(self.values) - - - def testSynchronization(self): - """ - Ensure that synchronized methods are called in the correct order. - """ - - num_threads = 4 - - times = 4 - values = 5 - - obj = SynchronizedTest.SynchronizedObject() - - thrs = [] - for _ in xrange(num_threads): - thrs.append(SynchronizedTest.SynchronizedThread(obj, times, values)) - - for thr in thrs: - thr.start() - - for thr in thrs: - thr.join() - - eq_(obj.buf, range(values) * times * num_threads) - - -class WithoutTest(TestCase): - def testWith(self): - """ - Use Without in with. - """ - - with box.Without(): - with box.Without() as w: - assert w is None - - def testException(self): - """ - Ensure that exceptions are passed through. - """ - - try: - with box.Without(): - raise IndexError - except IndexError: - pass - else: - assert False, 'Expected IndexError.' - - -if __name__ == '__main__': - main() From 0170709ebcdaf705d5657120026d11a752b7c28f Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:30:05 -0400 Subject: [PATCH 06/15] Delete CHANGELOG.rst --- CHANGELOG.rst | 103 -------------------------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 CHANGELOG.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100644 index e188044..0000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,103 +0,0 @@ -####### -Changes -####### -* 2019-08-09: **2.0.4** - Zachary Parrott - -* Added ability to use a CSV file to generate values for output variables. -* Additional program in \examples for setting up a virtual sweep titled 'Virtual Setup' - - -* 2015-04-13: **2.0.2b** - Kaveh Gharavi (kayghar@gmail.com) - -* Memory issues bugging plotting of large data files sloved; merge into second branch * -* Enhancements made by Kyle brought into second branch (version-merge-remote). - - -* 2014-04-10: **2.0.1a** - - Kaveh Gharavi is now the maintainer - email: kayghar@gmail.com - - Enhancements: - - * Added Implementation for Agilent 8753ET transmission/reflection network analyzer - * Added Implementation for Stanford Research Systems SR830 digital lock-in amplifier - * Added menu feature 'Math->Function' to Data Explorer - - - -* 2012-12-17: **2.0.0a1** - - Enhancements: - - * Added Cryomagnetics Model 4G power supply. - * Added condition variables throughout the code. - * Added dynamic quantity wrappers. - * Made measurement windows default to having "Capture" checked. - * Y-axis of measurement plots displays units. Currently just given in terms of fundamental SI units. - * Make title of Acquisition reflect version number. - - Fixes: - - * If the units of a resource are changed, the GUI display units of the resource will now change too. - * Fix runscripts script so that it can run files that have been made executable by Git. - - To be completed for 2.0.0: - - * Test the GUI for model4g (see TODOS). - * Use runtests script with device as opposed to just the mock device. - * Check rounding on Amps for the model 4g, and make changes in code for this (see TODOs). - * Test the *rst command with the model 4g (see TODO in source code). - -* 2012-09-19: **1.3.0** - - Enhancements: - - * Added Lakeshore TC335 device. - -* 2012-08-15: **1.2.0** - - Enhancements: - - * Added agilent dm34401a multimeter device. - -* 2012-07-30: **1.1.0** - - Enhancements: - - * Added implementation for older (6 channel) voltage source and Keithley voltage source. - * Added derivative functionality to data_explorer app - - Fixes: - - * Fixed a mismatch between data_explorer and installed python scripts - -* 2011-10-18: **1.0.3** - - Fixes: - - * Prevented pubsub messages from being parsed in the wrong thread - * Sped up interface when using variables with units - -* 2011-08-25: **1.0.2** - - Fixes: - - * Removed race condition in GUI - * Minor documentation updates - -* 2011-08-23: **1.0.1** - - Enhancements: - - * Changed DPO7104 to use "fastframe" averaging - - Fixes: - - * Minor documentation updates - -* 2011-08-22: **1.0.0** - - The initial release. From d61095fba4b893906b8a1755a966c8c5b081154e Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:30:16 -0400 Subject: [PATCH 07/15] Delete LICENSE --- LICENSE | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f976fdc..0000000 --- a/LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -Copyright (c) 2011, Dmitri Iouchtchenko. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 2d7a251e37bec488ea709d262e3caa4600c9a410 Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:30:25 -0400 Subject: [PATCH 08/15] Delete MANIFEST.in --- MANIFEST.in | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 62f88a4..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include *.rst -include test-config.py -recursive-include examples *.py From 968ebdfd14f7bc3c52c9ec63b0ca62a056a10d04 Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:30:32 -0400 Subject: [PATCH 09/15] Delete SEP_tuning.py --- SEP_tuning.py | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 SEP_tuning.py diff --git a/SEP_tuning.py b/SEP_tuning.py deleted file mode 100644 index 6d90d6b..0000000 --- a/SEP_tuning.py +++ /dev/null @@ -1,31 +0,0 @@ -from spacq.interface.units import Quantity - -# initialize voltage source -from spacq.devices.basel.dacsp927 import dacsp927 -kwargs1 = {'host_address':'192.168.0.5'} -vsource = dacsp927(**kwargs1) -port1 = vsource.subdevices['port1'] - -# initialize multimeter -from spacq.devices.agilent.dm34410a import DM34410A -kwargs2 = {'ip_address':'192.168.0.7'} -multimeter = DM34410A(**kwargs2) - -import time -target = Quantity(1, units='V') -while True: - v_set = port1.voltage - v_meas = multimeter.reading - print(v_meas) - next_step = target - v_meas - if next_step < Quantity(1.2, 'uV'): #limit of DAC resolution - break - else: - next_voltage = v_set + next_step - port1.voltage = next_voltage - time.sleep(1) - -time.sleep(5) -port1.voltage = Quantity(0, units='V') -time.sleep(0.5) -v_meas = multimeter.reading \ No newline at end of file From 8abd4912e1d897412b0d030ea4b0aff06ff432bc Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:30:40 -0400 Subject: [PATCH 10/15] Delete runtests --- runtests | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100755 runtests diff --git a/runtests b/runtests deleted file mode 100755 index ebfde8a..0000000 --- a/runtests +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -if (( $# == 0 )); then - EXCLUDE="--exclude=server" -else - EXCLUDE="" -fi - -TEST_CONFIG_PATH=~/.spacq-test-config.py -if [[ -r "${TEST_CONFIG_PATH}" ]]; then - TEST_CONFIG="--tc-file ${TEST_CONFIG_PATH} --tc-format python" -else - TEST_CONFIG="" -fi - -nosetests $TEST_CONFIG --exclude=test-config.py $EXCLUDE $@ --exe From eb68fc88b849de12bf1f7ab6858c719220d478cb Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:30:48 -0400 Subject: [PATCH 11/15] Delete test-config.py --- test-config.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100755 test-config.py diff --git a/test-config.py b/test-config.py deleted file mode 100755 index 219ff56..0000000 --- a/test-config.py +++ /dev/null @@ -1,34 +0,0 @@ -global config - -# Addresses of devices with which to test. -config['devices'] = {} -config['devices']['AWG5014B'] = { - 'address': {'ip_address': '192.0.2.123'}, - 'manufacturer': 'Tektronix', - 'model': 'AWG5014B', -} -config['devices']['DM34410A'] = { - 'address': {'gpib_board': 0, 'gpib_pad': 1}, - 'manufacturer': 'Agilent', - 'model': '34410A', -} -config['devices']['VoltageSource'] = { - 'address': {'usb_resource': 'USB0::0x3923::0x7166::01234567::RAW'}, #This is the wrong address - 'manufacturer': 'IQC', - 'model': 'Voltage source', -} -config['devices']['ch6VoltageSource'] = { - 'address': {'usb_resource': 'USB0::0x3923::0x7166::01300DB9::RAW'}, - 'manufacturer': 'IQC', - 'model': 'ch6 Voltage source', -} -config['devices']['TC335'] = { - 'address': {'gpib_board': 0, 'gpib_pad': 1}, #This address is wrong. - 'manufacturer': 'Lakeshore', - 'model': '335 Temperature Controller', -} -config['devices']['Model4G'] = { - 'address': {'gpib_board': 0, 'gpib_pad': 23}, - 'manufacturer': 'Cryomagnetics', - 'model': 'Model 4G', -} From b55eca49a6e890ad063d6f2d6bd41510cbfb6112 Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:30:56 -0400 Subject: [PATCH 12/15] Delete README.rst --- README.rst | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 README.rst diff --git a/README.rst b/README.rst deleted file mode 100644 index 56c5cbc..0000000 --- a/README.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************* -Spanish Acquisition -******************* - -Spanish Acquisition is a Python package for interfacing with test & measurement devices (primarily SCPI over Ethernet and GPIB) and building user interfaces for running experiments. - -The compiled documentation is `available online `_. - -As of version 1.1.0, further development of Spanish Acquisition has been done by the Quantum Spintronics Group at the Institute for Quantum Computing. Dmitri Iouchtchenko is responsible for writing the original code (<= v1.0.3), which is available `here `_. - -It is released under the FreeBSD (2-clause BSD) license. See the ``LICENSE`` file for details. From 9f98f2cb92bf0b2e5251e2207da1f895d0de3798 Mon Sep 17 00:00:00 2001 From: zmerino <31289560+zmerino@users.noreply.github.com> Date: Thu, 17 Mar 2022 12:31:03 -0400 Subject: [PATCH 13/15] Delete setup.py --- setup.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 1b1b1a5..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python2 - -from setuptools import setup, find_packages - - -def included_package(p): - return p.startswith('spacq.') or p == 'spacq' - - -setup( - name='SpanishAcquisition', - version='2.1', - author='Dmitri Iouchtchenko', - author_email='diouchtc@uwaterloo.ca', - maintainer='Kyle Willick', - maintainer_email='kyle.willick@uwaterloo.ca', - description='Package for interfacing with devices and building user ' - 'interfaces.', - license='BSD', - url='https://github.com/mainCSG/SpanishAcquisitionIQC', - packages=[p for p in find_packages() if included_package(p)], - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', - ], -) From fe8cc2025c20ff0d0c318538604c4c79bea23d50 Mon Sep 17 00:00:00 2001 From: zmerino Date: Fri, 18 Mar 2022 12:21:20 -0400 Subject: [PATCH 14/15] uploading SA code from dil fridge pc --- .gitignore | 0 CHANGELOG.rst | 0 LICENSE | 0 MANIFEST.in | 0 README.rst | 0 docs/.gitignore | 0 docs/Makefile | 0 docs/conf.py | 0 docs/devel/general_info.rst | 0 docs/devel/index.rst | 0 docs/devel/packages/devices/awg5014b.rst | 0 docs/devel/packages/devices/dpo7104.rst | 0 docs/devel/packages/devices/index.rst | 0 docs/devel/packages/devices/model4g.rst | 0 docs/devel/packages/gui.rst | 0 docs/devel/packages/index.rst | 0 docs/devel/packages/interface.rst | 0 docs/devel/packages/iteration.rst | 0 docs/devel/pulse_program_execution.rst | 0 docs/index.rst | 0 docs/installation.rst | 0 docs/user/examples/acquisition.rst | 0 docs/user/examples/acquisition_acquiring.png | Bin docs/user/examples/data_explorer.rst | 0 docs/user/examples/data_explorer_menu.png | Bin docs/user/examples/index.rst | 0 docs/user/general_concepts/devices.rst | 0 docs/user/general_concepts/index.rst | 0 docs/user/general_concepts/resources.rst | 0 docs/user/general_concepts/variables.rst | 0 docs/user/gui/action/data_capture.rst | 0 docs/user/gui/action/data_capture_panel.png | Bin docs/user/gui/action/data_capture_panel.xcf | Bin docs/user/gui/action/data_capture_sweep.png | Bin docs/user/gui/action/data_capture_sweep.xcf | Bin docs/user/gui/action/index.rst | 0 docs/user/gui/action/smooth_reset.rst | 0 docs/user/gui/action/smooth_reset_panel.png | Bin docs/user/gui/action/smooth_reset_panel.xcf | Bin docs/user/gui/config/device_config.rst | 0 docs/user/gui/config/device_config_connection.png | Bin docs/user/gui/config/device_config_connection.xcf | Bin docs/user/gui/config/device_config_list.png | Bin docs/user/gui/config/device_config_list.xcf | Bin docs/user/gui/config/device_config_resources.png | Bin docs/user/gui/config/device_config_resources.xcf | Bin docs/user/gui/config/index.rst | 0 docs/user/gui/config/measurement_config.rst | 0 docs/user/gui/config/measurement_config_list.png | Bin .../gui/config/measurement_config_list_settings.png | Bin docs/user/gui/config/measurement_config_scalar.png | Bin docs/user/gui/config/measurement_config_scalar.xcf | Bin .../config/measurement_config_scalar_settings.png | Bin docs/user/gui/config/measurement_config_scaling.png | Bin docs/user/gui/config/pulse_program_config.rst | 0 .../gui/config/pulse_program_config_acquisition.png | Bin .../user/gui/config/pulse_program_config_delays.png | Bin .../gui/config/pulse_program_config_outputs.png | Bin docs/user/gui/config/variable_config.rst | 0 .../gui/config/variable_config_condition_editor.png | Bin .../gui/config/variable_config_condition_editor.xcf | Bin .../variable_config_condition_variable_editor.png | Bin .../variable_config_condition_variable_editor.xcf | Bin docs/user/gui/config/variable_config_editor.png | Bin docs/user/gui/config/variable_config_editor.xcf | Bin docs/user/gui/config/variable_config_list.png | Bin docs/user/gui/config/variable_config_list.xcf | Bin docs/user/gui/display/index.rst | 0 docs/user/gui/display/plots/colormapped.rst | 0 .../gui/display/plots/colormapped_hilighted.png | Bin docs/user/gui/display/plots/colormapped_normal.png | Bin docs/user/gui/display/plots/index.rst | 0 docs/user/gui/display/plots/surface.rst | 0 docs/user/gui/display/plots/surface_normal.png | Bin docs/user/gui/display/plots/surface_waveform.png | Bin docs/user/gui/display/plots/two_dimensional.png | Bin docs/user/gui/display/plots/two_dimensional.rst | 0 docs/user/gui/display/table.png | Bin docs/user/gui/display/table.rst | 0 docs/user/gui/display/table_filter_editor.png | Bin docs/user/gui/display/table_filter_list.png | Bin docs/user/gui/index.rst | 0 docs/user/index.rst | 0 docs/user/pulse_programs.rst | 0 docs/user/pulse_programs_acquisition.png | Bin docs/user/pulse_programs_multiple_01.png | Bin docs/user/pulse_programs_multiple_02.png | Bin docs/user/pulse_programs_single.png | Bin examples/SpanishAcquisition.egg-info/PKG-INFO | 0 examples/SpanishAcquisition.egg-info/SOURCES.txt | 0 .../dependency_links.txt | 0 examples/SpanishAcquisition.egg-info/top_level.txt | 0 examples/__init__.py | 0 examples/dist/SpanishAcquisition-2.0.2b-py2.6.egg | Bin examples/virtual_setup.py | 0 setup.py | 0 spacq/__init__.py | 0 spacq/devices/abstract_device.py | 0 spacq/devices/agilent/__init__.py | 0 spacq/devices/agilent/dm34401a.py | 0 spacq/devices/agilent/dm34410a.py | 0 spacq/devices/agilent/mock/__init__.py | 0 spacq/devices/agilent/mock/mock_dm34401a.py | 0 spacq/devices/agilent/mock/mock_dm34410a.py | 0 spacq/devices/agilent/mock/mock_nwa8753et.py | 0 spacq/devices/agilent/mock/tests/__init__.py | 0 .../agilent/mock/tests/test_mock_dm34401a.py | 0 .../agilent/mock/tests/test_mock_dm34410a.py | 0 spacq/devices/agilent/nwa8753et.py | 0 spacq/devices/agilent/tests/__init__.py | 0 spacq/devices/agilent/tests/server/__init__.py | 0 spacq/devices/agilent/tests/server/test_dm34401a.py | 0 spacq/devices/agilent/tests/server/test_dm34410a.py | 0 spacq/devices/config.py | 0 spacq/devices/cryomagnetics/gui/__init__.py | 0 spacq/devices/iqc/__init__.py | 0 spacq/devices/iqc/ch4_voltage_source.py | 0 spacq/devices/iqc/ch6_voltage_source.py | 0 spacq/devices/iqc/gui/__init__.py | 0 spacq/devices/iqc/gui/ch4_voltage_source.py | 0 spacq/devices/iqc/gui/ch6_voltage_source.py | 0 spacq/devices/iqc/gui/voltage_source.py | 0 spacq/devices/iqc/mock/__init__.py | 0 spacq/devices/iqc/mock/mock_ch4_voltage_source.py | 0 spacq/devices/iqc/mock/mock_ch6_voltage_source.py | 0 spacq/devices/iqc/mock/mock_voltage_source.py | 0 spacq/devices/iqc/mock/tests/__init__.py | 0 .../iqc/mock/tests/test_mock_ch6_voltage_source.py | 0 .../iqc/mock/tests/test_mock_voltage_source.py | 0 spacq/devices/iqc/tests/__init__.py | 0 spacq/devices/iqc/tests/server/__init__.py | 0 .../iqc/tests/server/test_ch6_voltage_source.py | 0 .../devices/iqc/tests/server/test_voltage_source.py | 0 spacq/devices/iqc/tests/test_ch6_voltage_source.py | 0 spacq/devices/iqc/tests/test_voltage_source.py | 0 spacq/devices/iqc/voltage_source.py | 0 spacq/devices/keithley/__init__.py | 0 spacq/devices/keithley/mock/__init__.py | 0 spacq/devices/keithley/mock/mock_sourceMeter2401.py | 0 spacq/devices/keithley/mock/mock_sourceMeter2450.py | 0 .../devices/keithley/mock/mock_voltagesource230.py | 0 spacq/devices/keithley/mock/tests/__init__.py | 0 .../mock/tests/test_mock_voltagesource230.py | 0 spacq/devices/keithley/sourceMeter2401.py | 0 spacq/devices/keithley/sourceMeter2450.py | 0 spacq/devices/keithley/tests/__init__.py | 0 spacq/devices/keithley/tests/server/__init__.py | 0 .../keithley/tests/server/test_voltagesource230.py | 0 spacq/devices/keithley/voltagesource230.py | 0 spacq/devices/lakeshore/__init__.py | 0 spacq/devices/lakeshore/mock/__init__.py | 0 spacq/devices/lakeshore/mock/mock_model218.py | 0 spacq/devices/lakeshore/mock/mock_tc335.py | 0 spacq/devices/lakeshore/mock/tests/__init__.py | 0 .../devices/lakeshore/mock/tests/test_mock_tc335.py | 0 spacq/devices/lakeshore/model218.py | 0 spacq/devices/lakeshore/tc335.py | 0 spacq/devices/lakeshore/tests/__init__.py | 0 spacq/devices/lakeshore/tests/server/__init__.py | 0 spacq/devices/lakeshore/tests/server/test_tc335.py | 0 spacq/devices/mock/__init__.py | 0 spacq/devices/mock/mock_abstract_device.py | 0 spacq/devices/mock/tests/__init__.py | 0 .../devices/mock/tests/test_mock_abstract_device.py | 0 spacq/devices/oxford/__init__.py | 0 spacq/devices/oxford/ips120_10.py | 0 spacq/devices/oxford/mock/__init__.py | 0 spacq/devices/oxford/mock/mock_ips120_10.py | 0 spacq/devices/oxford/mock/tests/__init__.py | 0 .../oxford/mock/tests/test_mock_ips120_10.py | 0 spacq/devices/oxford/tests/__init__.py | 0 spacq/devices/oxford/tests/server/__init__.py | 0 spacq/devices/oxford/tests/server/test_ips120_10.py | 0 spacq/devices/rohde_schwarz/__init__.py | 0 spacq/devices/rohde_schwarz/mock/__init__.py | 0 spacq/devices/rohde_schwarz/mock/mock_smf100a.py | 0 spacq/devices/rohde_schwarz/mock/tests/__init__.py | 0 .../rohde_schwarz/mock/tests/test_mock_smf100a.py | 0 spacq/devices/rohde_schwarz/smf100a.py | 0 spacq/devices/rohde_schwarz/tests/__init__.py | 0 .../devices/rohde_schwarz/tests/server/__init__.py | 0 .../rohde_schwarz/tests/server/test_smf100a.py | 0 spacq/devices/sample/__init__.py | 0 spacq/devices/sample/abc1234.py | 0 spacq/devices/sample/mock/__init__.py | 0 spacq/devices/sample/mock/mock_abc1234.py | 0 spacq/devices/sample/mock/tests/__init__.py | 0 .../devices/sample/mock/tests/test_mock_abc1234.py | 0 spacq/devices/sample/tests/__init__.py | 0 spacq/devices/sample/tests/server/__init__.py | 0 spacq/devices/sample/tests/server/test_abc1234.py | 0 spacq/devices/stanford_research_systems/__init__.py | 0 .../stanford_research_systems/mock/__init__.py | 0 .../stanford_research_systems/mock/mock_sg382.py | 0 .../stanford_research_systems/mock/mock_sim900.py | 0 .../stanford_research_systems/mock/mock_sr830dsp.py | 0 spacq/devices/stanford_research_systems/sg382.py | 0 spacq/devices/stanford_research_systems/sim900.py | 0 spacq/devices/stanford_research_systems/sr830dsp.py | 0 spacq/devices/tektronix/__init__.py | 0 spacq/devices/tektronix/awg5014b.py | 0 spacq/devices/tektronix/dpo7104.py | 0 spacq/devices/tektronix/mock/__init__.py | 0 spacq/devices/tektronix/mock/mock_awg5014b.py | 0 spacq/devices/tektronix/mock/mock_dpo7104.py | 0 spacq/devices/tektronix/mock/tests/__init__.py | 0 .../tektronix/mock/tests/test_mock_awg5014b.py | 0 .../tektronix/mock/tests/test_mock_dpo7104.py | 0 spacq/devices/tektronix/tests/__init__.py | 0 spacq/devices/tektronix/tests/server/__init__.py | 0 .../devices/tektronix/tests/server/test_awg5014b.py | 0 .../devices/tektronix/tests/server/test_dpo7104.py | 0 spacq/devices/tests/__init__.py | 0 spacq/devices/tests/server/__init__.py | 0 spacq/devices/tests/server/test_abstract_device.py | 0 spacq/devices/tests/server/test_config.py | 0 spacq/devices/tests/test_abstract_device.py | 0 spacq/devices/tests/test_config.py | 0 spacq/devices/tests/test_tools.py | 0 spacq/gui/__init__.py | 0 spacq/gui/action/__init__.py | 0 spacq/gui/action/smooth_reset.py | 0 spacq/gui/config/__init__.py | 0 spacq/gui/config/device/__init__.py | 0 spacq/gui/config/device/device_config.py | 0 spacq/gui/config/device/resource_tree.py | 0 spacq/gui/config/devices.py | 0 spacq/gui/config/pulse.py | 0 spacq/gui/config/scaling.py | 0 spacq/gui/config/virtual_variables.py | 0 spacq/gui/display/__init__.py | 0 spacq/gui/display/plot/__init__.py | 0 spacq/gui/display/plot/colormapped.py | 0 spacq/gui/display/plot/common/__init__.py | 0 spacq/gui/display/plot/common/chaco_plot.py | 0 spacq/gui/display/plot/live/__init__.py | 0 spacq/gui/display/plot/live/list.py | 0 spacq/gui/display/plot/live/list_3d.py | 0 spacq/gui/display/plot/plotmath/__init__.py | 0 spacq/gui/display/plot/plotmath/common/__init__.py | 0 spacq/gui/display/plot/plotmath/derivative.py | 0 spacq/gui/display/plot/plotmath/function.py | 0 spacq/gui/display/plot/static/__init__.py | 0 spacq/gui/display/plot/static/colormapped.py | 0 spacq/gui/display/plot/static/common/__init__.py | 0 spacq/gui/display/plot/static/common/plot_setup.py | 0 spacq/gui/display/plot/static/delegator.py | 0 spacq/gui/display/plot/static/surface.py | 0 spacq/gui/display/plot/static/two_dimensional.py | 0 spacq/gui/display/plot/surface.py | 0 spacq/gui/display/plot/two_dimensional.py | 0 spacq/gui/display/table/__init__.py | 0 spacq/gui/display/table/filter.py | 0 spacq/gui/display/table/generic.py | 0 spacq/gui/display/waveform.py | 0 spacq/gui/global_store.py | 0 spacq/gui/tool/__init__.py | 0 spacq/gui/tool/box.py | 0 spacq/gui/tool/tests/__init__.py | 0 spacq/gui/tool/tests/test_box.py | 0 spacq/interface/__init__.py | 0 spacq/interface/list_columns.py | 0 spacq/interface/pulse/__init__.py | 0 spacq/interface/pulse/parser.py | 0 spacq/interface/pulse/program.py | 0 spacq/interface/pulse/tests/__init__.py | 0 spacq/interface/pulse/tests/resources/01.pulse | 0 spacq/interface/pulse/tests/resources/02.pulse | 0 spacq/interface/pulse/tests/resources/non-square | 0 spacq/interface/pulse/tests/test_parser.py | 0 spacq/interface/pulse/tests/test_program.py | 0 spacq/interface/pulse/tests/test_tree.py | 0 spacq/interface/pulse/tool/__init__.py | 0 spacq/interface/pulse/tool/box.py | 0 spacq/interface/pulse/tool/tests/__init__.py | 0 spacq/interface/pulse/tool/tests/test_box.py | 0 spacq/interface/pulse/tree.py | 0 spacq/interface/tests/__init__.py | 0 spacq/interface/tests/test_list_columns.py | 0 spacq/interface/tests/test_resources.py | 0 spacq/interface/tests/test_units.py | 0 spacq/interface/tests/test_waveform.py | 0 spacq/interface/waveform.py | 0 spacq/iteration/__init__.py | 0 spacq/iteration/tests/__init__.py | 0 spacq/iteration/tests/resources/01.pulse | 0 spacq/iteration/virtual_variables.py | 0 spacq/tests/__init__.py | 0 spacq/tests/tool/__init__.py | 0 spacq/tests/tool/box.py | 0 spacq/tool/__init__.py | 0 spacq/tool/box.py | 0 spacq/tool/tests/__init__.py | 0 spacq/tool/tests/test_box.py | 0 294 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 CHANGELOG.rst mode change 100644 => 100755 LICENSE mode change 100644 => 100755 MANIFEST.in mode change 100644 => 100755 README.rst mode change 100644 => 100755 docs/.gitignore mode change 100644 => 100755 docs/Makefile mode change 100644 => 100755 docs/conf.py mode change 100644 => 100755 docs/devel/general_info.rst mode change 100644 => 100755 docs/devel/index.rst mode change 100644 => 100755 docs/devel/packages/devices/awg5014b.rst mode change 100644 => 100755 docs/devel/packages/devices/dpo7104.rst mode change 100644 => 100755 docs/devel/packages/devices/index.rst mode change 100644 => 100755 docs/devel/packages/devices/model4g.rst mode change 100644 => 100755 docs/devel/packages/gui.rst mode change 100644 => 100755 docs/devel/packages/index.rst mode change 100644 => 100755 docs/devel/packages/interface.rst mode change 100644 => 100755 docs/devel/packages/iteration.rst mode change 100644 => 100755 docs/devel/pulse_program_execution.rst mode change 100644 => 100755 docs/index.rst mode change 100644 => 100755 docs/installation.rst mode change 100644 => 100755 docs/user/examples/acquisition.rst mode change 100644 => 100755 docs/user/examples/acquisition_acquiring.png mode change 100644 => 100755 docs/user/examples/data_explorer.rst mode change 100644 => 100755 docs/user/examples/data_explorer_menu.png mode change 100644 => 100755 docs/user/examples/index.rst mode change 100644 => 100755 docs/user/general_concepts/devices.rst mode change 100644 => 100755 docs/user/general_concepts/index.rst mode change 100644 => 100755 docs/user/general_concepts/resources.rst mode change 100644 => 100755 docs/user/general_concepts/variables.rst mode change 100644 => 100755 docs/user/gui/action/data_capture.rst mode change 100644 => 100755 docs/user/gui/action/data_capture_panel.png mode change 100644 => 100755 docs/user/gui/action/data_capture_panel.xcf mode change 100644 => 100755 docs/user/gui/action/data_capture_sweep.png mode change 100644 => 100755 docs/user/gui/action/data_capture_sweep.xcf mode change 100644 => 100755 docs/user/gui/action/index.rst mode change 100644 => 100755 docs/user/gui/action/smooth_reset.rst mode change 100644 => 100755 docs/user/gui/action/smooth_reset_panel.png mode change 100644 => 100755 docs/user/gui/action/smooth_reset_panel.xcf mode change 100644 => 100755 docs/user/gui/config/device_config.rst mode change 100644 => 100755 docs/user/gui/config/device_config_connection.png mode change 100644 => 100755 docs/user/gui/config/device_config_connection.xcf mode change 100644 => 100755 docs/user/gui/config/device_config_list.png mode change 100644 => 100755 docs/user/gui/config/device_config_list.xcf mode change 100644 => 100755 docs/user/gui/config/device_config_resources.png mode change 100644 => 100755 docs/user/gui/config/device_config_resources.xcf mode change 100644 => 100755 docs/user/gui/config/index.rst mode change 100644 => 100755 docs/user/gui/config/measurement_config.rst mode change 100644 => 100755 docs/user/gui/config/measurement_config_list.png mode change 100644 => 100755 docs/user/gui/config/measurement_config_list_settings.png mode change 100644 => 100755 docs/user/gui/config/measurement_config_scalar.png mode change 100644 => 100755 docs/user/gui/config/measurement_config_scalar.xcf mode change 100644 => 100755 docs/user/gui/config/measurement_config_scalar_settings.png mode change 100644 => 100755 docs/user/gui/config/measurement_config_scaling.png mode change 100644 => 100755 docs/user/gui/config/pulse_program_config.rst mode change 100644 => 100755 docs/user/gui/config/pulse_program_config_acquisition.png mode change 100644 => 100755 docs/user/gui/config/pulse_program_config_delays.png mode change 100644 => 100755 docs/user/gui/config/pulse_program_config_outputs.png mode change 100644 => 100755 docs/user/gui/config/variable_config.rst mode change 100644 => 100755 docs/user/gui/config/variable_config_condition_editor.png mode change 100644 => 100755 docs/user/gui/config/variable_config_condition_editor.xcf mode change 100644 => 100755 docs/user/gui/config/variable_config_condition_variable_editor.png mode change 100644 => 100755 docs/user/gui/config/variable_config_condition_variable_editor.xcf mode change 100644 => 100755 docs/user/gui/config/variable_config_editor.png mode change 100644 => 100755 docs/user/gui/config/variable_config_editor.xcf mode change 100644 => 100755 docs/user/gui/config/variable_config_list.png mode change 100644 => 100755 docs/user/gui/config/variable_config_list.xcf mode change 100644 => 100755 docs/user/gui/display/index.rst mode change 100644 => 100755 docs/user/gui/display/plots/colormapped.rst mode change 100644 => 100755 docs/user/gui/display/plots/colormapped_hilighted.png mode change 100644 => 100755 docs/user/gui/display/plots/colormapped_normal.png mode change 100644 => 100755 docs/user/gui/display/plots/index.rst mode change 100644 => 100755 docs/user/gui/display/plots/surface.rst mode change 100644 => 100755 docs/user/gui/display/plots/surface_normal.png mode change 100644 => 100755 docs/user/gui/display/plots/surface_waveform.png mode change 100644 => 100755 docs/user/gui/display/plots/two_dimensional.png mode change 100644 => 100755 docs/user/gui/display/plots/two_dimensional.rst mode change 100644 => 100755 docs/user/gui/display/table.png mode change 100644 => 100755 docs/user/gui/display/table.rst mode change 100644 => 100755 docs/user/gui/display/table_filter_editor.png mode change 100644 => 100755 docs/user/gui/display/table_filter_list.png mode change 100644 => 100755 docs/user/gui/index.rst mode change 100644 => 100755 docs/user/index.rst mode change 100644 => 100755 docs/user/pulse_programs.rst mode change 100644 => 100755 docs/user/pulse_programs_acquisition.png mode change 100644 => 100755 docs/user/pulse_programs_multiple_01.png mode change 100644 => 100755 docs/user/pulse_programs_multiple_02.png mode change 100644 => 100755 docs/user/pulse_programs_single.png mode change 100644 => 100755 examples/SpanishAcquisition.egg-info/PKG-INFO mode change 100644 => 100755 examples/SpanishAcquisition.egg-info/SOURCES.txt mode change 100644 => 100755 examples/SpanishAcquisition.egg-info/dependency_links.txt mode change 100644 => 100755 examples/SpanishAcquisition.egg-info/top_level.txt mode change 100644 => 100755 examples/__init__.py mode change 100644 => 100755 examples/dist/SpanishAcquisition-2.0.2b-py2.6.egg mode change 100644 => 100755 examples/virtual_setup.py mode change 100644 => 100755 setup.py mode change 100644 => 100755 spacq/__init__.py mode change 100644 => 100755 spacq/devices/abstract_device.py mode change 100644 => 100755 spacq/devices/agilent/__init__.py mode change 100644 => 100755 spacq/devices/agilent/dm34401a.py mode change 100644 => 100755 spacq/devices/agilent/dm34410a.py mode change 100644 => 100755 spacq/devices/agilent/mock/__init__.py mode change 100644 => 100755 spacq/devices/agilent/mock/mock_dm34401a.py mode change 100644 => 100755 spacq/devices/agilent/mock/mock_dm34410a.py mode change 100644 => 100755 spacq/devices/agilent/mock/mock_nwa8753et.py mode change 100644 => 100755 spacq/devices/agilent/mock/tests/__init__.py mode change 100644 => 100755 spacq/devices/agilent/mock/tests/test_mock_dm34401a.py mode change 100644 => 100755 spacq/devices/agilent/mock/tests/test_mock_dm34410a.py mode change 100644 => 100755 spacq/devices/agilent/nwa8753et.py mode change 100644 => 100755 spacq/devices/agilent/tests/__init__.py mode change 100644 => 100755 spacq/devices/agilent/tests/server/__init__.py mode change 100644 => 100755 spacq/devices/agilent/tests/server/test_dm34401a.py mode change 100644 => 100755 spacq/devices/agilent/tests/server/test_dm34410a.py mode change 100644 => 100755 spacq/devices/config.py mode change 100644 => 100755 spacq/devices/cryomagnetics/gui/__init__.py mode change 100644 => 100755 spacq/devices/iqc/__init__.py mode change 100644 => 100755 spacq/devices/iqc/ch4_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/ch6_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/gui/__init__.py mode change 100644 => 100755 spacq/devices/iqc/gui/ch4_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/gui/ch6_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/gui/voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/mock/__init__.py mode change 100644 => 100755 spacq/devices/iqc/mock/mock_ch4_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/mock/mock_ch6_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/mock/mock_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/mock/tests/__init__.py mode change 100644 => 100755 spacq/devices/iqc/mock/tests/test_mock_ch6_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/mock/tests/test_mock_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/tests/__init__.py mode change 100644 => 100755 spacq/devices/iqc/tests/server/__init__.py mode change 100644 => 100755 spacq/devices/iqc/tests/server/test_ch6_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/tests/server/test_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/tests/test_ch6_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/tests/test_voltage_source.py mode change 100644 => 100755 spacq/devices/iqc/voltage_source.py mode change 100644 => 100755 spacq/devices/keithley/__init__.py mode change 100644 => 100755 spacq/devices/keithley/mock/__init__.py mode change 100644 => 100755 spacq/devices/keithley/mock/mock_sourceMeter2401.py mode change 100644 => 100755 spacq/devices/keithley/mock/mock_sourceMeter2450.py mode change 100644 => 100755 spacq/devices/keithley/mock/mock_voltagesource230.py mode change 100644 => 100755 spacq/devices/keithley/mock/tests/__init__.py mode change 100644 => 100755 spacq/devices/keithley/mock/tests/test_mock_voltagesource230.py mode change 100644 => 100755 spacq/devices/keithley/sourceMeter2401.py mode change 100644 => 100755 spacq/devices/keithley/sourceMeter2450.py mode change 100644 => 100755 spacq/devices/keithley/tests/__init__.py mode change 100644 => 100755 spacq/devices/keithley/tests/server/__init__.py mode change 100644 => 100755 spacq/devices/keithley/tests/server/test_voltagesource230.py mode change 100644 => 100755 spacq/devices/keithley/voltagesource230.py mode change 100644 => 100755 spacq/devices/lakeshore/__init__.py mode change 100644 => 100755 spacq/devices/lakeshore/mock/__init__.py mode change 100644 => 100755 spacq/devices/lakeshore/mock/mock_model218.py mode change 100644 => 100755 spacq/devices/lakeshore/mock/mock_tc335.py mode change 100644 => 100755 spacq/devices/lakeshore/mock/tests/__init__.py mode change 100644 => 100755 spacq/devices/lakeshore/mock/tests/test_mock_tc335.py mode change 100644 => 100755 spacq/devices/lakeshore/model218.py mode change 100644 => 100755 spacq/devices/lakeshore/tc335.py mode change 100644 => 100755 spacq/devices/lakeshore/tests/__init__.py mode change 100644 => 100755 spacq/devices/lakeshore/tests/server/__init__.py mode change 100644 => 100755 spacq/devices/lakeshore/tests/server/test_tc335.py mode change 100644 => 100755 spacq/devices/mock/__init__.py mode change 100644 => 100755 spacq/devices/mock/mock_abstract_device.py mode change 100644 => 100755 spacq/devices/mock/tests/__init__.py mode change 100644 => 100755 spacq/devices/mock/tests/test_mock_abstract_device.py mode change 100644 => 100755 spacq/devices/oxford/__init__.py mode change 100644 => 100755 spacq/devices/oxford/ips120_10.py mode change 100644 => 100755 spacq/devices/oxford/mock/__init__.py mode change 100644 => 100755 spacq/devices/oxford/mock/mock_ips120_10.py mode change 100644 => 100755 spacq/devices/oxford/mock/tests/__init__.py mode change 100644 => 100755 spacq/devices/oxford/mock/tests/test_mock_ips120_10.py mode change 100644 => 100755 spacq/devices/oxford/tests/__init__.py mode change 100644 => 100755 spacq/devices/oxford/tests/server/__init__.py mode change 100644 => 100755 spacq/devices/oxford/tests/server/test_ips120_10.py mode change 100644 => 100755 spacq/devices/rohde_schwarz/__init__.py mode change 100644 => 100755 spacq/devices/rohde_schwarz/mock/__init__.py mode change 100644 => 100755 spacq/devices/rohde_schwarz/mock/mock_smf100a.py mode change 100644 => 100755 spacq/devices/rohde_schwarz/mock/tests/__init__.py mode change 100644 => 100755 spacq/devices/rohde_schwarz/mock/tests/test_mock_smf100a.py mode change 100644 => 100755 spacq/devices/rohde_schwarz/smf100a.py mode change 100644 => 100755 spacq/devices/rohde_schwarz/tests/__init__.py mode change 100644 => 100755 spacq/devices/rohde_schwarz/tests/server/__init__.py mode change 100644 => 100755 spacq/devices/rohde_schwarz/tests/server/test_smf100a.py mode change 100644 => 100755 spacq/devices/sample/__init__.py mode change 100644 => 100755 spacq/devices/sample/abc1234.py mode change 100644 => 100755 spacq/devices/sample/mock/__init__.py mode change 100644 => 100755 spacq/devices/sample/mock/mock_abc1234.py mode change 100644 => 100755 spacq/devices/sample/mock/tests/__init__.py mode change 100644 => 100755 spacq/devices/sample/mock/tests/test_mock_abc1234.py mode change 100644 => 100755 spacq/devices/sample/tests/__init__.py mode change 100644 => 100755 spacq/devices/sample/tests/server/__init__.py mode change 100644 => 100755 spacq/devices/sample/tests/server/test_abc1234.py mode change 100644 => 100755 spacq/devices/stanford_research_systems/__init__.py mode change 100644 => 100755 spacq/devices/stanford_research_systems/mock/__init__.py mode change 100644 => 100755 spacq/devices/stanford_research_systems/mock/mock_sg382.py mode change 100644 => 100755 spacq/devices/stanford_research_systems/mock/mock_sim900.py mode change 100644 => 100755 spacq/devices/stanford_research_systems/mock/mock_sr830dsp.py mode change 100644 => 100755 spacq/devices/stanford_research_systems/sg382.py mode change 100644 => 100755 spacq/devices/stanford_research_systems/sim900.py mode change 100644 => 100755 spacq/devices/stanford_research_systems/sr830dsp.py mode change 100644 => 100755 spacq/devices/tektronix/__init__.py mode change 100644 => 100755 spacq/devices/tektronix/awg5014b.py mode change 100644 => 100755 spacq/devices/tektronix/dpo7104.py mode change 100644 => 100755 spacq/devices/tektronix/mock/__init__.py mode change 100644 => 100755 spacq/devices/tektronix/mock/mock_awg5014b.py mode change 100644 => 100755 spacq/devices/tektronix/mock/mock_dpo7104.py mode change 100644 => 100755 spacq/devices/tektronix/mock/tests/__init__.py mode change 100644 => 100755 spacq/devices/tektronix/mock/tests/test_mock_awg5014b.py mode change 100644 => 100755 spacq/devices/tektronix/mock/tests/test_mock_dpo7104.py mode change 100644 => 100755 spacq/devices/tektronix/tests/__init__.py mode change 100644 => 100755 spacq/devices/tektronix/tests/server/__init__.py mode change 100644 => 100755 spacq/devices/tektronix/tests/server/test_awg5014b.py mode change 100644 => 100755 spacq/devices/tektronix/tests/server/test_dpo7104.py mode change 100644 => 100755 spacq/devices/tests/__init__.py mode change 100644 => 100755 spacq/devices/tests/server/__init__.py mode change 100644 => 100755 spacq/devices/tests/server/test_abstract_device.py mode change 100644 => 100755 spacq/devices/tests/server/test_config.py mode change 100644 => 100755 spacq/devices/tests/test_abstract_device.py mode change 100644 => 100755 spacq/devices/tests/test_config.py mode change 100644 => 100755 spacq/devices/tests/test_tools.py mode change 100644 => 100755 spacq/gui/__init__.py mode change 100644 => 100755 spacq/gui/action/__init__.py mode change 100644 => 100755 spacq/gui/action/smooth_reset.py mode change 100644 => 100755 spacq/gui/config/__init__.py mode change 100644 => 100755 spacq/gui/config/device/__init__.py mode change 100644 => 100755 spacq/gui/config/device/device_config.py mode change 100644 => 100755 spacq/gui/config/device/resource_tree.py mode change 100644 => 100755 spacq/gui/config/devices.py mode change 100644 => 100755 spacq/gui/config/pulse.py mode change 100644 => 100755 spacq/gui/config/scaling.py mode change 100644 => 100755 spacq/gui/config/virtual_variables.py mode change 100644 => 100755 spacq/gui/display/__init__.py mode change 100644 => 100755 spacq/gui/display/plot/__init__.py mode change 100644 => 100755 spacq/gui/display/plot/colormapped.py mode change 100644 => 100755 spacq/gui/display/plot/common/__init__.py mode change 100644 => 100755 spacq/gui/display/plot/common/chaco_plot.py mode change 100644 => 100755 spacq/gui/display/plot/live/__init__.py mode change 100644 => 100755 spacq/gui/display/plot/live/list.py mode change 100644 => 100755 spacq/gui/display/plot/live/list_3d.py mode change 100644 => 100755 spacq/gui/display/plot/plotmath/__init__.py mode change 100644 => 100755 spacq/gui/display/plot/plotmath/common/__init__.py mode change 100644 => 100755 spacq/gui/display/plot/plotmath/derivative.py mode change 100644 => 100755 spacq/gui/display/plot/plotmath/function.py mode change 100644 => 100755 spacq/gui/display/plot/static/__init__.py mode change 100644 => 100755 spacq/gui/display/plot/static/colormapped.py mode change 100644 => 100755 spacq/gui/display/plot/static/common/__init__.py mode change 100644 => 100755 spacq/gui/display/plot/static/common/plot_setup.py mode change 100644 => 100755 spacq/gui/display/plot/static/delegator.py mode change 100644 => 100755 spacq/gui/display/plot/static/surface.py mode change 100644 => 100755 spacq/gui/display/plot/static/two_dimensional.py mode change 100644 => 100755 spacq/gui/display/plot/surface.py mode change 100644 => 100755 spacq/gui/display/plot/two_dimensional.py mode change 100644 => 100755 spacq/gui/display/table/__init__.py mode change 100644 => 100755 spacq/gui/display/table/filter.py mode change 100644 => 100755 spacq/gui/display/table/generic.py mode change 100644 => 100755 spacq/gui/display/waveform.py mode change 100644 => 100755 spacq/gui/global_store.py mode change 100644 => 100755 spacq/gui/tool/__init__.py mode change 100644 => 100755 spacq/gui/tool/box.py mode change 100644 => 100755 spacq/gui/tool/tests/__init__.py mode change 100644 => 100755 spacq/gui/tool/tests/test_box.py mode change 100644 => 100755 spacq/interface/__init__.py mode change 100644 => 100755 spacq/interface/list_columns.py mode change 100644 => 100755 spacq/interface/pulse/__init__.py mode change 100644 => 100755 spacq/interface/pulse/parser.py mode change 100644 => 100755 spacq/interface/pulse/program.py mode change 100644 => 100755 spacq/interface/pulse/tests/__init__.py mode change 100644 => 100755 spacq/interface/pulse/tests/resources/01.pulse mode change 100644 => 100755 spacq/interface/pulse/tests/resources/02.pulse mode change 100644 => 100755 spacq/interface/pulse/tests/resources/non-square mode change 100644 => 100755 spacq/interface/pulse/tests/test_parser.py mode change 100644 => 100755 spacq/interface/pulse/tests/test_program.py mode change 100644 => 100755 spacq/interface/pulse/tests/test_tree.py mode change 100644 => 100755 spacq/interface/pulse/tool/__init__.py mode change 100644 => 100755 spacq/interface/pulse/tool/box.py mode change 100644 => 100755 spacq/interface/pulse/tool/tests/__init__.py mode change 100644 => 100755 spacq/interface/pulse/tool/tests/test_box.py mode change 100644 => 100755 spacq/interface/pulse/tree.py mode change 100644 => 100755 spacq/interface/tests/__init__.py mode change 100644 => 100755 spacq/interface/tests/test_list_columns.py mode change 100644 => 100755 spacq/interface/tests/test_resources.py mode change 100644 => 100755 spacq/interface/tests/test_units.py mode change 100644 => 100755 spacq/interface/tests/test_waveform.py mode change 100644 => 100755 spacq/interface/waveform.py mode change 100644 => 100755 spacq/iteration/__init__.py mode change 100644 => 100755 spacq/iteration/tests/__init__.py mode change 100644 => 100755 spacq/iteration/tests/resources/01.pulse mode change 100644 => 100755 spacq/iteration/virtual_variables.py mode change 100644 => 100755 spacq/tests/__init__.py mode change 100644 => 100755 spacq/tests/tool/__init__.py mode change 100644 => 100755 spacq/tests/tool/box.py mode change 100644 => 100755 spacq/tool/__init__.py mode change 100644 => 100755 spacq/tool/box.py mode change 100644 => 100755 spacq/tool/tests/__init__.py mode change 100644 => 100755 spacq/tool/tests/test_box.py diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/CHANGELOG.rst b/CHANGELOG.rst old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/MANIFEST.in b/MANIFEST.in old mode 100644 new mode 100755 diff --git a/README.rst b/README.rst old mode 100644 new mode 100755 diff --git a/docs/.gitignore b/docs/.gitignore old mode 100644 new mode 100755 diff --git a/docs/Makefile b/docs/Makefile old mode 100644 new mode 100755 diff --git a/docs/conf.py b/docs/conf.py old mode 100644 new mode 100755 diff --git a/docs/devel/general_info.rst b/docs/devel/general_info.rst old mode 100644 new mode 100755 diff --git a/docs/devel/index.rst b/docs/devel/index.rst old mode 100644 new mode 100755 diff --git a/docs/devel/packages/devices/awg5014b.rst b/docs/devel/packages/devices/awg5014b.rst old mode 100644 new mode 100755 diff --git a/docs/devel/packages/devices/dpo7104.rst b/docs/devel/packages/devices/dpo7104.rst old mode 100644 new mode 100755 diff --git a/docs/devel/packages/devices/index.rst b/docs/devel/packages/devices/index.rst old mode 100644 new mode 100755 diff --git a/docs/devel/packages/devices/model4g.rst b/docs/devel/packages/devices/model4g.rst old mode 100644 new mode 100755 diff --git a/docs/devel/packages/gui.rst b/docs/devel/packages/gui.rst old mode 100644 new mode 100755 diff --git a/docs/devel/packages/index.rst b/docs/devel/packages/index.rst old mode 100644 new mode 100755 diff --git a/docs/devel/packages/interface.rst b/docs/devel/packages/interface.rst old mode 100644 new mode 100755 diff --git a/docs/devel/packages/iteration.rst b/docs/devel/packages/iteration.rst old mode 100644 new mode 100755 diff --git a/docs/devel/pulse_program_execution.rst b/docs/devel/pulse_program_execution.rst old mode 100644 new mode 100755 diff --git a/docs/index.rst b/docs/index.rst old mode 100644 new mode 100755 diff --git a/docs/installation.rst b/docs/installation.rst old mode 100644 new mode 100755 diff --git a/docs/user/examples/acquisition.rst b/docs/user/examples/acquisition.rst old mode 100644 new mode 100755 diff --git a/docs/user/examples/acquisition_acquiring.png b/docs/user/examples/acquisition_acquiring.png old mode 100644 new mode 100755 diff --git a/docs/user/examples/data_explorer.rst b/docs/user/examples/data_explorer.rst old mode 100644 new mode 100755 diff --git a/docs/user/examples/data_explorer_menu.png b/docs/user/examples/data_explorer_menu.png old mode 100644 new mode 100755 diff --git a/docs/user/examples/index.rst b/docs/user/examples/index.rst old mode 100644 new mode 100755 diff --git a/docs/user/general_concepts/devices.rst b/docs/user/general_concepts/devices.rst old mode 100644 new mode 100755 diff --git a/docs/user/general_concepts/index.rst b/docs/user/general_concepts/index.rst old mode 100644 new mode 100755 diff --git a/docs/user/general_concepts/resources.rst b/docs/user/general_concepts/resources.rst old mode 100644 new mode 100755 diff --git a/docs/user/general_concepts/variables.rst b/docs/user/general_concepts/variables.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/action/data_capture.rst b/docs/user/gui/action/data_capture.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/action/data_capture_panel.png b/docs/user/gui/action/data_capture_panel.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/action/data_capture_panel.xcf b/docs/user/gui/action/data_capture_panel.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/action/data_capture_sweep.png b/docs/user/gui/action/data_capture_sweep.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/action/data_capture_sweep.xcf b/docs/user/gui/action/data_capture_sweep.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/action/index.rst b/docs/user/gui/action/index.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/action/smooth_reset.rst b/docs/user/gui/action/smooth_reset.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/action/smooth_reset_panel.png b/docs/user/gui/action/smooth_reset_panel.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/action/smooth_reset_panel.xcf b/docs/user/gui/action/smooth_reset_panel.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/device_config.rst b/docs/user/gui/config/device_config.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/device_config_connection.png b/docs/user/gui/config/device_config_connection.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/device_config_connection.xcf b/docs/user/gui/config/device_config_connection.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/device_config_list.png b/docs/user/gui/config/device_config_list.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/device_config_list.xcf b/docs/user/gui/config/device_config_list.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/device_config_resources.png b/docs/user/gui/config/device_config_resources.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/device_config_resources.xcf b/docs/user/gui/config/device_config_resources.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/index.rst b/docs/user/gui/config/index.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/measurement_config.rst b/docs/user/gui/config/measurement_config.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/measurement_config_list.png b/docs/user/gui/config/measurement_config_list.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/measurement_config_list_settings.png b/docs/user/gui/config/measurement_config_list_settings.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/measurement_config_scalar.png b/docs/user/gui/config/measurement_config_scalar.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/measurement_config_scalar.xcf b/docs/user/gui/config/measurement_config_scalar.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/measurement_config_scalar_settings.png b/docs/user/gui/config/measurement_config_scalar_settings.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/measurement_config_scaling.png b/docs/user/gui/config/measurement_config_scaling.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/pulse_program_config.rst b/docs/user/gui/config/pulse_program_config.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/pulse_program_config_acquisition.png b/docs/user/gui/config/pulse_program_config_acquisition.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/pulse_program_config_delays.png b/docs/user/gui/config/pulse_program_config_delays.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/pulse_program_config_outputs.png b/docs/user/gui/config/pulse_program_config_outputs.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/variable_config.rst b/docs/user/gui/config/variable_config.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/variable_config_condition_editor.png b/docs/user/gui/config/variable_config_condition_editor.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/variable_config_condition_editor.xcf b/docs/user/gui/config/variable_config_condition_editor.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/variable_config_condition_variable_editor.png b/docs/user/gui/config/variable_config_condition_variable_editor.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/variable_config_condition_variable_editor.xcf b/docs/user/gui/config/variable_config_condition_variable_editor.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/variable_config_editor.png b/docs/user/gui/config/variable_config_editor.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/variable_config_editor.xcf b/docs/user/gui/config/variable_config_editor.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/variable_config_list.png b/docs/user/gui/config/variable_config_list.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/config/variable_config_list.xcf b/docs/user/gui/config/variable_config_list.xcf old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/index.rst b/docs/user/gui/display/index.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/plots/colormapped.rst b/docs/user/gui/display/plots/colormapped.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/plots/colormapped_hilighted.png b/docs/user/gui/display/plots/colormapped_hilighted.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/plots/colormapped_normal.png b/docs/user/gui/display/plots/colormapped_normal.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/plots/index.rst b/docs/user/gui/display/plots/index.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/plots/surface.rst b/docs/user/gui/display/plots/surface.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/plots/surface_normal.png b/docs/user/gui/display/plots/surface_normal.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/plots/surface_waveform.png b/docs/user/gui/display/plots/surface_waveform.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/plots/two_dimensional.png b/docs/user/gui/display/plots/two_dimensional.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/plots/two_dimensional.rst b/docs/user/gui/display/plots/two_dimensional.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/table.png b/docs/user/gui/display/table.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/table.rst b/docs/user/gui/display/table.rst old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/table_filter_editor.png b/docs/user/gui/display/table_filter_editor.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/display/table_filter_list.png b/docs/user/gui/display/table_filter_list.png old mode 100644 new mode 100755 diff --git a/docs/user/gui/index.rst b/docs/user/gui/index.rst old mode 100644 new mode 100755 diff --git a/docs/user/index.rst b/docs/user/index.rst old mode 100644 new mode 100755 diff --git a/docs/user/pulse_programs.rst b/docs/user/pulse_programs.rst old mode 100644 new mode 100755 diff --git a/docs/user/pulse_programs_acquisition.png b/docs/user/pulse_programs_acquisition.png old mode 100644 new mode 100755 diff --git a/docs/user/pulse_programs_multiple_01.png b/docs/user/pulse_programs_multiple_01.png old mode 100644 new mode 100755 diff --git a/docs/user/pulse_programs_multiple_02.png b/docs/user/pulse_programs_multiple_02.png old mode 100644 new mode 100755 diff --git a/docs/user/pulse_programs_single.png b/docs/user/pulse_programs_single.png old mode 100644 new mode 100755 diff --git a/examples/SpanishAcquisition.egg-info/PKG-INFO b/examples/SpanishAcquisition.egg-info/PKG-INFO old mode 100644 new mode 100755 diff --git a/examples/SpanishAcquisition.egg-info/SOURCES.txt b/examples/SpanishAcquisition.egg-info/SOURCES.txt old mode 100644 new mode 100755 diff --git a/examples/SpanishAcquisition.egg-info/dependency_links.txt b/examples/SpanishAcquisition.egg-info/dependency_links.txt old mode 100644 new mode 100755 diff --git a/examples/SpanishAcquisition.egg-info/top_level.txt b/examples/SpanishAcquisition.egg-info/top_level.txt old mode 100644 new mode 100755 diff --git a/examples/__init__.py b/examples/__init__.py old mode 100644 new mode 100755 diff --git a/examples/dist/SpanishAcquisition-2.0.2b-py2.6.egg b/examples/dist/SpanishAcquisition-2.0.2b-py2.6.egg old mode 100644 new mode 100755 diff --git a/examples/virtual_setup.py b/examples/virtual_setup.py old mode 100644 new mode 100755 diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 diff --git a/spacq/__init__.py b/spacq/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/abstract_device.py b/spacq/devices/abstract_device.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/__init__.py b/spacq/devices/agilent/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/dm34401a.py b/spacq/devices/agilent/dm34401a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/dm34410a.py b/spacq/devices/agilent/dm34410a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/mock/__init__.py b/spacq/devices/agilent/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/mock/mock_dm34401a.py b/spacq/devices/agilent/mock/mock_dm34401a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/mock/mock_dm34410a.py b/spacq/devices/agilent/mock/mock_dm34410a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/mock/mock_nwa8753et.py b/spacq/devices/agilent/mock/mock_nwa8753et.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/mock/tests/__init__.py b/spacq/devices/agilent/mock/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/mock/tests/test_mock_dm34401a.py b/spacq/devices/agilent/mock/tests/test_mock_dm34401a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/mock/tests/test_mock_dm34410a.py b/spacq/devices/agilent/mock/tests/test_mock_dm34410a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/nwa8753et.py b/spacq/devices/agilent/nwa8753et.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/tests/__init__.py b/spacq/devices/agilent/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/tests/server/__init__.py b/spacq/devices/agilent/tests/server/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/tests/server/test_dm34401a.py b/spacq/devices/agilent/tests/server/test_dm34401a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/agilent/tests/server/test_dm34410a.py b/spacq/devices/agilent/tests/server/test_dm34410a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/config.py b/spacq/devices/config.py old mode 100644 new mode 100755 diff --git a/spacq/devices/cryomagnetics/gui/__init__.py b/spacq/devices/cryomagnetics/gui/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/__init__.py b/spacq/devices/iqc/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/ch4_voltage_source.py b/spacq/devices/iqc/ch4_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/ch6_voltage_source.py b/spacq/devices/iqc/ch6_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/gui/__init__.py b/spacq/devices/iqc/gui/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/gui/ch4_voltage_source.py b/spacq/devices/iqc/gui/ch4_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/gui/ch6_voltage_source.py b/spacq/devices/iqc/gui/ch6_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/gui/voltage_source.py b/spacq/devices/iqc/gui/voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/mock/__init__.py b/spacq/devices/iqc/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/mock/mock_ch4_voltage_source.py b/spacq/devices/iqc/mock/mock_ch4_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/mock/mock_ch6_voltage_source.py b/spacq/devices/iqc/mock/mock_ch6_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/mock/mock_voltage_source.py b/spacq/devices/iqc/mock/mock_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/mock/tests/__init__.py b/spacq/devices/iqc/mock/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/mock/tests/test_mock_ch6_voltage_source.py b/spacq/devices/iqc/mock/tests/test_mock_ch6_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/mock/tests/test_mock_voltage_source.py b/spacq/devices/iqc/mock/tests/test_mock_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/tests/__init__.py b/spacq/devices/iqc/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/tests/server/__init__.py b/spacq/devices/iqc/tests/server/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/tests/server/test_ch6_voltage_source.py b/spacq/devices/iqc/tests/server/test_ch6_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/tests/server/test_voltage_source.py b/spacq/devices/iqc/tests/server/test_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/tests/test_ch6_voltage_source.py b/spacq/devices/iqc/tests/test_ch6_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/tests/test_voltage_source.py b/spacq/devices/iqc/tests/test_voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/iqc/voltage_source.py b/spacq/devices/iqc/voltage_source.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/__init__.py b/spacq/devices/keithley/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/mock/__init__.py b/spacq/devices/keithley/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/mock/mock_sourceMeter2401.py b/spacq/devices/keithley/mock/mock_sourceMeter2401.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/mock/mock_sourceMeter2450.py b/spacq/devices/keithley/mock/mock_sourceMeter2450.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/mock/mock_voltagesource230.py b/spacq/devices/keithley/mock/mock_voltagesource230.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/mock/tests/__init__.py b/spacq/devices/keithley/mock/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/mock/tests/test_mock_voltagesource230.py b/spacq/devices/keithley/mock/tests/test_mock_voltagesource230.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/sourceMeter2401.py b/spacq/devices/keithley/sourceMeter2401.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/sourceMeter2450.py b/spacq/devices/keithley/sourceMeter2450.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/tests/__init__.py b/spacq/devices/keithley/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/tests/server/__init__.py b/spacq/devices/keithley/tests/server/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/tests/server/test_voltagesource230.py b/spacq/devices/keithley/tests/server/test_voltagesource230.py old mode 100644 new mode 100755 diff --git a/spacq/devices/keithley/voltagesource230.py b/spacq/devices/keithley/voltagesource230.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/__init__.py b/spacq/devices/lakeshore/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/mock/__init__.py b/spacq/devices/lakeshore/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/mock/mock_model218.py b/spacq/devices/lakeshore/mock/mock_model218.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/mock/mock_tc335.py b/spacq/devices/lakeshore/mock/mock_tc335.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/mock/tests/__init__.py b/spacq/devices/lakeshore/mock/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/mock/tests/test_mock_tc335.py b/spacq/devices/lakeshore/mock/tests/test_mock_tc335.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/model218.py b/spacq/devices/lakeshore/model218.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/tc335.py b/spacq/devices/lakeshore/tc335.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/tests/__init__.py b/spacq/devices/lakeshore/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/tests/server/__init__.py b/spacq/devices/lakeshore/tests/server/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/lakeshore/tests/server/test_tc335.py b/spacq/devices/lakeshore/tests/server/test_tc335.py old mode 100644 new mode 100755 diff --git a/spacq/devices/mock/__init__.py b/spacq/devices/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/mock/mock_abstract_device.py b/spacq/devices/mock/mock_abstract_device.py old mode 100644 new mode 100755 diff --git a/spacq/devices/mock/tests/__init__.py b/spacq/devices/mock/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/mock/tests/test_mock_abstract_device.py b/spacq/devices/mock/tests/test_mock_abstract_device.py old mode 100644 new mode 100755 diff --git a/spacq/devices/oxford/__init__.py b/spacq/devices/oxford/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/oxford/ips120_10.py b/spacq/devices/oxford/ips120_10.py old mode 100644 new mode 100755 diff --git a/spacq/devices/oxford/mock/__init__.py b/spacq/devices/oxford/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/oxford/mock/mock_ips120_10.py b/spacq/devices/oxford/mock/mock_ips120_10.py old mode 100644 new mode 100755 diff --git a/spacq/devices/oxford/mock/tests/__init__.py b/spacq/devices/oxford/mock/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/oxford/mock/tests/test_mock_ips120_10.py b/spacq/devices/oxford/mock/tests/test_mock_ips120_10.py old mode 100644 new mode 100755 diff --git a/spacq/devices/oxford/tests/__init__.py b/spacq/devices/oxford/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/oxford/tests/server/__init__.py b/spacq/devices/oxford/tests/server/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/oxford/tests/server/test_ips120_10.py b/spacq/devices/oxford/tests/server/test_ips120_10.py old mode 100644 new mode 100755 diff --git a/spacq/devices/rohde_schwarz/__init__.py b/spacq/devices/rohde_schwarz/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/rohde_schwarz/mock/__init__.py b/spacq/devices/rohde_schwarz/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/rohde_schwarz/mock/mock_smf100a.py b/spacq/devices/rohde_schwarz/mock/mock_smf100a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/rohde_schwarz/mock/tests/__init__.py b/spacq/devices/rohde_schwarz/mock/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/rohde_schwarz/mock/tests/test_mock_smf100a.py b/spacq/devices/rohde_schwarz/mock/tests/test_mock_smf100a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/rohde_schwarz/smf100a.py b/spacq/devices/rohde_schwarz/smf100a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/rohde_schwarz/tests/__init__.py b/spacq/devices/rohde_schwarz/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/rohde_schwarz/tests/server/__init__.py b/spacq/devices/rohde_schwarz/tests/server/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/rohde_schwarz/tests/server/test_smf100a.py b/spacq/devices/rohde_schwarz/tests/server/test_smf100a.py old mode 100644 new mode 100755 diff --git a/spacq/devices/sample/__init__.py b/spacq/devices/sample/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/sample/abc1234.py b/spacq/devices/sample/abc1234.py old mode 100644 new mode 100755 diff --git a/spacq/devices/sample/mock/__init__.py b/spacq/devices/sample/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/sample/mock/mock_abc1234.py b/spacq/devices/sample/mock/mock_abc1234.py old mode 100644 new mode 100755 diff --git a/spacq/devices/sample/mock/tests/__init__.py b/spacq/devices/sample/mock/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/sample/mock/tests/test_mock_abc1234.py b/spacq/devices/sample/mock/tests/test_mock_abc1234.py old mode 100644 new mode 100755 diff --git a/spacq/devices/sample/tests/__init__.py b/spacq/devices/sample/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/sample/tests/server/__init__.py b/spacq/devices/sample/tests/server/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/sample/tests/server/test_abc1234.py b/spacq/devices/sample/tests/server/test_abc1234.py old mode 100644 new mode 100755 diff --git a/spacq/devices/stanford_research_systems/__init__.py b/spacq/devices/stanford_research_systems/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/stanford_research_systems/mock/__init__.py b/spacq/devices/stanford_research_systems/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/stanford_research_systems/mock/mock_sg382.py b/spacq/devices/stanford_research_systems/mock/mock_sg382.py old mode 100644 new mode 100755 diff --git a/spacq/devices/stanford_research_systems/mock/mock_sim900.py b/spacq/devices/stanford_research_systems/mock/mock_sim900.py old mode 100644 new mode 100755 diff --git a/spacq/devices/stanford_research_systems/mock/mock_sr830dsp.py b/spacq/devices/stanford_research_systems/mock/mock_sr830dsp.py old mode 100644 new mode 100755 diff --git a/spacq/devices/stanford_research_systems/sg382.py b/spacq/devices/stanford_research_systems/sg382.py old mode 100644 new mode 100755 diff --git a/spacq/devices/stanford_research_systems/sim900.py b/spacq/devices/stanford_research_systems/sim900.py old mode 100644 new mode 100755 diff --git a/spacq/devices/stanford_research_systems/sr830dsp.py b/spacq/devices/stanford_research_systems/sr830dsp.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/__init__.py b/spacq/devices/tektronix/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/awg5014b.py b/spacq/devices/tektronix/awg5014b.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/dpo7104.py b/spacq/devices/tektronix/dpo7104.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/mock/__init__.py b/spacq/devices/tektronix/mock/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/mock/mock_awg5014b.py b/spacq/devices/tektronix/mock/mock_awg5014b.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/mock/mock_dpo7104.py b/spacq/devices/tektronix/mock/mock_dpo7104.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/mock/tests/__init__.py b/spacq/devices/tektronix/mock/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/mock/tests/test_mock_awg5014b.py b/spacq/devices/tektronix/mock/tests/test_mock_awg5014b.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/mock/tests/test_mock_dpo7104.py b/spacq/devices/tektronix/mock/tests/test_mock_dpo7104.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/tests/__init__.py b/spacq/devices/tektronix/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/tests/server/__init__.py b/spacq/devices/tektronix/tests/server/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/tests/server/test_awg5014b.py b/spacq/devices/tektronix/tests/server/test_awg5014b.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tektronix/tests/server/test_dpo7104.py b/spacq/devices/tektronix/tests/server/test_dpo7104.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tests/__init__.py b/spacq/devices/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tests/server/__init__.py b/spacq/devices/tests/server/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tests/server/test_abstract_device.py b/spacq/devices/tests/server/test_abstract_device.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tests/server/test_config.py b/spacq/devices/tests/server/test_config.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tests/test_abstract_device.py b/spacq/devices/tests/test_abstract_device.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tests/test_config.py b/spacq/devices/tests/test_config.py old mode 100644 new mode 100755 diff --git a/spacq/devices/tests/test_tools.py b/spacq/devices/tests/test_tools.py old mode 100644 new mode 100755 diff --git a/spacq/gui/__init__.py b/spacq/gui/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/action/__init__.py b/spacq/gui/action/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/action/smooth_reset.py b/spacq/gui/action/smooth_reset.py old mode 100644 new mode 100755 diff --git a/spacq/gui/config/__init__.py b/spacq/gui/config/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/config/device/__init__.py b/spacq/gui/config/device/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/config/device/device_config.py b/spacq/gui/config/device/device_config.py old mode 100644 new mode 100755 diff --git a/spacq/gui/config/device/resource_tree.py b/spacq/gui/config/device/resource_tree.py old mode 100644 new mode 100755 diff --git a/spacq/gui/config/devices.py b/spacq/gui/config/devices.py old mode 100644 new mode 100755 diff --git a/spacq/gui/config/pulse.py b/spacq/gui/config/pulse.py old mode 100644 new mode 100755 diff --git a/spacq/gui/config/scaling.py b/spacq/gui/config/scaling.py old mode 100644 new mode 100755 diff --git a/spacq/gui/config/virtual_variables.py b/spacq/gui/config/virtual_variables.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/__init__.py b/spacq/gui/display/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/__init__.py b/spacq/gui/display/plot/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/colormapped.py b/spacq/gui/display/plot/colormapped.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/common/__init__.py b/spacq/gui/display/plot/common/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/common/chaco_plot.py b/spacq/gui/display/plot/common/chaco_plot.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/live/__init__.py b/spacq/gui/display/plot/live/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/live/list.py b/spacq/gui/display/plot/live/list.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/live/list_3d.py b/spacq/gui/display/plot/live/list_3d.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/plotmath/__init__.py b/spacq/gui/display/plot/plotmath/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/plotmath/common/__init__.py b/spacq/gui/display/plot/plotmath/common/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/plotmath/derivative.py b/spacq/gui/display/plot/plotmath/derivative.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/plotmath/function.py b/spacq/gui/display/plot/plotmath/function.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/static/__init__.py b/spacq/gui/display/plot/static/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/static/colormapped.py b/spacq/gui/display/plot/static/colormapped.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/static/common/__init__.py b/spacq/gui/display/plot/static/common/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/static/common/plot_setup.py b/spacq/gui/display/plot/static/common/plot_setup.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/static/delegator.py b/spacq/gui/display/plot/static/delegator.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/static/surface.py b/spacq/gui/display/plot/static/surface.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/static/two_dimensional.py b/spacq/gui/display/plot/static/two_dimensional.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/surface.py b/spacq/gui/display/plot/surface.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/plot/two_dimensional.py b/spacq/gui/display/plot/two_dimensional.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/table/__init__.py b/spacq/gui/display/table/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/table/filter.py b/spacq/gui/display/table/filter.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/table/generic.py b/spacq/gui/display/table/generic.py old mode 100644 new mode 100755 diff --git a/spacq/gui/display/waveform.py b/spacq/gui/display/waveform.py old mode 100644 new mode 100755 diff --git a/spacq/gui/global_store.py b/spacq/gui/global_store.py old mode 100644 new mode 100755 diff --git a/spacq/gui/tool/__init__.py b/spacq/gui/tool/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/tool/box.py b/spacq/gui/tool/box.py old mode 100644 new mode 100755 diff --git a/spacq/gui/tool/tests/__init__.py b/spacq/gui/tool/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/gui/tool/tests/test_box.py b/spacq/gui/tool/tests/test_box.py old mode 100644 new mode 100755 diff --git a/spacq/interface/__init__.py b/spacq/interface/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/interface/list_columns.py b/spacq/interface/list_columns.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/__init__.py b/spacq/interface/pulse/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/parser.py b/spacq/interface/pulse/parser.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/program.py b/spacq/interface/pulse/program.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tests/__init__.py b/spacq/interface/pulse/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tests/resources/01.pulse b/spacq/interface/pulse/tests/resources/01.pulse old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tests/resources/02.pulse b/spacq/interface/pulse/tests/resources/02.pulse old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tests/resources/non-square b/spacq/interface/pulse/tests/resources/non-square old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tests/test_parser.py b/spacq/interface/pulse/tests/test_parser.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tests/test_program.py b/spacq/interface/pulse/tests/test_program.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tests/test_tree.py b/spacq/interface/pulse/tests/test_tree.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tool/__init__.py b/spacq/interface/pulse/tool/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tool/box.py b/spacq/interface/pulse/tool/box.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tool/tests/__init__.py b/spacq/interface/pulse/tool/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tool/tests/test_box.py b/spacq/interface/pulse/tool/tests/test_box.py old mode 100644 new mode 100755 diff --git a/spacq/interface/pulse/tree.py b/spacq/interface/pulse/tree.py old mode 100644 new mode 100755 diff --git a/spacq/interface/tests/__init__.py b/spacq/interface/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/interface/tests/test_list_columns.py b/spacq/interface/tests/test_list_columns.py old mode 100644 new mode 100755 diff --git a/spacq/interface/tests/test_resources.py b/spacq/interface/tests/test_resources.py old mode 100644 new mode 100755 diff --git a/spacq/interface/tests/test_units.py b/spacq/interface/tests/test_units.py old mode 100644 new mode 100755 diff --git a/spacq/interface/tests/test_waveform.py b/spacq/interface/tests/test_waveform.py old mode 100644 new mode 100755 diff --git a/spacq/interface/waveform.py b/spacq/interface/waveform.py old mode 100644 new mode 100755 diff --git a/spacq/iteration/__init__.py b/spacq/iteration/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/iteration/tests/__init__.py b/spacq/iteration/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/iteration/tests/resources/01.pulse b/spacq/iteration/tests/resources/01.pulse old mode 100644 new mode 100755 diff --git a/spacq/iteration/virtual_variables.py b/spacq/iteration/virtual_variables.py old mode 100644 new mode 100755 diff --git a/spacq/tests/__init__.py b/spacq/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/tests/tool/__init__.py b/spacq/tests/tool/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/tests/tool/box.py b/spacq/tests/tool/box.py old mode 100644 new mode 100755 diff --git a/spacq/tool/__init__.py b/spacq/tool/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/tool/box.py b/spacq/tool/box.py old mode 100644 new mode 100755 diff --git a/spacq/tool/tests/__init__.py b/spacq/tool/tests/__init__.py old mode 100644 new mode 100755 diff --git a/spacq/tool/tests/test_box.py b/spacq/tool/tests/test_box.py old mode 100644 new mode 100755 From d8b04f957d198c2029ddc77e6b3281631bb41c5e Mon Sep 17 00:00:00 2001 From: zmerino Date: Fri, 18 Mar 2022 14:43:47 -0400 Subject: [PATCH 15/15] copying code from dil-fridge computer --- examples/acquisition.py | 154 +++ examples/data_explorer.py | 252 +++++ runtests | 16 + spacq/devices/__init__.py | 2 + spacq/devices/cryomagnetics/__init__.py | 13 + spacq/devices/cryomagnetics/gui/model4g.py | 560 +++++++++++ spacq/devices/cryomagnetics/mock/__init__.py | 0 .../cryomagnetics/mock/mock_model4g.py | 236 +++++ .../cryomagnetics/mock/tests/__init__.py | 0 .../mock/tests/test_mock_model4g.py | 25 + spacq/devices/cryomagnetics/model4g.py | 798 +++++++++++++++ spacq/devices/cryomagnetics/tests/__init__.py | 0 .../cryomagnetics/tests/server/__init__.py | 0 .../tests/server/test_model4g.py | 250 +++++ spacq/devices/tools.py | 268 +++++ spacq/gui/action/data_capture.py | 639 ++++++++++++ spacq/gui/config/measurement.py | 213 ++++ spacq/gui/config/variables.py | 920 ++++++++++++++++++ spacq/gui/display/plot/live/scalar.py | 588 +++++++++++ .../plot/plotmath/common/math_setup.py | 159 +++ spacq/interface/resources.py | 315 ++++++ spacq/interface/units.py | 320 ++++++ spacq/iteration/sweep.py | 642 ++++++++++++ spacq/iteration/tests/test_sweep.py | 512 ++++++++++ spacq/iteration/tests/test_variables.py | 286 ++++++ spacq/iteration/variables.py | 327 +++++++ test-config.py | 34 + 27 files changed, 7529 insertions(+) create mode 100755 examples/acquisition.py create mode 100755 examples/data_explorer.py create mode 100755 runtests create mode 100755 spacq/devices/__init__.py create mode 100755 spacq/devices/cryomagnetics/__init__.py create mode 100755 spacq/devices/cryomagnetics/gui/model4g.py create mode 100755 spacq/devices/cryomagnetics/mock/__init__.py create mode 100755 spacq/devices/cryomagnetics/mock/mock_model4g.py create mode 100755 spacq/devices/cryomagnetics/mock/tests/__init__.py create mode 100755 spacq/devices/cryomagnetics/mock/tests/test_mock_model4g.py create mode 100755 spacq/devices/cryomagnetics/model4g.py create mode 100755 spacq/devices/cryomagnetics/tests/__init__.py create mode 100755 spacq/devices/cryomagnetics/tests/server/__init__.py create mode 100755 spacq/devices/cryomagnetics/tests/server/test_model4g.py create mode 100755 spacq/devices/tools.py create mode 100755 spacq/gui/action/data_capture.py create mode 100755 spacq/gui/config/measurement.py create mode 100755 spacq/gui/config/variables.py create mode 100755 spacq/gui/display/plot/live/scalar.py create mode 100755 spacq/gui/display/plot/plotmath/common/math_setup.py create mode 100755 spacq/interface/resources.py create mode 100755 spacq/interface/units.py create mode 100755 spacq/iteration/sweep.py create mode 100755 spacq/iteration/tests/test_sweep.py create mode 100755 spacq/iteration/tests/test_variables.py create mode 100755 spacq/iteration/variables.py create mode 100755 test-config.py diff --git a/examples/acquisition.py b/examples/acquisition.py new file mode 100755 index 0000000..b1023ce --- /dev/null +++ b/examples/acquisition.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python2 + +import logging +logging.basicConfig(level=logging.WARNING) + +import wx + +from spacq import VERSION +from spacq.gui.action.data_capture import DataCapturePanel +from spacq.gui.action.smooth_reset import SmoothResetPanel +from spacq.gui.config.devices import DeviceConfigFrame +from spacq.gui.config.pulse import PulseProgramFrame +from spacq.gui.config.variables import VariablesPanel +from spacq.gui.display.plot.live.list import ListMeasurementFrame +from spacq.gui.display.plot.live.scalar import ScalarMeasurementFrame +from spacq.gui.global_store import GlobalStore +from spacq.gui.tool.box import MessageDialog + + +class SweepingAcquisitionFrame(wx.Frame): + def __init__(self, parent, global_store, *args, **kwargs): + wx.Frame.__init__(self, parent, *args, **kwargs) + + # Frame. + frame_box = wx.BoxSizer(wx.VERTICAL) + + ## Variables. + self.variables_panel = VariablesPanel(self, global_store) + self.variables_panel.SetMinSize((800, 300)) + frame_box.Add(self.variables_panel, proportion=1, flag=wx.EXPAND) + + ## Bottom. + bottom_box = wx.BoxSizer(wx.HORIZONTAL) + frame_box.Add(bottom_box, flag=wx.EXPAND) + + ### Data capture. + self.data_capture_panel = DataCapturePanel(self, global_store, style=wx.BORDER_RAISED) + bottom_box.Add(self.data_capture_panel, proportion=1, flag=wx.EXPAND) + + ### Smooth reset. + self.smooth_reset_panel = SmoothResetPanel(self, global_store, style=wx.BORDER_RAISED) + bottom_box.Add(self.smooth_reset_panel, flag=wx.EXPAND) + + self.SetSizerAndFit(frame_box) + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + def OnClose(self, evt): + if self.data_capture_panel.capture_dialogs > 0: + msg = 'Cannot exit, as a sweep is currently in progress.' + MessageDialog(self, msg, 'Sweep in progress').Show() + + evt.Veto() + else: + evt.Skip() + + +class AcquisitionApp(wx.App): + def OnInit(self): + self.global_store = GlobalStore() + + # Frames. + self.acq_frame = SweepingAcquisitionFrame(None, self.global_store, title='Acquisition (v{0})'.format(VERSION)) + self.device_config_frame = None + self.pulse_program_frame = None + + # Menu. + menuBar = wx.MenuBar() + + ## Configuration. + menu = wx.Menu() + menuBar.Append(menu, '&Configuration') + + ### Devices. + item = menu.Append(wx.ID_ANY, '&Devices...') + self.Bind(wx.EVT_MENU, self.OnMenuConfigurationDevices, item) + + ### Measurements. + submenu = wx.Menu() + menu.AppendMenu(wx.ID_ANY, '&Measurements', submenu) + + item = submenu.Append(wx.ID_ANY, 'Add &scalar...') + self.Bind(wx.EVT_MENU, self.OnMenuConfigurationMeasurementsAddScalar, item) + + item = submenu.Append(wx.ID_ANY, 'Add &list...') + self.Bind(wx.EVT_MENU, self.OnMenuConfigurationMeasurementsAddList, item) + + ### Pulse program. + item = menu.Append(wx.ID_ANY, '&Pulse program...') + self.Bind(wx.EVT_MENU, self.OnMenuConfigurationPulseProgram, item) + + ## Help. + menu = wx.Menu() + menuBar.Append(menu, '&Help') + + ### About. + item = menu.Append(wx.ID_ABOUT, '&About...') + self.Bind(wx.EVT_MENU, self.OnMenuHelpAbout, item) + + self.acq_frame.SetMenuBar(menuBar) + + # Display. + self.acq_frame.SetSizerAndFit(self.acq_frame.Sizer) + self.acq_frame.Show() + self.SetTopWindow(self.acq_frame) + self.acq_frame.Raise() + + return True + + def OnMenuConfigurationDevices(self, evt=None): + def close_callback(): + self.device_config_frame = None + + if self.device_config_frame is None: + self.device_config_frame = DeviceConfigFrame(self.acq_frame, self.global_store, close_callback) + self.device_config_frame.Fit() + self.device_config_frame.Show() + + self.device_config_frame.Raise() + + def OnMenuConfigurationMeasurementsAddScalar(self, evt=None): + measurement_frame = ScalarMeasurementFrame(self.acq_frame, self.global_store) + measurement_frame.Show() + + def OnMenuConfigurationMeasurementsAddList(self, evt=None): + measurement_frame = ListMeasurementFrame(self.acq_frame, self.global_store) + measurement_frame.Show() + + def OnMenuConfigurationPulseProgram(self, evt=None): + def close_callback(): + self.pulse_program_frame = None + + if self.pulse_program_frame is None: + self.pulse_program_frame = PulseProgramFrame(self.acq_frame, self.global_store, close_callback) + self.pulse_program_frame.Fit() + self.pulse_program_frame.Show() + + self.pulse_program_frame.Raise() + + def OnMenuHelpAbout(self, evt=None): + info = wx.AboutDialogInfo() + info.SetName('Acquisition') + info.SetDescription( + 'An application for sweeping device values and acquiring data.\n' + '\n' + 'Using Spanish Acquisition version {0}.'.format(VERSION) + ) + + wx.AboutBox(info) + + +if __name__ == "__main__": + app = AcquisitionApp() + app.MainLoop() diff --git a/examples/data_explorer.py b/examples/data_explorer.py new file mode 100755 index 0000000..0ab8af0 --- /dev/null +++ b/examples/data_explorer.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python2 + +import logging +logging.basicConfig(level=logging.WARNING) + +from functools import partial +import wx + +from numpy import concatenate + +from spacq import VERSION +from spacq.gui.display.plot.static.delegator import formats, available_formats +from spacq.gui.display.table.filter import FilterListDialog +from spacq.gui.display.table.generic import TabularDisplayFrame +from spacq.gui.display.plot.plotmath.derivative import DerivativeMathSetupDialog +from spacq.gui.display.plot.plotmath.function import FunctionMathSetupDialog, FunctionMathSetupDialog2arg + +from spacq.gui.tool.box import load_csv, MessageDialog + + +class DataExplorerApp(wx.App): + default_title = 'Data Explorer' + + def OnInit(self): + self.filters = {} + self.filter_columns = {} + self.filter_dialog = None + + # Frames. + self.csv_frame = TabularDisplayFrame(None, title=self.default_title) + + # Menu. + menuBar = wx.MenuBar() + + ## File. + menu = wx.Menu() + menuBar.Append(menu, '&File') + + item = menu.Append(wx.ID_OPEN, '&Open...') + self.Bind(wx.EVT_MENU, self.OnMenuFileOpen, item) + + item = menu.Append(wx.ID_CLOSE, '&Close') + self.Bind(wx.EVT_MENU, self.OnMenuFileClose, item) + + menu.AppendSeparator() + + self.filter_menu_item = menu.Append(wx.ID_ANY, '&Filters...') + self.filter_menu_item.Enable(False) + self.Bind(wx.EVT_MENU, self.OnMenuFileFilters, self.filter_menu_item) + + menu.AppendSeparator() + + item = menu.Append(wx.ID_EXIT, 'E&xit') + self.Bind(wx.EVT_MENU, self.OnMenuFileExit, item) + + ## Plot. + menu = wx.Menu() + menuBar.Append(menu, '&Plot') + + menu.Append(wx.ID_ANY, ' 2D:').Enable(False) + + self.two_dimensional_menu = menu.Append(wx.ID_ANY, '&Curve...') + self.Bind(wx.EVT_MENU, partial(self.create_plot, formats.two_dimensional), + self.two_dimensional_menu) + + menu.AppendSeparator() + + menu.Append(wx.ID_ANY, ' 3D:').Enable(False) + + self.colormapped_menu = menu.Append(wx.ID_ANY, '&Colormapped...') + self.Bind(wx.EVT_MENU, partial(self.create_plot, formats.colormapped), + self.colormapped_menu) + + self.surface_menu = menu.Append(wx.ID_ANY, '&Surface...') + self.Bind(wx.EVT_MENU, partial(self.create_plot, formats.surface), + self.surface_menu) + + menu.AppendSeparator() + + menu.Append(wx.ID_ANY, ' List:').Enable(False) + + self.waveforms_menu = menu.Append(wx.ID_ANY, '&Waveforms...') + self.Bind(wx.EVT_MENU, partial(self.create_plot, formats.waveforms, type='list'), + self.waveforms_menu) + + ## Math. + menu = wx.Menu() + menuBar.Append(menu, '&Math') + + item = menu.Append(wx.ID_ANY, '&Derivative...') + self.Bind(wx.EVT_MENU, self.OnMenuMathDerivative, item) + + item = menu.Append(wx.ID_ANY, '&Function f: y=f(X)...') + self.Bind(wx.EVT_MENU, self.OnMenuMathFunction, item) + + item = menu.Append(wx.ID_ANY, '&Function f: z=f(X,Y)...') + self.Bind(wx.EVT_MENU, self.OnMenuMathFunction2arg, item) + + ## Help. + menu = wx.Menu() + menuBar.Append(menu, '&Help') + + ### About. + item = menu.Append(wx.ID_ABOUT, '&About...') + self.Bind(wx.EVT_MENU, self.OnMenuHelpAbout, item) + + self.csv_frame.SetMenuBar(menuBar) + + self.update_plot_menus(False) + + # Display. + self.csv_frame.Show() + self.csv_frame.SetSize((800, 600)) + + self.SetTopWindow(self.csv_frame) + self.csv_frame.Raise() + + return True + + def update_plot_menus(self, status): + """ + If status is True, enable the plot menus corresponding to the available formats. Otherwise, disable all. + """ + + pairs = [ + (formats.two_dimensional, self.two_dimensional_menu), + (formats.colormapped, self.colormapped_menu), + (formats.surface, self.surface_menu), + (formats.waveforms, self.waveforms_menu), + ] + + for format, menu in pairs: + if not status or format in available_formats: + menu.Enable(status) + + def create_plot(self, format, evt=None, type='scalar'): + """ + Open up a dialog to configure the selected plot format. + """ + + headings, rows, types = self.csv_frame.display_panel.GetValue(types=[type]) + available_formats[format](self.csv_frame, headings, rows).Show() + + def OnMenuFileOpen(self, evt=None): + try: + result = load_csv(self.csv_frame) + except IOError as e: + MessageDialog(self.csv_frame, str(e), 'Could not load data').Show() + return + + if result is None: + return + else: + self.OnMenuFileClose() + + has_header, values, filename = result + + self.csv_frame.display_panel.from_csv_data(has_header, values) + self.csv_frame.Title = '{0} - {1}'.format(filename, self.default_title) + + self.update_plot_menus(len(self.csv_frame.display_panel) > 0) + + self.filter_menu_item.Enable(True) + + def OnMenuFileClose(self, evt=None): + self.csv_frame.display_panel.SetValue([], []) + self.csv_frame.Title = self.default_title + + self.update_plot_menus(False) + + self.filter_menu_item.Enable(False) + + if self.filter_dialog is not None: + self.filter_dialog.Close() + + self.filters = {} + self.filter_columns = {} + + def OnMenuFileFilters(self, evt=None): + def close_callback(dlg): + self.filters = dlg.filters + self.filter_columns = dlg.filter_columns + + self.filter_dialog = None + + if self.filter_dialog is None: + self.filter_dialog = FilterListDialog(self.csv_frame, self.csv_frame.display_panel.table, + close_callback, self.filters, self.filter_columns) + self.filter_dialog.Show() + + self.filter_dialog.Raise() + + def OnMenuFileExit(self, evt=None): + if self.csv_frame: + self.csv_frame.Close() + + def OnMenuMathDerivative(self, format, evt=None, type='scalar'): + """ + Open up a dialog to calculate derivative + """ + headings, rows, types = self.csv_frame.display_panel.GetValue(types=[type]) + dmath = DerivativeMathSetupDialog(self.csv_frame, headings, rows) + dmath_open = dmath.ShowModal() + + new_headings = headings + new_headings.append(dmath.dheading) + new_rows = concatenate([rows.astype(float),dmath.ddata],1) + + self.csv_frame.display_panel.SetValue(new_headings,new_rows) + + def OnMenuMathFunction(self, format, evt=None, type='scalar'): + """ + Open up a dialog to apply a scalar function of one variable + """ + headings, rows, types = self.csv_frame.display_panel.GetValue(types=[type]) + dmath = FunctionMathSetupDialog(self.csv_frame, headings, rows) + dmath_open = dmath.ShowModal() + + new_headings = headings + new_headings.append(dmath.dheading) + new_rows = concatenate([rows.astype(float),dmath.ddata],1) + + self.csv_frame.display_panel.SetValue(new_headings,new_rows) + + def OnMenuMathFunction2arg(self, format, evt=None, type='scalar'): + """ + Open up a dialog to apply a scalar function of two variables + """ + headings, rows, types = self.csv_frame.display_panel.GetValue(types=[type]) + dmath = FunctionMathSetupDialog2arg(self.csv_frame, headings, rows) + dmath_open = dmath.ShowModal() + + new_headings = headings + new_headings.append(dmath.dheading) + new_rows = concatenate([rows.astype(float),dmath.ddata],1) + + self.csv_frame.display_panel.SetValue(new_headings,new_rows) + + def OnMenuHelpAbout(self, evt=None): + info = wx.AboutDialogInfo() + info.SetName('Data Explorer') + info.SetDescription('An application for displaying data in tabular and graphical form.\n' + '\n' + 'Using Spanish Acquisition version {0}.'.format(VERSION) + ) + + wx.AboutBox(info) + + +if __name__ == "__main__": + app = DataExplorerApp() + app.MainLoop() diff --git a/runtests b/runtests new file mode 100755 index 0000000..ebfde8a --- /dev/null +++ b/runtests @@ -0,0 +1,16 @@ +#!/bin/bash + +if (( $# == 0 )); then + EXCLUDE="--exclude=server" +else + EXCLUDE="" +fi + +TEST_CONFIG_PATH=~/.spacq-test-config.py +if [[ -r "${TEST_CONFIG_PATH}" ]]; then + TEST_CONFIG="--tc-file ${TEST_CONFIG_PATH} --tc-format python" +else + TEST_CONFIG="" +fi + +nosetests $TEST_CONFIG --exclude=test-config.py $EXCLUDE $@ --exe diff --git a/spacq/devices/__init__.py b/spacq/devices/__init__.py new file mode 100755 index 0000000..5251995 --- /dev/null +++ b/spacq/devices/__init__.py @@ -0,0 +1,2 @@ +from . import agilent, iqc, oxford, rohde_schwarz, tektronix, keithley, cryomagnetics, lakeshore, stanford_research_systems +manufacturers = [agilent, iqc, oxford, rohde_schwarz, tektronix, keithley, cryomagnetics, lakeshore, stanford_research_systems] diff --git a/spacq/devices/cryomagnetics/__init__.py b/spacq/devices/cryomagnetics/__init__.py new file mode 100755 index 0000000..c452246 --- /dev/null +++ b/spacq/devices/cryomagnetics/__init__.py @@ -0,0 +1,13 @@ +import logging +log = logging.getLogger(__name__) + + +name = 'Cryomagnetics' + +from . import model4g +models = [model4g] +log.debug('Found models for "{0}": {1}'.format(name, ''.join(str(x) for x in models))) + +from .mock import mock_model4g +mock_models = [mock_model4g] +log.debug('Found mock models for "{0}": {1}'.format(name, ''.join(str(x) for x in mock_models))) diff --git a/spacq/devices/cryomagnetics/gui/model4g.py b/spacq/devices/cryomagnetics/gui/model4g.py new file mode 100755 index 0000000..04f504b --- /dev/null +++ b/spacq/devices/cryomagnetics/gui/model4g.py @@ -0,0 +1,560 @@ +from functools import partial +from pubsub import pub +from threading import Lock +from threading import Thread +import wx + +from spacq.gui.tool.box import Dialog, OK_BACKGROUND_COLOR, MessageDialog +from spacq.interface.units import Quantity, IncompatibleDimensions +from spacq.interface.resources import AcquisitionThread + +""" +Magnet control front panel. + +Dev notes: -Temporary design. Magnet control is planned to be integrated with + the resource automation functionality of this program. + -spacq.devices.iqc.gui.voltage_source.py was used as a guide + in creating this. + -the live aspects of this gui are derived from gui.display.plot.live.scalar.py +""" + +class Model4GChannelPanel(wx.Panel): + + def __init__(self, parent, global_store, subdevice, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + self.global_store = global_store + self.delay = Quantity(1.0, 's') + + # This lock blocks the acquisition thread from acquiring. + self.running_lock = Lock() + self.channel_subdevice = subdevice + + self.displays = {} + self.control_state_displays = {} + self.readout_displays = {} + self.control_state_list = ['persistent_switch_heater','virt_sync_currents'] + self.readout_list = [ 'magnet_current','power_supply_current', + 'persistent_switch_heater', 'high_limit','low_limit','sweep', + 'rate_0','rate_1','rate_2','rate_3','rate_4', 'virt_sync_currents'] + self.measurement_resources = [] + for name in self.readout_list: + self.displays[name] = [] + self.measurement_resources.append((name, self.channel_subdevice.resources[name])) + # A list to save acquired data to before outputting to GUI. + self.measurements = [None] * len(self.measurement_resources) + + # Main Box. + + main_box = wx.BoxSizer(wx.VERTICAL) + + # Channel Header Box. + + channel_header_box = wx.BoxSizer(wx.HORIZONTAL) + main_box.Add(channel_header_box, flag=wx.EXPAND) + + self.channel_button = wx.ToggleButton(self, label='Channel {0} Toggle'.format(self.channel_subdevice.channel)) + self.Bind(wx.EVT_TOGGLEBUTTON, self.OnChannelToggle, self.channel_button) + self.channel_button.SetValue(False) + channel_header_box.Add(self.channel_button) + + ## Control states. + + control_state_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) + channel_header_box.Add((0, 0), 1, wx.EXPAND) + channel_header_box.Add(control_state_grid, flag=wx.ALIGN_RIGHT, border = 20) + + for control_state_name in self.control_state_list: + control_state_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) + control_state_display.BackgroundColour = wx.LIGHT_GREY + control_state_grid.Add(control_state_display, flag=wx.ALIGN_RIGHT) + self.displays[control_state_name].append(control_state_display) + self.control_state_displays[control_state_name] = control_state_display + + # reverse our dictionary for key retrieval by item. + self.inv_control_state_displays = dict((v,k) for k, v in self.control_state_displays.iteritems()) + + # Readouts. + + readout_static_box = wx.StaticBox(self, label = 'Readouts') + readout_box = wx.StaticBoxSizer(readout_static_box, wx.VERTICAL) + main_box.Add(readout_box, flag=wx.EXPAND, proportion=1) + +# readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=2, hgap=1) + readout_grid = wx.FlexGridSizer(rows=len(self.readout_list), cols=3, hgap=1) #TODO: for debugging model4g GUI...replace when no longer needed. + readout_box.Add(readout_grid, flag=wx.ALIGN_RIGHT) + + self.checkboxes = {} + + ## Setup individual labels + displays + + for resource_name in self.readout_list: + + ### Checkbox. #TODO: for debugging model4g GUI...remove when no longer needed. + checkbox = wx.CheckBox(self) + readout_grid.Add(checkbox, flag = wx.ALIGN_LEFT) + self.checkboxes[resource_name] = checkbox + + ### Label. + label = resource_name.replace('_',' ').title() + readout_grid.Add(wx.StaticText(self, label=label + ':'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + + ### Display. + display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) + display.BackgroundColour = wx.LIGHT_GREY + self.displays[resource_name].append(display) + self.readout_displays[resource_name] = display + + ### Connect display to GUI. + readout_grid.Add(self.displays[resource_name][-1], flag=wx.ALIGN_RIGHT) + + # reverse our dictionary for key retrieval by item. + self.inv_readout_displays = dict((v,k) for k, v in self.readout_displays.iteritems()) + + # Controls. + + self.control_static_box = wx.StaticBox(self, label='Controls') + self.control_box=wx.StaticBoxSizer(self.control_static_box, wx.VERTICAL) + main_box.Add(self.control_box, flag=wx.EXPAND) + + ## Persistent Heater Switch. + + heater_box = wx.BoxSizer(wx.HORIZONTAL) + self.control_box.Add(heater_box, flag=wx.ALIGN_RIGHT) + + heater_box.Add(wx.StaticText(self, label='Persistent Switch Heater:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + + self.heater_toggle = wx.ToggleButton(self, label='on/off', size=(100,-1)) + initial_state = self.channel_subdevice.persistent_switch_heater + self.heater_toggle.SetValue(True if initial_state == 1 else 0) + self.Bind(wx.EVT_TOGGLEBUTTON, self.OnHeaterToggle, self.heater_toggle) + heater_box.Add(self.heater_toggle,flag=wx.ALIGN_RIGHT) + + ## Sweeper Control Box. + + sweeper_static_box = wx.StaticBox(self, label = 'Sweep') + sweeper_box = wx.StaticBoxSizer(sweeper_static_box, wx.VERTICAL) + self.control_box.Add(sweeper_box, flag=wx.EXPAND) + + sweep_buttons_box = wx.BoxSizer(wx.HORIZONTAL) + sweeper_box.Add(sweep_buttons_box, flag = wx.CENTER|wx.ALL) + + ### Sweep buttons. + + sweep_buttons_grid = wx.FlexGridSizer(rows=2, cols=2, hgap=1) + sweep_buttons_box.Add(sweep_buttons_grid, flag=wx.CENTER|wx.ALL) + + sweepup_button = wx.Button(self, label='up') + sweepzero_button = wx.Button(self, label='zero') + sweepdown_button = wx.Button(self, label='down') + sweeppause_button = wx.Button(self, label='pause') + self.Bind(wx.EVT_BUTTON, self.OnSweepUp, sweepup_button) + self.Bind(wx.EVT_BUTTON, self.OnSweepZero, sweepzero_button) + self.Bind(wx.EVT_BUTTON, self.OnSweepDown, sweepdown_button) + self.Bind(wx.EVT_BUTTON, self.OnSweepPause, sweeppause_button) + sweep_buttons_grid.Add(sweepup_button) + sweep_buttons_grid.Add(sweepzero_button) + sweep_buttons_grid.Add(sweepdown_button) + sweep_buttons_grid.Add(sweeppause_button) + + ### Current syncing. + + ####some space + + + sync_button = wx.Button(self, label='sync currents') + self.Bind(wx.EVT_BUTTON, self.OnSyncCurrents, sync_button) + sweep_buttons_box.Add(sync_button, flag=wx.LEFT|wx.CENTER, border = 20) + + ## Limits. + + limit_static_box = wx.StaticBox(self, label = 'Limits') + limit_box = wx.StaticBoxSizer(limit_static_box, wx.VERTICAL) + self.control_box.Add(limit_box,flag=wx.EXPAND) + + limits_grid = wx.FlexGridSizer(rows=2, cols=3, hgap=1) + limits_grid.AddGrowableCol(1,1) + + limit_box.Add(limits_grid, flag=wx.ALIGN_RIGHT) + + ### High Limit + + limits_grid.Add(wx.StaticText(self, label='High Limit:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + + set_hilim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) + self.Bind(wx.EVT_BUTTON, self.OnSetHighLimit, set_hilim_button) + limits_grid.Add(set_hilim_button,flag=wx.ALIGN_RIGHT) + + self.hilim_input = wx.TextCtrl(self, size=(100, -1)) + limits_grid.Add(self.hilim_input, flag=wx.EXPAND) + + ### Low Limit + + limits_grid.Add(wx.StaticText(self, label='Low Limit:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + + set_lolim_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) + self.Bind(wx.EVT_BUTTON, self.OnSetLowLimit, set_lolim_button) + limits_grid.Add(set_lolim_button,flag=wx.ALIGN_RIGHT) + + self.lolim_input = wx.TextCtrl(self, size=(100, -1)) + limits_grid.Add(self.lolim_input) + + ## Rates. + + rates_static_box = wx.StaticBox(self, label = 'Rates') + rates_box = wx.StaticBoxSizer(rates_static_box, wx.VERTICAL) + self.control_box.Add(rates_box,flag=wx.EXPAND) + + ## used to have content of rates box all right aligned. + rates_inner_box = wx.BoxSizer(wx.HORIZONTAL) + rates_box.Add(rates_inner_box, flag=wx.ALIGN_RIGHT) + + menu_items = [] + for resource in self.readout_list: + if resource.startswith('rate_'): + menu_items.append(resource) + + self.rates_menu = wx.ComboBox(self,choices = menu_items, style=wx.CB_READONLY) + self.rates_menu.SetStringSelection(menu_items[0]) + rates_inner_box.Add(self.rates_menu, flag=wx.ALIGN_RIGHT) + + set_rate_button = wx.Button(self, label='Set', style=wx.BU_EXACTFIT) + self.Bind(wx.EVT_BUTTON, self.OnSetRate, set_rate_button) + rates_inner_box.Add(set_rate_button,flag=wx.ALIGN_RIGHT) + + self.rate_input = wx.TextCtrl(self, size=(100, -1)) + rates_inner_box.Add(self.rate_input, flag=wx.ALIGN_RIGHT) + + # Finish GUI building. + + self.SetSizerAndFit(main_box) + + # Default behaviour. + + ## start with... + ### ...the threads locked out of acquisition. + self.running_lock.acquire() + ### ...controls disabled. + self.RecursiveEnableSizer(self.control_box,False) + + # Threading. + + self.acqthreads = [] + #TODO: implement with a normal thread instead, to avoid the use of a dummy call to a resource. + guicallback = partial(wx.CallAfter, self.Update) + self.guiupdatethread = AcquisitionThread(self.delay, guicallback, resource=self.channel_subdevice.resources['persistent_switch_heater'], running_lock=self.running_lock) + self.acqthreads.append(self.guiupdatethread) + self.guiupdatethread.daemon = True + self.guiupdatethread.start() + + def __del__(self): + try: + +# if self.channel_button.GetValue() == False: +# self.running_lock.release() + for thread in self.acqthreads: + thread.resource = None + thread.done = True + thread.join() + del thread + +# self.close() + except Exception: + pass + + def UpdateReadouts(self, resource_name, value): + """ + Update appropriate readouts with a new resource value. + Also update button permissions. + """ + if resource_name in self.readout_displays.keys(): + if self.checkboxes[resource_name].Value == False: #TODO: for debugging model4g GUI...remove when no longer needed. + return + + for display in self.displays[resource_name]: + + #perform alterations to output based on where the resource is being readout in GUI. + + inv_cont_dict = self.inv_control_state_displays + if display in inv_cont_dict.keys(): + if inv_cont_dict[display] == 'persistent_switch_heater': + if value == 'on': + display.BackgroundColour = OK_BACKGROUND_COLOR + elif value == 'off': + display.BackgroundColour = wx.LIGHT_GREY + + value_readout = 'heater {0}'.format(value) + + elif inv_cont_dict[display] == 'virt_sync_currents': + if value == 'synced': + display.BackgroundColour = OK_BACKGROUND_COLOR + elif value == 'not synced': + display.BackgroundColour = wx.LIGHT_GREY + #display as-is + value_readout = value + elif display in self.inv_readout_displays.keys(): + #display as-is + value_readout = value + + display.SetValue(str(value_readout)) + + # User Permissions + + # if currents don't match, heater toggle should be disabled. + if display in inv_cont_dict.keys(): + if inv_cont_dict[display] == 'virt_sync_currents': + if value == 'synced': + self.heater_toggle.Enable() + else: + self.heater_toggle.Disable() + + + def Update(self, dummyval): + """ + Acquire data and then update the GUI with this data. + + Note: code taken from sweep controller. + """ + + #this loop sets up separate threads for each resource. + thrs = [] + for i, (name, resource) in enumerate(self.measurement_resources): + if resource is not None: + def save_callback(value, i=i): + self.measurements[i] = value + + callback = partial(wx.CallAfter, partial(self.read_resource, name, resource, save_callback)) + thr = Thread(target=callback) + thrs.append(thr) + thr.daemon = True + thr.start() + + for thr in thrs: + thr.join() + + #this code saves the values to the GUI readouts. + for i, (name,_) in enumerate(self.measurement_resources): + self.UpdateReadouts(name, self.measurements[i]) + + + def read_resource(self, name, resource, save_callback): + """ + Read a value from a resource and handle exceptions. + + Note: code taken from sweep controller. + """ + + if name in self.readout_displays.keys(): + if self.checkboxes[name].Value == False: #TODO: for debugging model4g GUI...remove when no longer needed. + return + + try: + value = resource.value + except Exception as e: + if self.resource_exception_handler is not None: + self.resource_exception_handler(name, e, write=False) + return + + save_callback(value) + + def OnChannelToggle(self, evt=None): + toggle = self.channel_button.GetValue() + if toggle == True: + self.running_lock.release() + elif toggle == False: + self.running_lock.acquire() + + self.RecursiveEnableSizer(self.control_box, toggle) + + #permission defaults. + self.heater_toggle.Disable() + + + def OnSweepDown(self, evt=None): + self.channel_subdevice.resources['sweep'].value = 'down' + + def OnSweepUp(self, evt=None): + self.channel_subdevice.resources['sweep'].value = 'up' + + def OnSweepZero(self, evt=None): + self.channel_subdevice.resources['sweep'].value = 'zero' + + def OnSweepPause(self, evt=None): + self.channel_subdevice.resources['sweep'].value = 'pause' + + def OnSetRate(self, evt=None): + try: + Quantity(self.rate_input.GetValue()) + except ValueError as e: + MessageDialog(self, str(e), 'Invalid value').Show() + return False + + range_id = self.rates_menu.GetCurrentSelection() + resource = self.channel_subdevice.resources['rate_{0}'.format(range_id)] + new_value = self.rate_input.GetValue() + try: + resource.value = resource.convert(new_value) + except IncompatibleDimensions: + MessageDialog(self, ValueError('Expected dimensions to match "{0}"'.format(resource.units))).Show() + + def OnHeaterToggle(self, evt=None): + if self.heater_toggle.GetValue() == True: + new_value = 'on' + if self.heater_toggle.GetValue() == False: + new_value = 'off' + self.channel_subdevice.resources['persistent_switch_heater'].value = new_value + + def OnSetHighLimit(self, evt=None): + try: + Quantity(self.hilim_input.GetValue()) + except ValueError as e: + MessageDialog(self, str(e), 'Invalid value').Show() + return False + + new_value = self.hilim_input.GetValue() + resource = self.channel_subdevice.resources['high_limit'] + try: + resource.value = resource.convert(new_value) + except IncompatibleDimensions: + MessageDialog(self, str(ValueError('Expected dimensions to match "{0}"'.format(resource.units)))).Show() + + def OnSetLowLimit(self, evt=None): + try: + Quantity(self.lolim_input.GetValue()) + except ValueError as e: + MessageDialog(self, str(e), 'Invalid value').Show() + return False + + new_value = self.lolim_input.GetValue() + resource = self.channel_subdevice.resources['low_limit'] + try: + resource.value = resource.convert(new_value) + except IncompatibleDimensions: + MessageDialog(self, ValueError('Expected dimensions to match "{0}"'.format(resource.units))).Show() + + + def OnSyncCurrents(self, evt=None): + self.channel_subdevice.resources['virt_sync_currents'].value = 'start' + + def close(self): + """ + Perform cleanup. + """ + # Ensure the threads exits. + if self.channel_button.GetValue() == False: + self.running_lock.release() + for thread in self.acqthreads: + thread.resource = None + thread.done = True + thread.join() + del thread + + def RecursiveEnableSizer(self,wx_sizer, toggle): + ''' + Helper function that accesses all subwindows of a wxPython + sizer, and enables or disables them based on toggle. + ''' + children = wx_sizer.GetChildren() + for item in children: + + window = item.GetWindow() + sizer = item.GetSizer() + + if sizer: + #recurse + self.RecursiveEnableSizer(sizer,toggle) + elif window: + window.Enable(toggle) + + +class Model4GFrontPanel(wx.Panel): + """ + GUI for controlling the magnet. + """ + + def __init__(self, parent, global_store, model4g, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + self.global_store = global_store + self.model4g = model4g + self.running = False + + + # Main Panel. + panel_box = wx.BoxSizer(wx.VERTICAL) + + ## Channels box. + channels_box = wx.BoxSizer(wx.HORIZONTAL) + panel_box.Add(channels_box) + + ### Channel boxes. + self.channel_panels = [] + for channel_subdevice in self.model4g.channels: + if channel_subdevice is not None: + + channel_static_box = wx.StaticBox(self) + channel_box_sizer = wx.StaticBoxSizer(channel_static_box, wx.VERTICAL) + + #### Channel Inputs/Outputs. + channel_panel = Model4GChannelPanel(self, global_store, channel_subdevice) + channel_box_sizer.Add(channel_panel) + channels_box.Add(channel_box_sizer) + + self.channel_panels.append(channel_panel) + + + + self.SetSizerAndFit(panel_box) + + def close(self): + #TODO: wxPython would probably have a nicer way of sending a close down through children. + for channel_panel in self.channel_panels: + channel_panel.close() + +class Model4GFrontPanelDialog(Dialog): + """ + A wrapper for Model4GFrontPanel. + """ + + def __init__(self, parent, global_store, model4g_name, *args, **kwargs): + # If the device doesn't exist, give up. + try: + model4g = global_store.devices[model4g_name].device + except (KeyError, AttributeError): + self.Destroy() + + return + + Dialog.__init__(self, parent, title='Model4G Front Panel', *args, **kwargs) + + self.model4g_name = model4g_name + + # Dialog. + dialog_box = wx.BoxSizer(wx.VERTICAL) + + ## Settings panel. + self.panel = Model4GFrontPanel(self, global_store, model4g) + dialog_box.Add(self.panel) + + self.SetSizerAndFit(dialog_box) + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + # Subscriptions. + pub.subscribe(self.msg_device, 'device.added') + pub.subscribe(self.msg_device, 'device.removed') + + def msg_device(self, name, value=None): + if name == self.model4g_name: + # Device has changed, so we can't trust it anymore. + self.Destroy() + + return + + def OnClose(self, evt): + self.panel.close() + evt.Skip() \ No newline at end of file diff --git a/spacq/devices/cryomagnetics/mock/__init__.py b/spacq/devices/cryomagnetics/mock/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/spacq/devices/cryomagnetics/mock/mock_model4g.py b/spacq/devices/cryomagnetics/mock/mock_model4g.py new file mode 100755 index 0000000..bef1775 --- /dev/null +++ b/spacq/devices/cryomagnetics/mock/mock_model4g.py @@ -0,0 +1,236 @@ +from ...mock.mock_abstract_device import MockAbstractDevice +from ..model4g import Model4G +import time + +""" +Mock Model4G Power Supply +""" + +class MockChannel(object): + """ + A mock channel for a mock 4G power supply. + """ + def __init__(self, device, channel, *args, **kwargs): + self.mock_state = {} + + # Internals. (not read or write) + self.mock_state['device'] = device + self.mock_state['channel'] = channel + self.mock_state['units'] = 'G' + + # Read-only. + self.mock_state['magnet_current'] = 0 + self.mock_state['power_supply_current'] = 0 + + # Read-write. + self.mock_state['high_limit'] = 20 + self.mock_state['low_limit'] = 10 + self.mock_state['sweep'] = 'Standby' + self.mock_state['persistent_switch_heater'] = 0 + self.mock_state['rate_0'] = 0.001 + self.mock_state['rate_1'] = 0.002 + self.mock_state['rate_2'] = 0.003 + self.mock_state['rate_3'] = 0.004 + self.mock_state['rate_4'] = 0.005 + + self.mockdata = MockTimeData(self.mock_state['power_supply_current'],self.mock_state['power_supply_current'],1) + +class MockData(object): + ''' + Simulates the data that can be acquired from a sweep. + It wraps a list that contains N datapoints in min to max, including min and max. + Note that it is possible to have max < min. This will merely make the list go in + descending order (such as if the 4G was sweeping down). + ''' + + def __init__(self, data_min, data_max, N): + + self.index = 0 + # We map a range [0,1,2,...,N] to the range [min,max]. + if N > 1: + self.data_list = map( (lambda n:(data_max-data_min)*(float(n)/(N-1))+data_min), range(N)) + elif N == 1: + # if we have only 1 data point, then we just set it to min. + self.data_list = [data_min] + + def GetDatum(self): + + result = self.data_list[self.index] + + #once the magnet sweeps to the last value, if one keeps retrieving values, it will stay + #at the last value. + if (self.index + 1) < len(self.data_list): + self.index += 1 + + + return result + + def Reset(self): + self.index = 0 + +class MockTimeData(object): + ''' + Simulates the data that can be acquired from a sweep. + It will return data as a linear function of time such that: + t = 0 <-> data_min + t = time_to_max <-> data_max + where t is the time since instantiation + ''' + + def __init__(self, data_min, data_max, time_to_max): + + self.t_init = time.time() + + def data_fn(t): + if (t > time_to_max): + return data_max + else: + return (data_max-data_min)*(float(t)/time_to_max)+data_min + + self.f = data_fn + + def GetDatum(self): + result = self.f(time.time() - self.t_init) + return result + + def Reset(self): + self.t_init = time.time() + + + +class MockModel4G(MockAbstractDevice, Model4G): + """ + Mock interface for Model 4G Power Supply. + """ + + def __init__(self, *args, **kwargs): + self.mocking = Model4G + + MockAbstractDevice.__init__(self, *args, **kwargs) + + def _reset(self): + # Many other state properties are in the mock subdevice class MockChannel. + + self.mock_state['channels'] = [None] # There is no channel 0. + for x in xrange(1, 3): + self.mock_state['channels'].append(MockChannel(self,x)) + + self.mock_state['active_channel'] = 1 + + def write(self, message, result=None, done=False): + if not done: + cmd, args, query = self._split_message(message) + + mock_chan = self.mock_state['channels'][int(self.mock_state['active_channel'])] + + #should really take in kilogaussian and output in kilogaussian... + multiplier = None + output_unit = None + if mock_chan.mock_state['units'] == 'G': + multiplier = 1. + output_unit = 'kG' + elif mock_chan.mock_state['units'] == 'A': + multiplier = 1. + output_unit = 'A' + + + + if cmd[0] == 'ulim': + if query: + result = '{0} {1}'.format(mock_chan.mock_state['high_limit'] * multiplier, output_unit) + else: + if float(args) >= mock_chan.mock_state['low_limit']: + mock_chan.mock_state['high_limit'] = float(args) + done = True + if cmd[0] == 'llim': + if query: + result = '{0} {1}'.format(mock_chan.mock_state['low_limit'] * multiplier, output_unit) + else: + if float(args) <= mock_chan.mock_state['high_limit']: + mock_chan.mock_state['low_limit'] = float(args) + done = True + if cmd[0] == 'pshtr': + if query: + result = mock_chan.mock_state['persistent_switch_heater'] + else: + if args == 'on': + mock_chan.mock_state['persistent_switch_heater'] = 1 + elif args == 'off': + mock_chan.mock_state['persistent_switch_heater'] = 0 + done = True + elif cmd[0] == 'sweep': + if query: + result = mock_chan.mock_state['sweep'] + else: + last_state = mock_chan.mock_state['sweep'] + + #if sweep changes, then the data that will acquired is changed. + if args == 'up' and last_state != 'up' : #up + new_min = mock_chan.mock_state['power_supply_current'] + new_max = mock_chan.mock_state['high_limit'] + mock_chan.mockdata = MockTimeData(new_min,new_max,1) + elif args == 'zero' and last_state != 'zero': #zero + new_min = mock_chan.mock_state['power_supply_current'] + new_max = 0 + mock_chan.mockdata = MockTimeData(new_min,new_max,1) + elif args == 'down' and last_state != 'down': #down + new_min = mock_chan.mock_state['power_supply_current'] + new_max = mock_chan.mock_state['low_limit'] + mock_chan.mockdata = MockTimeData(new_min,new_max,1) + elif args == 'pause' and last_state != 'paused': #paused + new_min = new_max = mock_chan.mock_state['power_supply_current'] + mock_chan.mockdata = MockTimeData(new_min,new_max,1) + + if args == 'pause': + mock_chan.mock_state['sweep'] = 'Pause' + elif args == 'zero': + mock_chan.mock_state['sweep'] = 'Sweeping to zero' + else: + mock_chan.mock_state['sweep'] = 'Sweeping {0}'.format(args) + done = True + elif cmd[0] == 'chan': + if query: + result = self.mock_state['active_channel'] + else: + self.mock_state['active_channel'] = args + done = True + elif cmd[0] == 'units': + if query: + result = mock_chan.mock_state['units'] + else: + mock_chan.mock_state['units'] = args + done = True + elif cmd[0] == 'rate': + if query: + result = mock_chan.mock_state['rate_{0}'.format(args[0])] + else: + args = args.split() + mock_chan.mock_state['rate_{0}'.format(args[0])] = args[1] + done = True + elif cmd[0] == 'imag': + if query: + if mock_chan.mock_state['persistent_switch_heater'] == 1: + mock_chan.mock_state['power_supply_current'] = mock_chan.mockdata.GetDatum() + mock_chan.mock_state['magnet_current'] = mock_chan.mock_state['power_supply_current'] + result = '{0} {1}'.format(mock_chan.mock_state['magnet_current'] * multiplier, output_unit) + else: + result = '{0} {1}'.format(mock_chan.mock_state['magnet_current'] * multiplier, output_unit) + done = True + elif cmd[0] == 'iout': + if query: + mock_chan.mock_state['power_supply_current'] = mock_chan.mockdata.GetDatum() + result = '{0} {1}'.format(mock_chan.mock_state['power_supply_current'] * multiplier, output_unit) + done = True + + # Perform rounding as defined by the precision of the actual device. Rounds to nearest Gaussian. + if result is not None and ' kG' in str(result): + rounded_number = round(float(result.replace(' kG','')), 3) + result = '{0} {1}'.format(rounded_number, 'kG') + else: + pass #TODO: need to support rounding on amps. + + MockAbstractDevice.write(self, message, result, done) + + +name = 'Model 4G' +implementation = MockModel4G diff --git a/spacq/devices/cryomagnetics/mock/tests/__init__.py b/spacq/devices/cryomagnetics/mock/tests/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/spacq/devices/cryomagnetics/mock/tests/test_mock_model4g.py b/spacq/devices/cryomagnetics/mock/tests/test_mock_model4g.py new file mode 100755 index 0000000..7a7b35f --- /dev/null +++ b/spacq/devices/cryomagnetics/mock/tests/test_mock_model4g.py @@ -0,0 +1,25 @@ +from unittest import main + +from ... import model4g +from .. import mock_model4g + +from ...tests.server.test_model4g import Model4GTest + +# Don't lose the real device. +real_Model4G = model4g.Model4G +is_mock = Model4GTest.mock + + +def setup(): + # Run the tests with a fake device. + model4g.Model4G = mock_model4g.MockModel4G + Model4GTest.mock = True + +def teardown(): + # Restore the real device for any remaining tests. + model4g.Model4G = real_Model4G + Model4GTest.mock = is_mock + + +if __name__ == '__main__': + main() diff --git a/spacq/devices/cryomagnetics/model4g.py b/spacq/devices/cryomagnetics/model4g.py new file mode 100755 index 0000000..ae8f6b3 --- /dev/null +++ b/spacq/devices/cryomagnetics/model4g.py @@ -0,0 +1,798 @@ +import logging +log = logging.getLogger(__name__) + +from spacq.interface.resources import Resource +from spacq.tool.box import Synchronized +from spacq.interface.units import Quantity +from time import sleep +from functools import wraps + +from ..abstract_device import AbstractDevice, AbstractSubdevice +from ..tools import quantity_wrapped, quantity_unwrapped +from ..tools import dynamic_quantity_wrapped, dynamic_converted_quantity_unwrapped + + +""" +Cryomagnetics model 4G Device +""" + +class Channel(AbstractSubdevice): + """ + Interface for a channel on the Model4G + """ + + + allowed_switch_heater = set(['on','off']) + allowed_heater_wait_mode = set(['on','off']) + allowed_sweep = set(['up','zero','down', 'pause']) + allowed_sync = set(['start','stop']) + allowed_energysave_mode = set([0,1,2,3,4]) + allowed_units = set(['kG','A']) + + def _setup(self): + AbstractSubdevice._setup(self) + + # Resources. + + read_only = ['magnet_current','power_supply_current'] + for name in read_only: + self.resources[name] = Resource(self, name) + + read_write = ['persistent_switch_heater', 'high_limit','low_limit','sweep', + 'rate_0','rate_1','rate_2','rate_3','rate_4', 'virt_sync_currents', 'virt_imag', 'virt_iout', + 'virt_imag_sweep_to','virt_iout_sweep_to','virt_energysave_mode', 'virt_heater_wait_mode' + ,'virt_sweep_sleep', 'units'] + for name in read_write: + self.resources[name] = Resource(self, name, name) + + # Resources with their units controlled by self._units. + self.dynamic_unit_resources = ['virt_imag_sweep_to','virt_iout_sweep_to','virt_imag','virt_iout','magnet_current', + 'power_supply_current','high_limit','low_limit' ] + for name in self.dynamic_unit_resources: + self.resources[name].units = self._units + + self.resources['virt_sweep_sleep'].units = 's' + for x in range(5): + self.resources['rate_{0}'.format(x)].units = 'A.s-1' + + self.resources['virt_heater_wait_mode'].allowed_values = self.allowed_heater_wait_mode + self.resources['virt_energysave_mode'].allowed_values = self.allowed_energysave_mode + self.resources['virt_energysave_mode'].converter = int + self.resources['persistent_switch_heater'].allowed_values = self.allowed_switch_heater + self.resources['sweep'].allowed_values = self.allowed_sweep + self.resources['virt_sync_currents'].allowed_values = self.allowed_sync + self.resources['units'].allowed_values = self.allowed_units + + def __init__(self, device, channel, *args, **kwargs): + self.channel = channel + + # internal attributes used for virtual features not present in actual device. + self._units = 'kG' # default units. + self._energysave_mode = 0 + self._iout_target = None + self._imag_target = None + self._heater_wait_mode = 'on' + self._sweep_sleep = 1. + + AbstractSubdevice.__init__(self, device, *args, **kwargs) + + def _connected(self): + # Initializations requiring GPIB. + self.units = self._units + self._iout_target = self.power_supply_current.original_value + self._imag_target = self.magnet_current.original_value + + def _wait_for_sweep(self): + ''' + This is an internal function that loops until a sweep is complete. + This allows some of the virtual features to wait until a sweep is complete before performing other commands. + ''' + + for i in xrange(0,2): + current_sweep = self.sweep + if current_sweep == 'Sweeping up': + while current_sweep != 'Pause' and self.power_supply_current != self.high_limit: + sleep(0.1) #give the GPIB some breathing space. + current_sweep = self.sweep + elif current_sweep == 'Sweeping to zero': + while current_sweep != 'Pause' and self.power_supply_current != Quantity(0,self._units): + sleep(0.1) + current_sweep = self.sweep + elif current_sweep == 'Sweeping down': + while current_sweep != 'Pause' and self.power_supply_current != self.low_limit: + sleep(0.1) + current_sweep = self.sweep + if i == 0 and self.virt_sweep_sleep.value != 0: + sleep(self.virt_sweep_sleep.value) # we sleep, then check once more to ensure the sweep has stabilized. + + + class _set_channel(object): + """ + A decorator which prefixes a method with a setting of the channel. + Note: Usually, this decorator should be wrapped underneath the @Synchronized() method + """ + @staticmethod + def __call__(f): + @wraps(f) + def decorated(self, *args, **kwargs): + + # Set channel here. + if self.device.active_channel_store is not self.channel: + self.device.active_channel = self.channel + + return f(self, *args, **kwargs) + + return decorated + + + # Low-level Direct Controls. #################################################################################### + + + @property + @Synchronized() + @dynamic_quantity_wrapped('_units') + @_set_channel() + def high_limit(self): + """ + The upper limit on the magnetic current + """ + response = self.device.ask('ulim?') + stripped_response = Quantity.from_string(response)[0] + return stripped_response + + @high_limit.setter + @Synchronized() + @dynamic_converted_quantity_unwrapped('_units') + @_set_channel() + def high_limit(self,value): + self.device.write('ulim {0}'.format(value)) + + @property + @Synchronized() + @dynamic_quantity_wrapped('_units') + @_set_channel() + def low_limit(self): + """ + The lower limit on the magnetic current + """ + response = self.device.ask('llim?') + stripped_response = Quantity.from_string(response)[0] + return stripped_response + + @low_limit.setter + @Synchronized() + @dynamic_converted_quantity_unwrapped('_units') + @_set_channel() + def low_limit(self,value): + + self.device.write('llim {0}'.format(value)) + + @property + @Synchronized() + @dynamic_quantity_wrapped('_units') + @_set_channel() + def magnet_current(self): + """ + This is the persistent magnet current setting + """ + response = self.device.ask('imag?') + stripped_response = Quantity.from_string(response)[0] + return stripped_response + + @property + @Synchronized() + @_set_channel() + def persistent_switch_heater(self): + """ + The persistent switch heater + """ + response = float(self.device.ask('pshtr?')) + + # when getting from the device immediately after a set, the power supply returns a 2 if it has not fully + # changed + while response == 2: + sleep(0.5) + response = float(self.device.ask('pshtr?')) + + + response_dict = {0:'off', 1:'on'} + + return response_dict[response] + + @persistent_switch_heater.setter + @Synchronized() + @_set_channel() + def persistent_switch_heater(self,value): + + if value not in self.allowed_switch_heater: + raise ValueError('Invalid heater switch value: {0}'.format(value)) + + # don't allow the heater switch to go on if currents not synced + if self.virt_sync_currents != 'synced' and value == 'on': + raise ValueError('Currents are not synced in channel {0}.'.format(self.channel)) + + self.device.write('pshtr {0}'.format(value)) + + # Give the heaters time to warm up, if desired. + if self.virt_heater_wait_mode == 'on': + sleep(1) + + @property + @Synchronized() + @dynamic_quantity_wrapped('_units') + @_set_channel() + def power_supply_current(self): + """ + The power supply output current + """ + response = self.device.ask('iout?') + stripped_response = Quantity.from_string(response)[0] + return stripped_response + + @quantity_wrapped('A') + @Synchronized() + @_set_channel() + def ranges(self,range_id): + ''' + Used to grab the range for a given range id. + ''' + if range_id is not 0: + lower = self.device.ask('range? {0}'.format(range_id-1)) + else: + lower = 0 + + upper = self.device.ask('range? {0}'.format(range_id)) + + return ('{0} to {1}'.format(lower,upper)) + + @property + @quantity_wrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_0(self): + """ + A rate for a rate range + """ + return float(self.device.ask('rate? 0')) + + @rate_0.setter + @quantity_unwrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_0(self,value): + + self.device.write('rate 0 {0}'.format(value)) + + @property + @quantity_wrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_1(self): + """ + A rate for a rate range + """ + return float(self.device.ask('rate? 1')) + + @rate_1.setter + @quantity_unwrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_1(self,value): + + self.device.write('rate 1 {0}'.format(value)) + + @property + @quantity_wrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_2(self): + """ + A rate for a rate range + """ + return float(self.device.ask('rate? 2')) + + @rate_2.setter + @quantity_unwrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_2(self,value): + + self.device.write('rate 2 {0}'.format(value)) + + @property + @quantity_wrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_3(self): + """ + A rate for a rate range + """ + + return float(self.device.ask('rate? 3')) + + @rate_3.setter + @quantity_unwrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_3(self,value): + + self.device.write('rate 3 {0}'.format(value)) + + @property + @quantity_wrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_4(self): + """ + A rate for a rate range + """ + return float(self.device.ask('rate? 4')) + + @rate_4.setter + @quantity_unwrapped('A.s-1') + @Synchronized() + @_set_channel() + def rate_4(self,value): + + self.device.write('rate 4 {0}'.format(value)) + + @property + @Synchronized() + @_set_channel() + def sweep(self): + """ + The sweeper control. + """ + response = str(self.device.ask('sweep?')) + return response + + @sweep.setter + @Synchronized() + @_set_channel() + def sweep(self,value): + + if value not in self.allowed_sweep: + raise ValueError('Invalid sweep value: {0}'.format(value)) + + self.device.write('sweep {0}'.format(value)) + + @property + @Synchronized() + @_set_channel() + def units(self): + """ + Get current units of the device. + """ + self.device.active_channel = self.channel + + response = self.device.ask('units?') + + # We perform a conversion between the GUI and the device with this dict. + read_dict = {'G':'kG','A':'A'} + + # if the device is different from the local copy, then we convert device to local copy. + if self._units != read_dict[response]: + self.units = self._units + + return read_dict[response] + + + @units.setter + @Synchronized() + @_set_channel() + def units(self,value): + """ + Set current units. + """ + if value not in self.allowed_units: + raise ValueError('Invalid units: {0}'.format(value)) + + # Perform a conversion between the GUI and the device with this dict. + write_dict = {'kG':'G','A':'A'} + + self.device.write('units {0}'.format(write_dict[value])) + + # Change our locally stored copy to the new user-defined units. + self._units = value + + # Change all the appropriate resources' units + for name in self.dynamic_unit_resources: + self.resources[name].units = self._units + + + # High-level Virtual Controls. ################################################################################## + + + @property + def virt_energysave_mode(self): + ''' + If enabled: after incrementing virt_imag_sweep_to, the magnet will go into persistent mode, and then + start a sweep of the power supply current to 0 + ''' + return self._energysave_mode + + @virt_energysave_mode.setter + def virt_energysave_mode(self, value): + ''' + If enabled: after incrementing virt_imag_sweep_to, the magnet will go into persistent mode, and then + start a sweep of the power supply current to 0 + ''' + self._energysave_mode = value + + @property + @Synchronized() + @dynamic_quantity_wrapped('_units') + def virt_imag(self): + ''' + Getter: + Simply returns the magnet current. + Setter: + This wraps virt_iout_sweep_to with current syncing, as well as optional energy saving. + ''' + return self.magnet_current.original_value + + @virt_imag.setter + @dynamic_converted_quantity_unwrapped('_units') + def virt_imag(self, value): + + if self.virt_energysave_mode == 4: + self.virt_sync_currents = 'start' + self._wait_for_sweep() + self.persistent_switch_heater = 'on' + self.virt_iout = Quantity(value,self._units) + self.persistent_switch_heater = 'off' + self.sweep = 'zero' + elif self.virt_energysave_mode == 3: + self.virt_sync_currents = 'start' + self._wait_for_sweep() + self.device.virt_both_persistent_switch_heaters = 'on' + self.virt_iout = Quantity(value,self._units) + self.device.virt_both_persistent_switch_heaters = 'off' + self.sweep = 'zero' + elif self.virt_energysave_mode == 2: + self.persistent_switch_heater = 'on' + self.virt_iout = Quantity(value,self._units) + self.persistent_switch_heater = 'off' + elif self.virt_energysave_mode == 1: + self.device.virt_both_persistent_switch_heaters = 'on' + self.virt_iout = Quantity(value,self._units) + self.device.virt_both_persistent_switch_heaters = 'off' + elif self.virt_energysave_mode == 0: + if self.persistent_switch_heater != 'on': + raise ValueError('Heater switch is not on in channel {0}.'.format(self.channel)) + self.virt_iout = Quantity(value, self._units) + + @property + @dynamic_quantity_wrapped('_units') + def virt_imag_sweep_to(self): + ''' + Getter: + Simply returns the magnet current. + Setter: + This wraps virt_iout_sweep_to with current syncing, as well as optional energy saving. + ''' + return self._imag_target + + @virt_imag_sweep_to.setter + @dynamic_converted_quantity_unwrapped('_units') + def virt_imag_sweep_to(self, value): + + if self.persistent_switch_heater != 'on': + self.persistent_switch_heater = 'on' + + self.virt_iout_sweep_to = Quantity(value, self._units) + + if self._units == 'kG': + self._imag_target = round(value,3) #round to the nearest Gaussian. + else: + self._imag_target = value #TODO: need to support rounding on amps. + + @property + @Synchronized() + @dynamic_quantity_wrapped('_units') + def virt_iout(self): + """ + Getter: + Simply returns the power supply current. + Setter: + Used to increment the output power supply current using lower level controls. + + Note: power_supply_current is already wrapped with units, so no need to wrap this function. + """ + return self.power_supply_current.original_value + + @virt_iout.setter + @dynamic_converted_quantity_unwrapped('_units') + def virt_iout(self, value): + + # determine whether to set the hilim or lolim for increment, then sweep + if value == 0: + self.sweep = 'zero' + elif self.power_supply_current.value < value: + + if self.low_limit > Quantity(value, self._units): + self.low_limit = Quantity(value, self._units) + + self.high_limit = Quantity(value, self._units) + self.sweep = 'up' + + elif self.power_supply_current.value > value: + + if self.high_limit < Quantity(value, self._units): + self.high_limit = Quantity(value, self._units) + + self.low_limit = Quantity(value, self._units) + self.sweep = 'down' + + self._wait_for_sweep() + + + + @property + @Synchronized() + @dynamic_quantity_wrapped('_units') + def virt_iout_sweep_to(self): + """ + Getter: + Simply returns the power supply current. + Setter: + Used to increment the output power supply current using lower level controls. + + Note: power_supply_current is already wrapped with units, so no need to wrap this function. + """ + return self._iout_target + + @virt_iout_sweep_to.setter + @dynamic_converted_quantity_unwrapped('_units') + def virt_iout_sweep_to(self, value): + + # determine whether to set the hilim or lolim for increment, then sweep + if value == 0: + self.sweep = 'zero' + elif self.power_supply_current.value < value: + + if self.low_limit > Quantity(value, self._units): + self.low_limit = Quantity(value, self._units) #we put the low limit to zero + + self.high_limit = Quantity(value, self._units) + self.sweep = 'up' + + elif self.power_supply_current.value > value: + + if self.high_limit < Quantity(value, self._units): + self.high_limit = Quantity(value, self._units) + + self.low_limit = Quantity(value, self._units) + self.sweep = 'down' + + + if self._units == 'kG': + self._iout_target = round(value,3) + else: + self._iout_target = value #TODO: need to support rounding on amps. + + @property + @quantity_wrapped('s') + def virt_sweep_sleep(self): + """ + Debugging purposes, although potentially the basis for a feature in the future. + Changes the sleep period in _wait_for_sweep after detecting the sweep is done. + """ + return self._sweep_sleep + + @virt_sweep_sleep.setter + @quantity_unwrapped('s') + def virt_sweep_sleep(self,value): + + self._sweep_sleep = value + + @property + @Synchronized() + def virt_sync_currents(self): + """ + Used to sync the currents. Note this is a virtual feature not present in the actual device. + """ + if self.magnet_current == self.power_supply_current: + return 'synced' + else: + return 'not synced' + + @virt_sync_currents.setter + @Synchronized() + def virt_sync_currents(self, value): + + if value == 'start': + self.virt_iout_sweep_to = self.magnet_current + elif value == 'stop': + self.sweep = 'pause' + + @property + def virt_heater_wait_mode(self): + + return self._heater_wait_mode + + @virt_heater_wait_mode.setter + def virt_heater_wait_mode(self, value): + + if value not in self.allowed_heater_wait_mode: + raise ValueError('Invalid heater wait mode value: {0}'.format(value)) + + self._heater_wait_mode = value + + +class Model4G(AbstractDevice): + """ + Interface for Model 4G + """ + allowed_active_channel = set([1,2]) + allowed_both_heaters = set(['on','off']) + allowed_both_units = set(['kG','A']) + + @property + def _gui_setup(self): + try: + from .gui.model4g import Model4GFrontPanelDialog + + return Model4GFrontPanelDialog + except ImportError as e: + log.debug('Could not load GUI setup for device "{0}": {1}'.format(self.name, str(e))) + + return None + + def _setup(self): + AbstractDevice._setup(self) + + # Channel subdevices. + self.channels = [None] # There is no channel 0. + for chan_num in xrange(1, 3): + channel = Channel(self,chan_num) + self.subdevices['channel{0}'.format(chan_num)] = channel + # this list is useful as it is actually ordered, as opposed to the dict above. + self.channels.append(channel) + + #This saves the last channel set to the device for programming purposes...its a way of minimizing + #calls to the device. + self.active_channel_store = None + + # Resources. + + read_write = ['active_channel', 'virt_both_persistent_switch_heaters', 'virt_both_units'] + for name in read_write: + self.resources[name] = Resource(self, name, name) + + self.resources['active_channel'].allowed_values = self.allowed_active_channel + self.resources['active_channel'].converter = int + self.resources['virt_both_persistent_switch_heaters'].allowed_values = self.allowed_both_heaters + self.resources['virt_both_units'].allowed_values = self.allowed_both_units + + @Synchronized() + def _connected(self): + AbstractDevice._connected(self) + + # Start off with active channel as 1. Doing this to initialize active_channel_store. + self.active_channel = 1 + + + @Synchronized() + def reset(self): + """ + Reset the device to its default state. + """ + + log.info('Resetting "{0}".'.format(self.name)) + self.write('*rst') + + #TODO: test if the *rst actually DOES do something to the magnet controller. + + self.virt_both_units = 'kG' + + for channel in [1,2]: + + subdev = self.channels[channel] + + subdev.virt_energysave_mode = 0 + subdev.virt_sync_currents = 'start' + subdev._wait_for_sweep() + subdev.persistent_switch_heater = 'on' + subdev.virt_imag = Quantity('0 kG') + + subdev.low_limit = Quantity('0 kG') + subdev.high_limit = Quantity('0 kG') + + subdev.virt_imag_sweep_to = Quantity('0 kG') + subdev.virt_iout_sweep_to = Quantity('0 kG') + subdev.persistent_switch_heater = 'off' + + #ensure the defaults set appropriately for both channels + for channel in [1,2]: + subdev = self.channels[channel] + + resource_check_dict = { subdev.virt_energysave_mode:0, + subdev.virt_sync_currents:'synced', + self.virt_both_units:'kG', + subdev.power_supply_current:Quantity('0 kG'), + subdev.magnet_current:Quantity('0 kG'), + subdev.persistent_switch_heater:'off', + subdev.high_limit:Quantity('0 kG'), + subdev.low_limit:Quantity('0 kG'), + subdev.virt_imag_sweep_to:Quantity('0 kG'), + subdev.virt_iout_sweep_to:Quantity('0 kG') + } + + for resource, value in resource_check_dict.items(): + if resource != value: + raise ValueError('A resource with value {0} did not set to its default value of {1}'.format(resource, value)) + + + @property + def active_channel(self): + """ + The active device channel. + """ + return float(self.ask('chan?')) + + @active_channel.setter + def active_channel(self,value): + if value not in self.allowed_active_channel: + raise ValueError('Invalid channel value: {0}'.format(value)) + + self.write('chan {0}'.format(value)) + self.active_channel_store = value + + @property + @Synchronized() + def virt_both_persistent_switch_heaters(self): + """ + The heaters on both channels. Control over both is desirable in order to avoid eddy currents in the system. + """ + + heaters = [channel.persistent_switch_heater for channel in self.channels[1:]] + all_heaters_same = all(heaters[0] == heater for heater in heaters) + + # Output based on states. + if all_heaters_same == True: + return heaters[0] + else: + return 'unequal' + + @virt_both_persistent_switch_heaters.setter + @Synchronized() + def virt_both_persistent_switch_heaters(self,value): + if value not in self.allowed_both_heaters: + raise ValueError('Invalid heater switch value: {0}'.format(value)) + + #can set value on or off and it will write to all channels + for channel in self.channels[1:]: + channel.persistent_switch_heater = value + + @property + @Synchronized() + def virt_both_units(self): + """ + Get current units of both device's channels. + """ + both_units = [channel.units for channel in self.channels[1:]] + all_units_same = all(both_units[0] == units for units in both_units) + + # Output based on states. + if all_units_same == True: + return both_units[0] + else: + return 'unequal' + + + @virt_both_units.setter + def virt_both_units(self,value): + """ + Set current units on both channels. + """ + if value not in self.allowed_both_units: + raise ValueError('Invalid units: {0}'.format(value)) + + #write to all channels + for channel in self.channels[1:]: + channel.units = value + + +name = 'Model 4G' +implementation = Model4G diff --git a/spacq/devices/cryomagnetics/tests/__init__.py b/spacq/devices/cryomagnetics/tests/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/spacq/devices/cryomagnetics/tests/server/__init__.py b/spacq/devices/cryomagnetics/tests/server/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/spacq/devices/cryomagnetics/tests/server/test_model4g.py b/spacq/devices/cryomagnetics/tests/server/test_model4g.py new file mode 100755 index 0000000..764abfd --- /dev/null +++ b/spacq/devices/cryomagnetics/tests/server/test_model4g.py @@ -0,0 +1,250 @@ +from nose.tools import eq_ +from unittest import main +from time import sleep + +from spacq.tests.tool.box import DeviceServerTestCase +from spacq.interface.units import Quantity, IncompatibleDimensions + +from ... import model4g + +""" +model4g: +-go over code looking for missed cases. +""" + +class Model4GTest(DeviceServerTestCase): + + channel_to_test = 1 + + def obtain_device(self): + return DeviceServerTestCase.obtain_device(self, impl=model4g.Model4G, + manufacturer='Cryomagnetics', model='Model 4G') + + def testLimits(self): + """ + Test the limits. + """ + magctrler = self.obtain_device() + magctrler.reset() + chanctrler = magctrler.channels[self.channel_to_test] + + # Test setting the high limit. + + chanctrler.high_limit = Quantity('50 G') + eq_(chanctrler.high_limit, Quantity('50 G')) + + try: + chanctrler.high_limit = 55 + except AttributeError: + pass + else: + assert False, 'Expected AttributeError.' + + # ...and the low limit. + + chanctrler.low_limit = Quantity('40 G') + eq_(chanctrler.low_limit, Quantity('40 G')) + + try: + chanctrler.low_limit = 35 + except AttributeError: + pass + else: + assert False, 'Expected AttributeError.' + + # Test the limit restrictions. + + chanctrler.low_limit = Quantity('60 G') + eq_(chanctrler.low_limit, Quantity('40 G')) + + chanctrler.high_limit = Quantity('30 G') + eq_(chanctrler.high_limit, Quantity('50 G')) + + def testHeaters(self): + """ + Test the heaters. + """ + magctrler = self.obtain_device() + magctrler.reset() + chanctrler = magctrler.channels[self.channel_to_test] + + # Test turning a heater on and off. + + chanctrler.persistent_switch_heater = 'on' + eq_(chanctrler.persistent_switch_heater,'on') + + chanctrler.persistent_switch_heater = 'off' + eq_(chanctrler.persistent_switch_heater,'off') + + try: + chanctrler.persistent_switch_heater = 'something else' + except ValueError: + pass + else: + assert False, 'Expected ValueError.' + + # Test both heaters option. + + magctrler.virt_both_persistent_switch_heaters = 'on' + eq_(magctrler.virt_both_persistent_switch_heaters, 'on') + + chanctrler.persistent_switch_heater = 'off' + eq_(magctrler.virt_both_persistent_switch_heaters, 'unequal') + + + def testUnits(self): + """ + Test the units. + """ + magctrler = self.obtain_device() + magctrler.reset() + chanctrler = magctrler.channels[self.channel_to_test] + + # Test turning a heater on and off. + + chanctrler.units = 'A' + eq_(chanctrler.units,'A') + + chanctrler.units = 'kG' + eq_(chanctrler.units,'kG') + + try: + chanctrler.units = 'T' + except ValueError: + pass + else: + assert False, 'Expected ValueError.' + + # Test both heaters option. + + magctrler.virt_both_units = 'A' + eq_(magctrler.virt_both_units, 'A') + + chanctrler.units = 'kG' + eq_(magctrler.virt_both_units, 'unequal') + + # Try setting hilim with something not in the right units, and something that is in related units. + try: + chanctrler.high_limit = Quantity('5 A') + except IncompatibleDimensions: + pass + else: + assert False, 'Expected IncompatibleDimensions.' + + chanctrler.high_limit = Quantity('0.5 T') + eq_(chanctrler.high_limit, Quantity('5 kG')) + + def testCurrentSync(self): + """ + Test to see if the current syncing works in a variety of circumstances. + """ + magctrler = self.obtain_device() + magctrler.reset() + chanctrler = magctrler.channels[self.channel_to_test] + + # set the curs different. + # check to see if not synced. + chanctrler.virt_iout = Quantity('20 G') + chanctrler.virt_sync_currents = 'start' + chanctrler._wait_for_sweep() + eq_(chanctrler.virt_iout,chanctrler.virt_imag) + + def testRounding(self): + """ + The model4g rounds to a nearest gaussian, and amp (??). Test this since the code relies on + this with the sweep_to higher order functions. + """ + + magctrler = self.obtain_device() + magctrler.reset() + chanctrler = magctrler.channels[self.channel_to_test] + + chanctrler.virt_iout = Quantity('20.19 G') + eq_(chanctrler.virt_iout, Quantity('20 G')) + + + def testVirtIout(self): + """ + Test the resource virt_iout. + """ + magctrler = self.obtain_device() + magctrler.reset() + chanctrler = magctrler.channels[self.channel_to_test] + + chanctrler.virt_iout = Quantity('20 G') + eq_(chanctrler.virt_iout, Quantity('20 G')) + + try: + chanctrler.virt_iout = 55 + except AttributeError: + pass + else: + assert False, 'Expected AttributeError.' + + def testVirtImag(self): + """ + Test the resource virt_imag. + """ + + magctrler = self.obtain_device() + magctrler.reset() + chanctrler = magctrler.channels[self.channel_to_test] + + # Test a simple setting of the magnetic field. + + chanctrler.virt_energysave_mode = 0 + chanctrler.virt_sync_currents = 'start' + chanctrler.persistent_switch_heater = 'on' + chanctrler.virt_imag = Quantity('20 G') + + eq_(chanctrler.virt_imag, Quantity('20 G')) + + try: + chanctrler.virt_imag = 55 + except AttributeError: + pass + else: + assert False, 'Expected AttributeError.' + + # Energy mode 1. + + chanctrler.virt_energysave_mode = 1 + chanctrler.persistent_switch_heater = 'off' + chanctrler.virt_imag = Quantity('30 G') + + ## Check if it was set and if the heater was turned off. + eq_(magctrler.virt_both_persistent_switch_heaters, 'off') + eq_(chanctrler.virt_imag, Quantity('30 G')) + + # Energy mode 3. + + chanctrler.virt_energysave_mode = 3 + chanctrler.persistent_switch_heater = 'off' + chanctrler.virt_imag = Quantity('10 G') + + ## Check if the heater is off. + eq_(magctrler.virt_both_persistent_switch_heaters, 'off') + ## Check if the power is going down. + sleep(0.5) + eq_(chanctrler.magnet_current > chanctrler.power_supply_current, True) + ## Check if it was set properly. + eq_(chanctrler.virt_imag, Quantity('10 G')) + + + def testWaitForSweep(self): + """ + Tests the function _wait_for_sweep() + """ + magctrler = self.obtain_device() + magctrler.reset() + chanctrler = magctrler.channels[self.channel_to_test] + + chanctrler.virt_iout_sweep_to = Quantity('20 G') + chanctrler._wait_for_sweep() + + # Check if sweep is actually done several times. + for _ in xrange(1,10): + eq_(chanctrler.power_supply_current, Quantity('20 G')) + +if __name__ == '__main__': + main() diff --git a/spacq/devices/tools.py b/spacq/devices/tools.py new file mode 100755 index 0000000..701d76f --- /dev/null +++ b/spacq/devices/tools.py @@ -0,0 +1,268 @@ +import logging +log = logging.getLogger(__name__) + +from functools import wraps +import string + +from spacq.interface.units import Quantity + +""" +Tools for working with hardware devices. +""" + + +def str_to_bool(value): + """ + False and 'False' => False + otherwise => True + """ + + return bool(value) and value.lower() != 'false' + + +def quantity_wrapped(units, multiplier=1.0): + """ + A decorator for getters to wrap the plain device value into a quantity with a unit. + """ + + def wrap(f): + @wraps(f) + def wrapped(self): + return Quantity(f(self) * multiplier, units) + + return wrapped + + return wrap + +def quantity_unwrapped(units, multiplier=1.0): + """ + A decorator for setters to extract the plain device value from the quantity. + """ + + def wrap(f): + @wraps(f) + def wrapped(self, value): + value.assert_dimensions(units) + + return f(self, value.value * multiplier) + + return wrapped + + return wrap + +def converted_quantity_unwrapped(units, multiplier=1.0): + """ + A variation of quantity_unwrapped that extracts the value in the units provided, and then applies the multiplier + if given. + """ + + def wrap(f): + @wraps(f) + def wrapped(self, value): + value.assert_dimensions(units) + + # Perform conversion. Note that this is a bit of a trick. We must use a value of 1.0 because + # normalization messes up for Quantities with 0 magnitude. + new_value = Quantity(1.0, units) + new_value += value + new_value -= Quantity(1.0, units) + + return f(self, new_value.original_value * multiplier) + + return wrapped + + return wrap + + +def dynamic_quantity_wrapped(units_attr_string, multiplier=1.0): + """ + A decorator for getters to wrap the plain device value into a quantity with a unit, where the unit + is defined by an attribute extracted from the device. + + Note: Will work on a chain of dotted attributes. + """ + + def wrap(f): + @wraps(f) + def wrapped(self): + + units = reduce(getattr, units_attr_string.split('.'), self) +# units = getattr(self, units_attr_string) + + return Quantity(f(self) * multiplier, units) + + return wrapped + + return wrap + +def dynamic_converted_quantity_unwrapped(units_attr_string, multiplier=1.0): + """ + A variation of dynamic_quantity_unwrapped that will extract the units from an attribute of the device. + """ + + def wrap(f): + @wraps(f) + def wrapped(self, value): + +# units = getattr(self, units_attr_string) + units = reduce(getattr, units_attr_string.split('.'), self) + + value.assert_dimensions(units) + + # Perform conversion. Note that this is a bit of a trick. We must use a value of 1.0 because + # normalization messes up for Quantities with 0 magnitude. + new_value = Quantity(1.0, units) + new_value += value + new_value -= Quantity(1.0, units) + + return f(self, new_value.original_value * multiplier) + + return wrapped + + return wrap + + +class BlockDataError(Exception): + """ + Problem reading block data. + """ + + pass + + +class BlockData(object): + """ + Utility methods for conversion between binary and 488.2 block data. + """ + + @staticmethod + def to_block_data(data): + """ + Packs binary data into 488.2 block data. + + As per section 7.7.6 of IEEE Std 488.2-1992. + + Note: Does not produce indefinitely-formatted block data. + """ + + log.debug('Converting to block data: {0!r}'.format(data)) + + length = len(data) + length_length = len(str(length)) + + return '#{0}{1}{2}'.format(length_length, length, data) + + @staticmethod + def from_block_data(block_data): + """ + Extracts binary data from 488.2 block data. + + As per section 7.7.6 of IEEE Std 488.2-1992. + """ + + log.debug('Converting from block data: {0!r}'.format(block_data)) + + # Must have at least "#0\n" or "#XX". + if len(block_data) < 3: + raise BlockDataError('Not enough data.') + + if block_data[0] != '#': + raise BlockDataError('Leading character is "{0}", not "#".'.format(block_data[0])) + + if block_data[1] == '0': + log.debug('Indefinite format.') + + if block_data[-1] != '\n': + raise BlockDataError('Final character is "{0}", not NL.'.format(block_data[-1])) + + return block_data[2:-1] + else: + log.debug('Definite format.') + + try: + length_length = int(block_data[1]) + except ValueError: + raise BlockDataError('Length length incorrectly specified: {0}'.format(block_data[1])) + + data_start = 2 + length_length + + if data_start > len(block_data): + raise BlockDataError('Not enough data.') + + try: + length = int(block_data[2:data_start]) + except ValueError: + raise BlockDataError('Length incorrectly specified: {0}'.format(block_data[2:data_start])) + + data_end = data_start + length + + if data_end > len(block_data): + raise BlockDataError('Not enough data.') + elif data_end < len(block_data): + if block_data[data_end:] != '\n': + log.warning('Extra data ignored: {0!r}'.format(block_data[data_end:])) + + return block_data[data_start:data_end] + + +class BinaryEncoder(object): + """ + Utility methods for dealing with encoding and decoding binary data. + """ + + @staticmethod + def encode(msg): + """ + Convert a string of hexadecimal digits to a byte string. + """ + + log.debug('Encoding to byte string: {0}'.format(msg)) + + # Discard non-hexadecimal characters. + msg_filtered = [x for x in msg if x in string.hexdigits] + # Grab pairs. + idxs = xrange(0, len(msg_filtered), 2) + msg_paired = [''.join(msg_filtered[i:i+2]) for i in idxs] + # Convert to bytes. + msg_encoded = ''.join([chr(int(x, 16)) for x in msg_paired]) + + log.debug('Encoded to: {0!r}'.format(msg_encoded)) + + return msg_encoded + + @staticmethod + def decode(msg, pair_size=2, pair_up=True): + """ + Convert a byte string to a string of hexadecimal digits. + """ + + log.debug('Decoding from byte string: {0!r}'.format(msg)) + + # Get the hex string for each byte. + msg_decoded = ['{0:02x}'.format(ord(x)) for x in msg] + + if pair_up: + idxs = xrange(0, len(msg_decoded), pair_size) + msg_formatted = [''.join(msg_decoded[i:i+pair_size]) for i in idxs] + + result = ' '.join(msg_formatted) + else: + result = ''.join(msg_decoded) + + log.debug('Decoded to: {0}'.format(result)) + + return result + + @staticmethod + def length(msg): + """ + Calculate the number of bytes an unencoded message takes up when encoded. + """ + + log.debug('Finding encoded length: {0}'.format(msg)) + + result = len(BinaryEncoder.encode(msg)) + + log.debug('Found encoded length: {0}'.format(result)) + + return result diff --git a/spacq/gui/action/data_capture.py b/spacq/gui/action/data_capture.py new file mode 100755 index 0000000..02c4f54 --- /dev/null +++ b/spacq/gui/action/data_capture.py @@ -0,0 +1,639 @@ +import csv +from datetime import timedelta +from functools import partial +import os +from pubsub import pub +from threading import Lock, Thread +from time import localtime, sleep, time +import wx +from wx.lib.filebrowsebutton import DirBrowseButton + +from spacq.interface.pulse.parser import PulseError +from spacq.interface.units import IncompatibleDimensions +from spacq.iteration.sweep import PulseConfiguration, SweepController +from spacq.iteration.variables import sort_output_variables, sort_condition_variables, InputVariable, OutputVariable, ConditionVariable +from spacq.tool.box import flatten, sift + + +from ..tool.box import Dialog, MessageDialog, YesNoQuestionDialog + + +class DataCaptureDialog(Dialog, SweepController): + """ + A progress dialog which runs over iterators, sets the corresponding resources, and captures the measured data. + """ + + max_value_len = 50 # characters + + timer_delay = 50 # ms + stall_time = 2 # s + + status_messages = { + None: 'Starting up', + 'init': 'Initializing', + 'next': 'Getting next values', + 'transition': 'Smooth setting', + 'write': 'Writing to devices', + 'dwell': 'Waiting for resources to settle', + 'pulse': 'Running pulse program', + 'read': 'Taking measurements', + 'condition': 'Testing conditions', + 'condition_dwell': 'Waiting for conditions to settle', + 'ramp_down': 'Smooth setting', + 'end': 'Finishing', + } + + def __init__(self, parent, resources, variables, num_items, measurement_resources, + measurement_variables, condition_resources, condition_variables, pulse_config, continuous=False, + *args, **kwargs): + kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER + + Dialog.__init__(self, parent, title='Sweeping...', *args, **kwargs) + SweepController.__init__(self, resources, variables, num_items, measurement_resources, + measurement_variables, condition_resources, condition_variables, pulse_config, continuous=continuous) + + self.parent = parent + + # Show only elapsed time in continuous mode. + self.show_remaining_time = not self.continuous + + self.last_checked_time = -1 + self.elapsed_time = 0 # us + + self.timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer) + + self.cancelling = False + + def write_callback(pos, i, value): + self.value_outputs[pos][i].Value = str(value)[:self.max_value_len] + self.write_callback = partial(wx.CallAfter, write_callback) + + def read_callback(i, value): + self.value_inputs[i].Value = str(value)[:self.max_value_len] + self.read_callback = partial(wx.CallAfter, read_callback) + + self.general_exception_handler = partial(wx.CallAfter, self._general_exception_handler) + self.resource_exception_handler = partial(wx.CallAfter, self._resource_exception_handler) + + # Dialog. + dialog_box = wx.BoxSizer(wx.VERTICAL) + + ## Progress. + progress_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(progress_box, flag=wx.EXPAND|wx.ALL, border=5) + + ### Message. + self.progress_percent = wx.StaticText(self, label='', size=(40, -1)) + progress_box.Add(self.progress_percent, + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) + + ### Bar. + self.progress_bar = wx.Gauge(self, range=num_items, style=wx.GA_HORIZONTAL) + progress_box.Add(self.progress_bar, proportion=1) + + ## Status. + self.status_message_output = wx.TextCtrl(self, style=wx.TE_READONLY) + self.status_message_output.BackgroundColour = wx.LIGHT_GREY + dialog_box.Add(self.status_message_output, flag=wx.EXPAND) + + ## Values. + self.values_box = wx.FlexGridSizer(rows=len(self.variables), cols=2, hgap=20) + self.values_box.AddGrowableCol(1, 1) + dialog_box.Add(self.values_box, flag=wx.EXPAND|wx.ALL, border=5) + + self.value_outputs = [] + for group in self.variables: + group_outputs = [] + + for var in group: + output = wx.TextCtrl(self, style=wx.TE_READONLY) + output.BackgroundColour = wx.LIGHT_GREY + group_outputs.append(output) + + self.values_box.Add(wx.StaticText(self, label=var.name), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.values_box.Add(output, flag=wx.EXPAND) + + self.value_outputs.append(group_outputs) + + # Spacer. + for _ in xrange(2): + self.values_box.Add((-1, 15)) + + # Separator. + for _ in xrange(2): + self.values_box.Add(wx.StaticLine(self), flag=wx.EXPAND|wx.ALL, border=5) + + self.value_inputs = [] + for var in self.measurement_variables: + input = wx.TextCtrl(self, style=wx.TE_READONLY) + input.BackgroundColour = wx.LIGHT_GREY + self.value_inputs.append(input) + + self.values_box.Add(wx.StaticText(self, label=var.name), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.values_box.Add(input, flag=wx.EXPAND) + + ## Times. + times_box = wx.FlexGridSizer(rows=2 if self.show_remaining_time else 1, cols=2, hgap=5) + dialog_box.Add(times_box, proportion=1, flag=wx.CENTER|wx.ALL, border=15) + + ### Elapsed. + times_box.Add(wx.StaticText(self, label='Elapsed time:')) + self.elapsed_time_output = wx.StaticText(self, label='---:--:--') + times_box.Add(self.elapsed_time_output) + + ### Remaining. + if self.show_remaining_time: + times_box.Add(wx.StaticText(self, label='Remaining time:')) + self.remaining_time_output = wx.StaticText(self, label='---:--:--') + times_box.Add(self.remaining_time_output) + + ## Last continuous. + if self.continuous: + self.last_continuous_input = wx.CheckBox(self, label='Last loop of continuous sweep') + dialog_box.Add(self.last_continuous_input, flag=wx.CENTER) + + ## End button. + button_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(button_box, flag=wx.CENTER) + + self.cancel_button = wx.Button(self, label='Cancel') + self.Bind(wx.EVT_BUTTON, self.OnCancel, self.cancel_button) + button_box.Add(self.cancel_button) + + self.SetSizerAndFit(dialog_box) + + # Try to cancel cleanly instead of giving up. + self.Bind(wx.EVT_CLOSE, self.OnCancel) + + def _general_exception_handler(self, f, e): + """ + Called when a trampolined function raises e. + """ + + MessageDialog(self.parent, '{0}'.format(str(e)), 'Sweep error in "{0}"'.format(f)).Show() + + def _resource_exception_handler(self, resource_name, e, write=True): + """ + Called when a write to or read from a Resource raises e. + """ + + msg = 'Resource: {0}\nError: {1}'.format(resource_name, str(e)) + dir = 'writing to' if write else 'reading from' + MessageDialog(self.parent, msg, 'Error {0} resource'.format(dir)).Show() + + self.abort(fatal=write) + + def start(self): + thr = Thread(target=SweepController.run, args=(self,)) + thr.daemon = True + thr.start() + + self.timer.Start(self.timer_delay) + + def dwell(self): + result = SweepController.dwell(self) + + # Prevent the GUI from locking up. + sleep(0.005) + + return result + + def end(self): + try: + SweepController.end(self) + except AssertionError: + return + + # In case the sweep is too fast, ensure that the user has some time to see the dialog. + span = time() - self.sweep_start_time + if span < self.stall_time: + sleep(self.stall_time - span) + + wx.CallAfter(self.timer.Stop) + wx.CallAfter(self.Destroy) + + def OnCancel(self, evt=None): + if not self.cancel_button.Enabled: + return + + self.cancel_button.Disable() + self.cancelling = True + + def OnTimer(self, evt=None): + self.status_message_output.Value = self.status_messages[self.current_f] + if self.continuous: + self.last_continuous = self.last_continuous_input.Value + + # Update progress. + if self.num_items > 0 and self.item >= 0: + amount_done = float(self.item) / self.num_items + + self.progress_bar.Value = self.item + self.progress_percent.Label = '{0}%'.format(int(100 * amount_done)) + + if self.last_checked_time > 0: + self.elapsed_time += int((time() - self.last_checked_time) * 1e6) + self.elapsed_time_output.Label = str(timedelta(seconds=self.elapsed_time//1e6)) + + self.last_checked_time = time() + + if self.show_remaining_time and amount_done > 0: + total_time = self.elapsed_time / amount_done + remaining_time = int(total_time - self.elapsed_time) + self.remaining_time_output.Label = str(timedelta(seconds=remaining_time//1e6)) + + # Prompt to abort. + if self.cancelling: + def abort(): + self.cancelling = False + + thr = Thread(target=self.abort) + thr.daemon = True + thr.start() + + self.timer.Start(self.timer_delay) + + def resume(): + self.cancelling = False + self.cancel_button.Enable() + + self.unpause() + + self.timer.Start(self.timer_delay) + + self.pause() + + self.last_checked_time = -1 + self.timer.Stop() + + YesNoQuestionDialog(self, 'Abort processing?', abort, resume).Show() + + return + + +class DataCapturePanel(wx.Panel): + """ + A panel to start the data capture process, optionally exporting the results to a file. + """ + + def __init__(self, parent, global_store, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + self.global_store = global_store + + self.capture_dialogs = 0 + + # Panel. + panel_box = wx.BoxSizer(wx.HORIZONTAL) + + ## Capture. + capture_static_box = wx.StaticBox(self, label='Capture') + capture_box = wx.StaticBoxSizer(capture_static_box, wx.VERTICAL) + panel_box.Add(capture_box, flag=wx.CENTER|wx.ALL, border=5) + + ### Start. + self.start_button = wx.Button(self, label='Start') + self.Bind(wx.EVT_BUTTON, self.OnBeginCapture, self.start_button) + capture_box.Add(self.start_button, flag=wx.CENTER) + + ### Continuous. + self.continuous_checkbox = wx.CheckBox(self, label='Continuous') + capture_box.Add(self.continuous_checkbox, flag=wx.CENTER) + + ## Export. + export_static_box = wx.StaticBox(self, label='Export') + export_box = wx.StaticBoxSizer(export_static_box, wx.HORIZONTAL) + panel_box.Add(export_box, proportion=1, flag=wx.CENTER|wx.ALL, border=5) + + ### Enabled. + self.export_enabled = wx.CheckBox(self, label='') + self.export_enabled.Value = True + export_box.Add(self.export_enabled, flag=wx.CENTER) + + ### Export path. + export_path_box = wx.BoxSizer(wx.VERTICAL) + export_box.Add(export_path_box, proportion=1, flag=wx.CENTER) + + #### Directory. + self.directory_browse_button = DirBrowseButton(self, labelText='Directory:') + export_path_box.Add(self.directory_browse_button, flag=wx.EXPAND) + + #### Last file. + last_file_box = wx.BoxSizer(wx.HORIZONTAL) + export_path_box.Add(last_file_box, flag=wx.EXPAND) + + last_file_box.Add(wx.StaticText(self, label='Last output: '), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.last_file_name = wx.TextCtrl(self, style=wx.TE_READONLY) + self.last_file_name.BackgroundColour = wx.LIGHT_GREY + last_file_box.Add(self.last_file_name, proportion=1) + + self.SetSizer(panel_box) + + def OnBeginCapture(self, evt=None): + # Prevent accidental double-clicking. + self.start_button.Disable() + def enable_button(): + sleep(1) + wx.CallAfter(self.start_button.Enable) + thr = Thread(target=enable_button) + thr.daemon = True + thr.start() + + all_variables = [var for var in self.global_store.variables.values() if var.enabled] + output_variables = sift(all_variables, OutputVariable) + input_variables = [var for var in sift(all_variables, InputVariable) if var.resource_name != ''] + condition_variables = sift(all_variables, ConditionVariable) + + if not output_variables: + output_variables.append(OutputVariable(order=0, name='', enabled=True)) + + output_variables, num_items = sort_output_variables(output_variables) + condition_variables = sort_condition_variables(condition_variables) + + resource_names = [tuple(var.resource_name for var in group) for group in output_variables] + measurement_resource_names = [var.resource_name for var in input_variables] + condition_resource_names = [tuple(set(flatten([var.resource_names for var in group]))) for group in condition_variables] + + + continuous = self.continuous_checkbox.Value + + missing_resources = set() + unreadable_resources = set() + unwritable_resources = set() + missing_devices = set() + + pulse_program = self.global_store.pulse_program + + if pulse_program is not None: + pulse_program = pulse_program.with_resources + + try: + pulse_program.generate_waveforms(dry_run=True) + except PulseError as e: + MessageDialog(self, '\n'.join(e[0]), 'Pulse program error', monospace=True).Show() + return + except Exception as e: + MessageDialog(self, str(e), 'Pulse program error').Show() + return + + pulse_awg, pulse_oscilloscope = None, None + pulse_channels = {} + + try: + pulse_awg = self.global_store.devices[pulse_program.awg].device + if pulse_awg is None: + raise KeyError + except KeyError: + missing_devices.add(pulse_program.awg) + else: + # Gather used channel numbers. + pulse_channels = dict((k, v) for k, v in pulse_program.output_channels.items() if v is not None) + + actual_channels = range(1, len(pulse_awg.channels)) + invalid_channels = [k for k, v in pulse_channels.items() if v not in actual_channels] + + if invalid_channels: + MessageDialog(self, 'Invalid channels for: {0}'.format(', '.join(invalid_channels)), 'Invalid channels').Show() + return + + try: + pulse_oscilloscope = self.global_store.devices[pulse_program.oscilloscope].device + if pulse_oscilloscope is None: + raise KeyError + except KeyError: + missing_devices.add(pulse_program.oscilloscope) + + try: + pulse_config = PulseConfiguration(pulse_program, pulse_channels, pulse_awg, pulse_oscilloscope) + except TypeError as e: + MessageDialog(self, str(e), 'Device configuration error').Show() + return + else: + pulse_config = None + + resources = [] + for group in resource_names: + group_resources = [] + + for name in group: + if name == '': + group_resources.append((str(len(resources)), None)) + elif name not in self.global_store.resources: + missing_resources.add(name) + else: + resource = self.global_store.resources[name] + + if resource.writable: + group_resources.append((name, resource)) + else: + unwritable_resources.add(name) + + resources.append(tuple(group_resources)) + + measurement_resources = [] + measurement_units = [] + for name in measurement_resource_names: + if name not in self.global_store.resources: + missing_resources.add(name) + else: + resource = self.global_store.resources[name] + + if resource.readable: + measurement_resources.append((name, resource)) + measurement_units.append(resource.display_units) + else: + unreadable_resources.add(name) + + condition_resources = [] + for group in condition_resource_names: + group_resources = [] + + for name in group: + if name not in self.global_store.resources: + missing_resources.add(name) + else: + resource = self.global_store.resources[name] + + if resource.readable: + group_resources.append((name, resource)) + else: + #the name may already have been put here by the loop assigning + #to measurement_resources + if name not in unreadable_resources: + unreadable_resources.add(name) + + condition_resources.append(tuple(group_resources)) + + mismatched_resources = [] + for (res_name, resource), var in zip(flatten(resources), flatten(output_variables)): + if resource is None: + continue + + if resource.units is not None: + if not (var.type == 'quantity' and + resource.verify_dimensions(var.units, exception=False, from_string=True)): + mismatched_resources.append((res_name, var.name)) + else: + if var.type not in ['float', 'integer']: + mismatched_resources.append((res_name, var.name)) + + for items, msg in [ + (missing_resources, 'Missing resources'), + (unreadable_resources, 'Unreadable resources'), + (unwritable_resources, 'Unwritable resources'), + (missing_devices, 'Missing devices')]: + + if items: + MessageDialog(self, ', '.join('"{0}"'.format(x) for x in sorted(items)), msg).Show() + + if mismatched_resources: + MessageDialog(self, ', '.join('Mismatched resource type for resource name {0} with variable name {1}'.format(x[0], x[1]) for x in mismatched_resources), + 'Mismatched resources').Show() + + if (missing_resources or unreadable_resources or unwritable_resources or + missing_devices or mismatched_resources): + return + + # Check that all the condition arguments are compatible with one another. + + for cvar in flatten(condition_variables): + + # Get a condition. + for cond in cvar.conditions: + + value1 = cond.arg1 + value2 = cond.arg2 + resource1 = None + resource2 = None + + # If working with resources, use their values as the values, and make the resource available + + if cond.type1 == 'resource name': + resource1 = [resource for (name, resource) in flatten(condition_resources) if name == cond.arg1][0] + value1 = resource1.value + if cond.type2 == 'resource name': + resource2 = [resource for (name, resource) in flatten(condition_resources) if name == cond.arg2][0] + value2 = resource2.value + + # Check if the other argument is in the allowed values + + if hasattr(resource1,'allowed_values') and resource1.allowed_values is not None: + if value2 not in resource1.allowed_values: + MessageDialog(self, 'In the condition {0}, {1} is not in allowed_values of {2}.'.format(cond, value2, cond.arg1),'Condition error').Show() + return + if hasattr(resource2,'allowed_values') and resource2.allowed_values is not None: + if value1 not in resource2.allowed_values: + MessageDialog(self, 'In the condition {0}, {1} is not in allowed_values of {2}.'.format(cond, value1, cond.arg2),'Condition error').Show() + return + + # Check if units agree. + + if resource1 is not None and resource1.units is not None: + try: + value1.assert_dimensions(value2) + except ValueError: + MessageDialog(self, 'In the condition {0}, {1} does not have a dimension.'.format(cond, value2),'Condition error').Show() + return + except IncompatibleDimensions: + MessageDialog(self, 'In the condition {0}, {1} and {2} do not have matching dimensions.'.format(cond, value1, value2),'Condition error').Show() + return + if resource2 is not None and resource2.units is not None: + try: + value2.assert_dimensions(value1) + except ValueError: + MessageDialog(self, 'In the condition {0}, {1} does not have a dimension.'.format(cond, value1),'Condition error').Show() + return + except IncompatibleDimensions: + MessageDialog(self, 'In the condition {0}, {1} and {2} do not have matching dimensions.'.format(cond, value1, value2),'Condition error').Show() + return + + + exporting = False + if self.export_enabled.Value: + dir = self.directory_browse_button.GetValue() + # YYYY-MM-DD_HH-MM-SS.csv + name = '{0:04}-{1:02}-{2:02}_{3:02}-{4:02}-{5:02}.csv'.format(*localtime()) + + if not dir: + MessageDialog(self, 'No directory selected.', 'Export path').Show() + return + + if not os.path.isdir(dir): + MessageDialog(self, 'Invalid directory selected', 'Export path').Show() + return + + file_path = os.path.join(dir, name) + if os.path.exists(file_path): + MessageDialog(self, file_path, 'File exists').Show() + return + + # Everything looks alright, so open the file. + export_file = open(file_path, 'w') + export_csv = csv.writer(export_file) + exporting = True + + # Show the path in the GUI. + self.last_file_name.Value = file_path + + # Write the header. + export_csv.writerow(['Time (s)'] + + ['{0.name} ({0.units})'.format(var) if var.units is not None else var.name + for var in flatten(output_variables)] + + ['{0.name} ({1})'.format(var, units) if units is not None else var.name + for var, units in zip(input_variables, measurement_units)]) + + self.capture_dialogs += 1 + + dlg = DataCaptureDialog(self, resources, output_variables, num_items, measurement_resources, + input_variables, condition_resources, condition_variables, pulse_config, continuous=continuous) + dlg.SetMinSize((500, -1)) + + for name in measurement_resource_names: + wx.CallAfter(pub.sendMessage, 'data_capture.start', name=name) + + # Export buffer. + max_buf_size = 10 + buf = [] + buf_lock = Lock() + + def flush(): + export_csv.writerows(buf) + export_file.flush() + + while buf: + buf.pop() + + def data_callback(cur_time, values, measurement_values): + for name, value in zip(measurement_resource_names, measurement_values): + wx.CallAfter(pub.sendMessage, 'data_capture.data', name=name, value=value) + + # Extract values out of quantities, since the units have already been taken care of in the header. + values = [x.original_value if hasattr(x, 'original_value') else x for x in values] + measurement_values = [x.original_value if hasattr(x, 'original_value') else x for x in measurement_values] + + if exporting: + with buf_lock: + buf.append([cur_time] + values + measurement_values) + + if len(buf) >= max_buf_size: + flush() + + def close_callback(): + self.capture_dialogs -= 1 + + if exporting: + with buf_lock: + flush() + export_file.close() + + for name in measurement_resource_names: + wx.CallAfter(pub.sendMessage, 'data_capture.stop', name=name) + + dlg.data_callback = data_callback + dlg.close_callback = close_callback + dlg.Show() + dlg.start() diff --git a/spacq/gui/config/measurement.py b/spacq/gui/config/measurement.py new file mode 100755 index 0000000..2b750d2 --- /dev/null +++ b/spacq/gui/config/measurement.py @@ -0,0 +1,213 @@ +from pubsub import pub +import wx + +from spacq.iteration.variables import InputVariable + +from ..tool.box import OK_BACKGROUND_COLOR, MessageDialog +from .scaling import ScalingSettings, ScalingSettingsDialog + + +class MeasurementConfigPanel(wx.Panel): + """ + Measurement configuration panel. + """ + + def __init__(self, parent, global_store, scaling=True, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + self.parent = parent + self.global_store = global_store + self.scaling = scaling + + if self.scaling: + self.scaling_settings = ScalingSettings() + + # Ensure that we get a unique name. + with self.global_store.variables.lock: + num = 1 + done = False + while not done: + name = 'New measurement {0}'.format(num) + self.var = InputVariable(name=name, enabled=True) + + try: + self.global_store.variables[name] = self.var + except KeyError: + num += 1 + else: + done = True + + # Keep track of the scaling wrapper and resource. + if self.scaling: + self.scaling_wrap_token = '{0}.{1}'.format(self.__class__.__name__, self.wrap_with_scaling.__name__) + self.resource = None + self.unwrapping = False + + # Panel. + panel_box = wx.BoxSizer(wx.VERTICAL) + + ## Configuration. + configuration_box = wx.BoxSizer(wx.HORIZONTAL) + panel_box.Add(configuration_box, flag=wx.EXPAND|wx.ALL, border=5) + + self.enabled_checkbox = wx.CheckBox(self, label='Capture') + self.enabled_checkbox.Value = self.var.enabled + configuration_box.Add(self.enabled_checkbox, flag=wx.CENTER|wx.RIGHT, border=15) + + self.Bind(wx.EVT_CHECKBOX, self.OnCaptureChecked, self.enabled_checkbox) + + ### Names. + names_box = wx.FlexGridSizer(rows=2, cols=2, hgap=5) + names_box.AddGrowableCol(1, 1) + configuration_box.Add(names_box, flag=wx.EXPAND, proportion=1) + + names_box.Add(wx.StaticText(self, label='Resource name:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.resource_name_input = wx.TextCtrl(self, value=self.var.resource_name, style=wx.TE_PROCESS_ENTER) + self.resource_name_input.default_background_color = self.resource_name_input.BackgroundColour + self.resource_name_input.BackgroundColour = OK_BACKGROUND_COLOR + names_box.Add(self.resource_name_input, flag=wx.EXPAND) + + self.Bind(wx.EVT_TEXT, self.OnResourceNameChange, self.resource_name_input) + self.Bind(wx.EVT_TEXT_ENTER, self.OnResourceNameInput, self.resource_name_input) + + names_box.Add(wx.StaticText(self, label='Measurement name:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.measurement_name_input = wx.TextCtrl(self, value=self.var.name, style=wx.TE_PROCESS_ENTER) + self.measurement_name_input.default_background_color = self.measurement_name_input.BackgroundColour + self.measurement_name_input.BackgroundColour = OK_BACKGROUND_COLOR + names_box.Add(self.measurement_name_input, flag=wx.EXPAND) + + self.Bind(wx.EVT_TEXT, self.OnMeasurementNameChange, self.measurement_name_input) + self.Bind(wx.EVT_TEXT_ENTER, self.OnMeasurementNameInput, self.measurement_name_input) + + ### Scaling. + if self.scaling: + scaling_button = wx.Button(self, label='Scaling...', style=wx.BU_EXACTFIT) + self.Bind(wx.EVT_BUTTON, self.OnScaling, scaling_button) + configuration_box.Add(scaling_button, flag=wx.EXPAND|wx.LEFT, border=10) + + self.SetSizerAndFit(panel_box) + + self.set_title() + + # Subscriptions. + if self.scaling: + pub.subscribe(self.msg_resource, 'resource.added') + pub.subscribe(self.msg_resource, 'resource.removed') + + @property + def live_view_panel(self): + return self.parent.live_view_panel + + def wrap_with_scaling(self, name, resource): + if not self.scaling: + return + + # Don't double-wrap. + if resource.is_wrapped_by(self.scaling_wrap_token): + return + + # Modify the resource value by the scaling. + def transform(x): + # Close over self, so that updating scaling settings automatically takes effect. + return self.scaling_settings.transform(x) + wrapped_resource = resource.wrapped(self.scaling_wrap_token, transform) + + with self.global_store.lock: + del self.global_store.resources[name] + self.global_store.resources[name] = wrapped_resource + + def unwrap_with_scaling(self): + if not self.scaling: + return + + if self.resource is None: + return + + # Don't allow immediate re-wrapping. + self.unwrapping = True + + name = self.live_view_panel.measurement_resource_name + unwrapped_resource = self.resource.unwrapped(self.scaling_wrap_token) + + with self.global_store.lock: + del self.global_store.resources[name] + self.global_store.resources[name] = unwrapped_resource + + self.resource = None + self.unwrapping = False + + def set_title(self): + self.parent.Title = '{0} ({1}){2}'.format(self.var.name, self.var.resource_name, + '' if self.var.enabled else ' [Disabled]') + + def close(self): + self.unwrap_with_scaling() + + del self.global_store.variables[self.var.name] + + def OnCaptureChecked(self, evt=None): + self.var.enabled = self.live_view_panel.enabled = self.enabled_checkbox.Value + self.set_title() + + def OnResourceNameChange(self, evt=None): + self.resource_name_input.BackgroundColour = self.resource_name_input.default_background_color + + def OnResourceNameInput(self, evt=None): + if self.var.resource_name != self.resource_name_input.Value: + # Ensure that the resource is unwrapped before releasing it. + self.unwrap_with_scaling() + + self.var.resource_name = name = self.resource_name_input.Value + + # Inform the panel. + self.live_view_panel.measurement_resource_name = name + + # Grab the new resource if it already exists. + try: + self.resource = self.global_store.resources[name] + except KeyError: + pass + else: + self.wrap_with_scaling(name, self.resource) + + self.resource_name_input.BackgroundColour = OK_BACKGROUND_COLOR + self.set_title() + + def OnMeasurementNameChange(self, evt=None): + self.measurement_name_input.BackgroundColour = self.measurement_name_input.default_background_color + + def OnMeasurementNameInput(self, evt=None): + if self.var.name != self.measurement_name_input.Value: + # Attempt to add a new entry first. + var_new_name = self.measurement_name_input.Value + try: + self.global_store.variables[var_new_name] = self.var + except KeyError: + MessageDialog(self, var_new_name, 'Variable name conflicts').Show() + else: + # Remove the old entry. + del self.global_store.variables[self.var.name] + + self.var.name = var_new_name + + self.measurement_name_input.BackgroundColour = OK_BACKGROUND_COLOR + self.set_title() + + def OnScaling(self, evt=None): + def ok_callback(dlg): + self.scaling_settings = dlg.GetValue() + + dlg = ScalingSettingsDialog(self, ok_callback) + dlg.SetValue(self.scaling_settings) + dlg.Show() + + def msg_resource(self, name, value=None): + resource_name = self.var.resource_name + + if name == resource_name: + self.resource = value + + if value is not None and not self.unwrapping: + self.wrap_with_scaling(resource_name, value) diff --git a/spacq/gui/config/variables.py b/spacq/gui/config/variables.py new file mode 100755 index 0000000..7ec2cfc --- /dev/null +++ b/spacq/gui/config/variables.py @@ -0,0 +1,920 @@ +import ObjectListView +import wx + +from numpy import array +from spacq.interface.units import Quantity +from spacq.iteration.variables import OutputVariable, LinSpaceConfig, ArbitraryConfig +from spacq.iteration.variables import ConditionVariable, Condition + +from ..tool.box import Dialog, MessageDialog, load_pickled, save_pickled, load_csv +from spacq.gui.display.table.generic import VirtualListCtrl + +""" +An interface for creating and editing Variable objects. +""" + + +class VariableColumnDefn(ObjectListView.ColumnDefn): + """ + A column with useful defaults. + """ + + def __init__(self, width=90, align='centre', groupKeyGetter='order', *args, **kwargs): + ObjectListView.ColumnDefn.__init__(self, width=width, align=align, + groupKeyGetter=groupKeyGetter, *args, **kwargs) + + # No auto-width if space filling. + if self.isSpaceFilling: + self.width = 0 + + +class LinSpaceConfigPanel(wx.Panel): + def __init__(self, parent, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + # Panel. + panel_box = wx.BoxSizer(wx.VERTICAL) + + ## Config. + config_sizer = wx.FlexGridSizer(rows=3, cols=2) + config_sizer.AddGrowableCol(1, 1) + panel_box.Add(config_sizer, proportion=1, flag=wx.EXPAND) + + ### Initial. + config_sizer.Add(wx.StaticText(self, label='Initial:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) + self.initial_input = wx.TextCtrl(self) + config_sizer.Add(self.initial_input, flag=wx.EXPAND|wx.ALL, border=5) + + ### Final. + config_sizer.Add(wx.StaticText(self, label='Final:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) + self.final_input = wx.TextCtrl(self) + config_sizer.Add(self.final_input, flag=wx.EXPAND|wx.ALL, border=5) + + ### Steps. + config_sizer.Add(wx.StaticText(self, label='Steps:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) + self.steps_input = wx.SpinCtrl(self, min=1, initial=1, max=1e9) + config_sizer.Add(self.steps_input, flag=wx.EXPAND|wx.ALL, border=5) + + self.SetSizerAndFit(panel_box) + + def GetValue(self): + # Ensure the values are sane. + try: + initial = float(self.initial_input.Value) + except ValueError: + raise ValueError('Invalid initial value.') + + try: + final = float(self.final_input.Value) + except ValueError: + raise ValueError('Invalid final value.') + + return LinSpaceConfig(initial, final, self.steps_input.Value) + + def SetValue(self, config): + self.initial_input.Value, self.final_input.Value, self.steps_input.Value = (str(config.initial), + str(config.final), config.steps) + + +class ArbitraryConfigPanel(wx.Panel): + def __init__(self, parent, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + # Panel. + panel_box = wx.BoxSizer(wx.VERTICAL) + + ## Config. + config_sizer = wx.FlexGridSizer(rows=1, cols=2) + config_sizer.AddGrowableCol(1, 1) + panel_box.Add(config_sizer, proportion=1, flag=wx.EXPAND) + + ### Values. + config_sizer.Add(wx.StaticText(self, label='Values:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) + self.values_input = wx.TextCtrl(self) + config_sizer.Add(self.values_input, flag=wx.EXPAND|wx.ALL, border=5) + + self.SetSizerAndFit(panel_box) + + def GetValue(self): + raw_values = self.values_input.Value.split(',') + + # Ensure the values are sane. + try: + values = [float(x) for x in raw_values] + except ValueError as e: + raise ValueError('Invalid value: {0}'.format(str(e))) + + return ArbitraryConfig(values) + + def SetValue(self, config): + self.values_input.Value = ', '.join('{0:n}'.format(x) for x in config.values) + +# New config panel for getting arbitary variable values from CSV. +class FileConfigPanel(wx.Panel): + def __init__(self, parent, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + # Make parent attribute of FileConfigPanel to play nice with MessageDialog + self.parent = parent + + # Panel. + panel_box = wx.BoxSizer(wx.VERTICAL) + + ## Table for csv data from spacq.gui.display.table.generic + self.table = VirtualListCtrl(self) + self.table.Hide() + + self.column_names = [] + + ## Config. + config_sizer = wx.GridBagSizer(3,2) + panel_box.Add(config_sizer, proportion=1, flag=wx.EXPAND) + + ### Load button + load_button = wx.Button(self, wx.ID_OPEN, label='Load CSV') + ## # TODO: write method for file selection + load_button.Bind(wx.EVT_BUTTON, self.OnMenuFileOpen ) + config_sizer.Add(load_button, pos=(0,0), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) + ### File name display + self.csv_address = '' + self.csv_filename = wx.StaticText(self, label=self.csv_address) + config_sizer.Add(self.csv_filename,pos=(0,1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL, border=5) + + ### Variable select + config_sizer.Add(wx.StaticText(self, label='Select Column:'), pos=(1,0), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) + ### Display column names + self.variable_list = wx.ListBox(self, size=wx.Size(200,100), choices=self.column_names) + self.Bind(wx.EVT_LISTBOX, self.OnAxisSelection, self.variable_list) + config_sizer.Add(self.variable_list, pos=(1,1), flag=wx.ALL, border=5) + + ### Values. + # TODO: add enable/disable for needing csv selected first + config_sizer.Add(wx.StaticText(self, label='Values:'), pos=(2,0), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT|wx.ALL, border=5) + # TODO: find wx box that allows overflow + self.value_bar = wx.StaticText(self, label='No CSV column selected.', + style=wx.ST_NO_AUTORESIZE) + config_sizer.Add(self.value_bar,pos=(2,1), flag=wx.ST_NO_AUTORESIZE|wx.ALL, border=5) + + + self.SetSizerAndFit(panel_box) + + # TODO: return to finishing this up or what not + # From math_setup for data explorer function for callback + def OnAxisSelection(self, evt=None): + # Get what list index was selected + if self.variable_list.Selection == wx.NOT_FOUND: + resultIndex = None + else: + resultIndex = self.variable_list.Selection + + # Retrieve table data, and select chosen column + headings, rows, types = self.table.GetValue(types=['scalar']) + + # Ensure the values are sane, because types is only determined off first entries. + try: + self.values_input = [float(x) for x in rows[:,resultIndex]] + except ValueError as e: + # not really working + MessageDialog(self.parent, str(e), 'Invalid value: {0}'.format(str(e))) + return + + # Formatting to remove newlines that numpy will add into array for printing + valuesLabel = str(self.values_input)[1:-1].replace('\n', ' ') + + # Display string of list + self.value_bar.SetLabel(valuesLabel) + + # loads csv and adds headers and data to self.object + def OnMenuFileOpen(self, evt=None): + try: + result = load_csv(self.parent) + except IOError as e: + # May need to be self.parent if this error ever gets called + MessageDialog(self.panel_box, str(e), 'Could not load data').Show() + return + + has_header, values, filename = result + + # TODO: load_csv will return has_header for first line being data + # not what wanted. Come back later + # If has_header is True, the first row becomes column names + + if has_header: + headers, rows = values[0], array(values[1:]) + else: + headers, rows = [''] * len(values[0]), array(values) + # Ensure that all columns have a header. + for i, header in enumerate(headers): + if not header: + headers[i] = 'Column {0}'.format(i + 1) + + # put filename and column variables into update display + self.csv_address = filename + self.column_names = headers + self.csv_filename.SetLabel(self.csv_address) + self.variable_list.Set(self.column_names) + + # put header names and data in the self.table for VirtualListCtrl + self.table.SetValue(headers, rows) + ############################################### + + # Regular Get and Set that finally set variables + def GetValue(self): + raw_values = self.values_input + + # Ensure the values are sane. + try: + values = [float(x) for x in raw_values] + except ValueError as e: + raise ValueError('Invalid value: {0}'.format(str(e))) + + return ArbitraryConfig(values) + + def SetValue(self, config): + self.values_input.Value = ', '.join('{0:n}'.format(x) for x in config.values) + +class ConditionEditor(Dialog): + def __init__(self, parent, ok_callback, *args, **kwargs): + kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER + + Dialog.__init__(self, parent, *args, **kwargs) + + self.ok_callback = ok_callback + + # Dialog. + dialog_box = wx.BoxSizer(wx.VERTICAL) + + ## Condition Editor + + condition_static_box = wx.StaticBox(self, label = 'Condition') + condition_box = wx.StaticBoxSizer(condition_static_box, wx.HORIZONTAL) + dialog_box.Add(condition_box) + + ### Left Argument + + left_arg_box = wx.BoxSizer(wx.VERTICAL) + condition_box.Add(left_arg_box) + left_arg_input = wx.TextCtrl(self, size=(150, -1)) + left_arg_box.Add(left_arg_input) + + ### Operator + + operators = ['<','==', '!=', '>'] + self.op_menu = wx.ComboBox(self,choices = operators, style=wx.CB_READONLY, size =(70,-1)) + self.op_menu.SetStringSelection(operators[0]) + condition_box.Add(self.op_menu) + + ### Right Argument + + right_arg_box = wx.BoxSizer(wx.VERTICAL) + condition_box.Add(right_arg_box) + right_arg_input = wx.TextCtrl(self, size=(150, -1)) + right_arg_box.Add(right_arg_input) + + self.args = [left_arg_input, right_arg_input] + arg_boxes = [left_arg_box, right_arg_box] + + ## Type. + + #This loop setup is a bit messy coding. + self.arg_type_setters = [] + self.units_input = [] + for i in xrange(0,2): + type_static_box = wx.StaticBox(self, label='Argument {0}'.format(i+1)) + type_box = wx.StaticBoxSizer(type_static_box, wx.VERTICAL) + arg_boxes[i].Add(type_box, flag=wx.CENTER|wx.ALL, border=5) + + types = {} + + types['float'] = wx.RadioButton(self, label='Float', style=wx.RB_GROUP) + type_box.Add(types['float'], flag=wx.ALIGN_LEFT|wx.ALL, border=5) + + types['integer'] = wx.RadioButton(self, label='Integer') + type_box.Add(types['integer'], flag=wx.ALIGN_LEFT|wx.ALL, border=5) + + types['string'] = wx.RadioButton(self, label='String') + type_box.Add(types['string'], flag=wx.ALIGN_LEFT|wx.ALL, border=5) + + types['resource name'] = wx.RadioButton(self, label='Resource') + type_box.Add(types['resource name'], flag=wx.ALIGN_LEFT|wx.ALL, border=5) + + ### Units. + types['quantity'] = wx.RadioButton(self, label='Quantity') + type_box.Add(types['quantity'], flag=wx.CENTER) + + self.arg_type_setters.append(types) + + ## End buttons. + button_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) + + ok_button = wx.Button(self, wx.ID_OK) + self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) + button_box.Add(ok_button) + + cancel_button = wx.Button(self, wx.ID_CANCEL) + button_box.Add(cancel_button) + + self.SetSizerAndFit(dialog_box) + + def OnOk(self, evt=None): + if self.ok_callback(self): + self.Destroy() + + def SetValue(self, condition): + self.arg_type_setters[0][condition.type1].Value = True + self.arg_type_setters[1][condition.type2].Value = True + self.args[0].Value = str(condition.arg1) + self.args[1].Value = str(condition.arg2) + self.op_menu.SetStringSelection(condition.op_symbol) + + + def GetValue(self): + cond_args = [] + arg_types = [] + for i, type in enumerate(self.arg_type_setters): + # We ensure values are sane along the way. + if type['float'].Value: + arg_types.append('float') + cond_args.append(float(self.args[i].Value)) + elif type['integer'].Value: + arg_types.append('integer') + cond_args.append(int(self.args[i].Value)) + elif type['resource name'].Value: + arg_types.append('resource name') + cond_args.append(self.args[i].Value) + elif type['quantity'].Value: + arg_types.append('quantity') + cond_args.append(Quantity(self.args[i].Value)) + elif type['string'].Value: + arg_types.append('string') + cond_args.append(self.args[i].Value) + + condition = Condition(arg_types[0], arg_types[1], cond_args[0], self.op_menu.Value, cond_args[1]) + + return condition + + +class ConditionVariableEditor(Dialog): + col_conditions = VariableColumnDefn(title='Condition', valueGetter=lambda x: str(x), + isSpaceFilling=True, align='left') + + + def __init__(self, parent, ok_callback, *args, **kwargs): + kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER + + Dialog.__init__(self, parent, *args, **kwargs) + + self.ok_callback = ok_callback + + # Dialog. + dialog_box = wx.BoxSizer(wx.VERTICAL) + dialog_box.SetMinSize((350,200)) + + ## List (left op, op, right op). + + ## OLV. + self.condition_olv = ObjectListView.FastObjectListView(self) + dialog_box.Add(self.condition_olv, proportion=1, flag=wx.ALL|wx.EXPAND) + + self.condition_olv.SetColumns([self.col_conditions]) + + self.condition_olv.cellEditMode = self.condition_olv.CELLEDIT_DOUBLECLICK + self.condition_olv.Bind(ObjectListView.EVT_CELL_EDIT_STARTING, self.OnCellEditStarting) + + row_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(row_box, proportion=0, flag=wx.ALL|wx.CENTER) + + ## Add Condition. + add_button = wx.Button(self, wx.ID_ADD) + self.Bind(wx.EVT_BUTTON, self.OnAddCondition, add_button) + row_box.Add(add_button) + + ## Remove Condition. + remove_button = wx.Button(self, wx.ID_REMOVE) + remove_button.Bind(wx.EVT_BUTTON, self.OnRemoveConditions) + row_box.Add(remove_button) + + ## End buttons. + button_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) + + ok_button = wx.Button(self, wx.ID_OK) + self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) + button_box.Add(ok_button) + + cancel_button = wx.Button(self, wx.ID_CANCEL) + button_box.Add(cancel_button) + + self.SetSizerAndFit(dialog_box) + + def GetValue(self): + conditions = self.condition_olv.GetObjects() + + # Get the resource_names from the conditions. + resource_names = [] + for condition in conditions: + if condition.type1 == 'resource name': + resource_names.append(condition.arg1) + if condition.type2 == 'resource name': + resource_names.append(condition.arg2) + + return resource_names, conditions + + def SetValue(self, resource_names, conditions): + + self.condition_olv.SetObjects(conditions) + + def OnOk(self, evt=None): + if self.ok_callback(self): + self.Destroy() + + def OnAddCondition(self, evt=None): + """ + Add a condition to the listctrl + """ + + def ok_callback(dlg): + try: + self.condition_olv.AddObject(dlg.GetValue()) + except ValueError as e: + MessageDialog(self, str(e), 'Invalid value').Show() + return False + + # OLV likes to select a random item at this point. + self.condition_olv.DeselectAll() + return True + + #Start up the new dialog. + dlg = ConditionEditor(self, ok_callback, title='Condition Editor') + dlg.Show() + + def OnRemoveConditions(self, evt=None): + """ + Remove all selected conditions from the OLV. + """ + + selected = self.condition_olv.GetSelectedObjects() + + if selected: + self.condition_olv.RemoveObjects(selected) + + def OnCellEditStarting(self, evt): + condition = evt.rowModel + + # Ignore frivolous requests. + if evt.rowIndex < 0: + evt.Veto() + return + + def ok_callback(dlg): + try: + value = dlg.GetValue() + except ValueError as e: + MessageDialog(self, str(e), 'Invalid value').Show() + return False + + condition.type1 = value.type1 + condition.type2 = value.type2 + condition.arg1 = value.arg1 + condition.arg2 = value.arg2 + condition.op_symbol = value.op_symbol + return True + + dlg = ConditionEditor(self, ok_callback, title='Condition Editor') + dlg.SetValue(condition) + dlg.Show() + + # No need to use the default editor. + evt.Veto() + + +class OutputVariableEditor(Dialog): + def __init__(self, parent, ok_callback, *args, **kwargs): + kwargs['style'] = kwargs.get('style', wx.DEFAULT_DIALOG_STYLE) | wx.RESIZE_BORDER + + Dialog.__init__(self, parent, *args, **kwargs) + + self.ok_callback = ok_callback + + # Dialog. + dialog_box = wx.BoxSizer(wx.VERTICAL) + + ## Config. + self.config_notebook = wx.Notebook(self) + dialog_box.Add(self.config_notebook, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) + + self.config_panel_types = [] + + ### Linear. + linspace_config_panel = LinSpaceConfigPanel(self.config_notebook) + self.config_panel_types.append(LinSpaceConfig) + self.config_notebook.AddPage(linspace_config_panel, 'Linear') + + ### Arbitrary. + arbitrary_config_panel = ArbitraryConfigPanel(self.config_notebook) + self.config_panel_types.append(ArbitraryConfig) + self.config_notebook.AddPage(arbitrary_config_panel, 'Arbitrary') + + ### File load + file_config_panel = FileConfigPanel(self.config_notebook) + self.config_panel_types.append(ArbitraryConfig) + self.config_notebook.AddPage(file_config_panel, 'From File') + + ## Smooth set. + smooth_static_box = wx.StaticBox(self, label='Smooth set') + smooth_box = wx.StaticBoxSizer(smooth_static_box, wx.HORIZONTAL) + dialog_box.Add(smooth_box, flag=wx.CENTER|wx.ALL, border=5) + + smooth_box.Add(wx.StaticText(self, label='Steps:'), flag=wx.CENTER) + + self.smooth_steps_input = wx.SpinCtrl(self, min=1, initial=10) + smooth_box.Add(self.smooth_steps_input, flag=wx.CENTER|wx.ALL, border=5) + + self.smooth_from_checkbox = wx.CheckBox(self, label='From const') + smooth_box.Add(self.smooth_from_checkbox, flag=wx.CENTER|wx.ALL, border=5) + + self.smooth_to_checkbox = wx.CheckBox(self, label='To const') + smooth_box.Add(self.smooth_to_checkbox, flag=wx.CENTER|wx.ALL, border=5) + + self.smooth_transition_checkbox = wx.CheckBox(self, label='Transition') + smooth_box.Add(self.smooth_transition_checkbox, flag=wx.CENTER|wx.ALL, border=5) + + ## Type. + type_static_box = wx.StaticBox(self, label='Type') + type_box = wx.StaticBoxSizer(type_static_box, wx.HORIZONTAL) + dialog_box.Add(type_box, flag=wx.CENTER|wx.ALL, border=5) + + self.type_float = wx.RadioButton(self, label='Float', style=wx.RB_GROUP) + type_box.Add(self.type_float, flag=wx.CENTER|wx.ALL, border=5) + + self.type_integer = wx.RadioButton(self, label='Integer') + type_box.Add(self.type_integer, flag=wx.CENTER|wx.ALL, border=5) + + ### Units. + quantity_static_box = wx.StaticBox(self, label='Quantity') + quantity_box = wx.StaticBoxSizer(quantity_static_box, wx.HORIZONTAL) + type_box.Add(quantity_box) + + self.type_quantity = wx.RadioButton(self) + quantity_box.Add(self.type_quantity, flag=wx.CENTER) + + self.units_input = wx.TextCtrl(self) + quantity_box.Add(self.units_input) + + ## End buttons. + button_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(button_box, flag=wx.CENTER|wx.ALL, border=5) + + ok_button = wx.Button(self, wx.ID_OK) + self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) + button_box.Add(ok_button) + + cancel_button = wx.Button(self, wx.ID_CANCEL) + button_box.Add(cancel_button) + + self.SetSizerAndFit(dialog_box) + + def GetValue(self): + if self.type_float.Value: + type = 'float' + units = None + elif self.type_integer.Value: + type = 'integer' + units = None + else: + type = 'quantity' + units = self.units_input.Value + + # Ensure that the units are valid. + Quantity(1, units) + + return (self.config_notebook.CurrentPage.GetValue(), self.smooth_steps_input.Value, + self.smooth_from_checkbox.Value, self.smooth_to_checkbox.Value, + self.smooth_transition_checkbox.Value, type, units) + + def SetValue(self, config, smooth_steps, smooth_from, smooth_to, smooth_transition, type, units): + config_type = self.config_panel_types.index(config.__class__) + self.config_notebook.ChangeSelection(config_type) + self.config_notebook.CurrentPage.SetValue(config) + + (self.smooth_steps_input.Value, self.smooth_from_checkbox.Value, + self.smooth_to_checkbox.Value, + self.smooth_transition_checkbox.Value) = smooth_steps, smooth_from, smooth_to, smooth_transition + + if type == 'float': + self.type_float.Value = True + elif type == 'integer': + self.type_integer.Value = True + else: + self.type_quantity.Value = True + self.units_input.Value = units if units is not None else '' + + def OnOk(self, evt=None): + if self.ok_callback(self): + self.Destroy() + + +class VariablesPanel(wx.Panel): + col_name = VariableColumnDefn(checkStateGetter='enabled', title='Name', valueGetter='name', + width=150, align='left') + col_order = VariableColumnDefn(title='#', valueGetter='order', width=40) + col_resource = VariableColumnDefn(title='Resource', valueGetter='resource_name', + width=150, align='left') + col_values = VariableColumnDefn(title='Values', valueGetter=lambda x: str(x), + isSpaceFilling=True, align='left') + col_wait = VariableColumnDefn(title='Wait time', valueGetter='wait') + col_const = VariableColumnDefn(checkStateGetter='use_const', title='Const. value', + valueGetter='const') + + def __init__(self, parent, global_store, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + self.global_store = global_store + + # Panel. + panel_box = wx.BoxSizer(wx.VERTICAL) + + ## OLV. + self.olv = ObjectListView.GroupListView(self) + panel_box.Add(self.olv, proportion=1, flag=wx.ALL|wx.EXPAND) + + self.olv.SetColumns([self.col_name, self.col_order, self.col_resource, self.col_values, + self.col_wait, self.col_const]) + self.olv.SetSortColumn(self.col_order) + + self.olv.cellEditMode = self.olv.CELLEDIT_DOUBLECLICK + self.olv.Bind(ObjectListView.EVT_CELL_EDIT_STARTING, self.OnCellEditStarting) + self.olv.Bind(ObjectListView.EVT_CELL_EDIT_FINISHING, self.OnCellEditFinishing) + self.olv.Bind(ObjectListView.EVT_CELL_EDIT_FINISHED, self.OnCellEditFinished) + + ## Buttons. + button_box = wx.BoxSizer(wx.HORIZONTAL) + panel_box.Add(button_box, proportion=0, flag=wx.ALL|wx.CENTER) + + ### Row buttons. + row_box = wx.BoxSizer(wx.HORIZONTAL) + button_box.Add(row_box, flag=wx.LEFT, border=20) + + add_button = wx.Button(self, wx.ID_ADD, label='Add Output') + add_button.Bind(wx.EVT_BUTTON, self.OnAddVariable) + row_box.Add(add_button) + + add_cond_button = wx.Button(self, wx.ID_ADD, label='Add Condition') + add_cond_button.Bind(wx.EVT_BUTTON, self.OnAddConditionVariable) + row_box.Add(add_cond_button) + + remove_button = wx.Button(self, wx.ID_REMOVE) + remove_button.Bind(wx.EVT_BUTTON, self.OnRemoveVariables) + row_box.Add(remove_button) + + ### Export buttons. + export_box = wx.BoxSizer(wx.HORIZONTAL) + button_box.Add(export_box, flag=wx.LEFT, border=20) + + save_button = wx.Button(self, wx.ID_SAVE, label='Save...') + save_button.Bind(wx.EVT_BUTTON, self.OnSave) + export_box.Add(save_button) + + load_button = wx.Button(self, wx.ID_OPEN, label='Load...') + load_button.Bind(wx.EVT_BUTTON, self.OnLoad) + export_box.Add(load_button) + + self.SetSizer(panel_box) + + def max_order(self): + """ + Find the highest-used order in the OLV. + """ + + try: + return max(x.order for x in self.olv.GetObjects()) + except ValueError: + return 0 + + def OnCellEditStarting(self, evt): + col = evt.objectListView.columns[evt.subItemIndex] + var = evt.rowModel + + # Ignore frivolous requests. + if evt.rowIndex < 0: + evt.Veto() + return + + if col == self.col_values: + def ok_callback(dlg): + try: + values = dlg.GetValue() + except ValueError as e: + MessageDialog(self, str(e), 'Invalid value').Show() + return False + + for i, name in enumerate(var.editor_parameters): + setattr(var, name, values[i]) + + return True + + editor = var.editor + dlg = editor(self, ok_callback, title=var.name) + dlg.SetValue(*[getattr(var,attr) for attr in var.editor_parameters]) + + dlg.Show() + + # No need to use the default editor. + evt.Veto() + + + elif col == self.col_const: + # We replace the editor with float editor, as ObjectListView picks the first entry by default + # in the column as what defines the editor. + evt.editor = ObjectListView.CellEditor.FloatEditor(self.olv, evt.subItemIndex) + + + # if there is something non-editable, we cancel the edit. + if col.valueGetter in var.edit_restrictions: + evt.Veto() + + def OnCellEditFinishing(self, evt): + col = evt.objectListView.columns[evt.subItemIndex] + + if col == self.col_name: + var = evt.rowModel # With old name. + var_new_name = evt.editor.Value + + if var_new_name == var.name: + # Not actually changed. + return + + # Attempt to add a new entry first. + try: + self.global_store.variables[var_new_name] = var.variable + except KeyError: + MessageDialog(self, var_new_name, 'Variable name conflicts').Show() + evt.Veto() + return + + # Remove the old entry. + del self.global_store.variables[var.name] + + def OnCellEditFinished(self, evt): + col = evt.objectListView.columns[evt.subItemIndex] + + if col == self.col_order: + self.olv.RebuildGroups() + + def OnSave(self, evt=None): + """ + Save all the rows in the OLV. + """ + + try: + save_pickled(self, [var.variable for var in self.olv.GetObjects()], extension='var', file_type='Variables') + except IOError as e: + MessageDialog(self, str(e), 'Save error').Show() + return + + def OnLoad(self, evt=None): + """ + Load some rows to the OLV. + """ + + try: + values = load_pickled(self, extension='var', file_type='Variables') + except IOError as e: + MessageDialog(self, str(e), 'Load error').Show() + return + + if values is not None: + # Clear the OLV. + for var in self.olv.GetObjects(): + del self.global_store.variables[var.name] + self.olv.RemoveObject(var) + + conflicting_names = [] + for var in values: + try: + self.global_store.variables[var.name] = var.variable + except KeyError: + conflicting_names.append(var.name) + continue + + self.olv.AddObject(var) + + if conflicting_names: + MessageDialog(self, ', '.join(conflicting_names), 'Variable names conflict').Show() + + def OnAddVariable(self, evt=None): + """ + Add a blank variable to the OLV. + """ + + # Ensure that we get a unique name. + with self.global_store.variables.lock: + num = 1 + done = False + while not done: + name = 'New variable {0}'.format(num) + var = OutputVariable(name=name, order=self.max_order()+1) + + try: + self.global_store.variables[name] = var + except KeyError: + num += 1 + else: + done = True + + guivar = GuiVariable(var,'output') + self.olv.AddObject(guivar) + + # OLV likes to select a random item at this point. + self.olv.DeselectAll() + + def OnAddConditionVariable(self, evt=None): + """ + Add a blank conditional variable to the OLV. + """ + + with self.global_store.variables.lock: + num = 1 + done = False + while not done: + name = 'New condition {0}'.format(num) + var = ConditionVariable(name=name, order=self.max_order()+1) + + try: + self.global_store.variables[name] = var + except KeyError: + num += 1 + else: + done = True + + guivar = GuiVariable(var,'condition') + self.olv.AddObject(guivar) + + # OLV likes to select a random item at this point. + self.olv.DeselectAll() + + def OnRemoveVariables(self, evt=None): + """ + Remove all selected variables from the OLV. + """ + + selected = self.olv.GetSelectedObjects() + + if selected: + self.olv.RemoveObjects(selected) + + for row in selected: + del self.global_store.variables[row.name] + + +class GuiVariable(object): + """ + Wraps variables for displaying in the ObjectListView. + This variable will act like the wrapped variable, but will also contain attributes + describing how the values should be edited. + If the variable doesn't have the attribute, then this class creates that attribute + and assigns it a value of 'N/A'. + """ + def __init__(self, var, vartype): + + # Make the gui variable quack like the wrapped variable. + self.__class__ = type(var.__class__.__name__, + (self.__class__, var.__class__), + {}) + self.__dict__ = var.__dict__ + + self.variable = var + self._allowed_types = set(['condition','output']) + self.edit_restrictions = [] + + if vartype not in self._allowed_types: + raise ValueError('Invalid variable type: {0}'.format(type)) + + # Depending on the type of variable, how the variable is displayed and edited is defined below. + if vartype == 'condition': + self.editor = ConditionVariableEditor + self.editor_parameters = ('resource_names', 'conditions') + + #columns that condition variables DONT have. + self.edit_restrictions.append('const') + self.edit_restrictions.append('resource_name') + + elif vartype == 'output': + self.editor = OutputVariableEditor + self.editor_parameters = ('config', 'smooth_steps', 'smooth_from', + 'smooth_to', 'smooth_transition', + 'type', 'units') + + def __getattr__(self, name): + #If the gui variable doesn't have the attribute, then create the attribute + #with a value of 'N/A', and return its value. + setattr(self,name,'N/A') + return getattr(self, name) diff --git a/spacq/gui/display/plot/live/scalar.py b/spacq/gui/display/plot/live/scalar.py new file mode 100755 index 0000000..084bea4 --- /dev/null +++ b/spacq/gui/display/plot/live/scalar.py @@ -0,0 +1,588 @@ +import logging +log = logging.getLogger(__name__) + +import functools +import math +import numpy +from pubsub import pub +from threading import Lock +import time +import wx +from wx.lib.agw import floatspin + +from spacq.interface.resources import AcquisitionThread +from spacq.interface.units import Quantity + +from ....config.measurement import MeasurementConfigPanel +from ....tool.box import Dialog, MessageDialog + +try: + from ..two_dimensional import TwoDimensionalPlot +except ImportError as e: + plot_available = False + log.debug('Could not import TwoDimensionalPlot: {0}'.format(str(e))) +else: + plot_available = True + +""" +A historical live view plot for scalar values. +""" + + +class PlotSettings(object): + """ + Wrapper for all the settings configured via dialog. + """ + + def __init__(self): + self.enabled = plot_available + self.num_points = 500 + self.delay = Quantity(0.2, 's') + self.update_x = True + self.time_value = 0 + self.time_mode = 0 + self.update_y = True + self.y_scale = 0 + self.units_from = '' + self.units_to = '' + + +class PlotSettingsDialog(Dialog): + """ + Set up the live view plot. + """ + + def __init__(self, parent, ok_callback, *args, **kwargs): + Dialog.__init__(self, parent=parent, title='Plot settings') + + self.ok_callback = ok_callback + + dialog_box = wx.BoxSizer(wx.VERTICAL) + + # Enabled. + self.enabled_checkbox = wx.CheckBox(self, label='Enabled') + if not plot_available: + self.enabled_checkbox.Disable() + dialog_box.Add(self.enabled_checkbox, flag=wx.ALL, border=5) + + # Capture. + capture_static_box = wx.StaticBox(self, label='Capture') + capture_box = wx.StaticBoxSizer(capture_static_box, wx.VERTICAL) + capture_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) + capture_box.Add(capture_sizer, flag=wx.CENTER) + dialog_box.Add(capture_box, flag=wx.EXPAND|wx.ALL, border=5) + + ## Number of points. + capture_sizer.Add(wx.StaticText(self, label='Points:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.points_input = wx.SpinCtrl(self, min=2, max=1e4, initial=100) + capture_sizer.Add(self.points_input, flag=wx.CENTER) + + ## Delay. + capture_sizer.Add(wx.StaticText(self, label='Delay (s):'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + # TODO: Input should be a time amount directly (eg. '200 ms'). + self.delay_input = floatspin.FloatSpin(self, min_val=0.2, max_val=1e4, increment=0.1, digits=2) + capture_sizer.Add(self.delay_input, flag=wx.CENTER) + + # Axes. + axes_static_box = wx.StaticBox(self, label='Axes') + axes_box = wx.StaticBoxSizer(axes_static_box, wx.HORIZONTAL) + dialog_box.Add(axes_box, flag=wx.EXPAND|wx.ALL, border=5) + + ## x + x_static_box = wx.StaticBox(self, label='x') + x_box = wx.StaticBoxSizer(x_static_box, wx.VERTICAL) + axes_box.Add(x_box, flag=wx.EXPAND) + + self.update_x_axis = wx.CheckBox(self, label='Autofit') + x_box.Add(self.update_x_axis) + + ### Value. + self.time_value = wx.RadioBox(self, label='Value', choices=['Time', 'Points']) + x_box.Add(self.time_value, flag=wx.EXPAND) + + ### Mode. + self.time_mode = wx.RadioBox(self, label='Mode', choices=['Relative', 'Absolute']) + x_box.Add(self.time_mode, flag=wx.EXPAND) + + ## y + y_static_box = wx.StaticBox(self, label='y') + y_box = wx.StaticBoxSizer(y_static_box, wx.VERTICAL) + axes_box.Add(y_box, flag=wx.EXPAND) + + self.update_y_axis = wx.CheckBox(self, label='Autofit') + y_box.Add(self.update_y_axis) + + ### Conversion. + conversion_static_box = wx.StaticBox(self, label='Conversion') + conversion_box = wx.StaticBoxSizer(conversion_static_box, wx.VERTICAL) + conversion_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5) + conversion_box.Add(conversion_sizer) + y_box.Add(conversion_box) + + conversion_sizer.Add(wx.StaticText(self, label='Exp. scale:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.y_scale = floatspin.FloatSpin(self, min_val=-100, max_val=100, increment=1, digits=2) + conversion_sizer.Add(self.y_scale) + + #### Units. + units_static_box = wx.StaticBox(self, label='Units') + units_box = wx.StaticBoxSizer(units_static_box, wx.VERTICAL) + units_sizer = wx.FlexGridSizer(rows=2, cols=2, hgap=5) + units_box.Add(units_sizer) + conversion_box.Add(units_box, flag=wx.EXPAND) + + units_sizer.Add(wx.StaticText(self, label='From:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.units_from_input = wx.TextCtrl(self) + units_sizer.Add(self.units_from_input) + + units_sizer.Add(wx.StaticText(self, label='To:'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.units_to_input = wx.TextCtrl(self) + units_sizer.Add(self.units_to_input) + + # End buttons. + button_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(button_box, flag=wx.CENTER) + + ok_button = wx.Button(self, wx.ID_OK) + self.Bind(wx.EVT_BUTTON, self.OnOk, ok_button) + button_box.Add(ok_button) + + cancel_button = wx.Button(self, wx.ID_CANCEL) + button_box.Add(cancel_button) + + self.SetSizerAndFit(dialog_box) + + def OnOk(self, evt=None): + self.ok_callback(self) + + self.Destroy() + + def GetValue(self): + plot_settings = PlotSettings() + plot_settings.enabled = self.enabled_checkbox.Value + plot_settings.num_points = self.points_input.Value + plot_settings.delay = Quantity(self.delay_input.GetValue(), 's') + plot_settings.update_x = self.update_x_axis.Value + plot_settings.time_value = self.time_value.Selection + plot_settings.time_mode = self.time_mode.Selection + plot_settings.update_y = self.update_y_axis.Value + plot_settings.y_scale = self.y_scale.GetValue() + plot_settings.units_from = self.units_from_input.Value + plot_settings.units_to = self.units_to_input.Value + + return plot_settings + + def SetValue(self, plot_settings): + self.enabled_checkbox.Value = plot_settings.enabled + self.points_input.Value = plot_settings.num_points + self.delay_input.SetValue(plot_settings.delay.value) + self.update_x_axis.Value = plot_settings.update_x + self.time_value.Selection = plot_settings.time_value + self.time_mode.Selection = plot_settings.time_mode + self.update_y_axis.Value = plot_settings.update_y + self.y_scale.SetValue(plot_settings.y_scale) + self.units_from_input.Value = plot_settings.units_from + self.units_to_input.Value = plot_settings.units_to + + +class ScalarLiveViewPanel(wx.Panel): + """ + A panel to display a live view plot of a scalar resource. + """ + + def __init__(self, parent, global_store, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + self.global_store = global_store + self._measurement_resource_name = None + + # Defaults. + self.plot_settings = PlotSettings() + self.unit_conversion = 0 + + self.enabled = False + self.capturing_data = False + self.restart_live_view = False + self.resource_backup = None + + # This lock blocks the acquisition thread from acquiring. + self.running_lock = Lock() + + # Initialize recorded values. + self.init_values() + + # Plot and toolbar. + display_box = wx.BoxSizer(wx.VERTICAL) + + ## Plot. + if plot_available: + self.plot = TwoDimensionalPlot(self, color='blue') + display_box.Add(self.plot.control, proportion=1, flag=wx.EXPAND) + + self.plot.x_label = 'Time (s)' + else: + display_box.Add((500, -1), proportion=1, flag=wx.EXPAND) + + ## Controls. + if plot_available: + controls_box = wx.BoxSizer(wx.HORIZONTAL) + display_box.Add(controls_box, flag=wx.CENTER|wx.ALL, border=5) + + ### Numeric display. + numeric_display_static_box = wx.StaticBox(self, label='Reading') + numeric_display_box = wx.StaticBoxSizer(numeric_display_static_box, wx.HORIZONTAL) + controls_box.Add(numeric_display_box, flag=wx.CENTER) + + self.numeric_display = wx.TextCtrl(self, size=(100, -1), style=wx.TE_READONLY) + self.numeric_display.BackgroundColour = wx.LIGHT_GREY + numeric_display_box.Add(self.numeric_display) + + ### Capture. + capture_static_box = wx.StaticBox(self, label='Control') + capture_box = wx.StaticBoxSizer(capture_static_box) + controls_box.Add(capture_box, flag=wx.CENTER|wx.LEFT, border=10) + + self.run_button = wx.Button(self, label='Run') + self.Bind(wx.EVT_BUTTON, self.OnRun, self.run_button) + capture_box.Add(self.run_button, flag=wx.CENTER) + + self.pause_button = wx.Button(self, label='Pause') + self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button) + capture_box.Add(self.pause_button, flag=wx.CENTER) + + self.reset_button = wx.Button(self, label='Reset') + self.Bind(wx.EVT_BUTTON, self.OnReset, self.reset_button) + capture_box.Add(self.reset_button, flag=wx.CENTER|wx.LEFT, border=10) + + ### Settings. + settings_static_box = wx.StaticBox(self, label='Settings') + settings_box = wx.StaticBoxSizer(settings_static_box, wx.HORIZONTAL) + controls_box.Add(settings_box, flag=wx.CENTER|wx.LEFT, border=10) + + self.plot_settings_button = wx.Button(self, label='Plot...') + self.Bind(wx.EVT_BUTTON, self.OnPlotSettings, self.plot_settings_button) + settings_box.Add(self.plot_settings_button, flag=wx.CENTER) + + self.SetSizer(display_box) + + # Acquisition thread. + callback = functools.partial(wx.CallAfter, self.add_value) + self.acq_thread = AcquisitionThread(self.plot_settings.delay, callback, + running_lock=self.running_lock) + self.acq_thread.daemon = True + self.acq_thread.start() + + # Wait for a resource to begin capturing. + self.OnPause() + self.run_button.Disable() + + # Subscriptions. + pub.subscribe(self.msg_resource, 'resource.added') + pub.subscribe(self.msg_resource, 'resource.removed') + pub.subscribe(self.msg_data_capture_start, 'data_capture.start') + pub.subscribe(self.msg_data_capture_data, 'data_capture.data') + pub.subscribe(self.msg_data_capture_stop, 'data_capture.stop') + + @property + def running(self): + return self.pause_button.Enabled + + @property + def resource(self): + return self.acq_thread.resource + + @resource.setter + def resource(self, value): + # Ignore unreadable resources. + if value is not None and not value.readable: + value = None + + if self.running: + # Currently running. + running = True + self.OnPause() + else: + running = False + + self.acq_thread.resource = value + + self.run_button.Enable(value is not None) + + # Resume if applicable. + if running: + self.OnRun() + + @property + def measurement_resource_name(self): + if self._measurement_resource_name is None: + return '' + else: + return self._measurement_resource_name + + @measurement_resource_name.setter + def measurement_resource_name(self, value): + if value: + self._measurement_resource_name = value + try: + self.resource = self.global_store.resources[self._measurement_resource_name] + except KeyError: + self.resource = None + else: + self._measurement_resource_name = None + self.resource = None + + def init_values(self): + """ + Clear captured values. + """ + + self._points = numpy.array([]) + self._times = numpy.array([]) + self._values = numpy.array([]) + + self.current_value = None + + self.start_time = None + + def update_plot(self): + """ + Redraw the plot. + """ + + if not len(self._points) > 0: + display_time = [0] + display_values = [0] + else: + if self.plot_settings.time_value == 0: # Time. + display_time = self._times + + if self.plot_settings.time_mode == 0: # Relative. + # Calculate the number of seconds passed since each point. + max_time = self._times[-1] + display_time = [x - max_time for x in display_time] + elif self.plot_settings.time_mode == 1: # Absolute. + display_time = [x - self.start_time for x in display_time] + elif self.plot_settings.time_value == 1: # Points. + display_time = self._points + + if self.plot_settings.time_mode == 0: # Relative. + # Calculate the number of seconds passed since each point. + max_point = self._points[-1] + display_time = [x - max_point for x in display_time] + + display_values = [x * 10 ** (self.plot_settings.y_scale + self.unit_conversion) for + x in self._values] + + if self.plot_settings.update_x: + self.plot.x_autoscale() + if self.plot_settings.update_y: + self.plot.y_autoscale() + + self.plot.x_data, self.plot.y_data = display_time, display_values + + def add_value(self, value): + """ + Update the plot with a new value. + """ + + if not self.plot_settings.enabled: + return + + # Extract the value of a Quantity. + try: + # Label with the base dimensions. + if self.unit_conversion == 0: + self.plot.y_label = '({0})'.format(value.original_units) + value = value.original_value + except AttributeError: + pass + + # Update values. + try: + self._points = numpy.append(self._points, self._points[-1] + 1) + except IndexError: + self._points = numpy.append(self._points, 0) + cur_time = time.time() + self._times = numpy.append(self._times, cur_time) + self._values = numpy.append(self._values, value) + + if self.start_time is None: + self.start_time = cur_time + + cut_idx = len(self._points) - int(self.plot_settings.num_points) + if cut_idx > 0: + self._points = self._points[cut_idx:] + self._times = self._times[cut_idx:] + self._values = self._values[cut_idx:] + + # Set number display. + self.current_value = value * 10 ** (self.plot_settings.y_scale + self.unit_conversion) + self.numeric_display.Value = '{0:.6g}'.format(self.current_value) + + # Plot. + self.update_plot() + + def close(self): + """ + Perform cleanup. + """ + + # Unsubscriptions. + pub.unsubscribe(self.msg_resource, 'resource.added') + pub.unsubscribe(self.msg_resource, 'resource.removed') + pub.unsubscribe(self.msg_data_capture_start, 'data_capture.start') + pub.unsubscribe(self.msg_data_capture_data, 'data_capture.data') + pub.unsubscribe(self.msg_data_capture_stop, 'data_capture.stop') + + # Ensure the thread exits. + self.acq_thread.resource = None + self.acq_thread.done = True + if not self.running: + self.running_lock.release() + self.acq_thread.join() + del self.acq_thread + + def OnRun(self, evt=None): + """ + Let the acquisition thread run. + """ + + self.run_button.Disable() + + if self.acq_thread.resource is None: + return + + self.running_lock.release() + + self.pause_button.Enable() + + def OnPause(self, evt=None): + """ + Block the acquisition thread. + """ + + if not self.running: + return + + self.running_lock.acquire() + + if self.acq_thread.resource is not None: + self.run_button.Enable() + self.pause_button.Disable() + + def OnReset(self, evt=None): + self.init_values() + self.update_plot() + + def OnPlotSettings(self, evt=None): + """ + Open the plot settings dialog. + """ + + def ok_callback(dlg): + self.plot_settings = dlg.GetValue() + + if self.plot_settings.units_from and self.plot_settings.units_to: + try: + quantity_from = Quantity(1, self.plot_settings.units_from) + quantity_to = Quantity(1, self.plot_settings.units_to) + except ValueError as e: + self.unit_conversion = 0 + MessageDialog(self, str(e), 'Invalid unit').Show() + else: + # We don't actually care about the units; just the prefix values. + self.unit_conversion = math.log(quantity_from.value, 10) - math.log(quantity_to.value, 10) + else: + self.unit_conversion = 0 + + self.acq_thread.delay = self.plot_settings.delay + + if self.plot_settings.time_value == 0: + self.plot.x_label = 'Time (s)' + elif self.plot_settings.time_value == 1: + self.plot.x_label = 'Points' + + if self.plot_settings.y_scale != 0: + self.plot.y_label = '/ 10 ^ {0}'.format(self.plot_settings.y_scale) + else: + self.plot.y_label = '' + + if self.plot_settings.units_to: + self.plot.y_label += ' ({0})'.format(self.plot_settings.units_to) + + self.update_plot() + + dlg = PlotSettingsDialog(self, ok_callback) + dlg.SetValue(self.plot_settings) + dlg.Show() + + def msg_resource(self, name, value=None): + if self.measurement_resource_name is not None and name == self.measurement_resource_name: + self.resource = value + + def msg_data_capture_start(self, name): + if name == self.measurement_resource_name: + if self.enabled: + self.capturing_data = True + + # Keep track of whether to restart the capture afterwards. + self.restart_live_view = self.running + + # Disable live view. + self.resource_backup = self.resource + self.resource = None + + def msg_data_capture_data(self, name, value): + if name == self.measurement_resource_name: + if self.capturing_data: + self.add_value(value) + + def msg_data_capture_stop(self, name): + if name == self.measurement_resource_name: + if self.capturing_data: + self.capturing_data = False + + # Re-enable live view. + self.resource = self.resource_backup + self.resource_backup = None + + if self.restart_live_view: + self.OnRun() + + +class ScalarMeasurementFrame(wx.Frame): + def __init__(self, parent, global_store, *args, **kwargs): + wx.Frame.__init__(self, parent, *args, **kwargs) + + # Frame. + frame_box = wx.BoxSizer(wx.VERTICAL) + + ## Measurement setup. + self.measurement_config_panel = MeasurementConfigPanel(self, global_store) + frame_box.Add(self.measurement_config_panel, flag=wx.EXPAND) + + ## Live view. + self.live_view_panel = ScalarLiveViewPanel(self, global_store) + self.live_view_panel.SetMinSize((-1, 400)) + frame_box.Add(self.live_view_panel, proportion=1, flag=wx.EXPAND) + + self.SetSizerAndFit(frame_box) + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + def OnClose(self, evt): + if self.live_view_panel.capturing_data: + msg = 'Cannot close, as a sweep is currently in progress.' + MessageDialog(self, msg, 'Sweep in progress').Show() + + evt.Veto() + return + + self.live_view_panel.close() + self.measurement_config_panel.close() + + evt.Skip() diff --git a/spacq/gui/display/plot/plotmath/common/math_setup.py b/spacq/gui/display/plot/plotmath/common/math_setup.py new file mode 100755 index 0000000..6bac71a --- /dev/null +++ b/spacq/gui/display/plot/plotmath/common/math_setup.py @@ -0,0 +1,159 @@ +import wx + +from .....tool.box import Dialog + +class AxisSelectionPanel(wx.Panel): + """ + A panel for choosing the headings to be used for the axes. + """ + + def __init__(self, parent, axes, headings, selection_callback, *args, **kwargs): + wx.Panel.__init__(self, parent, *args, **kwargs) + + self.selection_callback = selection_callback + + # Panel. + panel_box = wx.BoxSizer(wx.HORIZONTAL) + + ## Axes. + self.axis_lists = [] + + for axis in axes: + axis_static_box = wx.StaticBox(self, label=axis) + axis_box = wx.StaticBoxSizer(axis_static_box, wx.VERTICAL) + panel_box.Add(axis_box, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) + + axis_list = wx.ListBox(self, choices=headings) + axis_list.SetMinSize((-1, 300)) + self.Bind(wx.EVT_LISTBOX, self.OnAxisSelection, axis_list) + axis_box.Add(axis_list, proportion=1, flag=wx.EXPAND) + + self.axis_lists.append(axis_list) + + self.SetSizer(panel_box) + + def OnAxisSelection(self, evt=None): + """ + Announce the latest selection. + """ + + result = [None if list.Selection == wx.NOT_FOUND else list.Selection for + list in self.axis_lists] + + self.selection_callback(result) + +class MathSetupDialog_Derivative(Dialog): + """ + Math configuration dialog (for derivative). + """ + + def __init__(self, parent, headings, axis_names, *args, **kwargs): + Dialog.__init__(self, parent, *args, **kwargs) + + self.axes = [None for _ in axis_names] + + self.step_size = 1 + + # Dialog. + dialog_box = wx.BoxSizer(wx.VERTICAL) + + ## Axis setup. + axis_panel = AxisSelectionPanel(self, axis_names, headings, self.OnAxisSelection) + dialog_box.Add(axis_panel) + + # Derivative Slider + slider_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(slider_box, flag=wx.CENTER) + + self.slider_title = wx.StaticText(self, label='Step Size:') + slider_box.Add(self.slider_title) + + self.reading = wx.StaticText(self, label='01') + slider_box.Add(self.reading) + + self.step_slider = wx.Slider(self, value=1, minValue=1, maxValue= 20, size=(200,-1), style=wx.SL_HORIZONTAL) + self.Bind(wx.EVT_SCROLL, self.OnSliderScroll, self.step_slider) + slider_box.Add(self.step_slider) + + ## End buttons. + button_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(button_box, flag=wx.CENTER) + + self.ok_button = wx.Button(self, wx.ID_OK) + self.ok_button.Disable() + self.Bind(wx.EVT_BUTTON, self.OnOk, self.ok_button) + button_box.Add(self.ok_button) + + cancel_button = wx.Button(self, wx.ID_CANCEL) + button_box.Add(cancel_button) + + self.SetSizerAndFit(dialog_box) + + def OnAxisSelection(self, values): + self.axes = values + + self.ok_button.Enable(all(axis is not None for axis in self.axes)) + + def OnSliderScroll(self, evt=None): + self.step_size = self.step_slider.GetValue() + + self.reading.SetLabel(str(self.step_size)) + + def OnOk(self, evt=None): + title, d_data = self.calculate() + self.dheading = title + self.ddata = d_data + self.Destroy() + +class MathSetupDialog_Function(Dialog): + """ + Math configuration dialog(for functions). + """ + + def __init__(self, parent, headings, axis_names, *args, **kwargs): + Dialog.__init__(self, parent, *args, **kwargs) + + self.axes = [None for _ in axis_names] + + # Dialog. + dialog_box = wx.BoxSizer(wx.VERTICAL) + + ## Axis setup. + axis_panel = AxisSelectionPanel(self, axis_names, headings, self.OnAxisSelection) + dialog_box.Add(axis_panel) + + ## Inputs. + input_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5) + input_sizer.AddGrowableCol(1, 1) + dialog_box.Add(input_sizer, proportion=1, flag=wx.EXPAND|wx.ALL, border=5) + + input_sizer.Add(wx.StaticText(self, label='Scalar function of '+', '.join(axis_names)+':'), + flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT) + self.function_input = wx.TextCtrl(self) + self.function_input.SetMinSize((300, -1)) + input_sizer.Add(self.function_input, flag=wx.EXPAND) + + ## End buttons. + button_box = wx.BoxSizer(wx.HORIZONTAL) + dialog_box.Add(button_box, flag=wx.CENTER) + + self.ok_button = wx.Button(self, wx.ID_OK) + self.ok_button.Disable() + self.Bind(wx.EVT_BUTTON, self.OnOk, self.ok_button) + button_box.Add(self.ok_button) + + cancel_button = wx.Button(self, wx.ID_CANCEL) + button_box.Add(cancel_button) + + self.SetSizerAndFit(dialog_box) + + def OnAxisSelection(self, values): + self.axes = values + + self.ok_button.Enable(all(axis is not None for axis in self.axes)) + + def OnOk(self, evt=None): + title, d_data = self.calculate() + self.dheading = title + self.ddata = d_data + self.Destroy() diff --git a/spacq/interface/resources.py b/spacq/interface/resources.py new file mode 100755 index 0000000..9798430 --- /dev/null +++ b/spacq/interface/resources.py @@ -0,0 +1,315 @@ +import logging +log = logging.getLogger(__name__) + +from copy import copy +from numpy import linspace +from threading import Thread +import time + +from .units import IncompatibleDimensions, Quantity + +from spacq.tool.box import Without + +""" +Tools for working with generic resources. +""" + + +class NotReadable(Exception): + """ + Resource cannot be read from. + """ + + pass + + +class NotWritable(Exception): + """ + Resource cannot be written to. + """ + + pass + + +class Resource(object): + """ + A generic resource which can potentially be read from or written to. + """ + + def __init__(self, obj=None, getter=None, setter=None, converter=None, allowed_values=None): + """ + obj: The device to which the resource belongs. + getter: The method used to get the value. + setter: The method used to set the value. + converter: A function which returns a valid value for the resource, given a string. + allowed_values: A set of all values which are valid for the resource. + + The getter and setter can be actual methods, or just attribute/property name strings. + """ + + if getter is not None and not callable(getter) and obj is None: + raise ValueError('Cannot call getter with no object.') + + if setter is not None and not callable(setter) and obj is None: + raise ValueError('Cannot call setter with no object.') + + self.obj = obj + self.getter = getter + self.setter = setter + self.converter = converter + if allowed_values is not None: + self.allowed_values = set(allowed_values) + else: + self.allowed_values = None + + self.wrappers = [] + + # Dimensions (specified as unit symbol strings) which values for this resource must match. + self._units = None + # String used for display purposes. Typically the same as self.units. + self.display_units = None + + # Resources marked slow should not be fetched implicitly. + self.slow = False + + @property + def units(self): + return self._units + + @units.setter + def units(self, value): + self._units = value + + self.display_units = self.units + + def verify_dimensions(self, value, exception=True, from_string=False): + """ + Ensure that the type and dimensions of the value are as expected. + + If from_string is True, the value is expected to be a unit symbol string. + """ + + if from_string: + value = Quantity(1, value) + + try: + if self.units is not None: + try: + value.assert_dimensions(self.units) + except AttributeError: + raise TypeError('Expected a Quantity, not "{0}"'.format(value)) + except IncompatibleDimensions: + raise TypeError('Expected dimensions matching "{0}", not "{1}"'.format(self.units, value)) + else: + if isinstance(value, Quantity): + raise TypeError('Unexpected Quantity "{0}"'.format(value)) + except Exception: + if exception: + raise + else: + return False + + return True + + @property + def value(self): + """ + The value of the resource. + """ + + if self.getter is None: + raise NotReadable('Resource not readable.') + + if callable(self.getter): + result = self.getter() + elif self.obj is not None: + result = getattr(self.obj, self.getter) + else: + raise NotReadable('Cannot read from resource.') + + self.verify_dimensions(result) + + # Apply the wrappers. + for _, getter_filter, _ in self.wrappers: + if getter_filter is None: + continue + + result = getter_filter(result) + self.verify_dimensions(result) + + return result + + @value.setter + def value(self, v): + if self.setter is None: + raise NotWritable('Resource not writable.') + + self.verify_dimensions(v) + + # Apply the wrappers. + for _, _, setter_filter in self.wrappers: + if setter_filter is None: + continue + + v = setter_filter(v) + self.verify_dimensions(v) + + if self.allowed_values is not None and v not in self.allowed_values: + raise ValueError('Given disallowed value: {0}. Allowed values are: {1}'.format(v,self.allowed_values)) + + if callable(self.setter): + self.setter(v) + elif self.obj is not None: + setattr(self.obj, self.setter, v) + else: + raise NotWritable('Cannot write to resource.') + + def convert(self, value): + """ + Either use the specified converter, treat as a quantity, or do nothing. + """ + + if self.converter is not None: + return self.converter(value) + elif self.units is not None: + q = Quantity(value) + q.assert_dimensions(self.units) + + return q + else: + return value + + @property + def readable(self): + return self.getter is not None + + @property + def writable(self): + return self.setter is not None + + def _find_wrapper_by_name(self, name): + """ + Return the last index of the given wrapper. + """ + + for i, (n, _, _) in reversed(list(enumerate(self.wrappers))): + if n == name: + return i + + raise ValueError('Wrapper not found: {0}'.format(name)) + + def is_wrapped_by(self, name): + """ + Determine whether the given wrapper already wraps this Resource. + """ + + try: + self._find_wrapper_by_name(name) + except ValueError: + return False + else: + return True + + def wrapped(self, name, getter_filter=None, setter_filter=None): + """ + Produce a Resource which is a wrapper around this Resource. + + name: The name of the wrapper to add. + getter_filter: Function of one argument through which to pass any obtained values. + setter_filter: Function of one argument through which to pass values when setting. + """ + + result = copy(self) + + result.wrappers = self.wrappers + [(name, getter_filter, setter_filter)] + + return result + + def unwrapped(self, name): + """ + Produce a Resource with the last instance of the given wrapper removed. + + name: The name of the wrapper to remove. + """ + + result = copy(self) + + idx = self._find_wrapper_by_name(name) + result.wrappers = self.wrappers[:idx] + self.wrappers[idx+1:] + + return result + + def sweep(self, value_from, value_to, steps, delay=0.1, exception_callback=None): + """ + Sweep the Resource slowly over a linear space. + """ + + # Check for dimension mismatches. + if isinstance(value_from, Quantity) and not isinstance(value_to, Quantity) and value_to == 0: + value_to = Quantity(0, value_from.original_units) + elif isinstance(value_to, Quantity) and not isinstance(value_from, Quantity) and value_from == 0: + value_from = Quantity(0, value_to.original_units) + elif isinstance(value_from, Quantity) and isinstance(value_to, Quantity): + value_from.assert_dimensions(value_to) + + for value in linspace(value_from, value_to, steps): + try: + self.value = value + except Exception as e: + if exception_callback is not None: + exception_callback(e) + return + + time.sleep(delay) + + +class AcquisitionThread(Thread): + """ + Once every delay, call the callback with a fresh value from the resource. + + An optional running lock can block execution until it is released elsewhere. + """ + + def __init__(self, delay, callback, resource=None, running_lock=None): + Thread.__init__(self) + + delay.assert_dimensions('s') + + self.resource = resource + self.delay = delay + self.callback = callback + if running_lock is None: + self.running_lock = Without() + else: + self.running_lock = running_lock + + # Allow the thread to be stopped prematurely. + self.done = False + + def run(self): + while not self.done: + # If something goes wrong, sleep the maximum. + delay = self.delay.value + + with self.running_lock: + if time: + next_run = time.time() + self.delay.value + else: + # Weird things happen at shutdown. + return + + if self.resource is not None: + try: + value = self.resource.value + except Exception as e: + log.error('Could not obtain resource value: {0!r}'.format(e)) + else: + self.callback(value) + + if time: + delay = next_run - time.time() + else: + return + + if not self.done and delay > 0: + time.sleep(delay) diff --git a/spacq/interface/units.py b/spacq/interface/units.py new file mode 100755 index 0000000..44f9810 --- /dev/null +++ b/spacq/interface/units.py @@ -0,0 +1,320 @@ +import logging +log = logging.getLogger(__name__) + +from copy import deepcopy +from math import log10 +from numpy import allclose +import quantities as pq + +""" +Tools for working with quantities and units. +""" + + +class IncompatibleDimensions(TypeError): + """ + Operation on quantities with different dimensions. + """ + + pass + + +class SIValues(object): + """ + Values for SI units. + """ + + prefixes = { + 'y': -24, + 'z': -21, + 'a': -18, + 'f': -15, + 'p': -12, + 'n': -9, + 'u': -6, + 'm': -3, + 'c': -2, + 'd': -1, + '': 0, + 'da': 1, + 'h': 2, + 'k': 3, + 'M': 6, + 'G': 9, + 'T': 12, + 'P': 15, + 'E': 18, + 'Z': 21, + 'Y': 24, + } + prefixes_ = dict([(v, k) for (k, v) in prefixes.items()]) + + # SI base units. Note the g instead of kg. + units = set(['A', 'cd', 'g', 'K', 'm', 'mol', 's']) + # SI derived units. Add more as necessary. + units.update(['Hz', 'J', 'N', 'T', 'V', 'G']) + + +class Quantity(object): + """ + A quantity with a value and dimensions. + """ + + @staticmethod + def parse_units(string): + """ + Convert group of unit symbols to pq-acceptable notation: + Determine the multipliers and strip the prefixes. + Use "*" and "**" to combine units. + + eg. ' mN.m.ks-2' -> 'N*m*s**-2', 3 + """ + + symbols = [x.strip() for x in string.split('.')] + + result_units = [] + result_multiplier = 0 + + for symbol in symbols: + symbol_unit, symbol_multiplier = None, None + for prefix, multiplier in SIValues.prefixes.items(): + if not symbol.startswith(prefix): + continue + + unit = symbol[len(prefix):] + + exponent = None + for i in xrange(len(unit)): + try: + exponent = float(unit[i:]) + break + except ValueError: + pass + + if exponent is not None: + unit = unit[:i] + + if unit not in SIValues.units: + continue + + if symbol_unit is None: + if exponent is not None: + unit += '**{0}'.format(exponent) + symbol_unit, symbol_multiplier = unit, multiplier + else: + # Already found a match. + raise ValueError('Ambiguous unit symbol: "{0}"'.format(symbol)) + + if symbol_unit is not None: + result_units.append(symbol_unit) + result_multiplier += symbol_multiplier + else: + # Did not find anything. + raise ValueError('Unrecognized unit symbol: "{0}"'.format(symbol)) + + return ('*'.join(result_units), result_multiplier) + + @staticmethod + def from_string(string): + """ + Separate the value from the units. + """ + + for i in xrange(len(string), 0, -1): + try: + value = float(string[:i]) + except ValueError: + continue + + return value, string[i:] + + raise ValueError(string) + + def __init__(self, value, units=None): + """ + Both ('100 ms') and (100, 'ms') are acceptable. + """ + + if isinstance(value, basestring): + value, units = self.from_string(value) + + # Always work with single floats. + value = float(value) + + log.debug('Creating Quantity: {0}, {1}'.format(value, units)) + + # Remove unit prefixes. + new_units, multiplier = self.parse_units(units) + new_value = value * (10 ** multiplier) + + # Normalize to SI base units. + original_quantity = pq.Quantity(new_value, new_units) + self._q = original_quantity.simplified + + # Information to restore original representation. + self.original_units = units + self.original_multiplier = multiplier + + # Find the normalization factor. + q, orig = self._q.magnitude, original_quantity.magnitude + if q != orig: + self.original_multiplier += log10(abs(q)) - log10(abs(orig)) + + @property + def dimensions(self): + """ + The set of simplified units and their exponents. + """ + + return set(self._q.dimensionality.items()) + + @property + def dimensions_string(self): + """ + Returns the simplified units and their exponents in string form. + """ + + return self._q.dimensionality + + @property + def value(self): + """ + The magnitude of the quantity, normalized to the base units. + """ + + result = self._q.magnitude + + return result.tolist() + + @property + def original_value(self): + """ + The magnitude of the quantity that matches the units. + """ + + return self._q.magnitude / (10 ** self.original_multiplier) + + def assert_dimensions(self, other, exception=True): + """ + Whether the dimensions match. + + If exception is True and we would have returned False, raise an exception. + """ + + if isinstance(other, basestring): + # Given a units string. + other = Quantity(1, other).dimensions + elif isinstance(other, Quantity): + # Given a Quantity. + other = other.dimensions + + if self.dimensions == other: + return True + elif exception: + raise IncompatibleDimensions(self.dimensions, other) + else: + return False + + # FIXME: Python 2.7 provides functools.total_ordering() + def __eq__(self, other): + try: + self.assert_dimensions(other.dimensions) + except AttributeError: + raise TypeError('Expected dimensions for "{0!r}"'.format(other)) + + return allclose(self.value, other.value) + + def __lt__(self, other): + try: + self.assert_dimensions(other.dimensions) + except AttributeError: + raise TypeError('Expected dimensions for "{0!r}"'.format(other)) + + return self.value < other.value + + def __ne__(self, other): + return not self == other + + def __le__(self, other): + return self == other or self < other + + def __ge__(self, other): + return not self < other + + def __gt__(self, other): + return not self <= other + + def __abs__(self): + if self.value < 0: + return Quantity(abs(self.original_value), self.original_units) + else: + return self + + def __add__(self, other): + """ + Addition with matching dimensions. + """ + + try: + self.assert_dimensions(other.dimensions) + except AttributeError: + raise TypeError('Expected dimensions for "{0!r}"'.format(other)) + + result = deepcopy(self) + result._q += other._q + + return result + + def __sub__(self, other): + """ + Subtraction with matching dimensions. + """ + + try: + self.assert_dimensions(other.dimensions) + except AttributeError: + raise TypeError('Expected dimensions for "{0!r}"'.format(other)) + + result = deepcopy(self) + result._q -= other._q + + return result + + def __mul__(self, other): + """ + Multiplication by reals. + """ + + result = deepcopy(self) + result._q *= other + + return result + + def __rmul__(self, other): + return self * other + + def __div__(self, other): + """ + Division by reals. + """ + + result = deepcopy(self) + result._q /= other + + return result + + def __repr__(self): + return '{0}(\'{1}\')'.format(self.__class__.__name__, str(self)) + + def __str__(self): + value = self.original_value + symbol = self.original_units + + return '{0:.10g} {1}'.format(value, symbol) + + def __deepcopy__(self, memo): + """ + Rather than copying anything, simply create a new instance. + """ + + return Quantity(str(self)) diff --git a/spacq/iteration/sweep.py b/spacq/iteration/sweep.py new file mode 100755 index 0000000..decc825 --- /dev/null +++ b/spacq/iteration/sweep.py @@ -0,0 +1,642 @@ +import logging +log = logging.getLogger(__name__) + +from functools import partial, wraps +from itertools import izip, repeat +from threading import Condition, Thread +from time import sleep, time + +from spacq.tool.box import flatten + + +def update_current_f(f): + @wraps(f) + def wrapped(self): + self.current_f = f.__name__ + + log.debug('Entering function: {0}'.format(self.current_f)) + + return f(self) + + return wrapped + + +class PulseConfiguration(object): + """ + The configuration necessary to execute a pulse program with a device. + """ + + # All the directly-used attributes. + awg_attrs = ['channels', 'clear_channels', 'enabled', 'run_mode', 'sampling_rate', 'trigger'] + oscilloscope_attrs = ['acquiring', 'fastframe', 'fastframe_count', 'fastframe_sum', 'stopafter'] + + @staticmethod + def verify_device(name, device, attributes): + if device is None: + raise TypeError('No "{0}" device configured'.format(name)) + + d = dir(device) + + for attribute in attributes: + if attribute not in d: + raise TypeError('Given "{0}" device lacks "{1}"'.format(name, attribute)) + + def __init__(self, program, channels, awg, oscilloscope): + self.verify_device('AWG', awg, self.awg_attrs) + self.verify_device('Oscilloscope', oscilloscope, self.oscilloscope_attrs) + + self.program = program + self.channels = channels + self.awg = awg + self.oscilloscope = oscilloscope + + +class SweepController(object): + """ + A simple controller for a sweep of several variables. + + conditional_dwell + v ^ + init -> next -> transition -> write -> dwell -> pulse -> read -> condition -> ramp_down -> end + ^ ^ |_____________^ | | + | |____________________________________________________________| | + |__________________________________________________________________________________| + + """ + + def __init__(self, resources, variables, num_items, measurement_resources, measurement_variables, + condition_resources=[], condition_variables=[], pulse_config=None, continuous=False): + self.resources = resources + self.variables = variables + self.num_items = num_items + self.measurement_resources = measurement_resources + self.measurement_variables = measurement_variables + #TODO: Instead of flattening, make use of the group ordering for quicker access + self.condition_resources = [cond_tuple for cond_tuple in flatten(condition_resources)] + self.condition_variables = condition_variables + self.pulse_config = pulse_config + self.continuous = continuous + + # The callbacks should be set before calling run(), if necessary. + self.data_callback, self.close_callback, self.write_callback, self.read_callback = [None] * 4 + self.general_exception_handler = None + self.resource_exception_handler = None + + self.devices_configured = False + + self.current_f = None + + self.item = -1 + + self.paused = False + self.pause_lock = Condition() + + self.last_continuous = False + self.done = False + self.aborting = False + self.abort_fatal = False + + self.sweep_start_time = time() + self.first_time_point = None + + self.orders = [vars[0].order for vars in self.variables] + self.orders.reverse() + self.condition_orders = [group[0].order for group in self.condition_variables] + self.conditional_wait = 0 + self.order_periods = None + + def compute_order_periods(self): + """ + This function computes the number of elements iterated before each order changes. + """ + periods = [] + orders = [] + + + + for group in reversed(self.variables): + #skip if vars in group are consts. + if group[0].use_const != 1: + num_in_group = None + + #get the length of the group + for var in group: + num_in_var = len(var) + + if num_in_group is None or num_in_var < num_in_group: + num_in_group = num_in_var + + #append the period of this group and its order. + if not periods: + periods.append(num_in_group) + else: + periods.append(periods[-1]*num_in_group) + + orders.append(group[0].order) + + + for order in self.condition_orders: + if order not in orders: + orders.append(order) + orders.sort() + new_index = orders.index(order) + if new_index > 0: + periods.insert(new_index, periods[new_index - 1]) + else: #if the condition is the smallest order, it has a period of 1 + periods.insert(new_index, 1) + + # create a dict. + self.order_periods = dict(zip(orders, periods)) + + def create_iterator(self, pos): + """ + Create an iterator for an order of variables. + """ + + return izip(*(iter(var) for var in self.variables[pos])) + + def ramp(self, resources, values_from, values_to, steps): + """ + Slowly sweep the resources. + """ + + thrs = [] + for (name, resource), value_from, value_to, resource_steps in zip(resources, + values_from, values_to, steps): + if resource is None: + continue + + kwargs = {} + if self.resource_exception_handler is not None: + kwargs['exception_callback'] = partial(self.resource_exception_handler, name, write=True) + + thr = Thread(target=resource.sweep, args=(value_from, value_to, resource_steps), kwargs=kwargs) + thrs.append(thr) + thr.daemon = True + thr.start() + + for thr in thrs: + thr.join() + + def write_resource(self, name, resource, value): + """ + Write a value to a resource and handle exceptions. + """ + + try: + resource.value = value + except Exception as e: + if self.resource_exception_handler is not None: + self.resource_exception_handler(name, e, write=True) + return + + def read_resource(self, name, resource, save_callback): + """ + Read a value from a resource and handle exceptions. + """ + + try: + value = resource.value + except Exception as e: + if self.resource_exception_handler is not None: + self.resource_exception_handler(name, e, write=False) + return + + save_callback(value) + + + def run(self, next_f=None): + """ + Run the sweep. + """ + + try: + if next_f is None: + next_f = self.init + + # Trampoline. + while next_f is not None: + f_name = next_f.__name__ + + if self.paused: + log.debug('Paused before function: {0}'.format(f_name)) + + with self.pause_lock: + self.pause_lock.wait() + + if self.aborting: + log.debug('Aborting before function: {0}'.format(f_name)) + + if not self.abort_fatal: + self.continuous = False + self.ramp_down() + + return + + log.debug('Starting function: {0}'.format(f_name)) + + try: + next_f = next_f() + except Exception as e: + if self.general_exception_handler is not None: + self.general_exception_handler(f_name, e) + else: + log.exception('Caught exception in function: {0}'.format(f_name)) + + # Attempt to exit normally at this point. + next_f = None + finally: + self.end() + + @update_current_f + def init(self): + """ + Initialize values and possibly devices. + """ + + self.iterators = None + self.current_values = None + self.last_values = None + + self.item = -1 + + self.compute_order_periods() + + if not self.devices_configured: + log.debug('Configuring devices') + + if self.pulse_config is not None: + # AWG + awg = self.pulse_config.awg + + awg.enabled = False + awg.sampling_rate = self.pulse_config.program.frequency + awg.run_mode = 'triggered' + + self.devices_configured = True + + return self.next + + @update_current_f + def next(self): + """ + Get the next set of values from the iterators. + """ + + self.item += 1 + if self.current_values is not None: + self.last_values = self.current_values[:] + + if self.iterators is None: + # First time around. + self.iterators = [] + for pos in xrange(len(self.variables)): + self.iterators.append(self.create_iterator(pos)) + + self.current_values = [it.next() for it in self.iterators] + self.changed_indices = range(len(self.variables)) + + # Calculate where the end of each order is. + else: + pos = len(self.variables) - 1 + while pos >= 0: + try: + self.current_values[pos] = self.iterators[pos].next() + break + except StopIteration: + self.iterators[pos] = self.create_iterator(pos) + self.current_values[pos] = self.iterators[pos].next() + + + pos -= 1 + + self.changed_indices = range(pos, len(self.variables)) + + return self.transition + + @update_current_f + def transition(self): + """ + Perform a transition for variables, as required. + """ + + if self.last_values is None: + # Smooth set from const. + steps, resources, from_values, to_values = [], [], [], [] + + for pos in xrange(len(self.variables)): + # Extract values for this group. + group_vars, group_resources, current_values = (self.variables[pos], + self.resources[pos], self.current_values[pos]) + + for var, resource, current_value in zip(group_vars, group_resources, + current_values): + if var.use_const or not var.smooth_from: + continue + + steps.append(var.smooth_steps) + resources.append(resource) + from_values.append(var.with_type(var.const)) + to_values.append(current_value) + + self.ramp(resources, from_values, to_values, steps) + else: + # The first changed group is simply stepping; all others rolled over. + affected_groups = self.changed_indices[1:] + + steps, resources, from_values, to_values = [], [], [], [] + + for pos in affected_groups: + # Extract values for this group. + group_vars, group_resources, current_values, last_values = (self.variables[pos], + self.resources[pos], self.current_values[pos], self.last_values[pos]) + + for var, resource, current_value, last_value in zip(group_vars, group_resources, + current_values, last_values): + if var.use_const or not var.smooth_transition: + continue + + steps.append(var.smooth_steps) + resources.append(resource) + from_values.append(last_value) + to_values.append(current_value) + + self.ramp(resources, from_values, to_values, steps) + + return self.write + + @update_current_f + def write(self): + """ + Write the next values to their resources. + """ + + thrs = [] + for pos in self.changed_indices: + for i, ((name, resource), value) in enumerate(zip(self.resources[pos], self.current_values[pos])): + if resource is not None: + thr = Thread(target=self.write_resource, args=(name, resource, value)) + thrs.append(thr) + thr.daemon = True + thr.start() + + if self.write_callback is not None: + self.write_callback(pos, i, value) + + for thr in thrs: + thr.join() + + return self.dwell + + @update_current_f + def dwell(self): + """ + Wait for all changed variables. + """ + + delay = max(var._wait.value for pos in self.changed_indices for var in self.variables[pos]) + sleep(delay) + + if self.pulse_config is not None: + return self.pulse + else: + return self.read + + @update_current_f + def pulse(self): + """ + Run through the pulse program. + """ + + if self.pulse_config.channels: + waveforms = self.pulse_config.program.generate_waveforms() + times = self.pulse_config.program.times_average + + # AWG + awg = self.pulse_config.awg + awg.enabled = False + + awg.clear_channels() + + channels = [] + for output, number in self.pulse_config.channels.items(): + channel = awg.channels[number] + + waveform, markers = waveforms[output] + channel.set_waveform(waveform, markers, name=output) + + channels.append(channel) + + for channel in channels: + channel.enabled = True + + awg.enabled = True + + # Oscilloscope + osc = self.pulse_config.oscilloscope + osc.acquiring = False + + if times > 1: + osc.fastframe = True + osc.fastframe_sum = 'average' + osc.fastframe_count = times + 1 + + if osc.fastframe_count != times + 1: + raise ValueError('Cannot average {0} times; check the oscilloscope'.format(times)) + else: + osc.fastframe = False + + osc.stopafter = 'sequence' + + awg.opc + osc.opc + + osc.acquiring = True + # Wait for the oscilloscope to ready the trigger. + sleep(1) + + # All together now! + trigger = awg.trigger + delay = self.pulse_config.program.acq_delay.value + + for _ in repeat(None, times): + trigger() + awg.opc + + end_time = time() + delay + time_diff = end_time - time() + while time_diff > 0: + sleep(time_diff) + time_diff = end_time - time() + + osc.opc + + acqs = osc.acquisitions + if acqs != times: + raise ValueError('Incorrect number of acquisitions made: {0}'.format(acqs)) + + return self.read + + @update_current_f + def read(self): + """ + Take measurements. + """ + measurements = [None] * len(self.measurement_resources) + + thrs = [] + for i, (name, resource) in enumerate(self.measurement_resources): + if resource is not None: + def save_callback(value, i=i): + measurements[i] = value + if self.read_callback is not None: + self.read_callback(i, value) + + thr = Thread(target=self.read_resource, args=(name, resource, save_callback)) + thrs.append(thr) + thr.daemon = True + thr.start() + + for thr in thrs: + thr.join() + + if self.data_callback is not None: + if self.first_time_point is None: + cur_time = 0 + self.first_time_point = time() + else: + cur_time = time() - self.first_time_point + + self.data_callback(cur_time, tuple(flatten(self.current_values)), tuple(measurements)) + + + return self.condition + + + @update_current_f + def condition(self): + """ + Stalls an order's loop (after the order's variables have been exhausted) + with repeated measurements until conditions are true. + Once conditions are true, then the sweep controller moves on to the next order + and continues as usual. + """ + boolean = True + + if self.condition_variables: + + # Find the orders that have changed + orders_changed = [] + for order in self.order_periods.keys(): + if (self.item + 1) % self.order_periods[order] == 0: + orders_changed.append(order) + orders_changed.sort() + + # The wait time is defined by the max of the wait times of the lowest triggered order of condition variables + self.conditional_wait = 0 + for order in orders_changed: + if order in self.condition_orders: + corresponding_index = self.condition_orders.index(order) + for var in self.condition_variables[corresponding_index]: + if var._wait.value > self.conditional_wait: + self.conditional_wait = var._wait.value + break + + # Check the conditions for the changed orders, starting from the lowest order. + for order in orders_changed: + if order in self.condition_orders: + for var in self.condition_variables[self.condition_orders.index(order)]: + boolean_to_compound = var.evaluate_conditions(self.condition_resources) + boolean = boolean and boolean_to_compound + + if boolean == True: + if self.item == self.num_items - 1: + self.item += 1 + return self.ramp_down + else: + return self.next + if boolean == False: + return self.conditional_dwell + + def conditional_dwell(self): + """ + Dwell for the max time defined by the conditions in the current order. + """ + sleep(self.conditional_wait) + return self.read + + + @update_current_f + def ramp_down(self): + """ + Sweep from the last values to const. + """ + + if not self.current_values: + return + + # Smooth set to const. + steps, resources, from_values, to_values = [], [], [], [] + + for pos in xrange(len(self.variables)): + # Extract values for this group. + group_vars, group_resources, current_values = (self.variables[pos], + self.resources[pos], self.current_values[pos]) + + for var, resource, current_value in zip(group_vars, group_resources, + current_values): + if var.use_const or not var.smooth_to: + continue + + steps.append(var.smooth_steps) + resources.append(resource) + from_values.append(current_value) + to_values.append(var.with_type(var.const)) + + self.ramp(resources, from_values, to_values, steps) + + if self.continuous and not self.last_continuous: + return self.init + + @update_current_f + def end(self): + """ + The sweep is over. + """ + + assert not self.done + self.done = True + + if self.close_callback is not None: + self.close_callback() + + def pause(self): + log.debug('Pausing.') + + self.paused = True + + log.debug('Paused.') + + def unpause(self): + log.debug('Unpausing.') + + with self.pause_lock: + self.paused = False + self.pause_lock.notify() + + log.debug('Unpaused.') + + def abort(self, fatal=False): + """ + Ending abruptly for any reason. + """ + + log.debug('Aborting.') + + self.aborting = True + self.abort_fatal = fatal + + if self.abort_fatal: + log.warning('Aborting fatally.') + + self.unpause() diff --git a/spacq/iteration/tests/test_sweep.py b/spacq/iteration/tests/test_sweep.py new file mode 100755 index 0000000..2f3ce87 --- /dev/null +++ b/spacq/iteration/tests/test_sweep.py @@ -0,0 +1,512 @@ +from functools import partial +from nose.tools import eq_ +from os import path +from threading import Thread +from time import sleep, time +from unittest import main, TestCase +from itertools import cycle + +from spacq.devices.config import DeviceConfig +from spacq.interface.pulse.program import Program +from spacq.interface.resources import Resource +from spacq.interface.units import Quantity +from spacq.tool.box import flatten + +from ..variables import sort_condition_variables, sort_output_variables, InputVariable, OutputVariable +from ..variables import ConditionVariable, LinSpaceConfig, Condition + +from .. import sweep + +resource_dir = path.join(path.dirname(__file__), 'resources') + +class SweepControllerTest(TestCase): + def testSingle(self): + """ + Iterate over a single thing without any measurements. + """ + + res_buf = [] + + def setter(value): + res_buf.append(value) + + res = Resource(setter=setter) + var = OutputVariable(name='Var', order=1, enabled=True, const=-1.0) + var.config = LinSpaceConfig(1.0, 4.0, 4) + var.smooth_steps = 3 + var.smooth_from, var.smooth_to = [True] * 2 + + vars, num_items = sort_output_variables([var]) + ctrl = sweep.SweepController([(('Res', res),)], vars, num_items, [], []) + + # Callback verification buffers. + actual_values = [] + actual_measurement_values = [] + actual_writes = [] + actual_reads = [] + closed = [0] + + # Callbacks. + def data_callback(cur_time, values, measurement_values): + actual_values.append(values) + actual_measurement_values.append(measurement_values) + ctrl.data_callback = data_callback + + def close_callback(): + closed[0] += 1 + ctrl.close_callback = close_callback + + def write_callback(pos, i, value): + actual_writes.append((pos, i, value)) + ctrl.write_callback = write_callback + + def read_callback(i, value): + actual_reads.append((i, value)) + ctrl.read_callback = read_callback + + # Let it run. + ctrl.run() + + eq_(res_buf, [-1.0, 0.0, 1.0, 1.0, 2.0, 3.0, 4.0, 4.0, 1.5, -1.0]) + eq_(actual_values, [(1.0,), (2.0,), (3.0,), (4.0,)]) + eq_(actual_measurement_values, [()] * 4) + eq_(actual_writes, [(0, 0, x) for x in [1.0, 2.0, 3.0, 4.0]]) + eq_(actual_reads, []) + eq_(closed, [1]) + + def testProper(self): + """ + Testing everything that there is to test along the happy path: + nested and parallel variables + measurements + dwell time + """ + + res_bufs = [[], [], [], []] + measurement_counts = [0] * 2 + + def setter(i, value): + res_bufs[i].append(value) + + def getter(i): + measurement_counts[i] += (-1) ** i + + return measurement_counts[i] + + dwell_time = Quantity(50, 'ms') + + # Output. + res0 = Resource(setter=partial(setter, 0)) + res0.units = 'cm-1' + res1 = Resource(setter=partial(setter, 1)) + res2 = Resource(setter=partial(setter, 2)) + res3 = Resource(setter=partial(setter, 3)) + + var0 = OutputVariable(name='Var 0', order=2, enabled=True, const=0.0) + var0.config = LinSpaceConfig(-1.0, -2.0, 2) + var0.smooth_steps = 2 + var0.smooth_from, var0.smooth_to, var0.smooth_transition = [True] * 3 + var0.type = 'quantity' + var0.units = 'cm-1' + + var1 = OutputVariable(name='Var 1', order=1, enabled=True, const=-1.0) + var1.config = LinSpaceConfig(1.0, 4.0, 4) + var1.smooth_steps = 3 + var1.smooth_from, var1.smooth_to, var1.smooth_transition = [True] * 3 + + var2 = OutputVariable(name='Var 2', order=1, enabled=True, const=1.23, use_const=True) + + var3 = OutputVariable(name='Var 3', order=1, enabled=True, const=-9.0, wait=str(dwell_time)) + var3.config = LinSpaceConfig(-1.0, 2.0, 4) + var3.smooth_steps = 2 + var3.smooth_from, var3.smooth_to, var3.smooth_transition = True, True, False + + var4 = OutputVariable(name='Var 4', order=3, enabled=True, const=-20.0) + var4.config = LinSpaceConfig(-10.0, 20, 1) + var4.smooth_steps = 2 + var4.smooth_from = True + + # Input. + meas_res0 = Resource(getter=partial(getter, 0)) + meas_res1 = Resource(getter=partial(getter, 1)) + + meas0 = InputVariable(name='Meas 0') + meas1 = InputVariable(name='Meas 1') + + vars, num_items = sort_output_variables([var0, var1, var2, var3, var4]) + ctrl = sweep.SweepController([(('Res 2', res2),), (('Something', None),), (('Res 0', res0),), + (('Res 1', res1), ('Res 3', res3))], vars, num_items, + [('Meas res 0', meas_res0), ('Meas res 1', meas_res1)], [meas0, meas1]) + + # Callback verification buffers. + actual_values = [] + actual_measurement_values = [] + actual_writes = [] + actual_reads = [] + closed = [0] + + # Callbacks. + def data_callback(cur_time, values, measurement_values): + actual_values.append(values) + actual_measurement_values.append(measurement_values) + ctrl.data_callback = data_callback + + def close_callback(): + closed[0] += 1 + ctrl.close_callback = close_callback + + def write_callback(pos, i, value): + actual_writes.append((pos, i, value)) + ctrl.write_callback = write_callback + + def read_callback(i, value): + actual_reads.append((i, value)) + ctrl.read_callback = read_callback + + # Let it run. + start_time = time() + ctrl.run() + elapsed_time = time() - start_time + + expected_time = num_items * dwell_time.value + assert expected_time < elapsed_time, 'Took {0} s, expected at least {1} s.'.format(elapsed_time, expected_time) + + expected_res1 = [1.0, 2.0, 3.0, 4.0] + expected_res2 = [-1.0, 0.0, 1.0, 2.0] + + expected_inner_writes = list(flatten(((3, 0, x), (3, 1, x - 2.0)) for x in [1.0, 2.0, 3.0, 4.0])) + expected_writes = [(0, 0, 1.23), (1, 0, -10.0)] + list(flatten([(2, 0, x)] + expected_inner_writes + for x in [Quantity(x, 'cm-1') for x in [-1.0, -2.0]])) + + eq_(res_bufs, [ + [Quantity(x, 'cm-1') for x in [0.0, -1.0, -1.0, -2.0, -2.0, 0.0]], + [-1.0, 0.0, 1.0] + expected_res1 + [4.0, 2.5, 1.0] + expected_res1 + [4.0, 1.5, -1.0], + [1.23], + [-9.0, -1.0] + expected_res2 + expected_res2 + [2.0, -9.0], + ]) + eq_(measurement_counts, [8, -8]) + eq_(actual_values, [(1.23, -10.0, x, y, y - 2.0) + for x in [Quantity(x, 'cm-1') for x in [-1.0, -2.0]] + for y in [1.0, 2.0, 3.0, 4.0]]) + eq_(actual_measurement_values, [(x, -x) for x in xrange(1, 9)]) + eq_(actual_writes, expected_writes) + eq_(actual_reads, list(flatten(((0, x), (1, -x)) for x in xrange(1, 9)))) + eq_(closed, [1]) + + def testContinuous(self): + """ + Keep going, and then eventually stop. + """ + + res_buf = [] + + def setter(value): + res_buf.append(value) + + res = Resource(setter=setter) + var = OutputVariable(name='Var', order=1, wait='0 ms', enabled=True) + var.config = LinSpaceConfig(1.0, 4.0, 4) + + vars, num_items = sort_output_variables([var]) + ctrl = sweep.SweepController([(('Res', res),)], vars, num_items, [], [],[],[], continuous=True) + + thr = Thread(target=ctrl.run) + thr.daemon = True + thr.start() + + sleep(0.5) + ctrl.pause() + sleep(0.5) + ctrl.unpause() + sleep(0.5) + + ctrl.last_continuous = True + thr.join() + + expected_buf = [1.0, 2.0, 3.0, 4.0] + + eq_(res_buf[:len(expected_buf) * 50], expected_buf * 50) + + def testWriteException(self): + """ + Fail to read. + """ + + exceptions = [] + e = ValueError() + + def setter(value): + raise e + + res = Resource(setter=setter) + var = OutputVariable(name='Var', order=1, enabled=True, const=0.0) + var.config = LinSpaceConfig(1.0, 4.0, 4) + + vars, num_items = sort_output_variables([var]) + ctrl = sweep.SweepController([(('Res', res),)], vars, num_items, [], []) + + def resource_exception_handler(name, e, write): + exceptions.append((name, e)) + ctrl.abort(fatal=True) + assert write + ctrl.resource_exception_handler = resource_exception_handler + + ctrl.run() + + eq_(exceptions, [('Res', e)]) + + def testReadException(self): + """ + Fail to write. + """ + + exceptions = [] + e = ValueError() + + def getter(): + raise e + + res = Resource(setter=lambda x: x) + var = OutputVariable(name='Var', order=1, enabled=True) + var.config = LinSpaceConfig(1.0, 4.0, 4) + + meas_res = Resource(getter=getter) + meas_var = InputVariable(name='Meas var') + + vars, num_items = sort_output_variables([var]) + ctrl = sweep.SweepController([(('Res', res),)], vars, num_items, [('Meas res', meas_res)], [meas_var]) + + def resource_exception_handler(name, e, write): + exceptions.append((name, e)) + assert not write + ctrl.resource_exception_handler = resource_exception_handler + + ctrl.run() + + eq_(exceptions, [('Meas res', e)] * 4) + + def testPulseProgram(self): + """ + Iterate with a pulse program. + """ + + res_buf = [] + + def setter(value): + res_buf.append(value) + + res = Resource(setter=setter) + var1 = OutputVariable(name='Var 1', order=1, enabled=True) + var1.config = LinSpaceConfig(1.0, 4.0, 4) + + p = Program.from_file(path.join(resource_dir, '01.pulse')) + p.frequency = Quantity(1, 'GHz') + p.set_value(('_acq_marker', 'marker_num'), 1) + p.set_value(('_acq_marker', 'output'), 'f1') + + eq_(p.all_values, set([('_acq_marker', 'marker_num'), ('_acq_marker', 'output'), ('d',), ('i',), + ('p', 'amplitude'), ('p', 'length'), ('p', 'shape')])) + + parameters = [('i',), ('d',), ('p', 'amplitude'), ('p', 'length')] + for parameter in parameters: + p.resource_labels[parameter] = 'res_' + '.'.join(parameter) + p.resources[parameter] = Resource() + + var2 = OutputVariable(name='Var 2', order=1, enabled=True) + var2.config = LinSpaceConfig(1, 4, 4) + var2.type = 'integer' + + awg_cfg = DeviceConfig('awg') + awg_cfg.address_mode = awg_cfg.address_modes.gpib + awg_cfg.manufacturer, awg_cfg.model = 'Tektronix', 'AWG5014B' + awg_cfg.mock = True + awg_cfg.connect() + + osc_cfg = DeviceConfig('osc') + osc_cfg.address_mode = awg_cfg.address_modes.gpib + osc_cfg.manufacturer, osc_cfg.model = 'Tektronix', 'DPO7104' + osc_cfg.mock = True + osc_cfg.connect() + + pulse_config = sweep.PulseConfiguration(p.with_resources, {'f1': 1}, awg_cfg.device, osc_cfg.device) + + vars, num_items = sort_output_variables([var1, var2]) + ress = [(('Res 1', res), ('Res 2', p.resources[('i',)]))] + ctrl = sweep.SweepController(ress, vars, num_items, [], [],[],[],pulse_config) + + ctrl.run() + + eq_(res_buf, [1.0, 2.0, 3.0, 4.0]) + + def testConditionsSweep(self): + """ + Tests a setup with condition variables and output variables. Similar to testProper, but with + conditions mixed in. + """ + + res_bufs = [[], [], [], []] + measurement_counts = [0] * 2 + + def setter(i, value): + res_bufs[i].append(value) + + def getter(i): + measurement_counts[i] += (-1) ** i + + return measurement_counts[i] + + dwell_time = Quantity(50, 'ms') + + # Output. + res0 = Resource(setter=partial(setter, 0)) + res0.units = 'cm-1' + res1 = Resource(setter=partial(setter, 1)) + res2 = Resource(setter=partial(setter, 2)) + res3 = Resource(setter=partial(setter, 3)) + + var0 = OutputVariable(name='Var 0', order=2, enabled=True, const=0.0) + var0.config = LinSpaceConfig(-1.0, -2.0, 2) + var0.smooth_steps = 2 + var0.smooth_from, var0.smooth_to, var0.smooth_transition = [True] * 3 + var0.type = 'quantity' + var0.units = 'cm-1' + + var1 = OutputVariable(name='Var 1', order=1, enabled=True, const=-1.0) + var1.config = LinSpaceConfig(1.0, 4.0, 4) + var1.smooth_steps = 3 + var1.smooth_from, var1.smooth_to, var1.smooth_transition = [True] * 3 + + var2 = OutputVariable(name='Var 2', order=1, enabled=True, const=1.23, use_const=True) + + var3 = OutputVariable(name='Var 3', order=1, enabled=True, const=-9.0, wait=str(dwell_time)) + var3.config = LinSpaceConfig(-1.0, 2.0, 4) + var3.smooth_steps = 2 + var3.smooth_from, var3.smooth_to, var3.smooth_transition = True, True, False + + var4 = OutputVariable(name='Var 4', order=3, enabled=True, const=-20.0) + var4.config = LinSpaceConfig(-10.0, 20, 1) + var4.smooth_steps = 2 + var4.smooth_from = True + + # Condition variables. + + ## Resources used for checking the conditions. + + iters = [cycle([0,1]), cycle([0,1,2]), cycle([0,-1,-2])] + def cres_getter(i): + return iters[i].next() + + cres0 = Resource('cres0', getter=partial(cres_getter,0)) + cres1 = Resource('cres1', getter=partial(cres_getter,1)) + cres2 = Resource('cres2', getter=partial(cres_getter,2)) + + condition_resources = [(('cres0',cres0),),(('cres1',cres1),('cres2',cres2),)] + + + cond0 = Condition('resource name','integer','cres0','==',1) + cond1 = Condition('resource name','integer','cres1','==',2) + cond2 = Condition('resource name','integer','cres2','==',-2) + + cvar0 = ConditionVariable(name='cvar0',order=0,enabled=True,wait=str(dwell_time), + resource_names=['cres0',],conditions=[cond0]) + cvar1 = ConditionVariable(name='cvar1',order=1,enabled=True,wait=str(dwell_time), + resource_names=['cres1',],conditions=[cond1]) + cvar2 = ConditionVariable(name='cvar2',order=1,enabled=True,wait=str(dwell_time), + resource_names=['cres2',],conditions=[cond2]) + + # Input. + meas_res0 = Resource(getter=partial(getter, 0)) + meas_res1 = Resource(getter=partial(getter, 1)) + + meas0 = InputVariable(name='Meas 0') + meas1 = InputVariable(name='Meas 1') + + vars, num_items = sort_output_variables([var0, var1, var2, var3, var4]) + cvars = sort_condition_variables([cvar0,cvar1,cvar2]) + ctrl = sweep.SweepController([(('Res 2', res2),), (('Something', None),), (('Res 0', res0),), + (('Res 1', res1), ('Res 3', res3))], vars, num_items, + [('Meas res 0', meas_res0), ('Meas res 1', meas_res1)], [meas0, meas1], + condition_resources, cvars) + + # Callback verification buffers. + actual_values = [] + actual_measurement_values = [] + actual_writes = [] + actual_reads = [] + closed = [0] + + # Callbacks. + def data_callback(cur_time, values, measurement_values): + actual_values.append(values) + actual_measurement_values.append(measurement_values) + ctrl.data_callback = data_callback + + def close_callback(): + closed[0] += 1 + ctrl.close_callback = close_callback + + def write_callback(pos, i, value): + actual_writes.append((pos, i, value)) + ctrl.write_callback = write_callback + + def read_callback(i, value): + actual_reads.append((i, value)) + ctrl.read_callback = read_callback + + # Let it run. + start_time = time() + ctrl.run() + elapsed_time = time() - start_time + + expected_time = num_items * dwell_time.value + assert expected_time < elapsed_time, 'Took {0} s, expected at least {1} s.'.format(elapsed_time, expected_time) + + expected_res1 = [1.0, 2.0, 3.0, 4.0] + expected_res2 = [-1.0, 0.0, 1.0, 2.0] + + expected_inner_writes = list(flatten(((3, 0, x), (3, 1, x - 2.0)) for x in [1.0, 2.0, 3.0, 4.0])) + expected_writes = [(0, 0, 1.23), (1, 0, -10.0)] + list(flatten([(2, 0, x)] + expected_inner_writes + for x in [Quantity(x, 'cm-1') for x in [-1.0, -2.0]])) + + eq_(res_bufs, [ + [Quantity(x, 'cm-1') for x in [0.0, -1.0, -1.0, -2.0, -2.0, 0.0]], + [-1.0, 0.0, 1.0] + expected_res1 + [4.0, 2.5, 1.0] + expected_res1 + [4.0, 1.5, -1.0], + [1.23], + [-9.0, -1.0] + expected_res2 + expected_res2 + [2.0, -9.0], + ]) + + eq_(measurement_counts, [24, -24]) + + + # Construct predicted actual values. + expected_actual_values = [] + for x in [Quantity(x, 'cm-1') for x in [-1.0, -2.0]]: + for y in [1.0, 2.0, 3.0, 4.0]: + + # We append twice since the 0th order condition will create an extra measurement + # for every non_conditionally based measurement + expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) + expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) + + # If were at the end of order 1, we append 4 more times to have a total of 6 + # This is because in the condition checking stage, we require the 0th order condition + # and the 1st order conditions to all be true. So, cycling through entries in [0,1] + # and [0,1,2], it will take a total of 6 condition checks to get 1 in [0,1], and 2 in + # [0,1,2]. + if y == 4: + expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) + expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) + expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) + expected_actual_values.append((1.23, -10.0, x, y, y - 2.0)) + + + eq_(actual_values, expected_actual_values) + + eq_(actual_measurement_values, [(x, -x) for x in xrange(1, 25)]) + eq_(actual_writes, expected_writes) + eq_(actual_reads, list(flatten(((0, x), (1, -x)) for x in xrange(1, 25)))) + eq_(closed, [1]) + + +if __name__ == '__main__': + main() diff --git a/spacq/iteration/tests/test_variables.py b/spacq/iteration/tests/test_variables.py new file mode 100755 index 0000000..efb9712 --- /dev/null +++ b/spacq/iteration/tests/test_variables.py @@ -0,0 +1,286 @@ +from nose.tools import assert_raises, eq_ +from unittest import main, TestCase + +from spacq.interface.units import IncompatibleDimensions, Quantity +from spacq.interface.resources import Resource +from functools import partial + +from .. import variables + +class SortOutputVariablesTest(TestCase): + def testEmpty(self): + """ + Use no variables. + """ + + sorted_variables, num_items = variables.sort_output_variables([]) + + eq_(sorted_variables, []) + eq_(num_items, 0) + + def testSingle(self): + """ + Use a single variable. + """ + + var = variables.OutputVariable(config=variables.LinSpaceConfig(-5.0, 5.0, 11), + name='Name', order=0, enabled=True, const=60.0) + + sorted_variables, num_items = variables.sort_output_variables([var]) + + eq_(sorted_variables, [(var,)]) + eq_(num_items, 11) + + def testMultiple(self): + """ + Use many variables. + """ + + vars = [ + variables.OutputVariable(config=variables.LinSpaceConfig(1.0, 5.0, 3), + name='A', order=3, enabled=True), + variables.OutputVariable(config=variables.LinSpaceConfig(11.0, 12.0, 2), + name='B', order=2, enabled=True, const=10.0), + variables.OutputVariable(config=variables.LinSpaceConfig(-99.0, 0.0), + name='D', order=1, enabled=True, const=9.0, use_const=True), + variables.OutputVariable(config=variables.LinSpaceConfig(21.0, 25.0, 20), + name='C', order=2, enabled=True), + variables.OutputVariable(config=variables.LinSpaceConfig(0.0, 0.0, 1), + name='E', order=4), + variables.OutputVariable( + name='F', order=5, enabled=True, const=5.5, use_const=True), + ] + + sorted_variables, num_items = variables.sort_output_variables(vars) + + eq_(sorted_variables, [(vars[2], vars[5]), (vars[0],), (vars[1], vars[3])]) + eq_(num_items, 6) + +class ConditionVariableTest(TestCase): + + # It goes without saying that if condition variables are upgraded + # with a general boolean parser that these tests will have to be changed. + + def testEvaluateConditions(self): + """ + See if a condition variable evaluates its conditions + properly. + """ + + # Define conditions. + c1 = variables.Condition('integer','integer',1,'<',3) #True + c2 = variables.Condition('integer','integer',1,'>',3) #False + + # Define condition variables. + cv0 = variables.ConditionVariable(1,conditions=[c1], name='cv0') + cv1 = variables.ConditionVariable(1,conditions=[c1,c1], name='cv1') + cv2 = variables.ConditionVariable(1,conditions=[c1,c2], name='cv2') + cv3 = variables.ConditionVariable(1,conditions=[c2,c2,c2,c1], name='cv3') + cv4 = variables.ConditionVariable(1,conditions=[c2,c2], name='cv4') + + # Evaluate a simple case. + eq_(cv0.evaluate_conditions(),True) + + # Check if it is ORing properly. + eq_(cv1.evaluate_conditions(),True) + eq_(cv2.evaluate_conditions(),True) + eq_(cv3.evaluate_conditions(),True) + eq_(cv4.evaluate_conditions(),False) + + def testEvaluateCondition(self): + """ + Test if evaluating conditions works properly + for different combinations of types. + """ + # Some variables to use for tests. + + res_bufs = [[], [], [], []] + + def setter(i, value): + res_bufs[i].append(value) + + def getter(i): + return res_bufs[i][0] + + + a = Quantity('5 T') + b = Quantity('50 kG') + c = 'on' + + res0 = Resource(getter=partial(getter,0), setter=partial(setter, 0)) + res0.units = 'T' + res1 = Resource(getter=partial(getter,1), setter=partial(setter, 1)) + res1.units = 'kG' + res2 = Resource(getter=partial(getter,2), setter=partial(setter, 2)) + res2.allowed_values = ['on','off'] + + res0.value = a + res1.value = b + res2.value = c + + # Check 2 quantities of equivalent units. + c1 = variables.Condition('quantity','quantity',a,'==',b) + eq_(c1.evaluate(), True) + + # Check a resource against a quantity. + c2 = variables.Condition('resource','quantity',res0,'==',a) + eq_(c2.evaluate(), True) + + # Check a resource with a resource. + c3 = variables.Condition('resource','resource',res0,'==',res1) + eq_(c3.evaluate(), True) + + # Check a resource that has an allowed value with a string. + c4 = variables.Condition('resource','string',res2,'==',c) + eq_(c4.evaluate(), True) + + # Test evaluating resource names. + resources = [('res0',res0),('res1',res1),('res2',res2)] + c3 = variables.Condition('resource name','resource name','res0','==','res1') + eq_(c3.evaluate(resources), True) + + # Check some things that should mess up. + + ## string instead of quantity. + try: + c5 = variables.Condition('resource','string',res0,'==','5 T') + eq_(c5.evaluate(), True) + except TypeError: + pass + else: + assert False, 'Expected TypeError.' + + ## not matching units. + try: + c6 = variables.Condition('quantity','quantity',Quantity('5 A'),'==',Quantity('5 T')) + eq_(c6.evaluate(), True) + except IncompatibleDimensions: + pass + else: + assert False, 'Expected IncompatibleDimensions error.' + + +class OutputVariableTest(TestCase): + def testAdjust(self): + """ + Try to adjust the values after initialization. + """ + + var = variables.OutputVariable(name='Name', order=1) + + var.config = variables.LinSpaceConfig() + + var.config.steps = 1000 + eq_(var.config.steps, 1000) + + try: + var.config.steps = -1 + except ValueError: + pass + else: + assert False, 'Expected ValueError.' + + var.wait = '1e2 ms' + eq_(var.wait, '100 ms') + + try: + var.wait = '100 Hz' + except IncompatibleDimensions: + pass + else: + assert False, 'Expected IncompatibleDimensions.' + + def testStr(self): + """ + Ensure the variable looks right. + """ + + var = variables.OutputVariable(name='Name', order=1) + + # Very short. + var.config = variables.LinSpaceConfig(0.0, 5.0, 3) + var.type = 'integer' + eq_(str(var), '[0, 2, 5]') + + # Borderline. + var.config = variables.LinSpaceConfig(1.0, 5.0, 5) + var.type = 'float' + eq_(str(var), '[1, 2, 3, 4, 5]') + + # Short enough. + var.config = variables.LinSpaceConfig(-200.0, 200.0, 401) + eq_(str(var), '[-200, -199, -198, -197, ..., 200]') + + # Far too long. + var.config = variables.LinSpaceConfig(0.0, 100000.0, 100001) + eq_(str(var), '[0, 1, 2, 3, ...]') + + # Smooth from constant. + var.smooth_from = True + eq_(str(var), '(0, 1, 2, 3, ...]') + + # And to. + var.smooth_to = True + eq_(str(var), '(0, 1, 2, 3, ...)') + + def testUnits(self): + """ + Ensure that values are wrapped with units. + """ + + var = variables.OutputVariable(name='Name', order=1) + var.type = 'quantity' + var.units = 'g.m.s-1' + var.config = variables.LinSpaceConfig(0.0, -5.0, 3) + + eq_(list(var), [Quantity(x, 'g.m.s-1') for x in [0, -2.5, -5]]) + eq_(str(var), '[0, -2.5, -5] g.m.s-1') + + # Bad combination. + var.units = None + assert_raises(ValueError, list, var) + + +class LinSpaceConfigTest(TestCase): + def testIterator(self): + """ + Create an iterator from a linear space variable. + """ + + var = variables.OutputVariable(config=variables.LinSpaceConfig(-1.0, -3.0, 5), + name='Name', order=1, enabled=True, const=10.0) + + # Non-const. + eq_(len(var), 5) + + it1 = iter(var) + eq_(list(it1), [-1.0, -1.5, -2.0, -2.5, -3.0]) + + # Const. + var.use_const = True + + eq_(len(var), 1) + + it2 = iter(var) + eq_(list(it2), [10.0]) + + +class ArbitraryConfigTest(TestCase): + def testIterator(self): + """ + Create an iterator from an arbitrary variable. + """ + + values = [8, -5, 6.6, 3, 0, 0] + + var = variables.OutputVariable(config=variables.ArbitraryConfig(values), + name='Name', order=1, enabled=True) + + eq_(len(var), len(values)) + + it = iter(var) + eq_(list(it), values) + + +if __name__ == '__main__': + main() diff --git a/spacq/iteration/variables.py b/spacq/iteration/variables.py new file mode 100755 index 0000000..9a2ad3c --- /dev/null +++ b/spacq/iteration/variables.py @@ -0,0 +1,327 @@ +from itertools import groupby, islice +import numpy +import operator + +from spacq.interface.units import Quantity + + +def sort_output_variables(variables): + """ + Sort and group the variables based on their order. + + The returned values are: + variables sorted and grouped by their order + number of items in the Cartesian product of the orders + """ + + # Ignore disabled variables entirely! + variables = [var for var in variables if var.enabled] + + if not variables: + return [], 0 + + const_vars = tuple([var for var in variables if var.use_const]) + + order_attr = operator.attrgetter('order') + ordered = sorted((var for var in variables if not var.use_const), key=order_attr, reverse=True) + grouped = [tuple(vars) for order, vars in groupby(ordered, order_attr)] + + num_items = 1 + for group in grouped: + num_in_group = None + + for var in group: + num_in_var = len(var) + + if num_in_group is None or num_in_var < num_in_group: + num_in_group = num_in_var + + num_items *= num_in_group + + if const_vars: + grouped.insert(0, const_vars) + + return grouped, num_items + +def sort_condition_variables(variables): + """ + Sort and group condition variables based on their order. + This function is similar to sort_output_variables. + + The returned value is: + variables sorted and grouped by their order + + """ + + # Ignore disabled variables entirely! + variables = [var for var in variables if var.enabled] + + if not variables: + return [] + + order_attr = operator.attrgetter('order') + ordered = sorted(variables, key=order_attr, reverse=True) + grouped = [tuple(vars) for order, vars in groupby(ordered, order_attr)] + + return grouped + +class Variable(object): + """ + An abstract superclass for all variables. + """ + + def __init__(self, name, enabled=False): + self.name = name + self.enabled = enabled + + +class ConditionVariable(Variable): + """ + A condition variable. Used to define conditions to make loops in the sweep controller indefinite. + """ + + def __init__(self, order, resource_names=None, conditions=[], wait = '100 ms', *args, **kwargs): + Variable.__init__(self, *args,**kwargs) + + self.order = order + self.conditions = conditions + self._wait = Quantity(wait) + self.resource_names = resource_names + + def evaluate_conditions(self, condition_resources=None): + """ + Checks the conditions where condition_resources contains the resources required to evaluate the + conditions. + """ + # We take OR of all the conditions. + boolean = False + for condition in self.conditions: + boolean = boolean or condition.evaluate(condition_resources) + + if not self.conditions: + boolean = True + + return boolean + + @property + def wait(self): + return str(self._wait) + + @wait.setter + def wait(self, value): + wait = Quantity(value) + wait.assert_dimensions('s') + + self._wait = wait + + def __str__(self): + return '['+', '.join(map(str,self.conditions))+']' + +class Condition(object): + """ + A class used to represent a condition. + """ + + allowed_types = set(['string', 'float', 'integer', 'quantity', 'resource name','resource']) + + def __init__(self, type1, type2, arg1, op_symbol, arg2): + for type in [type1, type2]: + if type not in self.allowed_types: + raise ValueError('Condition cannot be of type {0}.'.format(type)) + + self.type1 = type1 + self.type2 = type2 + self.arg1 = arg1 + self.arg2 = arg2 + self.op_symbol = op_symbol + + def evaluate(self, resources=None): + """ + Evaluate a condition. 'resources' comes as a list of 2-tuples (name, resource obj). + """ + op = {'>':operator.gt, '==':operator.eq, '!=':operator.ne, '<':operator.lt} + + arg1_to_evaluate = self.arg1 + arg2_to_evaluate = self.arg2 + + # We retrieve the values from the resources if the arguments are resource names + if resources: + for name, resource in resources: + # Note that the ordering of the 'and' statements here makes use of python's shortcircuiting. + # Upon reversing boolean arguments, there could be a case that throws an exception if a string + # is being compared against a quantity. This is because of the overloading of __eq__ for Quantity. + if self.type1 == 'resource name' and name == self.arg1: + arg1_to_evaluate = resource.value + if self.type2 == 'resource name' and name == self.arg2: + arg2_to_evaluate = resource.value + + if self.type1 == 'resource': + arg1_to_evaluate = self.arg1.value + if self.type2 == 'resource': + arg2_to_evaluate = self.arg2.value + + boolean = op[self.op_symbol](arg1_to_evaluate, arg2_to_evaluate) + + return boolean + + + + def __str__(self): + return '{0} {1} {2}'.format(self.arg1,self.op_symbol,self.arg2) + + + + +class InputVariable(Variable): + """ + An input (measurement) variable. + """ + def __init__(self, resource_name='', *args, **kwargs): + Variable.__init__(self, *args, **kwargs) + + self.resource_name = resource_name + + +class OutputVariable(Variable): + """ + An abstract superclass for output variables. + """ + + # Maximum number of initial values to display in string form. + display_values = 4 + + # Maximum number of values to search through for the end. + search_values = 1000 + + def __init__(self, order, config=None, wait='100 ms', const=0.0, use_const=False, resource_name='', *args, **kwargs): + Variable.__init__(self, *args, **kwargs) + + self.resource_name = resource_name + + self.order = order + + if config is not None: + self.config = config + else: + self.config = LinSpaceConfig(0.0, 0.0, 1) + + # Iteration parameters. + self._wait = Quantity(wait) + self.const = const + self.use_const = use_const + + # Smooth set. + self.smooth_steps = 10 + self.smooth_from = False + self.smooth_to = False + self.smooth_transition = False + + self.type = 'float' + self.units = None + + @property + def wait(self): + return str(self._wait) + + @wait.setter + def wait(self, value): + wait = Quantity(value) + wait.assert_dimensions('s') + + self._wait = wait + + + def with_type(self, value): + """ + Set to the correct type, and wrap with the correct units. + """ + + if self.type == 'integer': + return int(value) + elif self.type == 'float': + return value + elif self.type == 'quantity' and self.units is not None: + return Quantity(value, self.units) + else: + raise ValueError('Invalid variable setup; type: {0}, units: {1}'.format(self.type, self.units)) + + @property + def raw_iter(self): + if self.use_const: + return [self.const] + else: + return iter(self.config) + + def __iter__(self): + return (self.with_type(x) for x in self.raw_iter) + + def __len__(self): + if self.use_const: + return 1 + else: + return len(self.config) + + def __str__(self): + found_values = islice(self.raw_iter, 0, self.search_values + 1) + + if self.type == 'integer': + found_values = [int(x) for x in found_values] + else: + found_values = list(found_values) + + shown_values = ', '.join('{0:g}'.format(x) for x in found_values[:self.display_values]) + + if len(found_values) > self.display_values: + if len(found_values) > self.display_values + 1: + shown_values += ', ...' + + if len(found_values) <= self.search_values: + shown_values += ', {0:g}'.format(found_values[-1]) + + smooth_from = '(' if not self.use_const and self.smooth_from else '[' + smooth_to = ')' if not self.use_const and self.smooth_to else ']' + units = ' {0}'.format(self.units) if self.units is not None else '' + return '{0}{1}{2}{3}'.format(smooth_from, shown_values, smooth_to, units) + + +class LinSpaceConfig(object): + """ + Linear space variable configuration. + """ + + def __init__(self, initial=0.0, final=0.0, steps=1): + self.initial = initial + self.final = final + self.steps = steps + + @property + def steps(self): + return self._steps + + @steps.setter + def steps(self, value): + if value <= 0: + raise ValueError('Number of steps must be positive, not "{0}".'.format(value)) + + self._steps = value + + def __iter__(self): + return iter(numpy.linspace(self.initial, self.final, self.steps)) + + def __len__(self): + return self.steps + + +class ArbitraryConfig(object): + """ + Variable configuration for arbitrary values. + """ + + def __init__(self, values): + self.values = values + + def __iter__(self): + return iter(self.values) + + def __len__(self): + return len(self.values) diff --git a/test-config.py b/test-config.py new file mode 100755 index 0000000..219ff56 --- /dev/null +++ b/test-config.py @@ -0,0 +1,34 @@ +global config + +# Addresses of devices with which to test. +config['devices'] = {} +config['devices']['AWG5014B'] = { + 'address': {'ip_address': '192.0.2.123'}, + 'manufacturer': 'Tektronix', + 'model': 'AWG5014B', +} +config['devices']['DM34410A'] = { + 'address': {'gpib_board': 0, 'gpib_pad': 1}, + 'manufacturer': 'Agilent', + 'model': '34410A', +} +config['devices']['VoltageSource'] = { + 'address': {'usb_resource': 'USB0::0x3923::0x7166::01234567::RAW'}, #This is the wrong address + 'manufacturer': 'IQC', + 'model': 'Voltage source', +} +config['devices']['ch6VoltageSource'] = { + 'address': {'usb_resource': 'USB0::0x3923::0x7166::01300DB9::RAW'}, + 'manufacturer': 'IQC', + 'model': 'ch6 Voltage source', +} +config['devices']['TC335'] = { + 'address': {'gpib_board': 0, 'gpib_pad': 1}, #This address is wrong. + 'manufacturer': 'Lakeshore', + 'model': '335 Temperature Controller', +} +config['devices']['Model4G'] = { + 'address': {'gpib_board': 0, 'gpib_pad': 23}, + 'manufacturer': 'Cryomagnetics', + 'model': 'Model 4G', +}

    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 diff --git a/docs/user/gui/config/measurement_config_scalar.xcf b/docs/user/gui/config/measurement_config_scalar.xcf deleted file mode 100644 index 763242b2d2eea1e308c54e6b9de8bd05c01323e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149586 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 diff --git a/docs/user/gui/config/measurement_config_scalar_settings.png b/docs/user/gui/config/measurement_config_scalar_settings.png deleted file mode 100644 index e0eee248444410dc6bf19c2da959fdf71d9e293e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22210 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%0A>LSt&GXgxtiqc*wivY+`1y2o7JumKR8~}= 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 diff --git a/docs/user/gui/config/pulse_program_config.rst b/docs/user/gui/config/pulse_program_config.rst deleted file mode 100644 index 2dcaaac..0000000 --- a/docs/user/gui/config/pulse_program_config.rst +++ /dev/null @@ -1,67 +0,0 @@ -.. _pulse_program_configuration: - -########################### -Pulse program configuration -########################### - -The pulse program configuration panel is used to set up the :ref:`pulse program ` for an application. - -If a pulse program is loaded (via ``File -> Open...`` or Ctrl+O), the sweep performed by the :ref:`data_capture_dialog` will include a stage involving the pulse program. If it is later closed (via ``File -> Close`` or Ctrl+W) or if one was not opened, the corresponding sweep stage is skipped. - -.. note:: - To run a pulse program, it is necessary to have configured both an AWG (to emit the waveforms) and an oscilloscope (to take measurements). - -Resources -********* - -Most parameterizable values of a pulse program may be given :ref:`resource labels `. These resource labels behave the same way as device resources, and so may be iterated over by a variable during a sweep. - -Setup -***** - -.. tip:: - Each setup tab is present only if the pulse program requires it. For example, the Acquisition tab appears only if the command ``acquire`` is in the program. - -Acquisition -=========== - -Configuration for the acquisition trigger. - -.. figure:: pulse_program_config_acquisition.* - :alt: Pulse program configuration: acquisition. - -* marker_num: The number of the marker of the AWG channel output used for the trigger. -* output: The waveform output name. -* Times to average: If this item is set to a value greater than 1, the oscilloscope is placed into "average" mode for the given number of waveforms, and the AWG is triggered that many times. -* Post-trigger delay: How long to wait after triggering the AWG. This delay occurs between individual waveforms when averaging. - -Delays, Integers, Pulses -======================== - -Configuration for the pulse program variables. - -.. figure:: pulse_program_config_delays.* - :alt: Pulse program configuration: delays. - -Delays, integers, and pulses are all configured in approximately the same manner, and most of their aspects can be parameterized. - -All items (for delays and integers, the variables themselves; for pulses, the variable :ref:`attributes `) appear exactly as they are named in the pulse program. Each must have a default value, and may also have an optional resource label. - -The default value is used when displaying the output waveform in the Outputs tab, and when sending the waveform to the AWG given that either no resource label is provided or no value is written to the resource during the sweep. - -Outputs -======= - -Configuration for the waveform outputs. - -.. figure:: pulse_program_config_outputs.* - :alt: Pulse program configuration: outputs. - -* Outputs: All the output waveforms in the program are listed, and can be viewed by clicking the "View" button. If a channel number is specified, the waveform generated is output on that channel of the AWG. -* Sampling rate: The waveforms are generated with this sampling rate. The AWG automatically has its output frequency set to this value. -* Devices: The device label (as specified in the :ref:`device_config_list`) must be provided for each device. - -.. warning:: - Setting the sampling rate too low will produce waveforms which are not faithful to the pulse program, since there is not enough resolution to create the desired shapes. On the other hand, setting the sampling rate too high will produce waveforms that have very many points, causing severe slowdowns. - - A good rule of thumb is to keep the waveforms to within one million points. This provides a maximum total duration of 1 ms per waveform at a resolution of 1 GHz. diff --git a/docs/user/gui/config/pulse_program_config_acquisition.png b/docs/user/gui/config/pulse_program_config_acquisition.png deleted file mode 100644 index 3b048545f24cbc61439738c167137d13dd930c51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12959 zcmcJV1z1~Ax297lR-m}M7YR_b#X>3W?oyz*6)#>&a4V&_ySsaV;ts*BKyh~nHmCoc z`_H}e&-0AjXCNFBNODg0-fQjm`_>9oR+PegLHYs&0%6KXi>rb_2-?8?PE-Won0lte z4E%cLtSTi2Djy}^1s))q$VrKVp8kGkwiUzze?j{st>X*=5iG)1hwf9A2fN>YSCytEbxpMyZj#Cv;Xe=wyS>RsLKjXz3Hx!@fj1Q}(Nn=1%A-|FgXd{p}4c3DnE9xXNkh^I;P=D?%< zy-iv+mj~W4>gATen3JJli+5FKW~PdIV!jpF4xAN9K|>Gb&8t#x39T)3tdme#9E*r> zl!xXMqoNttP%fDgBm2C=nQ0o?Mh?p04O9Dt(fwyjNGT-RLZ^3msO8^Z7g%zb!S7If zatntekiWQOyn>|&s8Yd!5rl%>=($pRm?e(YaX~EX|b}Cw0 z?w_3CcNHBAuJ~)pAcfc1xLwt3;qAXCawjeW>Z24LRVMtb&L0wVVT}vfWjS<&M8Dts zeOmE2)L8qgsC0-d9R{?bz30AxZ`H3zBC-U0BZi^iDlXjyH)Wq3`8gLIU{vBT$mmh zs0tzxaNTRQp59C5xO?VbIYE=FqWNzFjD#X?*-Y1!yd+ZPbxq07-_YORPr{wAhJT4> z^D}b$hx+d_jHj8aw4s^1kWV)!72ZA)tKR3lz2w9=eDTX@nY)pw%w1fiXMWL79^SWS zn`we>e>OJ_+MG0E*yE(P$5I?jO-u6f4i65_Y%VI;x(Qp^hy`2{)6?4n0!(2OujuGD z&bOaFkjxAW-0sBWgqdMtU~m|={^GLz5dBl5MAJFc>-xyq&20h(v!3YsxHE37pH16c z7?V?O)FI*n$H0s<&(AlHiwCo?k@CCghZ8a)_;-GJhL|Db+m#nC-i}VhM$Thd85&xo zZ0Y>xBxWypz<^gb?X_udeKjs{TbLKDKc-H8<9Ifb!f$g%(c;@jZrU61@OW1=C`Zis z>13vCk#yn}i?A@=$8~ysek?(+zAyH|kJm#lgN#>n4y5?by9xcaXV%=@#90O)Y z^ysIh~6EDpmzYWUOO%#dhEmK1GW`Q?ydTT-&@}jBTYR$N z4nw8+@$qpQta^H6Mn)535HQvG$ZR+vcyw&cLPwJ^w(MqiyZ%sM?(Lh)!`Y{;5j_Vi z)R$Cmapy`J(gI>rTDC7pxaDMJwd0qYY}^ydpM?qGhW;Aq-iJAK)Uc5eJlGVl8`vF? z>n{s4Lgnv&p5ooQ6Hin;{hYR$@fEahO=+=cd6^ws;A^VGXFKTLV9&s&(>yF|xxclR zmkYk-Fp8ppbaoE(%vPK`bUdx}PCpDzm&l%7??XuQ`*{Ep<-*}S?^~m^qhbH&LZPCX~OVLduV$3=S@p(lOx^Lq50K5{llDqG1% zabhB(jI^}n0(TBEZ6zfv;JF)p(U_D%Z8Q=mDYJZ(>~b@BZd4GB{SmQp`q9mGh16& zbzaZvz`)^R9Rsw^)~rZdTuQ1yRA1T5Yt`rOR|-n_M2@8WO7-R5w2i;NFd@6HH`D1+ z(B(b`CZvT|}w zITo(B!0QiJY8<8d_B zPfcZERo<-spq;-}hh&*dCzkCkPE10=Oc)N~=9-zAn4XzXP*7OLYtq6IU^VjZ~Q)>Szjff_n_ ze3U!c;3fR`tF2EdJ)~#7?e2=hsJT%gWrmRv+K_7U0CgJnA+MN ztR0Ckuw4w#*RT!kS=zYoF5OzHUz9i;sZ0nXdY#?gC){$N(X_YIzJLF|M6(R};`rLw zG%9SG5&Dp=bUX%e=&rUsHWa?~eq3(3W{Fca&^tRj8%={eZ7j{#^YMvgU6v+sf8-)D z4n3>C#yD8pI2@@iZEl7$S*e(B(TRm9zbgGR;{M;lk~Kg8Xdb=JLGyWgeapK9HNbM$jE3E zrW_rs+S}Wq-++B%SSp&F;(#^oX-4`2Qz%(FZ*tEw%$Ov|n8oaajwXA;Q`&dh*n#HD z&l?PsA}cGZML&#nf?}NI818Sk2jfa&zsC*@4x-ZR?d|EqiF`bjQeJa=?_u@y^bnb_ zA1$1QtFzQqa=N=a?@!jHNW-eiqLr@gpOKQ1PEVHwO`Ge?zgr4;C11(QDsEM`IiD&ux@Kql0Xf_hW2m^WFoN`Lwi*+J!BzP{ zQA0Dc+3wU3SlHG6l?!S)1&V1V#O5cN{@x-_uE_y8Io<6KcO5CXd`^EtWMY}hblMgM z2AIKM1=<*w!@boO_lnBO%8?YlNuGyp*vTwmNln#P!;1yBPVM-- zS1cxXGmSSlHnaA&%k_3cGj_pPM-D5kw_X|ECF*v2BV;O#;oyP&C#d>tJQNBU(2M?N zj#g572tE1yl>YOc9?ZqfO$W_g>EOWE)sk0g%h=mp>jX=vsP}lbue@;JQNzi`j}4>N zjqQP~&Fuc!=C@J}c@aIEErIUp>bj}$@AL2uwa5dAhd)IsVc23-w$o2n;zyXMsLCoT zWr=ew?iv04ZESL2qk8Z0%+3ua=$p4K^2y}7nwq24u%Jt_;SsR+gTVDMl6 zC7ZDhi??-SP;*e-c)h`0RGNUhrk2+BBwCiXSQXoJYtz)W32*6Ql&hLnLahS#rEUMD4fo?RF$-U|0CF3ZU)v!biDpB_{-FgUgG ztaB~YURWeht_H6Z)Pocp#0&4QLc4=swhO!WURZJGE73tW&oz8g1-JW}oLPK(rY9z% zmTL4~Q*b%bCX^Qxc>U=&mVp0!_Rw^((dWA*!V&s+AZAQSr@ zosncKTkQ_}Zn$e4VaG||ZJ5H%eudX`omQZ4pX;tfpacmC)<-7~P0g92RbPn587y_a zrnb%%m8P>ZfQVgJOszIN{5<^r9uNp8$CEuPD@W6fp$xsfy~V}FuZcL2Tn^zo6A(f7 zBSr%Dfa5!fjLb(wk|3Qb@3#pzvqm!f4$FIUSpoU+Thk@8AGwl3i>zFyOLmv>2sq|7 z$klXAY;9fcM$@*(oHaCV1q1}9>++nLIs(xXhEwHj{zMexs5*q`aRcW6l|VQuJ#9d}GkO&dH;jRA7L zxw$!=lF}3INpNg9J~^oiaC`M81Z;F#Q&lw)f@Ab>aW31l#|8#d92}n!19CZ7PnS)nwJLzl0RF_CBUrM`YCgh=`qY4jm5<7bHh8^E`80G07-m$KA=iyrFos=g6$_ z95waTCOY+u;l~SAIhysOHq+DZssuG=lQitZ*R$7?X9+XWvS;jos}30d$sR-pho6^Gb-zaJuf?ragw)+REn?KDMTK(ajfX0yOgu` zEq375nZK`Bm52xkZtvV5$6vrC>W-K1N-7KcS4R2x`5ktvHw_KbgMyIj>i3%Gv0cx* ziF*w}n0LR&a!@6v|2*J5`CMKG!GEUv07|2jWD1`M)A#3>>X&;ux|;Q$2PXTXGYU&e zZf|ae6DbY7_RCTTHJU?eRT_{$BDC0((4I>Zed^qOi$9*5suAmr_njyg0>6*8u54fv z%{mSgwU*bT6s|S2zutY3#72c2+QgB1Q2z?|n4qmi$_z#7nr%=LZF`Wsk$GAF)Ku1) z4F)Moq4;5-5H$9Ad>Hm8%mRZHSh2Iw9})Z50AB(L1iZ=gYxHT!@NvQ~`9!EgLqp>@ zsU5M8Vs#VQ`AhMuisapnpk##P{5n?`TiZhn42T*H)Y57nKMZ}GqgygUk_iOjK{CtB z!=lQ4a=kv+x#}1J^??QgZ5<|a9 zZ`VSxws*K^`CEGGjT$CMS&uk~xu0du)21cn=Uh*Jl9|r)F1QwZj5y+x@UbBCZ6 z5U9`&8I189`RhnRy)9XFU2AEC+%N20kt&SXD_R2sDGGyUAZ7kq`P#{|NtSf~_td2p zs!jM7;?F>P7}0$tF7J?Ec7j0TxzqO47&|jQcUOlyX25_}eyWo1{_e@nYcI7l^{c=P z$t;UstWM7|M+pQX-3(D?iJS34i;Sriw_Y<_Bijl$zyoDcA{dJ{9F=HQJi=2P!T$r z>pri|jHd2Xc279O-PP;lgoZ|(F&3=d*gr6z7Z_RuI3#m(jmbZhtI~(!IiAi+aKy;; z5@zgtA1;X#j@H&Z4(1#s_?*MzwJHqDJX%N*3PcpMWwPT}z*RN~AlO*Qr>XoA-*#Uj zQ})K@<|qn&UwA}Pjm60Q!^N?OhX<=KJanjgV*;>!8cC=cyspa^{Zk}v_M%f$={4mO~qn!W%~I6 z5dvThtAmBY0uV9^%A4V4e}F@6P_968(CF16mCUD(<575Wd`$KCf0h793)(nYVAe|#M`pFu4bU{IZLNxKO3TW?ghVW3 zMzL-{#7RP-`9=IpPa{8tE#w--ezY;FDd zCKQhecxv+J>!a^^a>pwz*Qb$Iv*m|?rW*wE+tASVNX9BLm4PNB`;hG5pxhW8^v}}R zfb8IRe7VXLz|zhX1u}Gt{X=t7jd`8@`RxrFo*1Ki;eaZzEPy~F6^1{t8@0=$>NI}n zOmt{Z61IpcNx92>7cez7g=zHk_2zZ^(~%%1rYnjRlTnAFudnakUgWXy%r(5C{W{~J%*404>F`tP`R%52s%1^_N7d`9#!2tn5$h%~_Is72d zp5(9M5F2AHIXT&1>t7Skw@=3HfP^qfdQ>AxB@FMI9w}4Ju8E!M34c9j3j&?2VnGQB zw}6?M69a*iOFmiGrk{Fv2N{oJfk3YB*%Qt^yq|Xkea*JYYIjvp#A!wVW!mG5fbm~I z38bh|p_ejo6#}Ir&e}4(?vf$L&I|EzHpCyJfpZ{ zvt3>Zuc1b8^8`(#6eVdxC1XyJ0^!1=sx-c5On7%3=V`d2<a>oY7m1eBR40IU1&QNrOF8;1o*F^8)mxIm;|NE0bC5fJaymjC3E>z+RF>^ZcOun90nNp>L zO3URE8WOe}x9^YumxKxh!YTIs`?5ej^DV;eAivv#d2aUQ?(Xxss*Nz)EkbaGVVi*4 zK_if#o1FddiHR5H7I4H6nq7@5Kdk+H7oe}(;OOiQaQYuF@Jp2Tx1a2W>T>a9&h#*oTfj0~Injq>^G{Vzzi4uXG~b@$&K(j3ttg3(+%fAxNlbus3;} z=Sw{a6}#9wFJ2u=y`(z3xM=;f)>UcT&7ptOJvv&x70&M%(10n@a#H$6ZpqKb@9)qX zM5CUc8H!6taTwLZmY0`DNA0J2F=Yk@_)Sb#OBy-acQ$r+Wl(ovMSw-Vn%*z%@*04S zDAC0D_+Cy7qFz)QNl8h zZNWEF-Tmi(Ug1yjP*72gDVSSWK;YX{UV>g?A@T9?+H6~s&d$zqfMGW#l&r;Eyxi&` zcX~ThrniIzvsb^f-f#Zn z>wIfWO9*!u2ICQVGEyH za5VOJX9yE3vz#j1y9R(>WU1+)rG{-l{&N2L^PRzc3&8jX%VYxvT<{0?RV&Lv!8Zc8 zaM!c*b2Tl$%Wpg1LTPq)ciGDpKYQGLV#a$fHk$f&f9nVb2dBzskq_8*u64;-WTItO z7fknKzR^Ly^>ffQT+P96#`<;$L0^?Ojlo!jhEeJ`XjbK!fjk^ zFbTqX5V25DFfk|jC#4*K%Zm)rz?tnTkB|Qm(u4GRdu(lh>o30003atCd&Rse;<$Bj zyrT>P{S3igLCom0AVz)wCWrrz)7y+bDQeqSuaXJ(^oTa`vsnM;y0s~>EKzO8=U+v& zGt_U3yB9Fp@Z6cU-$m)OO9ca`+tcQ}es`2NMbuaSRNvaF9}~q7n`LTeSsj|R3Dcb` zFKL;KwvgUby+8xvn!?G$+s3Daxm`_vL|-l}{MK6p!T2HPRhV&NJ)sC~mh|aQ3INy` zFuJAbzkgp}4+{nIU$oZ^IUFET!w>iXi=sk-ZRo#ph$J!WR$tZDq>Yiy#f>)}Cjo)# z!m_PIGAi=(gGA@P{P+>y3kij!dS4hnzHiN<1Z7_RZ*epv0fq>IQ3e^msD4quvS3fo z>Gs;mT!+xV@-a@1J|@&_S+Fr>J|zp-J*-pSp(FbHe>l$rF9z9yIaoJvUq3HWV(ZRr zlh&G5k#9=VGfwIx4%&^DVn-yU^4S{mqF$+EX5oU5_;@Zm#06iva}D>q#(auMg6=hRB) zoz70A@*&{@!S+`EcDli5K5o9)%3ryW11G}WRs<2d&p;xs)DfXgKYg@o%oQYi19)vT zon!gL*0wYJR$v0sEOCG@+^lC13hNr`$`2perBc#LX7FWs%zNT^xlI1`iD+1B!-C1e z^co3t^I0Q;Nk28M$qy(Lk8F()!~z~Cfl?&0%YM7(FAhJPZ!Pd^(%2v2d96{#UFP>j&yc3-!btowOhjF5}-`0Go<_y4V5R z!-FVE3Faa_0NDRCd24ae&_f_kB{*~n@*_)2%h6PjiNQhmdQZ5ltS{3)VR|*))oO&n z8SC6|$WsTdo!p$jypR$-@bhd_z*NScm_V9Zzn99pQd?-}xO@ z{v00iI;=zkoN=wDhDwu_W$fs9ih^|j@jJ4$vanf7C$?%bKn5QB(HBVBEI*Hd$K^`ESWqx zF3$DnV7@ITW|58-|Cy@H02SM%cX3tDX*o@cX01+D+x69j-0qwYprGDjBO@aNwLtk- z_>U(K?j$56Ig(NMq2gr*`jXL0Dd7AXrW@{jp35^Euvg~}kftNH2sZ2~Px3~iqua2whMm5sq;bN7kl$dml`A9MA zTYiq?hE-p^D(|G6oPf5rhwnY%d8$R>10g)TypVjZyu7^kK`$vF_De?wEti*<B0kTJJIPSOqzY&F!?N7Dq2G_dm|XeUzN)^Hd^c)^<@h~THPlojLWJpZGBZD{|HAec&B%V$6a5tm+%uxzBPJlA0B*#@h55zBt@d&1 zTGiu{{nZD+>^BYM=KcZdMAt00ZHY}un-*KFjOX0vbrH4yydD=7)gA)y0CNX_=J2mB%_ zJ>4+)l{lTqNZl4eEZh@qtSz*ZQ z(;M*&5k-a`*T`tK&5MpXtQg;Aw#j0$9Pw zDCVqLgJiUX)#EUEI+iim?ey6CQ7 z*FoX<|B;JO0Au4{xX1@e#Xv*zj*U>TrBP_iSHuo}0eAv4LN5SNdjCcAMn!gLFh59X zXn0s%OY8dl97ql%aG+nyzY@)AwcJ4e0P_M7B)Sn8tTDqa5(Fcr_p_IVRO5)C#VK0{vKsnR+Y6O~Wm6Lx{*Gmte_MxR5T#oM1%eoTUp zrqp-Tw%ED2GOCJ`^@}*=E9FthQEvqQ%D(^Xzv-NQr2`B&_5VKx9B-p2F|6VVj!Cvx zg-}*n3N$gzeX~IYJ}3cdu-|Rm&!5U4K3INS>H=T_NUnaOp+E*@3B?=S#uXEAKl=0G zc>H67)Ls)zxci#Tq0QTO=yu&=UDmn6p{;$;YuaS7$1CV?!0(4qw+^7%De1a?{`?8_ z^gK8|dW+7M)8H`~w)nP<76T3r8W0&`%`Ut8i#Ar3xeTi49O$c@s|^+mlrpU+=YdDg zEzxgzgH|It9AF|_mp+`b=FdPs?~ugo?Ci+M$e5V=ZqK)ql9JMdo#U}pRaLp(^1yU0 zP0j6J+ukIMr3-IxQ3<*9cTeqe)0x_2jTBDgpwXE2MTJL3rU+h3`TJ)@mKo%At)~AK z0)Pt`j6t$6IOsW&T=!=LFZ%ZO)+mzL%EWqW>&W;12CYYCXm8T&UomRX!wXCh+Ow{b zBgHDKmaCN`)Ua6=c~x4P6fIOno#js`fr;XsTb`%$Ne7AA~>?Gw?C^C557r z()DS2T*5UiivWBlrhCcQ2aw&P>}3wWdiQqsRPi^Dk1HxF9BgfEO-;{@c~SQk8-OJN z{boLrtRX)(GIV#?usWI{xWBU_imFrn*-!z@6)h78HDoBSy%f3u_905|mRT=CQ=S?u5Zy04H^AZ4G^EY<6mDe49IAw^z@p`jl_jYa zsW#c~3ItH{kHG>-Eb=!e0JA%y$1<<_@EhOX7`Ax=JnFcb#NIJvm0Ir)`jWUA`w zF0ZbxF3u*E@>{iCB^g?jsCT@8)Pv^?fty}d|R z2Uh0xG@P6mFDcLNJzge=eC+>LxMNwQo}7{42ejvP2B7%B6{LK|OB;w^V2%h6&)$}Y zzldyhzKO~e|Jv~a@cihyj+R@-QePWLNc0R3%L6?!I;eTOJB2w~<@%waO^r^|#Ti=V z5ltt-Ko4b)(Bq>)xna|=kNteL*)*^6n$P!mP7FCzj~};{04yBW5hmw*v}!pshppQ? zXc;b)sOVble#?5_7fsgSHEZwV(`GYUj!AyQ^4s+Y7CN?_A?(G+D!90`wA9_bL8n)c zRq*b7C+xVI!_)BNmU+H+{26FK*djW+sN%}!@&w);F+`FuYtUYMHJPYp3gekRX?O-w za1k%e$=Ue*`-7=zPRLiWtZ(_dH#eW{xq$GQfIQ7D&O6hH)8mdLao7cnhdH@oiq$?EFbTehjxMH#WNn3hEIM#5r$|c4*xJ^sXJR zASbuM$jwa^7Z*2OS$h!?@%8K1P`vlg5q!e3zkh#hdl}fbfrLfr4Rpx`pkQI5y?nem z{H3-@@|IM%_4GsoW0Yg0DZ>=rRCJ~jS7=j|K+x`4Z|G=WReLdwREw`rXT4fHRLj2l z_*iz6L=3@05RH8ab-+Wt2!LOuk&1w6XlNK1%$9D@6r-Y6Rt$mOfR#o^HEU~YO-)U8 z^;&gCoB7GF*e2H2q*Q*~f`WpOj4*jP1H)%>x<&h2x6d)N(tM&^qh z6}9eerPMd47rRrxTHF)Z3_Pz_+T82QEyjK#p%Ve>A2;;lT36swNF`%l(!W^enyG>x zrB8OJ3LKW3f!5V8$qvz6jyjsEnueZ~H1ylPikhnNxi`~Ivd1bbXkK{V{p3a!#)SDU z6WjBE2D1|P(c;29_n}`oq18@FrTT8ZyB=Zrs5DA=87p(Xbo}6kS1mggcz&!@aYLN(?YZTO%nzVFC>h zoBx>tSqSpH?HO!lOVfGY)`t6O(dCS7jiqFK`<7W;oKNoqFk-fj@SMO$3R!{*kALa*E1}Rc|4Ud7rvg`irNGed(I+)MTX9P0(aJq28 zy7p90PaY5z=H|zz=Ajq=6CI99)XhBT5p5GZ@m)7e-cQCGf`jk5pqd_+!jc1}VFHSK z#1y1$lcst3S^1XLuXaD;(umhJS?}G#s8 zBly7DN+wiWAKUp+KfssDSWN7zHk+H9qeuFK_rk?41<@>s>%Q zv1tY($g-c*g_;e_ngT3RZf-STIL5{tdd|!Sr^sHqV<~BfJQWbIvMkpFpo0oM&hlw- zbFAXGPG9*X)GyuFjszNi_1gz}88NoOjY0VhsEJDbXCqImm537HOO5{vRHv!a`>s|5uze|@^_S3$-L zx;G(E+M`fDz)}S0@E`RAht>>~zfM>}f);r$fXoJ6uK~1>yvD_YxI5>ZIXOEyr>`n05D>L~Bu1tHF7?TS lN5N))2he>lFd%n+lb#ILSDtYn0V5J5BcUi>F81N;e*->Laxnk^ diff --git a/docs/user/gui/config/pulse_program_config_delays.png b/docs/user/gui/config/pulse_program_config_delays.png deleted file mode 100644 index 9a82f070c269f3d06add65b6df87191b16e73ae2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12278 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 diff --git a/docs/user/gui/config/pulse_program_config_outputs.png b/docs/user/gui/config/pulse_program_config_outputs.png deleted file mode 100644 index d9eeaa3eeb46e8b733cbd87cee5f20622e0704bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14006 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 Wu{Cqd2VEWzh>WCyM7h{U|Njkb%PnyL diff --git a/docs/user/gui/config/variable_config.rst b/docs/user/gui/config/variable_config.rst deleted file mode 100644 index 65a4cd0..0000000 --- a/docs/user/gui/config/variable_config.rst +++ /dev/null @@ -1,150 +0,0 @@ -.. _variable_config: - -###################### -Variable configuration -###################### - -Variable configuration panel -**************************** - -The variable configuration panel is used to set up :ref:`output variables ` and :ref:`condition variables ` for an application. - -.. _variable_config_figure: - -.. figure:: variable_config_list.* - :alt: Variable configuration. - - .. - - 1. The "enabled" checkbox. Variables which do not have this checked (such as **gate 3**) effectively do not exist; this is useful for temporarily disabling variables. - 2. A unique label to identify the variable. This name appears, for example, as a column heading when capturing data. - 3. The order number of variables is used to group them during the sweep. **gate 1** and **gate 4** have the same order, so would be stepped together in the inner loop; **magnetic field** has a higher order number, and so will be stepped alone in the outer loop. **gate 1 condition** would be checked at the end of the inner loop's stepping (ie, before the outer loop changes). - 4. The resource label for the resource to which to write the values. All the resources provided must be writable. If a resource is not provided (such as with **gate 4**), the variable is still stepped in the usual fashion, but its values are discarded. Notice that for conditions, "N/A" is written. The user is not able to write to this field. - 5. The values over which the output variable will be stepped. If there are too many values, some are omitted from the display. The symbols on either side of the values specify whether that side is set smoothly: "(" and ")" if smoothly (as for **gate 1**); "[" and "]" if not (as for the other variables). - - If there are any units associated with a variable, they are displayed after the values. - - If it is a condition variable, then the conditions will be displayed. - - 6. For each step of an output variable, after writing the value to the resource, there is a delay of at least the wait time. In each order, the delay for all output variables is the longest of the wait times in that order. The effective wait time for **gate 4** is 200 ms. - - For condition variables, this represents a delay that is held after a condition is checked and before measurements are taken again. The condition variable's overall boolean value could change within this timespan. - 7. The "const" checkbox. Variables which have this checked (such as **gate 2**) are considered *constant* and are subject to special consideration in some scenarios. - - Condition variables do not have this attribute. - 8. The const value of a variable is that which it is assumed to take on at rest. The value does nothing on its own, but is used in conjunction with other settings and actions. - - The units associated with a variable apply to the const value as well. - - Condition variables do not have this attribute. - 9. Clicking "Add Output" creates a blank output variable. Similarly, clicking "Add Condition" creates a blank condition variable. Clicking "Remove" permanently removes all selected variables. - 10. The variable settings can be saved to and loaded from the disk. All the configured variables (both enabled and not) are saved at the same time, and existing variables are overwritten by any loaded variables. - -To select a variable, click on its row. To select multiple variables, hold down the "ctrl" key while clicking. When several variables are selected, some actions (such as clicking "Remove" or pressing the space bar) act on all of them. - -.. tip:: - The user interface is organized so that **gate 2** and **gate 3** are still displayed alongside the other variables with order number 1. However, **gate 2** is set to const, and so will be in a separate virtual order, and **gate 3** is disabled, so will not participate at all. - -Variable sweep example -====================== - -The configuration shown :ref:`above ` would result in the following actions during a sweep: - -#. Resource **v1** is smoothly stepped from -2.5 V to -5 V. -#. The values are set (with each being written to the appropriate resource, if any): - - ============== ============== ======== ======= ======= - Constant order Order 2 Order 1 - -------------- -------------- -------- ------- ------- - gate 2 magnetic field gate 1 gate 3 gate 4 - ============== ============== ======== ======= ======= - 5.6 V 0.001 T -5.0 V --- -5.0 V - \ \ -4.375 V \ -3.75 V - \ \ -3.75 V \ -2.5 V - **...** **...** **...** **...** **...** - \ \ -0.625 V \ 3.75 V - \ \ 0.0 V \ 5.0 V - \ 0.002 T -5.0 V \ -5.0 V - \ \ -4.375 V \ -3.75 V - \ \ -3.75 V \ -2.5 V - **...** **...** **...** **...** **...** - \ \ -0.625 V \ 3.75 V - \ \ 0.0 V \ 5.0 V - \ 0.005 T -5.0 V \ -5.0 V - \ \ -4.375 V \ -3.75 V - \ \ -3.75 V \ -2.5 V - \ \ -3.125 V \ -1.25 V - \ \ -2.5 V \ 0.0 V - \ \ -1.875 V \ 1.25 V - \ \ -1.25 V \ 2.5 V - \ \ -0.625 V \ 3.75 V - \ \ 0.0 V \ 5.0 V - ============== ============== ======== ======= ======= - - Between steps of variables in order 2, if **gate 1** set to smoothly transition, resource **v1** is smoothly stepped from 0.0 V to -5.0 V. - Also, before each step in order 2 occurs, **gate 1 condition** is evaluated for its current boolean value. Everytime, it should evaluate to true since **gate 1** always has a value of 0.0 V at the end of order 1. - -#. Resource **v1** is smoothly stepped from 0 V to -2.5 V. - -Output variable editor dialog -***************************** - -The variable editor dialog is used to configure the values over which a variable is stepped. It is opened by double-clicking in the "Values" column of the variable. - -.. figure:: variable_config_editor.* - :alt: Variable editor. - - .. - - 1. The value configuration is performed by using one of the available configuration panels. - 2. :ref:`Smooth setting ` configuration. - 3. :ref:`Type and units ` configuration. - -Configuration panels -==================== - -Linear ------- - -A linear space is described between the initial and final bounds (inclusive), consisting of the specified number of values. For example, if initial, final, and steps are were to 1, 5, and 9, respectively, the resulting values would be: 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5. - -Arbitrary ---------- - -Values are provided directly as a sequence of comma-separated numbers (with ignored whitespace). For example, the input "1, 32 , -5,6.543,0,0 , 1" would result in the values: 1, 32, -5, 6.543, 0, 0, 1. - -From File ---------- - -Values are provided from a column of comma-separated values (CSV) file. Clicking “Load CSV” opens dialog to select a CSV file from the directory. Heading names of columns of the CSV table are displayed which can be then be selected and preview first three values. Values from selected column of the CSV are auto-filled as an arbitrary input. - -.. _variable_config_condition_variable_editor_dialog: - -Condition variable editor dialog -******************************** - -The condition editor dialog is used to setup the conditions housed within a condition variable. It is opened by double-clicking the "Values" column of the condition variable. - - -.. figure:: variable_config_condition_variable_editor.* - :alt: Condition variable editor. - - .. - -1. Conditions are listed here. Double-clicking on a condition will open the :ref:`condition editor `. -2. Conditions can be added or removed. - - -.. _variable_config_condition_variable_editor_dialog_condition_editor: - -Condition editor -================ - -The condition editor is accessed by double-clicking a condition in the :ref:`condition variable editor `. - -.. figure:: variable_config_condition_editor.* - :alt: Condition editor. - - .. - -The operator, the arguments and their types are what define a condition as has been described :ref:`here `. diff --git a/docs/user/gui/config/variable_config_condition_editor.png b/docs/user/gui/config/variable_config_condition_editor.png deleted file mode 100644 index e3475095be7f5b3cb6b705f61f4e1f97fd53914d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15794 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 TQo;s`4n#^!UbIM9-|znb%KtRO diff --git a/docs/user/gui/config/variable_config_condition_editor.xcf b/docs/user/gui/config/variable_config_condition_editor.xcf deleted file mode 100644 index 36914bcd216aae3d6d6a0e37f71a3a01171f846d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68934 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 diff --git a/docs/user/gui/config/variable_config_editor.png b/docs/user/gui/config/variable_config_editor.png deleted file mode 100644 index caa49e57636cc0809ec1de8e4da61cf5ff8ca604..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20814 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 P91sx}Bx_=wi979Io!?<= diff --git a/docs/user/gui/config/variable_config_editor.xcf b/docs/user/gui/config/variable_config_editor.xcf deleted file mode 100644 index 83eb29b98b6c3fd77561abe3f6bf636125a028f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53627 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 NF^gpe!Z8&8{{V3CP=Npd diff --git a/docs/user/gui/config/variable_config_list.png b/docs/user/gui/config/variable_config_list.png deleted file mode 100644 index 1f5ac5dc6bbaa7926ed3c9d6920e7145038c962b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38587 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 N002ovPDHLkV1ip@oDKj0 diff --git a/docs/user/gui/config/variable_config_list.xcf b/docs/user/gui/config/variable_config_list.xcf deleted file mode 100644 index ce1932da4c04a5f1251a85f9b728789d231fe6d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 103445 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 diff --git a/docs/user/gui/display/index.rst b/docs/user/gui/display/index.rst deleted file mode 100644 index 91bd08d..0000000 --- a/docs/user/gui/display/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -####### -Display -####### - -.. toctree:: - :maxdepth: 2 - - plots/index - table diff --git a/docs/user/gui/display/plots/colormapped.rst b/docs/user/gui/display/plots/colormapped.rst deleted file mode 100644 index 2424aea..0000000 --- a/docs/user/gui/display/plots/colormapped.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. _colormapped_plot: - -################ -Colormapped plot -################ - -The colormapped plot display allows for the display of one dimension in the color space versus two other dimensions in the plane. - -.. figure:: colormapped_normal.* - :alt: Colormapped plot. - -Pan & zoom -********** - -In addition to zooming within the plane, the colormapped plot allows for zooming in the color space. Changing the values in the "Min" and "Max" fields, followed by pressing the enter key (once for each changed value) will change the bounds of the color space (and consequently of the legend). - -.. seealso:: :ref:`two_dimensional_plot_pan_zoom` - -In order to hilight a particular value region, one can also click the right mouse button on the legend and drag along the legend. This will select the region to be hilighted; everything outside this region will be dimmed: - -.. figure:: colormapped_hilighted.* - :alt: Highlighted colormapped plot. diff --git a/docs/user/gui/display/plots/colormapped_hilighted.png b/docs/user/gui/display/plots/colormapped_hilighted.png deleted file mode 100644 index 5fb38ae62c4abe937bb0afbc2755cad0096c0f58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13587 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 diff --git a/docs/user/gui/display/plots/colormapped_normal.png b/docs/user/gui/display/plots/colormapped_normal.png deleted file mode 100644 index ebff1903c6660d21c7b92ad6ba53d1608f31f2a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14075 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 IX`WUJy@-ts>P@pC;wu@y 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 ND%_JVle_=qe*wvcL;(N* diff --git a/docs/user/gui/display/plots/index.rst b/docs/user/gui/display/plots/index.rst deleted file mode 100644 index 7416c5b..0000000 --- a/docs/user/gui/display/plots/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -##### -Plots -##### - -Via other libraries, this package provides several ways of visualizing data though plots. - -.. toctree:: - :maxdepth: 2 - - colormapped - surface - two_dimensional diff --git a/docs/user/gui/display/plots/surface.rst b/docs/user/gui/display/plots/surface.rst deleted file mode 100644 index 3b48e62..0000000 --- a/docs/user/gui/display/plots/surface.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. _surface_plot: - -############ -Surface plot -############ - -The colormapped plot display allows for the display of one dimension in the vertical direction versus two other dimensions in the plane. - -.. figure:: surface_normal.* - :alt: Surface plot. - -Pan & zoom -********** - -Clicking anywhere on the plot with the left mouse button and dragging in any direction will cause the axes and the surface to rotate in the direction of the drag. - -Clicking anywhere on the plot with the right mouse button and dragging up or down will cause the surface to zoom in and out. - -.. _surface_plot_waveform: - -Waveform style -************** - -The surface plot supports an alternate style suited for the display of discrete waveforms rather than of a two-dimensional sweep. - -.. figure:: surface_waveform.* - :alt: Waveform-style surface plot. diff --git a/docs/user/gui/display/plots/surface_normal.png b/docs/user/gui/display/plots/surface_normal.png deleted file mode 100644 index 6e8cb63a44b00e74bb7c3bcb641335ae47092a2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56563 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%0D@{PxTrA6cfA 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 diff --git a/docs/user/gui/display/plots/surface_waveform.png b/docs/user/gui/display/plots/surface_waveform.png deleted file mode 100644 index 113e7c2c41197708ad2f2d693df259bfce57f15e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146758 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�jqx?TCJ6Hdp0^$;$6r>!p3w6MrJ+)A`CFf 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 diff --git a/docs/user/gui/display/plots/two_dimensional.rst b/docs/user/gui/display/plots/two_dimensional.rst deleted file mode 100644 index c99e5a9..0000000 --- a/docs/user/gui/display/plots/two_dimensional.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. _two_dimensional_plot: - -#################### -Two-dimensional plot -#################### - -The two-dimensional plot allows for the display of one dimension versus another dimension, both in a single plane. - -.. figure:: two_dimensional.* - :alt: Two-dimensional curve. - -.. _two_dimensional_plot_pan_zoom: - -Pan & zoom -********** - -Clicking anywhere on the plot with the left mouse button and dragging in any direction will cause the axes and the plot to pan in the direction of the drag. - -Scrolling the scroll wheel of the mouse will cause the plot to zoom in or out, depending on the direction of the scroll. To only zoom in a single direction at a time, one can hold the shift key while pressing the arrow keys; up/down control zoom in the vertical direction (in/out, respectively), while right/left control zoom in the horizontal direction (in/out, respectively). - -To get a more precise zoom, one can hold the control key while dragging with the left mouse button to draw an exact selection rectangle. Releasing the left mouse button will cause the selected area to take up the entire visible area. - -Pressing the escape key will reset the zoom (but not the pan) to the original settings. diff --git a/docs/user/gui/display/table.png b/docs/user/gui/display/table.png deleted file mode 100644 index f9ecc02ea41afc78fbf2368cebd75c66ec9fa8f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25327 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 diff --git a/docs/user/gui/display/table.rst b/docs/user/gui/display/table.rst deleted file mode 100644 index 301f4dd..0000000 --- a/docs/user/gui/display/table.rst +++ /dev/null @@ -1,146 +0,0 @@ -.. _tabular_display: - -############### -Tabular display -############### - -The tabular display panel is a read-only panel for arbitrary data. - -.. figure:: table.* - :alt: Populated tabular display. - -Column types -************ - -The panel is able to recognize certain data types: scalars, lists, and strings. Scalars are any real numbers, lists are number sequences with a particular syntax, and everything else is considered to be a string. - -Lists are of the form:: - - [(number, number), (number, number), ...] - -This is the format used by the :ref:`data_capture` panel for export of list data. - -.. _tabular_display_data_filters: - -Data filters -************ - -Data filters allow the user to narrow a large dataset into a smaller one, potentially making visualization simpler and all operations faster. - -.. tip:: - If there are already filters which have transformed an originally large dataset into a small one, adding a new filter is a very fast operation. However, removing or editing any filter will be as slow as applying the first filter, since the original dataset must be re-filtered in its entirety. - -Syntax -====== - -Filters are provided as Python-like expressions on a per-column basis. All occurrences of ``x`` are internally replaced with an identifier for the selected column, and the remainder of the expression is left intact. Thus, the user is free to use as much creativity as desired when constructing filters. - -Valid comparison operators include: ``<`` (less than), ``<=`` (less than or equal to), ``==`` (equal to), ``!=`` (not equal to), ``>=`` (greater than or equal to), ``>`` (greater than). Comparisons may be grouped with: ``and`` (both expressions must be true), ``or`` (at least one expression must be true); and negated with: ``not`` (expression must be false). - -.. tip:: - The Python equality comparison operator is two equal signs (``==``), not a single equal sign (``=``). Using the latter in place of the former may generate a generic "invalid syntax" error. - -Examples -======== - -All examples assume the following initial dataset: - -======== ========== ============ ============= -Time (s) field (mT) port out (V) port in (V) -======== ========== ============ ============= -0 1.0 -5.0 -0.0100000004 -0.251417 1.0 -4.0 -0.0100000008 -0.512650 1.0 -3.0 -0.0100000016 -0.766408 1.0 -2.0 -0.0100000032 -1.024776 1.0 -1.0 -0.0100000064 -1.300688 3.0 -5.0 -0.0100000128 -1.605982 3.0 -4.0 -0.0100000256 -1.876083 3.0 -3.0 -0.0100000512 -2.145252 3.0 -2.0 -0.0100001024 -4.211317 3.0 -1.0 -0.0100002048 -4.523829 5.0 -5.0 -0.0100004096 -4.788892 5.0 -4.0 -0.0100008192 -5.056252 5.0 -3.0 -0.0100016384 -5.353702 5.0 -2.0 -0.0100032768 -5.627074 5.0 -1.0 -0.0100065536 -======== ========== ============ ============= - -Time selection --------------- - -Select only the rows that fit within a slice of time. - -The filter: - -======= ======================= -Column: **Time (s)** -Filter: **x > 1.0 and x < 5.0** -======= ======================= - -results in: - -======== ========== ============ ============= -Time (s) field (mT) port out (V) port in (V) -======== ========== ============ ============= -1.024776 1.0 -1.0 -0.0100000064 -1.300688 3.0 -5.0 -0.0100000128 -1.605982 3.0 -4.0 -0.0100000256 -1.876083 3.0 -3.0 -0.0100000512 -2.145252 3.0 -2.0 -0.0100001024 -4.211317 3.0 -1.0 -0.0100002048 -4.523829 5.0 -5.0 -0.0100004096 -4.788892 5.0 -4.0 -0.0100008192 -======== ========== ============ ============= - -Dimensionality reduction ------------------------- - -Reduce an entire dimension to a single point. - -This allows for plots which would otherwise be impossible. For example, "port in (V)" vs "port out (V)" cannot be plotted since "port in (V)" is not a function of "port out (V)". - -The filter: - -======= ============== -Column: **field (mT)** -Filter: **x == 3.0** -======= ============== - -results in: - -======== ========== ============ ============= -Time (s) field (mT) port out (V) port in (V) -======== ========== ============ ============= -1.300688 3.0 -5.0 -0.0100000128 -1.605982 3.0 -4.0 -0.0100000256 -1.876083 3.0 -3.0 -0.0100000512 -2.145252 3.0 -2.0 -0.0100001024 -4.211317 3.0 -1.0 -0.0100002048 -======== ========== ============ ============= - -It becomes possible to plot "port in (V)" vs "port out (V)" at a constant "field (mT)". - -Configuration -============= - -Filter list ------------ - -The filter list displays all existing filters. - -.. figure:: table_filter_list.* - :alt: Data filter list. - -Filters can be added with the "Add" button, permanently removed with the "Remove" button, and edited by double-clicking on the respective row. - -Filter editor -------------- - -The filter editor allows the user to create new filters and to edit existing filters. - -.. figure:: table_filter_editor.* - :alt: Data filter editor. - -The given filter is added to the selected column. - -If there is an error in the input, the user is informed. For example, the filter "y == 5" results in the message "name 'y' is not defined". diff --git a/docs/user/gui/display/table_filter_editor.png b/docs/user/gui/display/table_filter_editor.png deleted file mode 100644 index ac3e040349870f1e38a02b1255f624352a1e58c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7994 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 diff --git a/docs/user/gui/display/table_filter_list.png b/docs/user/gui/display/table_filter_list.png deleted file mode 100644 index 4cef0e3f4ab176a8c3fde0e83102d1624189c191..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6592 zcmc&(hg(z6whcYfrAaS}fPjFZOErQtrHS;a(tED~L?nWM0@4XcSDGl&3{|Rvgx-4x z30*qmjlTPP_q+EWc$1HllXK?G>@$1MT5E42wKY{JNSR3?5D0~us**1Fu7N=CXrcJv zjP%^(68OUN)K!5)%6eGWzz^cb8Y)T<+@H_8`oaWo<&vAKktYPgaQ)98o|-QAHUz@- zP)+HMp5HhY6=+D?F&%2_>&X?zNptCnFdem$Vv%1UC7t$JIYT}XnS&gkys~_36$8D3 zj83a z{59q&3_X^L5EYd<)lh^&qC#^%F`f&v~Nh(AErs1g#>U1j zE-qLkT_&rXC(;oOOCJ6vCM=egPS4R`B0R%5sqav~ z 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 BZKMDI diff --git a/docs/user/gui/index.rst b/docs/user/gui/index.rst deleted file mode 100644 index 35e6813..0000000 --- a/docs/user/gui/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. _user_gui: - -######################## -Graphical user interface -######################## - -There are several GUI elements which work together, and can be assembled arbitrarily into graphical applications. - -.. toctree:: - :maxdepth: 2 - - action/index - config/index - display/index diff --git a/docs/user/index.rst b/docs/user/index.rst deleted file mode 100644 index 3566525..0000000 --- a/docs/user/index.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. _users_guide: - -############ -User's Guide -############ - -.. toctree:: - :maxdepth: 2 - - general_concepts/index - pulse_programs - gui/index - examples/index diff --git a/docs/user/pulse_programs.rst b/docs/user/pulse_programs.rst deleted file mode 100644 index e02feca..0000000 --- a/docs/user/pulse_programs.rst +++ /dev/null @@ -1,229 +0,0 @@ -.. _pulse_programs: - -############## -Pulse programs -############## - -Pulse programs are an unambiguous way to describe the parameterizable shape of one or more parallel waveforms. Pulse programs allow for: - -* **Adjustable time scales.** Waveforms can be on the order of nanoseconds, of seconds, or anything in between. -* **Arbitrary parameterization.** Delays, pulse amplitudes and length, and repetition counts can all be iterated over. -* **Arbitrary waveforms.** Any discretized waveform can be inserted. -* **External trigger.** An oscilloscope acquisition can be triggered at any point during the waveforms. -* **Nested looping.** Complex, repetitive waveforms can be created easily. - -Variables -********* - -In order to allow parameterization, any values which can be directly used (such as integers or delays) can alternatively be used with a variable. Some values (such as pulses and outputs) require the use of a variable. - -Types -===== - -Pulse programs can contain the following variable types: - - **Integer** (``int``) - A single integer value, such as 12 or -5. - - **Delay** (``delay``) - A single time quantity, such as 5.5 ns. - - **Pulse** (``pulse``) - A collection of three values set using a dictionary or attributes: - - * **amplitude**: A voltage quantity, such as 250 mV. - * **length**: A time quantity. - * **shape**: A string containing a valid file name. - - **Output** (``output``) - A special type which does not support assignment. Outputs are always configured when the pulse program is to be used. - -Dictionaries -============ - -Any variables which are collections of values can have several of their values assigned via a dictionary. Dictionaries are of the form ``{: , ...}`` where the keys must correspond to the attribute names of the variable being assigned to. For example:: - - # A valid dictionary assigned to a pulse: - p1 = {amplitude: -0.5 V, length: 0.5 us, shape: 'square'} - - # Not all attributes need be present: - p2 = {shape: 'non-square'} - -.. _pulse_programs_attributes: - -Attributes -========== - -Any variables which are collections of values can have a single value assigned via an attribute. For example:: - - # Assignment to a pulse attribute: - p3.amplitude = -500 mV - -Program syntax -************** - -A pulse program consists of several statements separated by line breaks or semicolons (``;``). A statement is one of: - - **Assignment** - An assignment sets the value of a variable or attribute. Any variable or attribute may only be assigned to once in a single program. For example:: - - p1 = {shape: 'square', length: 1 ns, amplitude: 1 V} - d1 = 100 ns - loops = 5 - - **Declaration** - A declaration is an announcement of intent to use a variable; all used variables must be declared at some point in the program. A declaration consists of a type, followed by one or more identifiers; any identifier may also be part of an assignment within the declaration. For example:: - - int bumps = 2 - delay bump_spacing = 20 ns, settle, end_delay - - **Command** - A command is an instruction that dictates the shape of the resulting waveforms. There exist several kinds of commands: - - * **Delay**: A lone identifier or a single time value causes a delay in all output waveforms. For example:: - - # Pause all waveforms for the length of delay d1: - d1 - - # Pause all waveforms for 100 ns: - 100 ns - - * **Pulse sequence**: Statements of the form ``([delay|pulse] [delay|pulse] ...):`` are treated as waveform-generating command. If there is only a single delay or pulse, the parentheses may be omitted. If several pulse sequences are included in the same statement, they are executed in parallel; otherwise they are executed in series. For example:: - - # Generate delay or pulse x on ouput f1: - x:f1 - # Alternatively: - (x):f1 - - # Generate delay or pulse x, followed by a 10 ns delay, followed by - # another instance of delay or pulse x, all in sequence on output f2: - (x 10 ns x):f2 - - # Generate identical simultaneous waveforms on outputs f1 and f2: - (x d1 y):f1 (x d1 y):f2 - - # Generate different simultaneous waveforms on outputs f1, f2, and f3: - x:f1 (x y):f2 y:f3 - - # Same output shapes as above, but the waveforms on the different outputs - # follow one another in time: - x:f1 - (x y):f2 - y:f3 - # Alternatively: - x:f1 ; (x y):f2 ; y:f3 - - .. note:: - All waveforms are synchronized before and after a pulse sequence. If any pulse sequence would be longer than the others, padding delays are automatically added to the end of the shorter sequences to ensure that all the lengths match. - - * **Acquisition trigger**: A statement of the form ``acquire`` signals that an oscilloscope acquisition trigger must occur on an output at that point. Such triggers are always created on output markers, rather than as part of the output waveform itself. - - **Loop** - A loop is a section of the program which is to be executed several times. The contents of a loop block are constrained to delays, pulse sequences, and loops. Loops are of the form:: - - times { - - ... - } - -Comments -======== - -Any text after (and including) a ``#`` character is ignored. For example:: - - # This is a pulse sequence. - (p1 d1 p1):f1 # (p2 d2 p2):f2 - -is identical to:: - - (p1 d1 p1):f1 - -Parameterization -**************** - -Any values which are not assigned in the body of the pulse program must be filled in at a later time. For example:: - - pulse p1 = {amplitude: 1 V, shape: 'square'} - output f1 - - p1:f1 - -is the entirety of valid pulse program, but **p1.length** is treated as an external parameter and must be known in order to generate the waveform for output **f1**. - -It it possible to fill these values in dynamically as part of a sweep, given that the parameters are assigned resource labels. - -.. seealso:: :ref:`pulse_program_configuration` - -Examples -******** - -The following examples all use a sampling rate of 1 GHz. - -Single waveform -=============== - -:: - - delay d1 = 5 ns - int bumps - pulse p1 = {amplitude: 1 V, shape: 'square'} - output f1 - - p1.length = 10 ns - - 3 ns - p1:f1 - - times bumps { - d1 - (p1 1 ns p1):f1 - } - -If the parameter **bumps** is filled in with the value **3**, the following waveform is generated: - -.. figure:: pulse_programs_single.* - :alt: Single waveform. - -Multiple waveforms -================== - -:: - - pulse p1 = {amplitude: 0.5 V, length: 10 ns, shape: 'non-square'} - pulse p2 = {amplitude: -1.5 V, length: 5 ns, shape: 'non-square'} - output f1, f2 - - 1 ns - p1:f1 - 1 ns - (p1 2 ns p1):f1 (p2 3 ns p2):f2 - 5 ns - p2:f2 - 8 ns - -If the file "non-square" contains the data "-0.1, 0.0, 0.1, 0.2, 0.4, 0.8, 1.6", the following pair of waveforms is generated: - -.. figure:: pulse_programs_multiple_01.* - :alt: One of multiple waveforms. - -.. figure:: pulse_programs_multiple_02.* - :alt: Another of multiple waveforms. - -With acquisition -================ - -:: - - pulse p1 = {amplitude: 0.25 V, length: 15 ns, shape: 'square'} - output markered - - 20 ns - p1:markered - acquire - p1:markered - 20 ns - -If the acquisition marker is set up to be marker **2** on output **markered**, the following output waveform and marker waveform are generated: - -.. figure:: pulse_programs_acquisition.* - :alt: A waveform with an acquisition trigger. diff --git a/docs/user/pulse_programs_acquisition.png b/docs/user/pulse_programs_acquisition.png deleted file mode 100644 index bdaab9c6015c1faeab993d56d696d7d22fc69245..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17189 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 diff --git a/docs/user/pulse_programs_multiple_01.png b/docs/user/pulse_programs_multiple_01.png deleted file mode 100644 index 650ace1341fd685f4dded0e5b584a0199d1396c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19802 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 F`W@y*nt=cS diff --git a/docs/user/pulse_programs_multiple_02.png b/docs/user/pulse_programs_multiple_02.png deleted file mode 100644 index a0f90ef2b22a6fed248670b81875f4862a8923b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15910 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 YYWDTgvZWb{8tmV42xaM#Cr1AN7f!rNWB>pF diff --git a/docs/user/pulse_programs_single.png b/docs/user/pulse_programs_single.png deleted file mode 100644 index 37ff7cdf70be3ae795497a979bb6c416468c5885..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20921 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