diff options
-rw-r--r-- | acquisition/chapter.tex | 141 | ||||
-rw-r--r-- | acquisition/driver.py | 43 | ||||
-rw-r--r-- | acquisition/hardware.py | 41 | ||||
-rw-r--r-- | acquisition/hardware_inheritance | 16 | ||||
-rw-r--r-- | acquisition/parent_hardware.py | 40 | ||||
-rw-r--r-- | active_correction/chapter.tex | 2 | ||||
-rw-r--r-- | bibliography.bib | 17 | ||||
-rw-r--r-- | dissertation.cls | 4 | ||||
-rw-r--r-- | dissertation.tex | 10 |
9 files changed, 302 insertions, 12 deletions
diff --git a/acquisition/chapter.tex b/acquisition/chapter.tex index 1525b9e..863bf96 100644 --- a/acquisition/chapter.tex +++ b/acquisition/chapter.tex @@ -56,6 +56,8 @@ Besides the extendable modular pieces, the rest of PyCMDS is a mostly-static cod modules and does the necessary things to handle display of information from, and communication
between them. %
+\subsection{Multithreading} % --------------------------------------------------------------------
+
For the kinds of acquisitions that the Wright Group has done the acquisition software spends the
vast majority of its run-time waiting---waiting for user input through mouse clicks or keyboard
presses, waiting for hardware to finish moving or for sensors to finish reading and return
@@ -72,7 +74,7 @@ acquisition requires. % Threads are powerful because they allow for ``semi-synchronous'' operation. %
Imagine PyCMDS is in the middle of a 2D delay-delay scan, and the scan thread has just told each of
-the two delay stages to head to their positions. %
+the two delay stages to head to their destinations. %
PyCMDS must sit in a tight loop to keep track of the position as closely as possible during motor
motion. %
In a single-threaded configuration, this tight loop would only run for one delay at a time, such
@@ -84,12 +86,102 @@ This switching is handled in an OS and hardware specific way---luckily it is all platform-agnostic Qt threads. %
Threads are dangerous because it is hard to pass information between them. %
-Thus, mutexes... signals and slots...
+Without any special protection, two threads have no reason not to simultaneously edit and read the
+same location in memory. %
+If a delay stage is writing its position to memory as a 64-bit double at the same time as the
+acquisition thread reads that memory address, the acquisition thread will read in nonsense (or
+worse), it will crash). %
+So some strategy is needed to ensure that threads respect each other. %
+The Mutex design allows threads to ``lock'' an object such that it cannot be modified by a
+different thread. %
+This lock is like the ``talking stick'' employed my many early child educators. %
+When the talking stick is being used, only the child that holds the stick is allowed to speak. %
+The stick must be passed to another child (as directed by the teacher) before they can share their
+thought. %
+PyCMDS makes heavy use of Mutexes, in particular the \bash{QMutex} class \cite{QMutex}. %
+
+Mutexes handle basic information transfer (two threads can both safely modify and read a particular
+object), but what about sending instructions between threads? %
+Here the problem is deciding what happens when multiple instructions are given simultaneously, or
+an instruction is given while another instruction is being carried out. %
+Again, this is a classic problem in computer science, and the classic answer is the queue. %
+Queues are just like lines at the coffee shop---each person (instruction) is served (carried out)
+in the order that they joined the line. %
+Queues are commonly referred to as FIFO (First In First Out) for this reason. %
+PyCMDS uses queues for almost all instructions. %
+
+Finally, PyCMDS makes extensive use of the ``signals and slots'' construct, which is somewhat
+unique (and certainly original) to Qt. %
+Signals and slots are powerful because they allow threads without instruction to go completely
+silent, making them essentially free in terms of CPU usage. %
+Normally, a thread needs to sit in a loop merely listening for instructions. %
+Within the Qt framework, a thread can be ``woken'' by a signal without needing that thread to
+explicitly ``listen''. %
+These concepts fit within the broader umbrella of ``event-driven programming'', a concept that has
+been used in many languages and frameworks (notably high level LabVIEW tends to be very
+event-driven). %
+The Qt signals and slots system massively simplifies programming within PyCMDS. %
+
+Note that multithreading is very different from multiprocessing. %
+
+\subsection{High level objects} % ----------------------------------------------------------------
+
+PyCMDS is made to be extended and developed by and for immature programmers, so it is crucial to
+create something that is less complicated...
+
+At it's most basic PyCMDS defines the following simple data types (derived from
+\python{PyCMDS_object}):
+\begin{ditemize}
+ \item Bool
+ \item Combo
+ \item Filepath
+ \item Number
+ \item String
+\end{ditemize}
+These classes do multiple things. %
+First, they \emph{are} Mutexes, with thread-safe \python{read} and \python{write} methods. %
+Secondly, they support ``implicit'' storage in ini files. %
+Third, they know how to participate in the GUI. %
+They can display their value, and if modified they will propagate that modification to the internal
+threads of outward...
+Finally, they have special properties like units and limits etc...
Without getting into details, let's investigate the key ``signals and slots'' that hardware and
sensors have. %
% TODO: elaborate
+The following is the top-level hardware class, parent of all hardware and sensors. %
+
+\begin{figure}
+ \includepython{"acquisition/parent_hardware.py"}
+ \caption[Parent to hardware and sensors.]{
+ Parent class of all hardware and sensors. %
+ For brevity, methods \python{close}, \python{update} and \python{wait_until_still} have been
+ omitted. %
+ }
+ \label{aqn:fig:parent_hardware_class}
+\end{figure}
+
+\begin{figure}
+ \includepython{"acquisition/driver.py"}
+ \caption[TODO]{
+ TODO
+ }
+ \label{aqn:fig:driver}
+\end{figure}
+
+\subsection{Graphical user interface} % ----------------------------------------------------------
+
+Made up of widgets...
+
+Table widget...
+
+Use of qt plots...
+
+pyqtgraph \cite{pyqtgraph}
+
+\subsection{Scans} % -----------------------------------------------------------------------------
+
The central loop of scans in PyCMDS. %
\begin{codefragment}{python, label=aqn:lst:loop_simple}
for coordinates in list_of_coordinates:
@@ -146,6 +238,45 @@ because indeed that is all that can be generalized. % \section{Hardware} \label{aqn:sec:hardware} % ====================================================
+Hardware are things that 1) have a position, 2) can be set to a destination. %
+Typically they also have associated units and limits. %
+Each hardware can be thought of as a dimension of the MR-CMDS experiment, and scans include a
+specific traversal through this multidimensional space. %
+
+\subsection{Hardware inheritance} % --------------------------------------------------------------
+
+All hardware classes are children of the parent \python{Hardware} class, which is itself subclassed
+from the global \python{Hardware} class shown in \autoref{aqn:lst:parent_hardware}. %
+
+\begin{figure}
+ \includepython{"acquisition/hardware.py"}
+ \caption[Parent hardware class.]{
+ Parent class of all hardware. %
+ For brevity, methods \python{close}, \python{get_destination}, \python{get_position},
+ \python{is_valid}, \python{on_address_initialized}, \python{poll}, and \python{@property units}
+ have been omitted. %
+ }
+ \label{aqn:fig:hardware_class}
+\end{figure}
+
+
+\begin{figure}
+ \includebash{"acquisition/hardware_inheritance"}
+ \caption[Hardware inheritance.]{
+ }
+ \label{aqn:fig:hardware_inheritance}
+\end{figure}
+
+
+
+\subsection{Delays} % ----------------------------------------------------------------------------
+
+\subsection{Spectrometers} % ---------------------------------------------------------------------
+
+\subsection{OPAs} % ------------------------------------------------------------------------------
+
+\subsection{Filters} % ---------------------------------------------------------------------------
+
\section{Sensors (devices)} \label{aqn:sec:sensors} % ============================================
\subsection{Sensors as axes} % -------------------------------------------------------------------
@@ -292,4 +423,8 @@ S_n &=& (1-c)\left(\frac{n}{N}\right)^{\frac{\tau_{\mathrm{step}}}{\tau_{\mathrm \subsection{Enhanced modularity} % ---------------------------------------------------------------
-\subsection{wt5 savefile} % ----------------------------------------------------------------------
\ No newline at end of file +\subsection{wt5 savefile} % ----------------------------------------------------------------------
+
+\subsection{Hotswappable hardware} % -------------------------------------------------------------
+
+\subsection{Better logging and error handling} % -------------------------------------------------
\ No newline at end of file diff --git a/acquisition/driver.py b/acquisition/driver.py new file mode 100644 index 0000000..da1b18a --- /dev/null +++ b/acquisition/driver.py @@ -0,0 +1,43 @@ +class Driver(QtCore.QObject): + update_ui = QtCore.pyqtSignal() + queue_emptied = QtCore.pyqtSignal() + initialized = Bool() + + def check_busy(self): + """ + Handles writing of busy to False. + + Must always write to busy. + """ + if self.is_busy(): + time.sleep(0.01) # don't loop like crazy + self.busy.write(True) + elif self.enqueued.read(): + time.sleep(0.1) # don't loop like crazy + self.busy.write(True) + else: + self.busy.write(False) + self.update_ui.emit() + + @QtCore.pyqtSlot(str, list) + def dequeue(self, method, inputs): + """ + Slot to accept enqueued commands from main thread. + + Method passed as qstring, inputs as list of [args, kwargs]. + + Calls own method with arguments from inputs. + """ + self.update_ui.emit() + method = str(method) # method passed as qstring + args, kwargs = inputs + if g.debug.read(): + print(self.name, ' dequeue:', method, inputs, self.busy.read()) + self.enqueued.pop() + getattr(self, method)(*args, **kwargs) + if not self.enqueued.read(): + self.queue_emptied.emit() + self.check_busy() + + def is_busy(self): + return False diff --git a/acquisition/hardware.py b/acquisition/hardware.py new file mode 100644 index 0000000..2d062e2 --- /dev/null +++ b/acquisition/hardware.py @@ -0,0 +1,41 @@ +class Hardware(pc.Hardware): + + def __init__(self, *args, **kwargs): + pc.Hardware.__init__(self, *args, **kwargs) + self.exposed = self.driver.exposed + for obj in self.exposed: + obj.updated.connect(self.update) + self.recorded = self.driver.recorded + self.offset = self.driver.offset + self.position = self.exposed[0] + self.native_units = self.driver.native_units + self.destination = pc.Number(units=self.native_units, display=True) + self.destination.write(self.position.read(self.native_units), + self.native_units) + self.limits = self.driver.limits + self.driver.initialized_signal.connect(self.on_address_initialized) + hardwares.append(self) + + def set_offset(self, offset, input_units=None): + if input_units is None: + pass + else: + offset = wt.units.converter(offset, input_units, + self.native_units) + # do nothing if new offset is same as current offset + if offset == self.offset.read(self.native_units): + return + self.q.push('set_offset', offset) + + def set_position(self, destination, input_units=None, force_send=False): + if input_units is None: + pass + else: + destination = wt.units.converter(destination, input_units, + self.native_units) + # do nothing if new destination is same as current destination + if destination == self.destination.read(self.native_units): + if not force_send: + return + self.destination.write(destination, self.native_units) + self.q.push('set_position', destination) diff --git a/acquisition/hardware_inheritance b/acquisition/hardware_inheritance new file mode 100644 index 0000000..0a13a1b --- /dev/null +++ b/acquisition/hardware_inheritance @@ -0,0 +1,16 @@ +Hardware +├── Delay +│ ├── Aerotech +│ ├── LTS300 +│ ├── MFA +│ ├── MFA +│ └── PMC +├── Filter +│ └── Homebuilt +├── OPA +│ ├── OPA800/PMC +│ └── TOPAS +│ ├── TOPAS-C +│ └── TOPAS-800 +└── Spectrometer + └── MicroHR
\ No newline at end of file diff --git a/acquisition/parent_hardware.py b/acquisition/parent_hardware.py new file mode 100644 index 0000000..8ced565 --- /dev/null +++ b/acquisition/parent_hardware.py @@ -0,0 +1,40 @@ +class Hardware(QtCore.QObject): + update_ui = QtCore.pyqtSignal() + initialized_signal = QtCore.pyqtSignal() + + def __init__(self, driver_class, driver_arguments, gui_class, + name, model, serial=None): + """ + Hardware representation object living in the main thread. + + Parameters + driver_class : Driver class + driver_arguments : dictionary + name : string + model : string + serial : string or None (optional) + """ + QtCore.QObject.__init__(self) + self.name = name + self.model = model + self.serial = serial + # create objects + self.thread = QtCore.QThread() + self.enqueued = Enqueued() + self.busy = Busy() + self.driver = driver_class(self, **driver_arguments) + self.initialized = self.driver.initialized + self.gui = gui_class(self) + self.q = Q(self.enqueued, self.busy, self.driver) + # start thread + self.driver.moveToThread(self.thread) + self.thread.start() + # connect to address object signals + self.driver.update_ui.connect(self.update) + self.busy.update_signal = self.driver.update_ui + # initialize drivers + self.q.push('initialize') + # integrate close into PyCMDS shutdown + self.shutdown_timeout = 30 # seconds + g.shutdown.add_method(self.close) + g.hardware_waits.add(self.wait_until_still) diff --git a/active_correction/chapter.tex b/active_correction/chapter.tex index 57093f4..ee20859 100644 --- a/active_correction/chapter.tex +++ b/active_correction/chapter.tex @@ -14,7 +14,7 @@ Run!
\dsignature{Long-hanging poster in Wright Group laser lab.}
-\end{dquote}
+\end{dquote} % TODO: verify this quote
\clearpage
diff --git a/bibliography.bib b/bibliography.bib index 86bd315..ee2d58f 100644 --- a/bibliography.bib +++ b/bibliography.bib @@ -1386,6 +1386,12 @@ year = {1998} pmid = 1463575,
}
+@misc{QMutex,
+ note = {Accessed: 2018-03-27},
+ title = {QMutex Class.},
+ url = {https://doc.qt.io/archives/qt-4.8/qmutex.html},
+}
+
@article{RentzepisPM1970a,
author = {Rentzepis, P. M.},
title = {{Ultrafast Processes}},
@@ -1882,6 +1888,7 @@ year = {1998} month = {dec},
}
+
@article{ZhaoWei2000a,
author = {Zhao, Wei and Urdoch, Keith M M and Besemann, Daniel M and Condon, Nicholas J.
and Meyer, Kent a. and Right, John C W},
@@ -1892,7 +1899,6 @@ year = {1998} year = 2000,
}
-
@article{ZhuBairen2014a,
author = {Bairen Zhu and Hualing Zeng and Junfeng Dai and Xiaodong Cui},
title = {The Study of Spin-Valley Coupling in Atomically Thin Group {VI} Transition Metal
@@ -1912,3 +1918,12 @@ year = {1998} title = {h5py Groups documentation.},
url = {http://docs.h5py.org/en/latest/high/group.html#groups},
}
+
+
+
+@misc{pyqtgraph,
+ note = {Accessed: 2018-03-27},
+ title = {PyQtGraph: Scientific Graphics and GUI Library for Python},
+ url = {http://pyqtgraph.org/},
+}
+
diff --git a/dissertation.cls b/dissertation.cls index 6d2364c..5fefce5 100644 --- a/dissertation.cls +++ b/dissertation.cls @@ -98,8 +98,6 @@ \definecolor{bg}{rgb}{0.95, 0.95, 0.95} -\setmintedinline[python]{bgcolor=bg} - \RequirePackage[many]{tcolorbox} \tcbuselibrary{minted} @@ -125,9 +123,11 @@ \BeforeBeginEnvironment{codefragment}{\begin{singlespace}\stepcounter{equation}} \AfterEndEnvironment{codefragment}{\end{singlespace}} +\newmintinline[bash]{bash}{bgcolor=bg} \newmintinline[python]{python}{bgcolor=bg} \newcommand{\includepython}[1]{\inputminted[bgcolor=bg]{python}{#1}} +\newcommand{\includebash}[1]{\inputminted[bgcolor=bg]{bash}{#1}} % --- graphics ------------------------------------------------------------------------------------ diff --git a/dissertation.tex b/dissertation.tex index 0fae477..5d6d456 100644 --- a/dissertation.tex +++ b/dissertation.tex @@ -95,12 +95,12 @@ This dissertation is approved by the following members of the Final Oral Committ \part{Appendix}
\begin{appendix}
-\include{public/chapter}
-\include{procedures/chapter}
-\include{hardware/chapter}
+%\include{public/chapter}
+%\include{procedures/chapter}
+%\include{hardware/chapter}
% TODO: consider inserting WrightTools documentation as PDF
-\include{errata/chapter}
-\include{colophon/chapter}
+%\include{errata/chapter}
+%\include{colophon/chapter}
\end{appendix}
% post --------------------------------------------------------------------------------------------
|