Repository: JuBiotech/robotools (language: python)

Issue:
Add usage example for tip subsets



Discussion / hints:

Fix patch:
diff --git a/docs/source/07_TipMasks.nblink b/docs/source/07_TipMasks.nblink
new file mode 100644
index 0000000..f3a1365
--- /dev/null
+++ b/docs/source/07_TipMasks.nblink
@@ -0,0 +1,3 @@
+{
+    "path": "../../notebooks/07_TipMasks.ipynb"
+}
\ No newline at end of file
diff --git a/docs/source/conf.py b/docs/source/conf.py
index f4e1eea..b589ef9 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -15,6 +15,8 @@
 # sys.path.insert(0, os.path.abspath('.'))
 import sphinx_rtd_theme
 
+from robotools import __version__
+
 # -- Project information -----------------------------------------------------
 
 project = "robotools"
@@ -22,7 +24,7 @@
 author = "Michael Osthege"
 
 # The full version, including alpha/beta/rc tags
-release = "1.0.0"
+release = __version__
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/docs/source/index.rst b/docs/source/index.rst
index fae2976..c307800 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -25,6 +25,7 @@ In the following chapters, we introduce the data structures, worklist-creation a
    04_Composition_Tracking
    05_DilutionPlan
    06_Advanced_Worklist_Commands
+   07_TipMasks
 
 
 You can find autogenerated API documentation below:
diff --git a/notebooks/07_TipMasks.ipynb b/notebooks/07_TipMasks.ipynb
new file mode 100644
index 0000000..3633f3d
--- /dev/null
+++ b/notebooks/07_TipMasks.ipynb
@@ -0,0 +1,179 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Pipetting with specific tips\n",
+    "Sometimes it is useful to specify exactly which tip to use for pipetting.\n",
+    "This can be to avoid cross-contaminations, but is __also needed if some tips are broken/deactivated__.\n",
+    "\n",
+    "To restrict pipetting operations to specific tips, one can pass the `tip` kwarg.\n",
+    "The `tip` kwarg can be used in three ways:\n",
+    "* Use `tip=1` to use only tip number one\n",
+    "* Alternatively `tip=robotools.Tip.T1` is the same as the above.\n",
+    "* Or you can pass an iterable such as a `set`, `tuple` or `list`. For example: `tip=[1, 3, 4]`\n",
+    "\n",
+    "The code examples below show this in action."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import robotools\n",
+    "\n",
+    "water = robotools.Trough(\"water\", 8, 1, min_volume=0, max_volume=100_000, initial_volumes=100_000)\n",
+    "target = robotools.Labware(\"target\", 4, 5, min_volume=0, max_volume=300)\n",
+    "\n",
+    "\n",
+    "with robotools.Worklist() as wl:\n",
+    "    wl.transfer(\n",
+    "        source=water,\n",
+    "        source_wells=robotools.get_trough_wells(10, water.wells),\n",
+    "        destination=target,\n",
+    "        # Only the 1st and 4th row\n",
+    "        destination_wells=[\n",
+    "            ['A01', 'A02', 'A03', 'A04', 'A05'],\n",
+    "            ['D01', 'D02', 'D03', 'D04', 'D05'],\n",
+    "        ],\n",
+    "        volumes=200,\n",
+    "        # Using tips 1 and 4\n",
+    "        tip=[1, 4],\n",
+    "        label=\"Transfer water with tips 1 and 4\",\n",
+    "    )"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The volumes are as expected: Water in the first and last row 👇"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "target\n",
+       "[[200. 200. 200. 200. 200.]\n",
+       " [  0.   0.   0.   0.   0.]\n",
+       " [  0.   0.   0.   0.   0.]\n",
+       " [200. 200. 200. 200. 200.]]"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "target"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Internally the EVOware identifies the tips in binary using the formula $2^{\\text{tip number} - 1}$.\n",
+    "This way it can get away with storing one number instead of a sequence of numbers:\n",
+    "\n",
+    "`Tip.T1` has the value $2^{1 - 1} = 1$ and `Tip.T4` has the value $2^{4 - 1} = 8$.\n",
+    "\n",
+    "The tip mask for using both together is the sum: $1 + 8 = 9$.\n",
+    "\n",
+    "If we look at the generated worklist `wl` we can see that each aspirate (A) and dispense (D) command had a tip mask of `9`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "C;Transfer water with tips 1 and 4\n",
+       "A;water;;;1;;200.00;;;9;\n",
+       "D;target;;;1;;200.00;;;9;\n",
+       "W1;\n",
+       "A;water;;;2;;200.00;;;9;\n",
+       "D;target;;;4;;200.00;;;9;\n",
+       "W1;\n",
+       "A;water;;;3;;200.00;;;9;\n",
+       "D;target;;;5;;200.00;;;9;\n",
+       "W1;\n",
+       "A;water;;;4;;200.00;;;9;\n",
+       "D;target;;;8;;200.00;;;9;\n",
+       "W1;\n",
+       "A;water;;;5;;200.00;;;9;\n",
+       "D;target;;;9;;200.00;;;9;\n",
+       "W1;\n",
+       "A;water;;;6;;200.00;;;9;\n",
+       "D;target;;;12;;200.00;;;9;\n",
+       "W1;\n",
+       "A;water;;;7;;200.00;;;9;\n",
+       "D;target;;;13;;200.00;;;9;\n",
+       "W1;\n",
+       "A;water;;;8;;200.00;;;9;\n",
+       "D;target;;;16;;200.00;;;9;\n",
+       "W1;\n",
+       "A;water;;;1;;200.00;;;9;\n",
+       "D;target;;;17;;200.00;;;9;\n",
+       "W1;\n",
+       "A;water;;;2;;200.00;;;9;\n",
+       "D;target;;;20;;200.00;;;9;\n",
+       "W1;"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "wl"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3.8.12 ('dibecs_6.0.5')",
+   "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.8.12"
+  },
+  "orig_nbformat": 4,
+  "vscode": {
+   "interpreter": {
+    "hash": "47708a100f5128723730fc4374c087f02a011f1146e5886fec6a9b8dd97015c0"
+   }
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}


Test patch:
diff --git a/robotools/tests.py b/robotools/tests.py
index 4adf6c5..d5498dd 100644
--- a/robotools/tests.py
+++ b/robotools/tests.py
@@ -1224,6 +1224,21 @@ def test_tip_selection(self) -> None:
             )
         return
 
+    def test_tip_mask(self) -> None:
+        A = liquidhandling.Labware("A", 3, 4, min_volume=10, max_volume=250)
+
+        # Only allow three specific tips to be used...
+        tips = [
+            evotools.Tip.T1,  # 1 +
+            evotools.Tip.T4,  # 8 +
+            evotools.Tip.T7,  # 64
+            # The sum of tips is = 73
+        ]
+        with evotools.Worklist() as wl:
+            wl.dispense(A, "A01", 10, tip=tips)
+        assert wl[-1] == "D;A;;;1;;10.00;;;73;"
+        pass
+
     def test_history_condensation(self) -> None:
         A = liquidhandling.Labware("A", 3, 2, min_volume=300, max_volume=4600, initial_volumes=1500)
         B = liquidhandling.Labware("B", 3, 2, min_volume=300, max_volume=4600, initial_volumes=1500)