concurrent.futures.ThreadPoolExecutor を用いて２台の Keithley 2401 sourcemeter から矩形波定電圧を印加する

# はじめに
複数台の Keithley 2401 sourcemeter(以下 2401)を動かす方法を述べる。
マルチプロセスで動かした方が正確なはずだが、なぜかProcessPoolExecutor では動かず、かつ、私の理解が足りないために理由もよくわからないため、ThreadPoolExecutor で動かす方法を述べる。

例として、2台とも矩形波定電圧を印加し、1台がもう1台に対して遅れて定電圧を印加する場合を考える。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669608/2a0251eb-3cf4-b046-43dd-ccdf6cc17bd1.png)

# 方法
### 事前準備
2台の2401本体を操作する。「MENU」→「COMMUNICATION」→「GPIB」から、2台の2401のGPIBアドレスをそれぞれ23および24に設定する（コード内で指定しているGPIBアドレスと一致させる）。

### コード
<details><summary>サンプルコード</summary>

``` python
# coding: UTF-8
import openpyxl
import time
from pymeasure.instruments.keithley import Keithley2400
from datetime import datetime
import concurrent.futures

### common settings ###
date = int(datetime.now().strftime("%Y%m%d"))-20000000 # 日付。2020年9月10日なら「200910」

keithley1 = Keithley2400("GPIB::23") 
keithley2 = Keithley2400("GPIB::24") 

def initial_settings():
    sheet.cell(row = 1, column = 1).value = 't1 (sec)'
    sheet.cell(row = 1, column = 2).value = 'I1(A)'
    sheet.cell(row = 1, column = 3).value = 't2(sec)'
    sheet.cell(row = 1, column = 4).value = 'I2 (A)'
    sheet.cell(row = 1, column = 5).value = datetime.now()

    ### keithley settings ###
    keithley1.reset()    
    keithley1.disable_buffer()
    keithley1.use_front_terminals()
    keithley1.apply_voltage()
    keithley1.source_voltage_range = 0.3   
    keithley1.source_voltage = 0
    keithley1.enable_source()
    keithley1.measure_current()
    keithley1.compliance_current = 1 

    keithley2.reset()    
    keithley2.disable_buffer()
    keithley2.use_front_terminals()
    keithley2.apply_voltage()
    keithley2.source_voltage_range = 0.3   
    keithley2.source_voltage = 0
    keithley2.enable_source()
    keithley2.measure_current()
    keithley2.compliance_current = 1 

# ペルチェセルに電流を印加する
def v_1(voltage):
    target_time = interval
    current_time = current_interval
    time_accuracy = 0.2

    rest_direction = time.time() - base_time - target_time
    for step in range(steps):
        keithley1.ramp_to_voltage(voltage,steps=3)
        while  rest_direction < 0 : # 電圧の向き不変
            rest_current = time.time()-base_time - current_time
            while rest_current < 0 and rest_direction<0: # 電流測定しない、かつ、電圧の向き不変
                time.sleep(time_accuracy)
                rest_current = time.time()-base_time - current_time
                rest_direction = time.time() - base_time - target_time
            # 電流測定する、かつ、電圧の向き不変
            sheet.cell(row = int(current_time/current_interval)+1, column = 1).value = time.time()-base_time
            sheet.cell(row = int(current_time/current_interval)+1, column = 2).value = keithley1.current # 電圧記録の予定時間が来たらエクセルへ記録。
            current_time = current_time + current_interval # 電圧記録時間を再設定。
        #電圧の向き変更
        target_time = target_time + interval
        rest_direction = time.time() - base_time - target_time

        #電圧 0 にする（後の電圧の向き反転のため、voltageに0を代入していない）
        keithley1.ramp_to_voltage(0,steps=3)
        while  rest_direction < 0 : # 電圧の向き不変
            rest_current = time.time()-base_time - current_time
            while rest_current < 0 and rest_direction<0: # 電流測定しない、かつ、電圧の向き不変
                time.sleep(time_accuracy)
                rest_current = time.time()-base_time - current_time
                rest_direction = time.time() - base_time - target_time
            # 電流測定する、かつ、電圧の向き不変
            sheet.cell(row = int(current_time/current_interval)+1, column = 1).value = time.time()-base_time
            sheet.cell(row = int(current_time/current_interval)+1, column = 2).value = keithley1.current # 電圧記録の予定時間が来たらエクセルへ記録。
            current_time = current_time + current_interval # 電圧記録時間を再設定。
        #電圧の向き変更
        voltage = voltage*(-1) # 電圧の向き反転
        print(voltage)
        target_time = target_time + interval
        rest_direction = time.time() - base_time - target_time
    keithley1.shutdown()
    print('v_1_done.')

def v_2(voltage):
#    print("v_2 is running")

    target_time = interval
    current_time = current_interval
    time_accuracy = 0.01
    while time.time() - base_time - delay_time < 0:
        time.sleep(time_accuracy)
    print("delayed")
    base_time_2 = time.time() #delayの時間はノーカウント
    rest_direction = time.time() - base_time_2 - target_time
#    rest_direction = time.time() - base_time - target_time
    for step in range(steps):
        keithley2.ramp_to_voltage(voltage,steps=3)
        while  rest_direction < 0 : # 電圧の向き不変
            rest_current = time.time()-base_time_2 - current_time
            while rest_current < 0 and rest_direction<0: # 電流測定しない、かつ、電圧の向き不変
                time.sleep(time_accuracy)
                rest_current = time.time()-base_time_2 - current_time
                rest_direction = time.time() - base_time_2 - target_time
            # 電流測定する、かつ、電圧の向き不変
            sheet.cell(row = int(current_time/current_interval)+1, column = 3).value = time.time()-base_time
            sheet.cell(row = int(current_time/current_interval)+1, column = 4).value = keithley2.current # 電圧記録の予定時間が来たらエクセルへ記録。
            current_time = current_time + current_interval # 電圧記録時間を再設定。
        #電圧の向き変更
        target_time = target_time + interval
        rest_direction = time.time() - base_time_2 - target_time

        #電圧 0 にする（後の電圧の向き反転のため、voltageに0を代入していない）
        keithley2.ramp_to_voltage(0,steps=3)
        while  rest_direction < 0 : # 電圧の向き不変
            rest_current = time.time()-base_time_2 - current_time
            while rest_current < 0 and rest_direction<0: # 電流測定しない、かつ、電圧の向き不変
                time.sleep(time_accuracy)
                rest_current = time.time()-base_time_2 - current_time
                rest_direction = time.time() - base_time_2 - target_time
            # 電流測定する、かつ、電圧の向き不変
            sheet.cell(row = int(current_time/current_interval)+1, column = 3).value = time.time()-base_time
            sheet.cell(row = int(current_time/current_interval)+1, column = 4).value = keithley2.current # 電圧記録の予定時間が来たらエクセルへ記録。
            current_time = current_time + current_interval # 電圧記録時間を再設定。
        #電圧の向き変更
        voltage = voltage*(-1) # 電圧の向き反転
        print(voltage)
        target_time = target_time + interval
        rest_direction = time.time() - base_time_2 - target_time
    print('v_2_done.')
    keithley2.shutdown()

### conducting functions ###
if __name__ == "__main__":

    sample_name = "test"
    V1 = 0.1
    V2 = 0.1
    steps = 2
    interval = 2 #(sec)
    delay_time = 2 #v_2がv_1に遅れる時間
    current_interval = 0.5 # 電流測定間隔

    Username = '***' 
    filename = "{0}_{1}_{2}V_{3}V_{4}sec_{5}.xlsx".format(date, sample_name, V1, V2, interval, steps)
    path = "C:\\Users\\{0}\\Desktop\\".format(Username)

    print(datetime.now())
    book = openpyxl.Workbook() # エクセルファイルを作成
    sheet = book.worksheets[0] 
    initial_settings() #ソースメータの電源を入れる

    base_time = time.time()
    executor = concurrent.futures.ThreadPoolExecutor(max_workers=2) # サーミスタと電流引加をマルチスレッドで実行。マルチプロセスだとなぜか上手く動かない。
    executor.submit(v_1,V1) #この形式で引数入れる。v_1(V1)だと駄目。
    executor.submit(v_2,V2)
    executor.shutdown() #両方の操作が終わったら次の操作へ。

    book.save(path + filename)
    print('finished.')
```
</details>

# 結果
V1=V2=0.1 V をinterval=2secごとに向きが入れ替わるように2サイクル印加した。
今回はintervalについて、V1およびV2で同じ値を用いた。
ファイル名：220315_test_0.1V_0.1V_2sec_2
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669608/e349b1c3-a9a4-3a8b-88e7-8d873d2b0036.png)

# 関連記事
[PythonおよびKeithley 2401 を用いた矩形波定電圧の印加](https://qiita.com/matoarea/items/62f71bd6981c6d6da6ae)
[concurrent.futures.ThreadPoolExecutor についての備忘録](https://qiita.com/matoarea/items/74825f6cc3c4f3e94f42)