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