From 99eac16d1e99ed5f2c674a4aae505c0965cc34bc Mon Sep 17 00:00:00 2001 From: unknown <asoalexandros@gmail.com> Date: Tue, 6 Aug 2024 17:16:10 +0200 Subject: [PATCH] First Version of Pulse measurement software (with an interface) availiable! --- hp4155/memristor_pulsed/help_new.py | 352 ++++++++++++++++++ hp4155/memristor_pulsed/interface.py | 195 ++++++++++ hp4155/memristor_pulsed/pulse.py | 71 ++++ hp4155/memristor_pulsed/pulse_interface.ipynb | 109 ++++++ 4 files changed, 727 insertions(+) create mode 100644 hp4155/memristor_pulsed/help_new.py create mode 100644 hp4155/memristor_pulsed/interface.py create mode 100644 hp4155/memristor_pulsed/pulse.py create mode 100644 hp4155/memristor_pulsed/pulse_interface.ipynb diff --git a/hp4155/memristor_pulsed/help_new.py b/hp4155/memristor_pulsed/help_new.py new file mode 100644 index 0000000..f2506b8 --- /dev/null +++ b/hp4155/memristor_pulsed/help_new.py @@ -0,0 +1,352 @@ +import sys +sys.path.insert(0, '..') #append parent directory + +import ipywidgets as widgets +import tkinter as tk +from tkinter import filedialog +import tkinter.messagebox +import os +from datetime import datetime +import matplotlib.pyplot as plt +import numpy as np +import module +import time +import pandas as pd +from IPython.display import display, clear_output + +#widgets interactivity + +def add_widgets_to_list(source_dictionary,target_list): + for widget in source_dictionary.values(): + target_list.append(widget) + +def change_state(widgets_list): + for widget in widgets_list: + widget.disabled = not widget.disabled + +def enable_widgets(widgets_list): + for widget in widgets_list: + widget.disabled = False + +def disable_widgets(widgets_list): + for widget in widgets_list: + widget.disabled = True + +def information_box(information): + #open dialog and hide the main window + root = tk.Tk() + root.withdraw() + root.lift() #show window above all other applications + + root.attributes("-topmost", True)#window stays above all other applications + + #display meaagebox + tkinter.messagebox.showinfo(message=information) + root.destroy() + +#choose directory to save measurement results +#and check if you have access +def check_writable(folder): + filename = "test.txt" + file = os.path.join(folder,filename) + + #protection against removing existing file in python + i=1 + while os.path.exists(file): + filename=f"test{i}.txt" + file = os.path.join(folder,filename) + try: + with open(file,'a'): + writable = True + os.remove(file) + except: + writable = False + information_box(f"{folder} is not writable!") + + return writable + +def choose_folder(): + root = tk.Tk() + root.withdraw() + root.lift() #show window above all other applications + + root.attributes("-topmost", True)#window stays above all other applications + + #choose nonemty folder + folder = tk.filedialog.askdirectory() + + while folder == '': + folder = tk.filedialog.askdirectory() + + #check if writable in a while loop + writable=check_writable(folder) + + while writable == False: + #choose a correct folder + folder = tk.filedialog.askdirectory() + + while folder == '': + folder = tk.filedialog.askdirectory() + + #check writable if not repeat + writable=check_writable(folder) + + root.destroy() + return folder + +def check_values(dictionary): + #check if number of pulses is ok + if dictionary['pulses'].value < 0: + dictionary['pulses'].value = -dictionary['pulses'].value + elif dictionary['pulses'].value==0: + dictionary['pulses'].value = 1 + else: + pass + + # Restriction: pulse period ≥ pulse width + 4 ms + if dictionary['period'].value < dictionary['width'].value+4e-3: + dictionary['period'].value = dictionary['width'].value+4e-3 + + +#sweep pulse measurement +def sweep_meas(dict): + device = module.HP4155a('GPIB0::17::INSTR') + device.reset() + device.smu_disable_sweep(1) + device.smu_disable_sweep(3) + + #disable vmus and vsus + device.disable_vsu(1) + device.disable_vsu(2) + device.disable_vmu(1) + device.disable_vmu(2) + + device.measurement_mode("SWE") + device.smu_function_sweep(2,"VAR1") + device.smu_mode_meas(4,"COMMON") + device.smu_function_sweep(4,"CONS") + + device.smu_mode_meas(2,"VPULSE") + device.start_value_sweep(dict["start"].value) + device.stop_value_sweep(dict["stop"].value) + + #define the number of steps given specific pulses + + step = (dict["stop"].value-dict["start"].value)/(dict["pulses"].value-1) + device.step_sweep(step) + + device.comp("VAR1",dict["comp"].value) + + device.display_variable("X","V2") + device.display_variable("Y1",'I2') + + device.range_mode(4,"AUTO") + device.range_mode(2,"AUTO") + + device.pulse_base(dict["base"].value) + device.pulse_width(dict["width"].value) + device.pulse_period(dict["period"].value) + device.integration_time(dict["integration"].value) + + t0 = time.time() + device.single_measurement() + while device.operation_completed()== False: + pass + + t1 = time.time() + # get the execution time + elapsed_time = t1 - t0 + device.autoscaling() + + I_i=device.return_data("I2") + V_i=device.return_data("V2") + R_i = np.divide(V_i,I_i) + + + expected_time = dict["period"].value*dict["pulses"].value + + times = (elapsed_time,expected_time) + values = (V_i,I_i,R_i) + + del device + + return times,values + +def plot_sweep(values): + fig, ax1 = plt.subplots() + color = 'tab:red' + + ax1.set_xlabel("V(V)") + ax1.set_ylabel("I(A)",color=color) + ax1.set_yscale('log') + + ax1.plot(values[0],np.abs(values[1]),color=color) + ax1.tick_params(axis ='y', labelcolor = color,which = 'both') + + # Adding Twin Axes + ax2 = ax1.twinx() + color = 'tab:green' + + ax2.set_ylabel("R(Ohm)",color = color) + ax2.plot(values[0],np.abs(values[2]),color = color) + + ax2.tick_params(axis ='y', labelcolor = color,which = 'both') + ax2.set_yscale('log') + + fig.suptitle("Sweep Pulse Measurement Results") + + plt.show() + +def save_sweep(folder,sample_dict,values,times,sweep_dict): + filename = f"{sample_dict['series'].value}_{sample_dict['field'].value}_{sample_dict['dut'].value}.txt" + + file = os.path.join(folder,filename) + + with open(file,"a") as f: + date = str(datetime.today().replace(microsecond=0)) + f.write(f"Sweep Pulse Measurement at {date}"+"\n") + f.write(f"period(s):{sweep_dict['period'].value}"+"\n") + f.write(f"width(s):{sweep_dict['width'].value}"+"\n") + f.write(f"base value(V):{sweep_dict['base'].value}"+"\n") + f.write(f"execution time(s):{times[0]}"+"\n") + f.write(f"expected time(s):{times[1]}"+"\n") + f.write(f"number of pulses:{sweep_dict['pulses'].value}"+"\n") + f.write(f"current compliance(A):{sweep_dict['comp'].value}"+"\n") + f.write(f"voltage:{sweep_dict['start'].value}V to {sweep_dict['stop'].value}V"+"\n") + f.write(f"integration time:{sweep_dict['integration'].value}"+"\n\n") + + zipped = list(zip(values[0],values[1], values[2])) + df = pd.DataFrame(zipped, columns=['VPULSE(V)', 'IPULSE(A)', 'RPULSE(Ohm)']) + + f.write("Results Sweep Pulse:\n") + f.write(df.to_string()) + f.write("\n\n\n") + +def constant_meas(dict): + device = module.HP4155a('GPIB0::17::INSTR') + device.reset() + device.user_function('V','V','V2') + device.user_function('I','A','I2') + + #disable vmus and vsus + device.disable_vsu(1) + device.disable_vsu(2) + device.disable_vmu(1) + device.disable_vmu(2) + device.smu_disable_sweep(1) + #device.smu_disable_sweep(3) + + device.measurement_mode("SWE") + device.smu_mode_meas(2,"VPULSE") + device.smu_function_sweep(2,'CONS') + device.smu_mode_meas(4,"COMM") + device.smu_function_sweep(2,"CONS") + device.smu_function_sweep(4,'CONS') + + #smu 3 is used to define the number of pulses not contacted + device.smu_mode_meas(3,'V') + device.smu_function_sweep(3,"VAR1") + + device.start_value_sweep(0) + device.stop_value_sweep(10) + + #define the number of steps given specific pulses + step = 10/(dict["pulses"].value-1) + device.step_sweep(step) + + device.comp("VAR1","MAX") + device.const_comp(2,dict["comp"].value) + + device.cons_smu_value(2,dict["voltage"].value) + + device.display_variable("X","@INDEX") + device.display_variable("Y1",'I') + + device.range_mode(4,"AUTO") + device.range_mode(2,"AUTO") + device.range_mode(3,"AUTO") + + device.pulse_base(dict["base"].value) + device.pulse_width(dict["width"].value) + device.pulse_period(dict["period"].value) + device.integration_time(dict["integration"].value) + + t0 = time.time() + device.single_measurement() + while device.operation_completed()== False: + pass + + t1 = time.time() + # get the execution time + elapsed_time = t1 - t0 + device.autoscaling() + + I_i=device.return_data("I") + V_i=device.return_data("V") + R_i = np.divide(V_i,I_i) + + + expected_time = dict["period"].value*dict["pulses"].value + + times = (elapsed_time,expected_time) + values = (V_i,I_i,R_i) + + del device + + return times,values + +def plot_constant(values): + index =[] + for i in range(len(values[0])): + index.append(i+1) + + fig, ax1 = plt.subplots() + color = 'tab:red' + + ax1.set_xlabel("Index(Pulse number)") + ax1.set_ylabel("I(A)",color=color) + ax1.set_yscale('log') + + ax1.plot(index,np.abs(values[1]),color=color,label = "Voltage(V):"+str(min(values[0]))) + ax1.tick_params(axis ='y', labelcolor = color,which = 'both') + + # Adding Twin Axes + ax2 = ax1.twinx() + color = 'tab:green' + + ax2.set_ylabel("R(Ohm)",color = color) + ax2.plot(index,np.abs(values[2]),color=color) + ax2.set_yscale('log') + + ax2.tick_params(axis ='y', labelcolor = color,which = 'both') + + fig.suptitle("Constant Pulse Measurement Results") + ax1.set_title("Voltage:"+str(min(values[0]))+"V") + + plt.show() + +def save_constant(folder,sample_dict,values,times,cons_dict): + filename = f"{sample_dict['series'].value}_{sample_dict['field'].value}_{sample_dict['dut'].value}.txt" + + file = os.path.join(folder,filename) + + with open(file,"a") as f: + date = str(datetime.today().replace(microsecond=0)) + f.write(f"Constant Pulse Measurement at {date}"+"\n") + f.write(f"period(s):{cons_dict['period'].value}"+"\n") + f.write(f"width(s):{cons_dict['width'].value}"+"\n") + f.write(f"base value(V):{cons_dict['base'].value}"+"\n") + f.write(f"execution time(s):{times[0]}"+"\n") + f.write(f"expected time(s):{times[1]}"+"\n") + f.write(f"number of pulses:{cons_dict['pulses'].value}"+"\n") + f.write(f"current compliance(A):{cons_dict['comp'].value}"+"\n") + f.write(f"constant voltage:{cons_dict['voltage'].value}V"+"\n") + f.write(f"integration time:{cons_dict['integration'].value}"+"\n\n") + + zipped = list(zip(values[0],values[1], values[2])) + df = pd.DataFrame(zipped, columns=['VPULSE(V)', 'IPULSE(A)', 'RPULSE(Ohm)']) + + f.write("Results Constant Pulse:\n") + f.write(df.to_string()) + f.write("\n\n\n") + diff --git a/hp4155/memristor_pulsed/interface.py b/hp4155/memristor_pulsed/interface.py new file mode 100644 index 0000000..2a234ed --- /dev/null +++ b/hp4155/memristor_pulsed/interface.py @@ -0,0 +1,195 @@ +import ipywidgets as widgets + +#sample interface + +style = {'description_width': 'initial'} +def sample(): + # the three naming fields + sample_series= widgets.Text( + value= '', + placeholder ='Enter text here:', + description = 'sample series:', + style = {'description_width': 'initial'} + ) + + field = widgets.Text( + value= '', + placeholder ='Enter text here:', + description = 'Field:', + style = {'description_width': 'initial'}, + ) + + DUT = widgets.Text( + value= '', + placeholder ='Enter text here:', + description = 'DUT:', + style = {'description_width': 'initial'}, + ) + + sample_information = widgets.VBox([sample_series,field,DUT]) + + #choose a new folder button + new_folder = widgets.Button(description='change folder') + sample_widgets = widgets.HBox([sample_information,new_folder]) + + sample_dict = { + 'series':sample_series, + 'field':field, + 'dut':DUT, + 'button':new_folder + } + return sample_widgets,sample_dict + + +def constant_pulse(): + voltage = widgets.BoundedFloatText( + value = 10, + min = -100, + max = 100, + step = 1, + description = 'Constant Voltage(V):', + style=style, + ) + + comp = widgets.BoundedFloatText( + value = 0.1, + min = -0.1, + max = 0.1, + step = 0.01, + description = 'Compliance(A):', + style=style, + ) + + pulses = widgets.IntText( + value = 100, + description = 'Number of Pulses:', + style=style, + ) + period = widgets.BoundedFloatText( + value = 5e-3, + min = 5e-3, + max = 1, + step = 5e-3, + description ='Pulse Period(s):', + style=style, + + ) + width = widgets.BoundedFloatText( + value = 5e-4, + min = 5e-4, + max = 1e-1, + step= 5e-4, + description ='Pulse Width(s):', + style=style, + ) + base = widgets.BoundedFloatText( + value = 0, + min = -100, + max = 100, + step = 1, + description = 'Base Voltage(V):', + style=style + ) + + integration =widgets.Dropdown( + options=['SHORt', 'MEDium', 'LONG'], + value='MEDium', + description='Integration:', + style=style + ) + + pulse_parameters = widgets.VBox([pulses,period,width,base]) + smu_parameters = widgets.VBox([voltage,comp,integration]) + + constant_pulse_widgets = widgets.HBox([smu_parameters,pulse_parameters]) + constant_pulse_dict = { + 'voltage': voltage, + 'comp':comp, + 'pulses':pulses, + 'period':period, + 'width':width, + 'base':base, + 'integration':integration + } + return constant_pulse_widgets,constant_pulse_dict + +def sweep_pulse(): + start_voltage = widgets.BoundedFloatText( + value = 0, + min = -100, + max = 100, + step = 1, + description = 'Start Voltage(V):', + style=style, + ) + stop_voltage = widgets.BoundedFloatText( + value = 15, + min = -100, + max = 100, + step = 1, + description = 'Stop Voltage(V):', + style=style, + ) + + comp = widgets.BoundedFloatText( + value = 0.1, + min = -0.1, + max = 0.1, + step = 0.01, + description = 'Compliance(A):', + style=style, + ) + + pulses = widgets.IntText( + value = 100, + description = 'Number of Pulses:', + style=style, + ) + period = widgets.BoundedFloatText( + value = 5e-3, + min = 5e-3, + max = 1, + step = 5e-3, + description ='Pulse Period(s):', + style=style, + + ) + width = widgets.BoundedFloatText( + value = 5e-4, + min = 5e-4, + max = 1e-1, + step= 5e-4, + description ='Pulse Width(s):', + style=style, + ) + base = widgets.BoundedFloatText( + value = 0, + min = -100, + max = 100, + step = 1, + description = 'Base Voltage(V):', + style=style + ) + + integration =widgets.Dropdown( + options=['SHORt', 'MEDium', 'LONG'], + value='MEDium', + description='Integration:', + style=style + ) + + pulse_parameters = widgets.VBox([pulses,period,width,base]) + smu_parameters = widgets.VBox([start_voltage,stop_voltage,comp,integration]) + + sweep_pulse_widgets = widgets.HBox([smu_parameters,pulse_parameters]) + sweep_pulse_dict = { + 'start': start_voltage, + 'stop':stop_voltage, + 'comp':comp, + 'pulses':pulses, + 'period':period, + 'width':width, + 'base':base, + 'integration':integration + } + return sweep_pulse_widgets,sweep_pulse_dict diff --git a/hp4155/memristor_pulsed/pulse.py b/hp4155/memristor_pulsed/pulse.py new file mode 100644 index 0000000..ceb97d2 --- /dev/null +++ b/hp4155/memristor_pulsed/pulse.py @@ -0,0 +1,71 @@ +from interface import * +from help_new import * + +sample_widgets,sample_dict = sample() +display(sample_widgets) +print() +print() + +cons_widgets,cons_dict = constant_pulse() +sweep_widgets,sweep_dict = sweep_pulse() + +children = [sweep_widgets,cons_widgets] +titles = ["Sweep Pulse","Constant Pulse"] +tab = widgets.Tab() +tab.children = children +tab.titles = titles + +display(tab) + +sweep_button = widgets.Button(description = "Sweep Pulse") +cons_button = widgets.Button(description = "Constant Pulse") + +buttons = widgets.HBox([sweep_button,cons_button]) +output = widgets.Output() +display(buttons,output) + +all_widgets = [sweep_button,cons_button] +add_widgets_to_list(sample_dict,all_widgets) +add_widgets_to_list(cons_dict,all_widgets) +add_widgets_to_list(sweep_dict,all_widgets) + +#choose directory +folder = choose_folder() +def on_sweep_button_clicked(b): + with output: + clear_output(wait = True) + change_state(all_widgets) + check_values(sweep_dict) + + times,values = sweep_meas(sweep_dict) + plot_sweep(values) + save_sweep(folder,sample_dict,values,times,sweep_dict) + change_state(all_widgets) + + +def on_constant_button_clicked(b): + with output: + clear_output(wait = True) + change_state(all_widgets) + check_values(sweep_dict) + + times,values = constant_meas(cons_dict) + plot_constant(values) + save_constant(folder,sample_dict,values,times,cons_dict) + change_state(all_widgets) + +#new_folder clicked +def on_new_folder_button_clicked(b): + global folder + with output: + change_state(all_widgets) #just to be sure + folder = choose_folder()#choose new folder + change_state(all_widgets) #just to be sure + + +#link buttons to widgets +sweep_button.on_click(on_sweep_button_clicked) +cons_button.on_click(on_constant_button_clicked) +sample_dict["button"].on_click(on_new_folder_button_clicked) + + \ No newline at end of file diff --git a/hp4155/memristor_pulsed/pulse_interface.ipynb b/hp4155/memristor_pulsed/pulse_interface.ipynb new file mode 100644 index 0000000..f02922c --- /dev/null +++ b/hp4155/memristor_pulsed/pulse_interface.ipynb @@ -0,0 +1,109 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "0ddc31ed-e5cd-47fa-9ce1-39d0b446c9f8", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "429a6850b6b84824a523ef99461535cc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(VBox(children=(Text(value='', description='sample series:', placeholder='Enter text here:', sty…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6030dc009ecf4be59885f71fb1e68ff6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tab(children=(HBox(children=(VBox(children=(BoundedFloatText(value=0.0, description='Start Voltage(V):', min=-…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "108bff59d41f4138bddef3d69fd939ff", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Button(description='Sweep Pulse', style=ButtonStyle()), Button(description='Constant Pulse', st…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "415c38133e4a415b8be88e11e0979d29", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib widget\n", + "%run pulse.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "454fa518-b7d0-43c8-9247-8d5e5c2aa254", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} -- GitLab