Repository: ContextLab/davos (language: python)

Issue:
badge to pdf of paper in repo



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 6b624933..3c96c4ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,20 +1,20 @@
 # Mac Finder display config
 *.DS_Store
 
-# PyCharm project config dir
-*.idea
+# PyCharm & VS Code project config dirs
+*.idea/
+*.vscode/
 
 # Python bytecode
-*__pycache__
+*__pycache__/
 
 # IPython notebook checkpoints
-*.ipynb_checkpoints
+*.ipynb_checkpoints/
 
-# Python package distribution egg
-davos.egg-info
-
-# build dir for non-editable installs
-build
+# Package distribution dirs for wheels & eggs
+dist/
+build/
+davos.egg-info/
 
 # Mypy daemon status file
 .dmypy.json
@@ -24,7 +24,18 @@ tests/geckodriver
 **/geckodriver.log
 
 # scripts & notebooks for scratch/debugging
-scratch
-
-# Illustrator source for snippets
-paper/snippets/snippets.ai
+scratch/
+
+# LaTeX generated files
+paper/**/*.aux
+paper/**/*.bbl
+paper/**/*.blg
+paper/**/*.log
+paper/**/*.out
+paper/**/*.spl
+paper/**/*.synctex.gz
+
+# Illustrator source files
+paper/figs/*.ai
+paper/main.fdb_latexmk
+paper/main.fls
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..f1a11335
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "paper/CDL-bibliography"]
+	path = paper/CDL-bibliography
+	url = https://github.com/ContextLab/CDL-bibliography.git
diff --git a/README.md b/README.md
index 8c1b1f7f..61cec650 100644
--- a/README.md
+++ b/README.md
@@ -6,12 +6,9 @@
   <a href="https://github.com/ContextLab/davos/actions/workflows/ci-tests-jupyter.yml">
     <img src="https://github.com/ContextLab/davos/actions/workflows/ci-tests-jupyter.yml/badge.svg?branch=main" alt="CI Tests (Jupyter)">
   </a>
-  <a href="https://github.com/ContextLab/davos/actions/workflows/ci-tests-colab.yml">
+  <!-- <a href="https://github.com/ContextLab/davos/actions/workflows/ci-tests-colab.yml">
     <img src="https://github.com/ContextLab/davos/actions/workflows/ci-tests-colab.yml/badge.svg?branch=main&event=push" alt="CI Tests (Colab)">
-  </a>
-  <img src="https://img.shields.io/codefactor/grade/github/paxtonfitzpatrick/davos/main?logo=codefactor&logoColor=brightgreen" alt="code quality (CodeFactor)">
-  <img src="https://img.shields.io/badge/mypy-type%20checked-blue" alt="mypy: checked">
-  <br>
+  </a> -->
   <a href="https://pypi.org/project/davos/">
     <img src="https://img.shields.io/pypi/pyversions/davos?logo=python&logoColor=white" alt="Python Versions">
   </a>
@@ -19,6 +16,12 @@
     <img src="https://static.pepy.tech/personalized-badge/davos?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Downloads" alt="PyPI Downloads">
   </a>
   <br>
+  <a href="https://github.com/ContextLab/davos/blob/main/paper/main.pdf">
+    <img src="https://img.shields.io/badge/paper-PDF-9cf" alt="paper (PDF)">
+  </a>
+  <img src="https://img.shields.io/codefactor/grade/github/paxtonfitzpatrick/davos/main?logo=codefactor&logoColor=brightgreen" alt="code quality (CodeFactor)">
+  <img src="https://img.shields.io/badge/mypy-type%20checked-blue" alt="mypy: checked">
+  <br>
   <a href="https://github.com/ContextLab/davos/blob/main/LICENSE">
     <img src="https://img.shields.io/github/license/ContextLab/davos" alt="License: MIT">
   </a>
@@ -26,7 +29,7 @@
   <br>
 </div>
 
-> _Someone once told me that the night is dark and full of terrors. And tonight I am no knight. Tonight I am Davos the 
+> _Someone once told me that the night is dark and full of terrors. And tonight I am no knight. Tonight I am Davos the
 smuggler again. Would that you were an onion._
 <div align="right">
   &mdash;<a href="https://gameofthrones.fandom.com/wiki/Davos_Seaworth">Ser Davos Seaworth</a>
@@ -34,29 +37,78 @@ smuggler again. Would that you were an onion._
   <a href="https://en.wikipedia.org/wiki/A_Song_of_Ice_and_Fire"><i>A Clash of Kings</i></a> by
   <a href="https://en.wikipedia.org/wiki/George_R._R._Martin">George R. R. Martin</a>
   <br>
-  <br>
 </div>
 
-
-The `davos` library provides Python with an additional keyword: **`smuggle`**. 
-
-[The `smuggle` statement](#the-smuggle-statement) works just like the built-in 
-[`import` statement](https://docs.python.org/3/reference/import.html), with one major difference: **you can `smuggle` a 
-package without installing it first**.
-
-A key use case for this package is turning standard [IPython notebooks](https://ipython.org/notebook.html) (including [Google Colaboratory notebooks](colab.research.google.com/)) into fully specified Python environments, without the need for external containers or virtualized environments like conda, docker, etc.  This can facilitate sharing, collaboration, and reproducibility.
-
-While the simplest way to use `davos` is as a drop-in replaceemnt for `import`, the way `davos` smuggles packages can be fully controlled and customized using ["_onion comments_"](#the-onion-comment).  Onion comments can be added to lines containing `smuggle` statements to specify which 
-**specific package versions** should be installed and imported, and to **fully control how missing packages are installed**.
-
-**To enable the `smuggle` keyword, simply `import davos`**:
+# Introduction
+
+The `davos` library provides Python with an additional keyword: **`smuggle`**.
+
+[The `smuggle` statement](#the-smuggle-statement) works just like the built-in [`import` statement](https://docs.python.org/3/reference/import.html), with two major differences: 
+1. You can `smuggle` a package _without installing it first_
+2. You can `smuggle` a _specific version_ of a package
+
+## Why would I want an alternative to `import`?
+
+In many cases, `smuggle` and `import` do the same thing&mdash;*if you're running code in the same environment you
+developed it in*.  But what if you want to share a [Jupyter notebook](https://jupyter.org/) containing your code with
+someone else?  If the user (i.e., the "someone else" in this example) doesn't have all of the packages your notebook
+imports, Python will raise an exception and the code won't run.  It's not a huge deal, of course, but it's inconvenient
+(e.g., the user might need to `pip`-install the missing packages, restart their kernel, re-run the code up to the point
+it crashed, etc.&mdash;possibly going through this cycle multiple times until the thing finally runs).  
+
+A second (and more subtle) issue arises when the developer (i.e., the person who *wrote* the code) used or assumed
+different versions of the imported packages than what the user has installed in their environment.  So maybe the
+original author was developing and testing their code using `pandas` 1.3.5, but the user hasn't upgraded their `pandas`
+installation since 0.25.0.  Python will happily "`import pandas`" in both cases, but any changes across those versions
+might change what the developer's code actually does in the user's (different) environment&mdash;or cause it to fail
+altogether.
+
+The problem `davos` tries to solve is similar to the idea motivating virtual environments, containers, and virtual
+machines: we want a way of replicating the original developer's environment on the user's machine, to a sufficiently
+good approximation that we can be "reasonably confident" that the code will continue to behave as expected.
+
+When you `smuggle` packages instead of importing them, it guarantees (for whatever environment the code is running in)
+that the packages are importable, even if they hadn't been installed previously.  Under the hood, `davos` figures out
+whether the package is available, and if not, it uses `pip` to download and install anything that's missing (including
+missing dependencies).  From that point, after having automatically handled those sorts of dependency issues, `smuggle`
+behaves just like `import`.
+
+The second powerful feature of `davos` comes from another construct, called "[_onion comments_](#the-onion-comment)."
+These are like standard Python comments, but they appear on the same line(s) as `smuggle` statements, and they are
+formatted in a particular way.  Onion comments provide a way of precisely controlling how, when, and where packages are
+installed, how (or if) the system checks for existing installations, and so on.  A key feature is the ability to specify
+exactly which version(s) of each package are imported into the current workspace.  When used in this way, `davos`
+enables authors to guarantee that the same versions of the packages they developed their code with will also be imported
+into the user's workspace at the appropriate times.
+
+## Why not use virtual environments, containers, and/or virtual machines instead?
+
+You can!  In fact, `davos` works great when used inside of virtual environments, containers, and virtual machines.
+There are a few specific advantages to `davos`, however:
+  - `davos` is very lightweight&mdash;importing `davos` into a notebook-based environment unlocks all of its
+    functionality without needed to install, set up, and learn how to use additional stuff.  There is none of the
+    typical overhead of setting up a new virtual environment (or container, virtual machine, etc.), installing
+    third-party tools, writing and sharing configuration files, and so on.  All of your code *and its dependencies* may
+    be contained in a single notebook file.
+  - using onion comments, `davos` can enable mutliple versions of the same package to be used or specified in different
+    parts of the same notebook.  Want to use some deprecated or removed function in `scikit-learn` in one cell, but then
+    use one of the latest features in another?  You can!  Just add onion comments specifying which versions of the
+    package you want to `smuggle` in which cells of your notebook.
+
+## Okay... so how do I use this thing?
+
+To turn a standard [Jupyter (IPython) notebook](https://jupyter.org/), including a [Google Colaboratory notebook](https://colab.research.google.com), into a `davos`-enhanced notebook, just add two lines to the first cell:
 ```python
+%pip install davos
 import davos
+```
+
+This will enable the `smuggle` keyword in your notebook environment.  Then you can do things like:
 
+```python
 # pip-install numpy v1.20.2, if needed
 smuggle numpy as np    # pip: numpy==1.20.2
 
-
 # the smuggled package is fully imported and usable
 arr = np.arange(15).reshape(3, 5)
 
@@ -64,15 +116,19 @@ arr = np.arange(15).reshape(3, 5)
 assert np.__version__ == '1.20.2'
 ```
 
+Interested?  Curious? Intrigued?  Check out the table of contents for more details!  You may also want to check out our [paper](paper/main.pdf) for more formal descriptions and explanations.
+
 ## Table of contents
+- [Table of contents](#table-of-contents)
 - [Installation](#installation)
   - [Latest Stable PyPI Release](#latest-stable-pypi-release)
   - [Latest GitHub Update](#latest-github-update)
+  - [Installing in Colaboratory](#installing-in-colaboratory)
 - [Overview](#overview)
   - [Smuggling Missing Packages](#smuggling-missing-packages)
   - [Smuggling Specific Package Versions](#smuggling-specific-package-versions)
   - [Use Cases](#use-cases)
-    - [Simplify Sharing Reproducible Code & Python Environments](#simplify-sharing-reproducible-code--python-environments)
+    - [Simplify sharing reproducible code & Python environments](#simplify-sharing-reproducible-code--python-environments)
     - [Guarantee your code always uses the latest version, release, or revision](#guarantee-your-code-always-uses-the-latest-version-release-or-revision)
     - [Compare behavior across package versions](#compare-behavior-across-package-versions)
 - [Usage](#usage)
@@ -87,11 +143,6 @@ assert np.__version__ == '1.20.2'
     - [Top-level Functions](#top-level-functions)
 - [How It Works: The `davos` Parser](#how-it-works-the-davos-parser)
 - [Additional Notes](#additional-notes)
-  - [Reimplementing installer programs' CLI parsers](#notes-reimplement-cli)
-  - [Installer options that affect `davos` behavior](#notes-installer-opts)
-  - [Smuggling packages with C-extensions](#notes-c-extensions)
-  - [_`from` ... `import` ..._ statements and reloading modules](#notes-from-reload)
-  - [Smuggling packages from version control systems](#notes-vcs-smuggle)
 
 
 ## Installation
@@ -110,117 +161,117 @@ pip install davos
 [![](https://img.shields.io/github/release-date/ContextLab/davos?label=last%20release)](https://github.com/ContextLab/davos/releases/latest)
 
 ```sh
-pip install git+https://github.com/ContextLab/davos.git#egg=davos
+pip install git+https://github.com/ContextLab/davos.git
 ```
 
 
 ### Installing in Colaboratory
-To use `davos` in [Google Colab](https://colab.research.google.com/), add a cell at the top of your notebook with an 
-exclamation point (`!`) followed by one of the commands above (e.g., `!pip install davos`). Run the cell to install 
-`davos` on the runtime virtual machine. 
+To use `davos` in [Google Colab](https://colab.research.google.com/), add a cell at the top of your notebook with an
+percentage sign (`%`) followed by one of the commands above (e.g., `%pip install davos`). Run the cell to install
+`davos` on the runtime virtual machine.
 
-**Note**: restarting the Colab runtime does not affect installed packages. However, if the runtime is "factory reset" 
-or disconnected due to reaching its idle timeout limit, you'll need to rerun the cell to reinstall `davos` on the fresh 
+**Note**: restarting the Colab runtime does not affect installed packages. However, if the runtime is "factory reset"
+or disconnected due to reaching its idle timeout limit, you'll need to rerun the cell to reinstall `davos` on the fresh
 VM instance.
 
 
 ## Overview
-The primary way to use `davos` is via [the `smuggle` statement](#the-smuggle-statement), which is made available 
-simply by running `import davos`. Like 
-[the built-in `import` statement](https://docs.python.org/3/reference/import.html), the `smuggle` statement is used to 
-load packages, modules, and other objects into the current namespace. The main difference between the two is in how 
+The primary way to use `davos` is via [the `smuggle` statement](#the-smuggle-statement), which is made available
+simply by running `import davos`. Like
+[the built-in `import` statement](https://docs.python.org/3/reference/import.html), the `smuggle` statement is used to
+load packages, modules, and other objects into the current namespace. The main difference between the two is in how
 they handle missing packages and specific package versions.
 
 
 ### Smuggling Missing Packages
-`import` requires that packages be installed _before_ the start of the interpreter session. Trying to `import` a package 
-that can't be found locally will throw a 
-[`ModuleNotFoundError`](https://docs.python.org/3/library/exceptions.html#ModuleNotFoundError), and you'll have to 
-install the package from the command line, restart the Python interpreter to make the new package importable, and rerun 
+`import` requires that packages be installed _before_ the start of the interpreter session. Trying to `import` a package
+that can't be found locally will throw a
+[`ModuleNotFoundError`](https://docs.python.org/3/library/exceptions.html#ModuleNotFoundError), and you'll have to
+install the package from the command line, restart the Python interpreter to make the new package importable, and rerun
 your code in full in order to use it.
 
-**The `smuggle` statement, however, can handle missing packages on the fly**. If you `smuggle` a package that isn't 
-installed locally, `davos` will install it for you, make its contents available to Python's 
-[import machinery](https://docs.python.org/3/reference/import.html), and load it into the namespace for immediate use. 
-You can control _how_ `davos` installs missing packages by adding a special type of inline comment called an 
+**The `smuggle` statement, however, can handle missing packages on the fly**. If you `smuggle` a package that isn't
+installed locally, `davos` will install it for you, make its contents available to Python's
+[import machinery](https://docs.python.org/3/reference/import.html), and load it into the namespace for immediate use.
+You can control _how_ `davos` installs missing packages by adding a special type of inline comment called an
 ["onion" comment](#the-onion-comment) next to a `smuggle` statement.
 
 
 ### Smuggling Specific Package Versions
-One simple but powerful use for [onion comments](#the-onion-comment) is making `smuggle` statements version-sensitive. 
-
-Python does not provide a native, viable way to ensure a third-party package imported at runtime matches a specific 
-version or fits a particular [version constraint](https://www.python.org/dev/peps/pep-0440/#version-specifiers). 
-Many packages expose their version info via a top-level `__version__` attribute (see 
-[PEP 396](https://www.python.org/dev/peps/pep-0396/)), and certain tools (such as the standard library's 
-[`importlib.metadata`](https://docs.python.org/3/library/importlib.metadata.html) and 
-[`setuptools`](https://setuptools.readthedocs.io/en/latest/index.html)'s 
-[`pkg_resources`](https://setuptools.readthedocs.io/en/latest/pkg_resources.html)) attempt to parse version info from 
-installed distributions. However, using these to constrain imported package would require writing extra code to compare 
-version strings and _still_ having manually installing the desired version and restarting the interpreter any time an 
+One simple but powerful use for [onion comments](#the-onion-comment) is making `smuggle` statements _version-sensitive_.
+
+Python doesn't provide a native, viable way to ensure a third-party package imported at runtime matches a specific
+version or satisfies a particular [version constraint](https://www.python.org/dev/peps/pep-0440/#version-specifiers).
+Many packages expose their version info via a top-level `__version__` attribute (see
+[PEP 396](https://www.python.org/dev/peps/pep-0396/)), and certain tools (such as the standard library's
+[`importlib.metadata`](https://docs.python.org/3/library/importlib.metadata.html) and
+[`setuptools`](https://setuptools.readthedocs.io/en/latest/index.html)'s
+[`pkg_resources`](https://setuptools.readthedocs.io/en/latest/pkg_resources.html)) attempt to parse version info from
+installed distributions. However, using these to constrain imported package would require writing extra code to compare
+version strings and _still_ manually installing the desired version and restarting the interpreter any time an
 invalid version is caught.
 
-Additionally, for packages installed through a version control system (e.g., [git](https://git-scm.com/)), this would be 
+Additionally, for packages installed through a version control system (e.g., [git](https://git-scm.com/)), this would be
 insensitive to differences between revisions (e.g., commits) within the same semantic version.
 
-**`davos` solves these issues** by allowing you to specify a specific version or set of acceptable versions for each 
-smuggled package. To do this, simply provide a 
-[version specifier](https://www.python.org/dev/peps/pep-0440/#version-specifiers) in an 
+**`davos` solves these issues** by allowing you to specify a specific version or set of acceptable versions for each
+smuggled package. To do this, simply provide a
+[version specifier](https://www.python.org/dev/peps/pep-0440/#version-specifiers) in an
 [onion comment](#the-onion-comment) next to the `smuggle` statement:
 ```python
 smuggle numpy as np              # pip: numpy==1.20.2
 from pandas smuggle DataFrame    # pip: pandas>=0.23,<1.0
 ```
-In this example, the first line will load [`numpy`](https://numpy.org/) into the local namespace under the alias "`np`", 
-just as "`import numpy as np`" would. First, `davos` will check whether `numpy` is installed locally, and if so, whether 
-the installed version _exactly_ matches `1.20.2`. If `numpy` is not installed, or the installed version is anything 
-other than `1.20.2`, `davos` will use the specified _installer program_, [`pip`](https://pip.pypa.io/en/stable/), to 
-install `numpy==1.20.2` before loading the package. 
-
-Similarly, the second line will load the "`DataFrame`" object from the [`pandas`](https://pandas.pydata.org/) library, 
-analogously to "`from pandas import DataFrame`". A local `pandas` version of `0.24.1` would be used, but a local version 
-of `1.0.2` would cause `davos` to replace it with a valid `pandas` version, as if you had manually run `pip install 
-pandas>=0.23,<1.0`. 
-
-In both cases, the imported versions will fit the constraints specified in their [onion comments](#the-onion-comment), 
+In this example, the first line will load [`numpy`](https://numpy.org/) into the local namespace under the alias "`np`",
+just as "`import numpy as np`" would. First, `davos` will check whether `numpy` is installed locally, and if so, whether
+the installed version _exactly_ matches `1.20.2`. If `numpy` is not installed, or the installed version is anything
+other than `1.20.2`, `davos` will use the specified _installer program_, [`pip`](https://pip.pypa.io/en/stable/), to
+install `numpy==1.20.2` before loading the package.
+
+Similarly, the second line will load the "`DataFrame`" object from the [`pandas`](https://pandas.pydata.org/) library,
+analogously to "`from pandas import DataFrame`". A local `pandas` version of `0.24.1` would be used, but a local version
+of `1.0.2` would cause `davos` to replace it with a valid `pandas` version, as if you had manually run `pip install
+pandas>=0.23,<1.0`.
+
+In both cases, the imported versions will fit the constraints specified in their [onion comments](#the-onion-comment),
 and the next time `numpy` or `pandas` is smuggled with the same constraints, valid local installations will be found.
 
-You can also force the state of a smuggled packages to match a specific VCS ref (branch, revision, tag, release, etc.). 
+You can also force the state of a smuggled packages to match a specific VCS ref (branch, revision, tag, release, etc.).
 For example:
 ```python
 smuggle hypertools as hyp    # pip: git+https://github.com/ContextLab/hypertools.git@98a3d80
 ```
-will load [`hypertools`](https://hypertools.readthedocs.io/en/latest/) (aliased as "`hyp`"), as the package existed 
-[on GitHub](https://github.com/ContextLab/hypertools), at commit 
-[98a3d80](https://github.com/ContextLab/hypertools/tree/98a3d80). The general format for VCS references in 
-[onion comments](#the-onion-comment) follows that of the 
-[`pip-install`](https://pip.pypa.io/en/stable/topics/vcs-support) command. See the 
+will load [`hypertools`](https://hypertools.readthedocs.io/en/latest/) (aliased as "`hyp`"), as the package existed
+[on GitHub](https://github.com/ContextLab/hypertools), at commit
+[98a3d80](https://github.com/ContextLab/hypertools/tree/98a3d80). The general format for VCS references in
+[onion comments](#the-onion-comment) follows that of the
+[`pip-install`](https://pip.pypa.io/en/stable/topics/vcs-support) command. See the
 [notes on smuggling from VCS](#notes-vcs-smuggle) below for additional info.
 
-And with [a few exceptions](#notes-c-extensions), smuggling a specific package version will work _even if the package 
+And with [a few exceptions](#notes-c-extensions), smuggling a specific package version will work _even if the package
 has already been imported_!
 
-**Note**: `davos` v0.1 supports [IPython](https://ipython.readthedocs.io/en/stable/) environments  (e.g., 
-[Jupyter](https://jupyter.org/) and [Colaboratory](https://colab.research.google.com/) notebooks) only. v0.2 will add 
+**Note**: `davos` v0.1 supports [IPython](https://ipython.readthedocs.io/en/stable/) environments  (e.g.,
+[Jupyter](https://jupyter.org/) and [Colaboratory](https://colab.research.google.com/) notebooks) only. v0.2 will add
 support for "regular" (i.e., non-interactive) Python scripts.
 
 
 ### Use Cases
 #### Simplify sharing reproducible code & Python environments
-Different versions of the same package can often behave quite differently&mdash;bugs are introduced and fixed, features 
-are implemented and removed, support for Python versions is added and dropped, etc. Because of this, Python code that is 
-meant to be _reproducible_ (e.g., tutorials, demos, data analyses) is commonly shared alongside a set of fixed versions 
-for each package used. And since there is no Python-native way to specify package versions at runtime (see 
-[above](#smuggling-specific-package-versions)), this typically takes the form of a pre-configured development 
-environment the end user must build themselves (e.g., a [Docker](https://www.docker.com/) container or 
-[conda](https://docs.conda.io/en/latest/) environment), which can be cumbersome, slow to set up, resource-intensive, and 
-confusing for newer users, as well as require shipping both additional specification files _and_ setup instructions 
-along with your code. And even then, a well-intentioned user may alter the environment in a way that affects your 
+Different versions of the same package can often behave quite differently&mdash;bugs are introduced and fixed, features
+are implemented and removed, support for Python versions is added and dropped, etc. Because of this, Python code that is
+meant to be _reproducible_ (e.g., tutorials, demos, data analyses) is commonly shared alongside a set of fixed versions
+for each package used. And since there is no Python-native way to specify package versions at runtime (see
+[above](#smuggling-specific-package-versions)), this typically takes the form of a pre-configured development
+environment the end user must build themselves (e.g., a [Docker](https://www.docker.com/) container or
+[conda](https://docs.conda.io/en/latest/) environment), which can be cumbersome, slow to set up, resource-intensive, and
+confusing for newer users, as well as require shipping both additional specification files _and_ setup instructions
+along with your code. And even then, a well-intentioned user may alter the environment in a way that affects your
 carefully curated set of pinned packages (such as installing additional packages that trigger dependency updates).
-   
-Instead, `davos` allows you to share code with one simple instruction: _just `pip install davos`!_ Replace your `import` 
-statements with `smuggle` statements, pin package versions in onion comments, and let `davos` take care of the rest. 
-Beyond its simplicity, this approach ensures your predetermined package versions are in place every time your code is 
+
+Instead, `davos` allows you to share code with one simple instruction: _just `pip install davos`!_ Replace your `import`
+statements with `smuggle` statements, pin package versions in onion comments, and let `davos` take care of the rest.
+Beyond its simplicity, this approach ensures your predetermined package versions are in place every time your code is
 run.
 
 
@@ -236,13 +287,13 @@ smuggle mypkg    # pip: git+https://username/reponame.git
 
 
 #### Compare behavior across package versions
-The ability to `smuggle` a specific package version even after a different version has been imported makes `davos` a 
+The ability to `smuggle` a specific package version even after a different version has been imported makes `davos` a
 useful tool for comparing behavior across multiple versions of the same package, within the same interpreter session:
 ```python
 def test_my_func_unchanged():
     """Regression test for `mypkg.my_func()`"""
     data = list(range(10))
-    
+
     smuggle mypkg                    # pip: mypkg==0.1
     result1 = mypkg.my_func(data)
 
@@ -259,7 +310,7 @@ def test_my_func_unchanged():
 ## Usage
 ### The `smuggle` Statement
 #### <a name="smuggle-statement-syntax"></a>Syntax
-The `smuggle` statement is meant to be used in place of 
+The `smuggle` statement is meant to be used in place of
 [the built-in `import` statement](https://docs.python.org/3/reference/import.html) and shares
 [its full syntactic definition](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement):
 ```ebnf
@@ -274,10 +325,10 @@ relative_module ::=  "."* module | "."+
 ```
 <sup>
   <i>
-    NB: uses the modified BNF grammar notation described in 
-    <a href="https://docs.python.org/3/reference">The Python Language Reference</a>, 
-    <a href="https://docs.python.org/3/reference/introduction.html#notation">here</a>; see 
-    <a href="https://docs.python.org/3/reference/lexical_analysis.html#identifiers">here</a> for the lexical definition 
+    NB: uses the modified BNF grammar notation described in
+    <a href="https://docs.python.org/3/reference">The Python Language Reference</a>,
+    <a href="https://docs.python.org/3/reference/introduction.html#notation">here</a>; see
+    <a href="https://docs.python.org/3/reference/lexical_analysis.html#identifiers">here</a> for the lexical definition
     of <code>identifier</code>
   </i>
 </sup>
@@ -287,14 +338,14 @@ In simpler terms, **any valid syntax for `import` is also valid for `smuggle`**.
 
 
 #### <a name="smuggle-statement-rules"></a>Rules
-- Like `import` statements, `smuggle` statements are whitespace-insensitive, unless a lack of whitespace between two 
+- Like `import` statements, `smuggle` statements are whitespace-insensitive, unless a lack of whitespace between two
   tokens would cause them to be interpreted as a different token:
   ```python
   from os.path smuggle dirname, join as opj                       # valid
   from   os   . path   smuggle  dirname    ,join      as   opj    # also valid
   from os.path smuggle dirname, join asopj                        # invalid ("asopj" != "as opj")
   ```
-- Any context that would cause an `import` statement _not_ to be executed will have the same effect on a `smuggle` 
+- Any context that would cause an `import` statement _not_ to be executed will have the same effect on a `smuggle`
   statement:
   ```python
   # smuggle matplotlib.pyplot as plt           # not executed
@@ -302,70 +353,70 @@ In simpler terms, **any valid syntax for `import` is also valid for `smuggle`**.
   foo = """
   smuggle matplotlib.pyplot as plt"""          # not executed
   ```
-- Because the `davos` parser is less complex than the full Python parser, there are two, fairly non-disruptive, edge 
+- Because the `davos` parser is less complex than the full Python parser, there are two fairly non-disruptive edge
   cases where an `import` statement would be syntactically valid but a `smuggle` statement would not:
   1. The [exec](https://docs.python.org/3.8/library/functions.html#exec) function
      ```python
      exec('from pathlib import Path')         # executed
      exec('from pathlib smuggle Path')        # raises SyntaxError
      ```
-  2. A one-line [compound statement](https://docs.python.org/3.9/reference/compound_stmts.html#compound-statements) 
+  2. A one-line [compound statement](https://docs.python.org/3.9/reference/compound_stmts.html#compound-statements)
      clause:
      ```python
      if True: import random                   # executed
      if True: smuggle random                  # raises SyntaxError
-     
+
      while True: import math; break           # executed
      while True: smuggle math; break          # raises SyntaxError
-     
+
      for _ in range(1): import json           # executed
      for _ in range(1): smuggle json          # raises SyntaxError
-     
+
      # etc...
      ```
-- In [IPython](https://ipython.readthedocs.io/en/stable/) environments (e.g., [Jupyter](https://jupyter.org/) & 
-  [Colaboratory](https://colab.research.google.com/) notebooks) `smuggle` statements always load names into the global 
+- In [IPython](https://ipython.readthedocs.io/en/stable/) environments (e.g., [Jupyter](https://jupyter.org/) &
+  [Colaboratory](https://colab.research.google.com/) notebooks) `smuggle` statements always load names into the global
   namespace:
   ```python
   # example.ipynb
   import davos
-  
-  
+
+
   def import_example():
       import datetime
-  
-  
+
+
   def smuggle_example():
       smuggle datetime
-  
-  
+
+
   import_example()
   type(datetime)                               # raises NameError
-  
+
   smuggle_example()
   type(datetime)                               # returns
   ```
 
 
 ### The Onion Comment
-An _onion comment_ is a special type of inline comment placed on a line containing a `smuggle` statement. Onion comments 
+An _onion comment_ is a special type of inline comment placed on a line containing a `smuggle` statement. Onion comments
 can be used to control how `davos`:
 1. determines whether the smuggled package should be installed
 2. installs the smuggled package, if necessary
 
-Onion comments are also useful when smuggling a package whose _distribution name_ (i.e., the name 
-used when installing it) is different from its _top-level module name_ (i.e., the name used when importing it). Take for 
+Onion comments are also useful when smuggling a package whose _distribution name_ (i.e., the name
+used when installing it) is different from its _top-level module name_ (i.e., the name used when importing it). Take for
 example:
 ```python
 from sklearn.decomposition smuggle pca    # pip: scikit-learn
 ```
-The onion comment here (`# pip: scikit-learn`) tells `davos` that if "`sklearn`" does not exist 
+The onion comment here (`# pip: scikit-learn`) tells `davos` that if "`sklearn`" does not exist
 locally, the "`scikit-learn`" package should be installed.
 
 #### <a name="onion-comment-syntax"></a>Syntax
-Onion comments follow a simple but specific syntax, inspired in part by the 
-[type comment syntax](https://www.python.org/dev/peps/pep-0484/#type-comments) introduced in 
-[PEP 484](https://www.python.org/dev/peps/pep-0484). The following is a loose (pseudo-)syntactic definition for an onion 
+Onion comments follow a simple but specific syntax, inspired in part by the
+[type comment syntax](https://www.python.org/dev/peps/pep-0484/#type-comments) introduced in
+[PEP 484](https://www.python.org/dev/peps/pep-0484). The following is a loose (pseudo-)syntactic definition for an onion
 comment:
 ```ebnf
 onion_comment   ::=  "#" installer ":" install_opt* pkg_spec install_opt*
@@ -374,29 +425,29 @@ pkg_spec        ::=  identifier [version_spec]
 ```
 <sup>
   <i>
-    NB: uses the modified BNF grammar notation described in 
-    <a href="https://docs.python.org/3/reference">The Python Language Reference</a>, 
-    <a href="https://docs.python.org/3/reference/introduction.html#notation">here</a>; see 
-    <a href="https://docs.python.org/3/reference/lexical_analysis.html#identifiers">here</a> for the lexical definition 
+    NB: uses the modified BNF grammar notation described in
+    <a href="https://docs.python.org/3/reference">The Python Language Reference</a>,
+    <a href="https://docs.python.org/3/reference/introduction.html#notation">here</a>; see
+    <a href="https://docs.python.org/3/reference/lexical_analysis.html#identifiers">here</a> for the lexical definition
     of <code>identifier</code>
   </i>
 </sup>
 
 where `installer` is the program used to install the package; `install_opt` is any option accepted by the installer's
-"`install`" command; and `version_spec` may be a 
-[version specifier](https://www.python.org/dev/peps/pep-0440/#version-specifiers) defined by 
-[PEP 440](https://www.python.org/dev/peps/pep-0440) followed by a 
-[version string](https://www.python.org/dev/peps/pep-0440/#public-version-identifiers), or an alternative syntax valid 
-for the given `installer` program. For example, [`pip`](https://pip.pypa.io/en/stable/) uses specific syntaxes for 
-[local](https://pip.pypa.io/en/stable/cli/pip_install/#local-project-installs), 
-[editable](https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs), and 
-[VCS-based](https://pip.pypa.io/en/stable/topics/vcs-support) installation.  
-
-Less formally, **an onion comment simply consists of two parts, separated by a colon**: 
+"`install`" command; and `version_spec` may be a
+[version specifier](https://www.python.org/dev/peps/pep-0440/#version-specifiers) defined by
+[PEP 440](https://www.python.org/dev/peps/pep-0440) followed by a
+[version string](https://www.python.org/dev/peps/pep-0440/#public-version-identifiers), or an alternative syntax valid
+for the given `installer` program. For example, [`pip`](https://pip.pypa.io/en/stable/) uses specific syntaxes for
+[local](https://pip.pypa.io/en/stable/cli/pip_install/#local-project-installs),
+[editable](https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs), and
+[VCS-based](https://pip.pypa.io/en/stable/topics/vcs-support) installation.
+
+Less formally, **an onion comment simply consists of two parts, separated by a colon**:
 1. the name of the installer program (e.g., [`pip`](https://pip.pypa.io/en/stable/))
 2. arguments passed to the program's "install" command
 
-Thus, you can essentially think of writing an onion comment as taking the full shell command you would run to install 
+Thus, you can essentially think of writing an onion comment as taking the full shell command you would run to install
 the package, and replacing "_install_" with "_:_". For instance, the command:
 ```sh
 pip install -I --no-cache-dir numpy==1.20.2 -vvv --timeout 30
@@ -413,13 +464,13 @@ In practice, onion comments are identified as matches for the
 ```
 <sup>
   <i>
-    NB: support for installing <code>smuggle</code>d packages via 
-    <a href="https://docs.conda.io/en/latest/"><code>conda</code></a> will be added in v0.2. For v0.1, 
+    NB: support for installing <code>smuggle</code>d packages via
+    <a href="https://docs.conda.io/en/latest/"><code>conda</code></a> will be added in v0.2. For v0.1,
     "<code>pip</code>" should be used exclusively.
   </i>
 </sup>
 
-**Note**: support for installing smuggled packages via the [`conda`](https://docs.conda.io/en/latest/) package manager 
+**Note**: support for installing smuggled packages via the [`conda`](https://docs.conda.io/en/latest/) package manager
 will be added in v0.2. For v0.1, onion comments should always specify "`pip`" as the `installer` program.
 
 
@@ -427,13 +478,13 @@ will be added in v0.2. For v0.1, onion comments should always specify "`pip`" as
 - An onion comment must be placed on the same line as a `smuggle` statement; otherwise, it is not parsed:
   ```python
   # assuming the dateutil package is not installed...
-  
+
   # pip: python-dateutil                       # <-- has no effect
   smuggle dateutil                             # raises InstallerError (no "dateutil" package exists)
-  
+
   smuggle dateutil                             # raises InstallerError (no "dateutil" package exists)
   # pip: python-dateutil                       # <-- has no effect
-  
+
   smuggle dateutil    # pip: python-dateutil   # installs "python-dateutil" package, if necessary
   ```
 - An onion comment may be followed by unrelated inline comments as long as they are separated by at least one space:
@@ -442,11 +493,11 @@ will be added in v0.2. For v0.1, onion comments should always specify "`pip`" as
   smuggle tqdm    # pip: tqdm>=4.46,<4.60            # so is this one
   smuggle tqdm    # pip: tqdm>=4.46,<4.60# but this comment raises OnionArgumentError
   ```
-- An onion comment must be the first inline comment immediately following a `smuggle` statement; otherwise, it is not 
+- An onion comment must be the first inline comment immediately following a `smuggle` statement; otherwise, it is not
   parsed:
   ```python
   smuggle numpy    # pip: numpy!=1.19.1        # <-- guarantees smuggled version is *not* v1.19.1
-  smuggle numpy    # has no effect -->         # pip: numpy==1.19.1 
+  smuggle numpy    # has no effect -->         # pip: numpy==1.19.1
   ```
   This also allows you to easily "comment out" onion comments:
   ```python
@@ -462,12 +513,12 @@ will be added in v0.2. For v0.1, onion comments should always specify "`pip`" as
   ```python
   smuggle threading    # pip: threading==9999  # <-- has no effect
   ```
-- When smuggling multiple packages with a _single_ `smuggle` statement, an onion comment may be used to refer to the 
+- When smuggling multiple packages with a _single_ `smuggle` statement, an onion comment may be used to refer to the
   **first** package listed:
   ```python
   smuggle nilearn, nibabel, nltools    # pip: nilearn==0.7.1
   ```
-- If multiple _separate_ `smuggle` statements are placed on a single line, an onion comment may be used to refer to the 
+- If multiple _separate_ `smuggle` statements are placed on a single line, an onion comment may be used to refer to the
   **last** statement:
   ```python
   smuggle gensim; smuggle spacy; smuggle nltk    # pip: nltk~=3.5 --pre
@@ -506,9 +557,9 @@ will be added in v0.2. For v0.1, onion comments should always specify "`pip`" as
       NearestNDInterpolator
   )                                   # pip: scipy==1.6.2    # <-- parsed
   ```
-- The onion comment is intended to describe how a specific smuggled package should be installed if it is not found 
-  locally, in order to make it available for immediate use. Therefore, installer options that either (A) install 
-  packages other than the smuggled package and its dependencies (e.g., from a specification file), or (B) cause the 
+- The onion comment is intended to describe how a specific smuggled package should be installed if it is not found
+  locally, in order to make it available for immediate use. Therefore, installer options that either (A) install
+  packages other than the smuggled package and its dependencies (e.g., from a specification file), or (B) cause the
   smuggled package not to be installed, are disallowed. The options listed below will raise an `OnionArgumentError`:
   - `-h`, `--help`
   - `-r`, `--requirement`
@@ -516,13 +567,13 @@ will be added in v0.2. For v0.1, onion comments should always specify "`pip`" as
 
 
 ### The `davos` Config
-The `davos` config object stores options and data that affect how `davos` behaves. After importing `davos`, the config 
-instance (a singleton) for the current session is available as `davos.config`, and its various fields are accessible as 
-attributes. The config object exposes a mixture of writable and read-only fields. Most `davos.config` attributes can be 
-assigned values to control aspects of `davos` behavior, while others are available for inspection but are set and used 
-internally. Additionally, certain config fields may be writable in some situations but not others (e.g. only if the 
-importing environment supports a particular feature). Once set, `davos` config options last for the lifetime of the 
-interpreter (unless updated); however, they do *not* persist across interpreter sessions. A full list of `davos` config 
+The `davos` config object stores options and data that affect how `davos` behaves. After importing `davos`, the config
+instance (a singleton) for the current session is available as `davos.config`, and its various fields are accessible as
+attributes. The config object exposes a mixture of writable and read-only fields. Most `davos.config` attributes can be
+assigned values to control aspects of `davos` behavior, while others are available for inspection but are set and used
+internally. Additionally, certain config fields may be writable in some situations but not others (e.g. only if the
+importing environment supports a particular feature). Once set, `davos` config options last for the lifetime of the
+interpreter (unless updated); however, they do *not* persist across interpreter sessions. A full list of `davos` config
 fields is available [below](#config-reference):
 
 #### <a name="config-reference"></a>Reference
@@ -541,13 +592,13 @@ fields is available [below](#config-reference):
 #### <a name="top-level-functions"></a>Top-level Functions
 `davos` also provides a few convenience for reading/setting config values:
 - **`davos.activate()`**
-  Activate the `davos` parser, enable the `smuggle` keyword, and inject the `smuggle()` function into the namespace. 
+  Activate the `davos` parser, enable the `smuggle` keyword, and inject the `smuggle()` function into the namespace.
   Equivalent to setting `davos.config.active = True`. See [How it Works](#how-it-works) for more info.
 
 - **`davos.deactivate()`**
-  Deactivate the `davos` parser, disable the `smuggle` keyword, and remove the name `smuggle` from the namespace if (and 
-  only if) it refers to the `smuggle()` function. If `smuggle` has been overwritten with a different value, the variable 
-  will not be deleted. Equivalent to setting `davos.config.active = False`. See [How it Works](#how-it-works) for more 
+  Deactivate the `davos` parser, disable the `smuggle` keyword, and remove the name `smuggle` from the namespace if (and
+  only if) it refers to the `smuggle()` function. If `smuggle` has been overwritten with a different value, the variable
+  will not be deleted. Equivalent to setting `davos.config.active = False`. See [How it Works](#how-it-works) for more
 - info.
 
 - **`davos.is_active()`**
@@ -568,28 +619,28 @@ fields is available [below](#config-reference):
   ```
 
 ## How It Works: The `davos` Parser
-Functionally, importing `davos` appears to enable a new Python keyword, "_`smuggle`_". However, `davos` doesn't actually 
-modify the rules or [reserved keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords) used by 
-Python's parser and lexical analyzer in order to do so&mdash;in fact, modifying the Python grammar is not possible at 
-runtime and would require rebuilding the interpreter. Instead, in [IPython](https://ipython.readthedocs.io/en/stable/) 
-enivonments like [Jupyter](https://jupyter.org/) and 
-[Colaboratory](https://colab.research.google.com/notebooks/intro.ipynb) notebooks, `davos` implements the `smuggle` 
+Functionally, importing `davos` appears to enable a new Python keyword, "_`smuggle`_". However, `davos` doesn't actually
+modify the rules or [reserved keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords) used by
+Python's parser and lexical analyzer in order to do so&mdash;in fact, modifying the Python grammar is not possible at
+runtime and would require rebuilding the interpreter. Instead, in [IPython](https://ipython.readthedocs.io/en/stable/)
+enivonments like [Jupyter](https://jupyter.org/) and
+[Colaboratory](https://colab.research.google.com/notebooks/intro.ipynb) notebooks, `davos` implements the `smuggle`
 keyword via a combination of namespace injections and its own (far simpler) custom parser.
 
-The `smuggle` keyword can be enabled and disabled at will by "activating" and "deactivating" `davos` (see the 
-[`davos` Config Reference](config-reference) and [Top-level Functions](#top-level-functions), above). When `davos` is 
+The `smuggle` keyword can be enabled and disabled at will by "activating" and "deactivating" `davos` (see the
+[`davos` Config Reference](config-reference) and [Top-level Functions](#top-level-functions), above). When `davos` is
 imported, it is automatically activated by default. Activating `davos` triggers two things:
 1. The _`smuggle()` function_ is injected into the `IPython` user namespace
 2. The _`davos` parser_ is registered as a
 [custom input transformer](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html)
 
-IPython preprocesses all executed code as plain text before it is sent to the Python parser in order to handle 
-special constructs like [`%magic`](https://ipython.readthedocs.io/en/stable/interactive/magics.html) and 
+IPython preprocesses all executed code as plain text before it is sent to the Python parser in order to handle
+special constructs like [`%magic`](https://ipython.readthedocs.io/en/stable/interactive/magics.html) and
 [`!shell`](https://ipython.readthedocs.io/en/stable/interactive/reference.html#system-shell-access) commands. `davos`
-hooks into this process to transform `smuggle` statements into syntactically valid Python code. The `davos` 
+hooks into this process to transform `smuggle` statements into syntactically valid Python code. The `davos`
 parser uses [this regular expression](https://github.com/ContextLab/davos/blob/main/davos/core/regexps.py) to match each
-line of code containing a `smuggle` statement (and, optionally, an onion comment), extracts information from its text, 
-and replaces it with an analogous call to the _`smuggle()` function_. Thus, even though the code visible to the user may 
+line of code containing a `smuggle` statement (and, optionally, an onion comment), extracts information from its text,
+and replaces it with an analogous call to the _`smuggle()` function_. Thus, even though the code visible to the user may
 contain `smuggle` statements, e.g.:
 ```python
 smuggle numpy as np    # pip: numpy>1.16,<=1.20 -vv
@@ -600,98 +651,98 @@ smuggle(name="numpy", as_="np", installer="pip", args_str="""numpy>1.16,<=1.20 -
 ```
 
 The `davos` parser can be deactivated at any time, and doing so triggers the opposite actions of activating it:
-1. The name "`smuggle`" is deleted from the `IPython` user namespace, *unless it has been overwritten and no longer 
+1. The name "`smuggle`" is deleted from the `IPython` user namespace, *unless it has been overwritten and no longer
    refers to the `smuggle()` function*
 2. The `davos` parser input transformer is deregistered.
 
-**Note**: in Jupyter and Colaboratory notebooks, IPython parses and transforms all text in a cell before sending it 
-to the kernel for execution. This means that importing or activating `davos` will not make the `smuggle` statement 
-available until the _next_ cell, because all lines in the current cell were transformed before the `davos` parser was 
-registered. However, _deactivating_ `davos` disables the `smuggle` statement immediately&mdash;although the `davos` 
-parser will have already replaced all `smuggle` statements with `smuggle()` function calls, removing the function from 
+**Note**: in Jupyter and Colaboratory notebooks, IPython parses and transforms all text in a cell before sending it
+to the kernel for execution. This means that importing or activating `davos` will not make the `smuggle` statement
+available until the _next_ cell, because all lines in the current cell were transformed before the `davos` parser was
+registered. However, _deactivating_ `davos` disables the `smuggle` statement immediately&mdash;although the `davos`
+parser will have already replaced all `smuggle` statements with `smuggle()` function calls, removing the function from
 the namespace causes them to throw `NameError`.
 
 
 ## Additional Notes
 - <a name="notes-reimplement-cli"></a>**Reimplementing installer programs' CLI parsers**
 
-  The `davos` parser extracts info from onion comments by passing them to a (slightly modified) reimplementation of 
-  their specified installer program's CLI parser. This is somewhat redundant, since the arguments will eventually be 
-  re-parsed by the _actual_ installer program if the package needs to be installed. However, it affords a number of 
-  advantages, such as: 
-  - detecting errors early during the parser phase, before spending any time running code above the line containing the 
+  The `davos` parser extracts info from onion comments by passing them to a (slightly modified) reimplementation of
+  their specified installer program's CLI parser. This is somewhat redundant, since the arguments will eventually be
+  re-parsed by the _actual_ installer program if the package needs to be installed. However, it affords a number of
+  advantages, such as:
+  - detecting errors early during the parser phase, before spending any time running code above the line containing the
     `smuggle` statement
-  - preventing shell injections in onion comments&mdash;e.g., `#pip: --upgrade numpy && rm -rf /` fails due to the 
+  - preventing shell injections in onion comments&mdash;e.g., `#pip: --upgrade numpy && rm -rf /` fails due to the
     `OnionParser`, but would otherwise execute successfully.
-  - allowing certain installer arguments to temporarily influence `davos` behavior while smuggling the current package 
+  - allowing certain installer arguments to temporarily influence `davos` behavior while smuggling the current package
     (see [Installer options that affect `davos` behavior](#notes-installer-opts) below for specific info)
 
 - <a name="notes-installer-opts"></a>**Installer options that affect `davos` behavior**
-  
-  Passing certain options to the installer program via an [onion comment](#the-onion-comment) will also affect the 
+
+  Passing certain options to the installer program via an [onion comment](#the-onion-comment) will also affect the
   corresponding `smuggle` statement in a predictable way:
 
-  - [**`--force-reinstall`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-force-reinstall) | 
-    [**`-I`, `--ignore-installed`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-I) | 
+  - [**`--force-reinstall`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-force-reinstall) |
+    [**`-I`, `--ignore-installed`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-I) |
     [**`-U`, `--upgrade`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-I)
-      
+
     The package will be installed, even if it exists locally
 
   - [**`--no-input`**](https://pip.pypa.io/en/stable/cli/pip/#cmdoption-no-input)
-      
-    Disables input prompts, analogous to temporarily setting `davos.config.noninteractive` to `True`. Overrides value 
+
+    Disables input prompts, analogous to temporarily setting `davos.config.noninteractive` to `True`. Overrides value
     of `davos.config.confirm_install`.
 
-  - [**`--src <dir>`**](https://pip.pypa.io/en/stable/cli/pip/#cmdoption-no-input) | 
+  - [**`--src <dir>`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-src) |
     [**`-t`, `--target <dir>`**](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-t)
-      
-    Prepends `<dir>` to [`sys.path`](https://docs.python.org/3/library/sys.html#sys.path) if not already present so 
+
+    Prepends `<dir>` to [`sys.path`](https://docs.python.org/3/library/sys.html#sys.path) if not already present so
     the package can be imported.
-    
+
 - <a name="notes-c-extensions"></a>**Smuggling packages with C-extensions**
 
-  Some Python packages that rely heavily on custom data types implemented via 
-  [C-extensions](https://docs.python.org/3.9/extending/extending.html) (e.g., `numpy`, `pandas`) dynamically generate 
-  modules defining various C functions and data structures, and link them to the Python interpreter when they are first 
-  imported. Depending on how these objects are initialized, they may not be subject to normal garbage collection, and 
-  persist despite their reference count dropping to zero. This can lead to unexpected errors when reloading the Python 
-  module that creates them, particularly if their dynamically generated source code has been changed (e.g., because the 
+  Some Python packages that rely heavily on custom data types implemented via
+  [C-extensions](https://docs.python.org/3.9/extending/extending.html) (e.g., `numpy`, `pandas`) dynamically generate
+  modules defining various C functions and data structures, and link them to the Python interpreter when they are first
+  imported. Depending on how these objects are initialized, they may not be subject to normal garbage collection, and
+  persist despite their reference count dropping to zero. This can lead to unexpected errors when reloading the Python
+  module that creates them, particularly if their dynamically generated source code has been changed (e.g., because the
   reloaded package is a newer version).
-  
-  This can occasionally affect `davos`'s ability to `smuggle` a new version of a package (or dependency) that was 
-  previously imported. To handle this, `davos` first checks each package it installs against 
-  [`sys.modules`](https://docs.python.org/3.9/library/sys.html#sys.modules). If a different version has already been 
-  loaded by the interpreter, `davos` will attempt to replace it with the requested version. If this fails, `davos` will 
-  restore the old package version _in memory_, while replacing it with the new package version _on disk_. This allows 
-  subsequent code that uses the non-reloadable module to still execute in most cases, while dependency checks for other 
-  packages run against the updated version. Then, depending on the value of `davos.config.auto_rerun`, `davos` will 
-  either either automatically restart the interpreter to load the updated package, prompt you to do so, or raise an 
+
+  This can occasionally affect `davos`'s ability to `smuggle` a new version of a package (or dependency) that was
+  previously imported. To handle this, `davos` first checks each package it installs against
+  [`sys.modules`](https://docs.python.org/3.9/library/sys.html#sys.modules). If a different version has already been
+  loaded by the interpreter, `davos` will attempt to replace it with the requested version. If this fails, `davos` will
+  restore the old package version _in memory_, while replacing it with the new package version _on disk_. This allows
+  subsequent code that uses the non-reloadable module to still execute in most cases, while dependency checks for other
+  packages run against the updated version. Then, depending on the value of `davos.config.auto_rerun`, `davos` will
+  either either automatically restart the interpreter to load the updated package, prompt you to do so, or raise an
   exception.
 
 - <a name="notes-from-reload"></a>**_`from` ... `import` ..._ statements and reloading modules**
 
-  The Python docs for [`importlib.reload()`](https://docs.python.org/3/library/importlib.html#importlib.reload) include 
+  The Python docs for [`importlib.reload()`](https://docs.python.org/3/library/importlib.html#importlib.reload) include
   the following caveat:
-  > If a module imports objects from another module using 
-  > [`from`](https://docs.python.org/3/reference/simple_stmts.html#from) … 
-  > [`import`](https://docs.python.org/3/reference/simple_stmts.html#import) …, calling 
-  > [`reload()`](https://docs.python.org/3/library/importlib.html#importlib.reload) for the other module does 
-  > not redefine the objects imported from it — one way around this is to re-execute the `from` statement, another is to 
+  > If a module imports objects from another module using
+  > [`from`](https://docs.python.org/3/reference/simple_stmts.html#from) …
+  > [`import`](https://docs.python.org/3/reference/simple_stmts.html#import) …, calling
+  > [`reload()`](https://docs.python.org/3/library/importlib.html#importlib.reload) for the other module does
+  > not redefine the objects imported from it — one way around this is to re-execute the `from` statement, another is to
   > use `import` and qualified names (_module.name_) instead.
 
-  The same applies to smuggling packages or modules from which objects have already been loaded. If object _`name`_ from 
-  module _`module`_ was loaded using either _`from module import name`_ or _`from module smuggle name`_, subsequently 
-  running _`smuggle module    # pip --upgrade`_ will in fact install and load an upgraded version of _`module`_, but the 
-  the _`name`_ object will still be that of the old version! To fix this, you can simply run _`from module smuggle 
+  The same applies to smuggling packages or modules from which objects have already been loaded. If object _`name`_ from
+  module _`module`_ was loaded using either _`from module import name`_ or _`from module smuggle name`_, subsequently
+  running _`smuggle module    # pip --upgrade`_ will in fact install and load an upgraded version of _`module`_, but the
+  the _`name`_ object will still be that of the old version! To fix this, you can simply run _`from module smuggle
   name`_ either instead in lieu of or after _`smuggle module`_.
-  
+
 
 - <a name="notes-vcs-smuggle"></a>**Smuggling packages from version control systems**
 
-  The first time during an interpreter session that a given package is installed from a VCS URL, it is assumed not to be 
-  present locally, and is therefore freshly installed. `pip` clones non-editable VCS repositories into a temporary 
-  directory, runs `setup.py install`, and then immediately deletes them. Since no information is retained about the 
-  state of the repository at installation, it is impossible to determine whether an existing package satisfies the state 
+  The first time during an interpreter session that a given package is installed from a VCS URL, it is assumed not to be
+  present locally, and is therefore freshly installed. `pip` clones non-editable VCS repositories into a temporary
+  directory, runs `setup.py install`, and then immediately deletes them. Since no information is retained about the
+  state of the repository at installation, it is impossible to determine whether an existing package satisfies the state
   (i.e., branch, tag, commit hash, etc.) requested for smuggled package.
 
 [comment]: <> (- As with _all_ code, you should use caution when running Python code containing `smuggle` statements that was not written by you or someone you know. )
diff --git a/davos/core/config.py b/davos/core/config.py
index 9eb32ad5..092b58c0 100644
--- a/davos/core/config.py
+++ b/davos/core/config.py
@@ -153,13 +153,13 @@ def __init__(self):
             self._environment = 'Python'
         else:
             import IPython
-            if IPython.version_info[0] < 7:
+            if IPython.version_info[0] >= 7:
                 if 'google.colab' in str(self._ipython_shell):
                     self._environment = 'Colaboratory'
                 else:
-                    self._environment = 'IPython<7.0'
+                    self._environment = 'IPython>=7.0'
             else:
-                self._environment = 'IPython>=7.0'
+                self._environment = 'IPython<7.0'
         self._conda_avail = None
         self._conda_envs_dirs = None
         self._ipy_showsyntaxerror_orig = None
@@ -215,11 +215,12 @@ def __init__(self):
                     self._pip_executable = f'{sys.executable} -m pip'
             else:
                 self._pip_executable = pip_exe
+        self._pip_executable_orig = self._pip_executable
 
     def __repr__(self):
         cls_name = self.__class__.__name__
         base_indent = len(cls_name) + 1
-        attrs_in_repr = ['active', 'auto_rerun', 'conda_avail']
+        attrs_in_repr = ['active', 'auto_rerun']
         if self._conda_avail is not None:
             attrs_in_repr.append('conda_avail')
             if self._conda_avail is True:
diff --git a/davos/core/core.py b/davos/core/core.py
index 94dd183c..5a12fea5 100644
--- a/davos/core/core.py
+++ b/davos/core/core.py
@@ -17,7 +17,7 @@
 import importlib
 import itertools
 import sys
-from contextlib import redirect_stdout
+from contextlib import contextmanager, redirect_stdout
 from io import StringIO
 from pathlib import Path
 from subprocess import CalledProcessError
@@ -247,6 +247,69 @@ def get_previously_imported_pkgs(install_cmd_stdout, installer):
     return prev_imported_pkgs
 
 
+@contextmanager
+def handle_alternate_pip_executable(installed_name):
+    """
+    Context manager that makes it possible to load packages installed
+    into a different Python environment by changing the `pip` executable
+    (`davos.config.pip_executable`).
+
+    Parameters
+    ----------
+    installed_name : str
+        Package name as passed to the `pip install` command. This is the
+        value of self.install_name for the Onion object for the
+        just-installed package.
+
+    Notes
+    -----
+    super-duper edge case not handled by this: user has used alternate
+    pip_executable to smuggle local package from CWD and supplies
+    relative path in onion comment (i.e., "# pip: . <args>").
+    """
+    if '/' in installed_name:
+        # handle local paths, local/remote VCS, PEP 440 direct ref, etc.
+        dist_name = installed_name.split('@')[0].split('#')[0].split('/')[-1]
+    else:
+        # common case
+        dist_name = installed_name
+
+    # get install location from `pip show ___` command.
+    # In most cases, this will be davos.config.pip_executable with
+    # trailing 'bin/pip' replaced with python<major.minor>/site-packages
+    # but since this runs only if non-default pip_executable is set,
+    # it's worth the extra run time to check the safer way in order to
+    # handle various edge cases (e.g., installing from VCS, etc.)
+    pip_show_cmd = f'{config._pip_executable} show {dist_name}'
+    try:
+        pip_show_stdout = run_shell_command(pip_show_cmd, live_stdout=False)
+        location_line = next(
+            l for l in pip_show_stdout.strip().splitlines()
+            if l.startswith('Location')
+        )
+    except (CalledProcessError, StopIteration) as e:
+        msg = (
+            "Unable to locate package installed installed with non-default "
+            f"'pip' executable: {dist_name}. Package has been successfully "
+            "installed but not loaded."
+        )
+        raise SmugglerError(msg) from e
+
+    install_location = location_line.split(': ', maxsplit=1)[1].strip()
+    sys.path.insert(0, install_location)
+    try:
+        # reload pkg_resources so import system will search dir
+        # containing just-installed package
+        importlib.reload(sys.modules['pkg_resources'])
+        yield
+    finally:
+        # remove temporary dir from path
+        sys.path.pop(0)
+        # reload pkg_resources again so import system no longer searches
+        # dir used by alternate pip_executable
+        importlib.reload(sys.modules['pkg_resources'])
+
+
 def import_name(name):
     """
     Import an object by its qualified name.
@@ -458,6 +521,8 @@ def install_cmd(self):
             args = self.args_str.replace("<", "'<'").replace(">", "'>'")
         if self.installer == 'pip':
             install_exe = config._pip_executable
+            if config.noninteractive:
+                args = f'{args} --no-input'
         else:
             install_exe = self.installer
 
@@ -945,7 +1010,11 @@ def smuggle(
             else:
                 prompt_restart_rerun_buttons(failed_reloads)
 
-        smuggled_obj = import_name(name)
+        if config._pip_executable != config._pip_executable_orig:
+            with handle_alternate_pip_executable(onion.install_name):
+                smuggled_obj = import_name(name)
+        else:
+            smuggled_obj = import_name(name)
 
     # add the object name/alias to the notebook's global namespace
     if as_ is None:
diff --git a/davos/core/core.pyi b/davos/core/core.pyi
index bda99d24..1ebb5db0 100644
--- a/davos/core/core.pyi
+++ b/davos/core/core.pyi
@@ -25,6 +25,7 @@ from typing import (
 __all__: list[
     Literal[
         'capture_stdout',
+        'check_conda',
         'Onion',
         'parse_line',
         'prompt_input',
diff --git a/davos/implementations/__init__.py b/davos/implementations/__init__.py
index 0c69be85..df9a27dc 100644
--- a/davos/implementations/__init__.py
+++ b/davos/implementations/__init__.py
@@ -70,8 +70,8 @@
 
     _set_custom_showsyntaxerror()
 
-    if import_environment == 'IPython>=7.0':
-        from davos.implementations.ipython_post7 import (
+    if import_environment == 'IPython<7.0':
+        from davos.implementations.ipython_pre7 import (
             _activate_helper,
             _deactivate_helper,
             generate_parser_func
@@ -81,7 +81,7 @@
             prompt_restart_rerun_buttons
         )
     else:
-        from davos.implementations.ipython_pre7 import (
+        from davos.implementations.ipython_post7 import (
             _activate_helper,
             _deactivate_helper,
             generate_parser_func
diff --git a/davos/implementations/ipython_common.py b/davos/implementations/ipython_common.py
index acf3c4d8..07bcd901 100644
--- a/davos/implementations/ipython_common.py
+++ b/davos/implementations/ipython_common.py
@@ -221,8 +221,8 @@ def _showsyntaxerror_davos(
     The `running_compiled_code` argument was added in `IPython` 6.1.0,
     and setting it to `True` accomplishes (something close to) the same
     thing this workaround does. However, since `davos` needs to support
-    `IPython` versions back to v5.5.0 (which is used by Colab), we can't
-    rely on it being available.
+    `IPython` versions back to v5.5.0, we can't rely on it being
+    available.
     """
     etype, value, tb = ipy_shell._get_exc_info()
     if issubclass(etype, DavosParserError):
diff --git a/davos/implementations/jupyter.py b/davos/implementations/jupyter.py
index a3742847..b49c64bc 100644
--- a/davos/implementations/jupyter.py
+++ b/davos/implementations/jupyter.py
@@ -48,18 +48,18 @@ def auto_restart_rerun(pkgs):
     1. The message displayed before restarting the kernel can be
        silenced by setting `davos.config.suppress_stdout` to `True`.
     2. After calling `JS_FUNCTIONS.jupyter.restartRunCellsAbove`, this
-       function sleeps until until the kernel restarts to prevent any
-       further code in the current cell or other queued cells from
-       executing. Restarting the kernel is often not instantaneous;
-       there's generally a 1-2s delay while the kernel sends & receives
-       various shutdown messages, but it can take significantly
-       longer on a slow machine, with older Python/Jupyter/ipykernel
-       versions, if a large amount of data was loaded into memory, if
-       multiple notebook kernels are running at once, etc. If this
-       function returned immediately, it's likely subsequent lines of
-       code would be run before the kernel disconnected. This can cause
-       problems if those lines of code use the package(s) that prompted
-       the restart, or have effects that persist across kernel sessions.
+       function sleeps until the kernel restarts to prevent any further
+       code in the current cell or other queued cells from executing.
+       Restarting the kernel is often not instantaneous; there's
+       generally a 1-2s delay while the kernel sends & receives various
+       shutdown messages, but it can take significantly longer on a slow
+       machine, with older Python/Jupyter/ipykernel versions, if a large
+       amount of data was loaded into memory, if multiple notebook
+       kernels are running at once, etc. If this function returned
+       immediately, it's likely subsequent lines of code would be run
+       before the kernel disconnected. This can cause problems if those
+       lines of code use the package(s) that prompted the restart, or
+       have effects that persist across kernel sessions.
     """
     msg = (
         "Restarting kernel and rerunning cells (required to smuggle "
diff --git a/paper/CDL-bibliography b/paper/CDL-bibliography
new file mode 160000
index 00000000..1403d485
--- /dev/null
+++ b/paper/CDL-bibliography
@@ -0,0 +1,1 @@
+Subproject commit 1403d48575e10a9e8388c9fe36f78036f257c7f3
diff --git a/paper/admin/cover_letter.pdf b/paper/admin/cover_letter.pdf
new file mode 100644
index 00000000..6d0ef96c
Binary files /dev/null and b/paper/admin/cover_letter.pdf differ
diff --git a/paper/admin/cover_letter.tex b/paper/admin/cover_letter.tex
new file mode 100644
index 00000000..49842a70
--- /dev/null
+++ b/paper/admin/cover_letter.tex
@@ -0,0 +1,103 @@
+\title{cover letter}
+%
+% See http://texblog.org/2013/11/11/latexs-alternative-letter-class-newlfm/
+% and http://www.ctan.org/tex-archive/macros/latex/contrib/newlfm
+% for more information.
+%
+\documentclass[11pt,stdletter,orderfromtodate,sigleft]{newlfm}
+\let\geometry\relax
+\usepackage{hyperref, pxfonts, geometry}
+
+  \setlength{\voffset}{0in}
+
+\newlfmP{dateskipbefore=0pt}
+\newlfmP{sigsize=20pt}
+\newlfmP{sigskipbefore=10pt}
+ 
+\newlfmP{Headlinewd=0pt,Footlinewd=0pt}
+ 
+\namefrom{\vspace{-0.3in}Jeremy R. Manning}
+\addrfrom{
+	Dartmouth College\\
+     Department of Psychological \& Brain Sciences\\
+    HB 6207 Moore Hall\\
+	Hanover, NH  03755}
+ 
+\addrto{}
+\dateset{\today}
+ 
+\greetto{To the editors of \textit{SoftwareX}:}
+
+
+ 
+\closeline{Sincerely,}
+%\usepackage{setspace}
+%\linespread{0.85}
+% The cover letter should explain the importance of the work, and why you consider it appropriate for the diverse readership of . 
+% OPTIONAL. Provide the name and institution of reviewers you would like to recommend and/or people you would like to be excluded from peer review (explaining why).
+
+\begin{document}
+\begin{newlfm}
+
+  We have enclosed our manuscript entitled \textit{\texttt{davos}: a
+    Python package ``smuggler'' for constructing lightweight
+    reproducible notebooks} to be considered for publication as
+  an \textit{Original Software Publication}.
+
+  Our manuscript describes a new Python package, \texttt{davos}.  When
+  used in combination with a notebook-based Python project, the
+  \texttt{davos} library provides tools for specifying and
+  automatically installing the correct versions of the project's
+  dependencies.  Our library also ensures that the correct versions of
+  those dependencies are in use any time the notebook's code is
+  executed.  This enables researchers to share a complete reproducible
+  copy of their code within a single Jupyter notebook (\texttt{.ipynb}) file.
+
+  Broadly, we designed the \texttt{davos} library to target a ``sweet
+  spot'' along a continuum of existing approaches to facilitating reproducible
+  code-based research.  At one end of this continuum, ``lightweight''
+  approaches entail simply sharing raw code (i.e., plain-text Python
+  scripts) or Jupyter notebooks (which can contain a mix
+  of text, code, and embedded media).  These lightweight solutions
+  benefit from very low setup costs (which increase accessibility),
+  but they typically do not make any attempt to manage or constrain
+  the computing environment in which the shared code is executed.
+  At best, when dependencies are missing on the end user's system, shared code
+ may fail to run entirely.  And when the \textit{versions} of a
+ project's dependencies differ between the original author's system and
+ the end user's system, shared code may (at worst) behave in
+ unexpected ways or even cause damage.
+
+ At the other end of this continuum, ``heavyweight'' approaches entail
+ simulating or replicating, to varying depths, the original computing
+ environment in which the shared code was developed.  For example,
+ virtual environments, containerized systems, and virtual machines
+ reproduce (respectively) a complete Python environment, operating system, and/or
+ full hardware simulation of the original environment.  Each of these
+ systems guarantees, to varying degrees, that shared code will behave
+ as expected for the end user.  A downside to these approaches is that
+ they are often effort- and/or resource-intensive, since they require
+ installing and learning additional tools (e.g., Anaconda, Docker, machine
+ emulators, etc.), as well as writing and distributing additional
+ configuration files (e.g., environment configuration files,
+ Dockerfiles, system images, etc.), in order to share and run reproducible code.
+
+ The \texttt{davos} library is lightweight in the sense that it does
+ not require any setup beyond that required to run standard Jupyter
+ notebooks.  But \texttt{davos} also provides infrastructure for
+ precisely controlling project dependencies in a way that can easily
+ be embedded into standard notebooks.  This provides a complete system
+ for sharing reproducible code inside of a standard notebook file.
+  
+ Beyond its intended primary role in facilitating reproducible
+ research, \texttt{davos} is also useful in pedagogical settings
+ (e.g., courses that involve programming in notebook-based
+ environments), or when putting together lightweight notebook-based
+ demonstrations.
+
+ Thank you for considering our manuscript, and we
+ hope you will find it suitable for publication in \textit{SoftwareX}.
+
+
+\end{newlfm}
+\end{document}
diff --git a/paper/admin/declarationStatement.docx b/paper/admin/declarationStatement.docx
new file mode 100644
index 00000000..cea64bc4
Binary files /dev/null and b/paper/admin/declarationStatement.docx differ
diff --git a/paper/boneyard.tex b/paper/boneyard.tex
new file mode 100644
index 00000000..d7396ce0
--- /dev/null
+++ b/paper/boneyard.tex
@@ -0,0 +1,419 @@
+% cut from impact:
+
+
+
+
+
+
+\comment{
+- emphasize research domain-independent application (Journal's goals/values page says they prioritize this in submissions)
+- applicability not limited to research
+    - regression (?) testing
+    - ***^^not sure this should be included -- hurts framing of davos as "research software"***
+- makes writing and running publicly available reproducible code more accessible (lowers required software comfort/familiarity level \& computing resources vs. installing docker/singularity/conda)
+    - also removes these requirements from other scenarios, e.g., easier for new/less experienced students/RAs to get involved with analyses
+- mention something about not having to teach environment management to teach day 1 python lesson, or spend hours debugging conda installation in order to share tutorial or demo with students
+    - e.g., "researchers who teach \_\_\_ may be familiar with the experience of running a lesson or workshop that requires students to use Docker (or similar...) to run their code, only to spend the majority of class time debugging students' computing environments rather than teaching the lesson itself."
+}
+
+Since its initial release, \texttt{davos} has found use in a variety
+of applications.  In addition to managing data analysis environments
+for multiple ongoing research studies, \texttt{davos} is being used by
+both students and instructors in programming courses such as
+\href{https://github.com/ContextLab/storytelling-with-data}{\textit{Storytelling
+    with Data}} \cite{Mann21b} (an open course on data science,
+visualization, and communication) to simplify distributing lessons and
+submitting assignments, as well as in online demos such as
+{\texttt{abstract2paper}} \cite{Mann21a} (an example application of
+\href{https://github.com/EleutherAI/gpt-neo}{GPT-Neo}) to share
+ready-to-run code that installs dependencies automatically.
+
+%\textbf{This is the main section of the article and the reviewers weight the description here appropriately}
+
+%Indicate in what way new research questions can be pursued as a result of the software (if any).
+
+%Indicate in what way, and to what extent, the pursuit of existing research questions is improved (if so).
+
+%Indicate in what way the software has changed the daily practice of its users (if so).
+
+%Indicate how widespread the use of the software is within and outside the intended user group.
+
+%Indicate in what way the software is used in commercial settings and/or how it led to the creation of spin-off companies (if so).
+
+
+
+
+
+
+% cut from introduction:
+
+
+
+% % STOPPED HERE...
+
+
+
+% Sharing a project's code base is most valuable when that code may be
+% run on 
+
+
+
+% Modern scientific research frequently entails writing software code
+% for a wide variety of purposes throughout the scientific process.
+% Researchers across disciplines may design and implement complex
+% experiments; collect, store, and analyze large datasets; create
+% visualizations for presentations and publications; and share their
+% findings and techniques with peers, students, and the broader public
+% through tutorials, demos, workshops, and classes.  However, one
+% fundamental requirement of virtually any form of research-related code
+% is that its behavior and outputs remain consistent and predictable, no
+% matter when, where, or by whom it is run.  This stability can be
+% crucial, for example, to ensuring that data are collected under the
+% same conditions (e.g., across recordings, subjects, or physical
+% locations)
+% %^ or locations, e.g., in different test rooms, on different machines, at different sites, etc.
+% over multiple months or years, and that they can be accessed,
+% processed, and analyzed consistently by a research team that may be
+% spread across multiple institutions or countries.  Additionally,
+% modern open science practices encourage publicly sharing research code
+% and data so that others may explore, reproduce, learn from, and build
+% upon existing work.  Much of the benefit afforded by freely available
+% research code depends on users' ability to execute it and achieve the
+% same result as its original author.
+
+% Python \cite{vanR95} has become one of the most widely used and
+% fastest-growing scientific programming languages, in part by combining
+% an accessible, high-level syntax with a rich ecosystem of powerful
+% third-party tools that facilitate rapid development and collaboration
+% \cite{MullEtal15}.  The Python ecosystem offers an extensive data
+% science toolkit with platforms for interactive programming (e.g.,
+% Project Jupyter \cite{KluyEtal16}, Google Colaboratory),
+% community-maintained libraries for data manipulation (e.g., NumPy
+% \cite{HarrEtal20}, SciPy \cite{VirtEtal20}, Pandas \cite{McKi10}) and
+% visualization (e.g., Matplotlib \cite{Hunt07}, seaborn \cite{Wask21}),
+% frameworks for training complex machine learning models (e.g.,
+% scikit-learn \cite{PedrEtal11}, TensorFlow \cite{AbadEtal15}, Hugging
+% Face~\cite{WolfEtal20}), and myriad other resources.  However, this
+% heavy emphasis on third-party libraries also presents a challenge to
+% writing and sharing stable, reproducible scientific Python code:
+% different versions of the same library may behave differently, adopt
+% changes in syntax, expose different functions and interfaces, add or
+% drop support for specific hardware or software, write (or expect to
+% read) files in different formats, fix (or introduce) bugs, and so on.
+% While these issues exist to some extent in any software language or
+% ecosystem, they have a particular impact on the Python community due
+% to its unusually rapid growth relative to other languages.  Ensuring
+% Python code behaves consistently over time and across users therefore
+% typically requires ensuring it is always run with the same specific
+% set of versions for each third-party package used.
+% %Ensuring Python code behaves consistently over time and across users therefore typically requires guaranteeing that the same specific version of each required third-party package is used, every time it is run.
+
+% One common approach to solving this problem is to create containerized
+% or virtualized Python environments (e.g., using Docker \cite{Merk14},
+% Singularity \cite{KurtEtal17}, or conda \cite{Anac12}) tailored to
+% individual applications.  A researcher may build such an environment
+% around a particular experiment or analysis pipeline, and exclusively
+% run their code from inside it, ``entering" and ``exiting" the
+% environment before and after each time they do so.  They can also
+% distribute their custom environment alongside their code as a
+% configuration file that explicitly lists required package versions,
+% enabling others to build identical copies for themselves.  This allows
+% research teams to deploy experiments on multiple machines for more
+% efficient data collection, collaborate on analyses without introducing
+% conflicts or inconsistencies, and publicly share their study designs
+% and results for others to reproduce, replicate, or adapt to study new
+% questions in the future.
+
+% While often effective, this approach bears two notable drawbacks.
+% First, it can add substantially to the technical knowledge, computing
+% resources, and initial setup needed to run or share the actual code of
+% interest.  For example, sharing code for an analysis or tutorial that
+% relies on a particular Docker image to run properly would of course
+% necessitate writing and distributing extra configuration files and
+% setup instructions.  But far more burdensomely, it also requires that
+% anyone who may want to run the code (in addition to the author seeking
+% to share it!) first be able to install and navigate additional
+% software that is likely far more complex and resource-intensive than
+% the actual analysis or tutorial code it facilitates running.  This can
+% introduce a need for both a degree of computer literacy and
+% computational resources that may not be universally accessible,
+% particularly to students or other early-career scientists hoping to
+% learn from publicly available tutorials.  These added prerequisites
+% clash with the simplicity and accessibility that have contributed to
+% Python's popularity, and can create significant barriers to both
+% contributing to and taking advantage of open science resources.
+
+% Second, while many existing tools allow users to initially populate a
+% Python environment with a fixed set of packages and package versions
+% (e.g., from a \texttt{requirements.txt}, \texttt{pyproject.toml},
+% \texttt{environment.yml}, \texttt{Pipfile}, \texttt{Dockerfile RUN}
+% instruction, etc.), few, if any, ensure that these specified
+% requirements \textit{remain} satisfied after they are first installed.
+% The ability to modify an environment after its creation is useful in
+% many cases (e.g., to install additional software, when
+% needed). However, this also makes it easy to inadvertently alter
+% existing packages, potentially leading to subtle issues with code that
+% relies on them.  For instance, suppose a researcher has implemented a
+% series of analyses using version 1.0 of ``Package \textit{X}," and
+% decides to perform an additional analysis that requires installing
+% ``Package \textit{Y}."  If Package \textit{Y} depends on version 0.9
+% of Package \textit{X}, then Package \textit{X} will be downgraded to
+% accommodate this new requirement, potentially altering or breaking
+% previous analyses when they are rerun later, either by the researcher
+% or someone with whom they've shared their code.  Further, if some
+% analyses require Package \textit{Y} while others rely on features of
+% Package \textit{X} not implemented until version 1.0, it's unclear
+% which version the researcher should install in their environment.
+
+% %The \texttt{davos} package provides a novel, Python-native framework for creating, sharing, and running reproducible workflows that was designed to address each of these issues.
+% The \texttt{davos} package provides a novel, Python-native framework
+% for creating reproducible workflows that was designed to address each
+% of these issues. \texttt{davos} allows users to specify dependencies
+% directly within the code that...
+
+% \comment {
+% POINTS TO MAKE/THINGS TO SAY HERE TO WRAP UP (or maybe save some for impact section?):
+% - davos simplifies the process of writing, sharing, and running reproducible code (eliminates need for additional complex env management tools, syntax is simple, packages are just installed at runtime without additional setup beforehand, etc.)...
+% - ...it also provides stability/additional protections against accidental changes in ways other tools don't (i.e., addresses issue 2 above) by verifying (and installing if necessary) requirements each time code is run
+% - ... while simultaneously enabling new package management patterns (using multiple versions versions of package in same notebook, etc.)
+% - it's lightweight and intuitive (unlike big/complex env management tools like docker, etc.)
+% - "davos was specifically designed for use with Jupyter notebook environments, and can be used either on its own or in tandem with another environment management tool"
+% - "\texttt{davos} was designed specifically for use in Jupyter notebook (formerly called IPython notebook \cite{PereGran07}) environments, and supports Jupyter notebooks \cite{KluyEtal16}, JupyterLab \cite{GranGrou16}, Google Colaboratory, Binder \cite{RagaWill18}, and IDE-based notebook editors."
+
+% - maybe don't mention smuggle statement \& onion comment explicitly in this paragraph since they haven't been defined/explained yet, or if we do, then add some sort of "see \_\_\_ below" with \\ref to relevant subsection
+% }
+
+
+%Introduce the scientific background and the motivation for developing the software.
+
+%Explain why the software is important, and describe the exact (scientific) problem(s) it solves.
+
+%Indicate in what way the software has contributed (or how it will contribute in the future) to the process of scientific discovery; if available, this is to be supported by citing a research paper using the software.
+
+%Provide a description of the experimental setting (how does the user use the software?).
+
+%Introduce related work in literature (cite or list algorithms used, other software etc.).
+
+
+
+
+
+% ABSTRACT:
+
+%Many important parts of modern scientific research depend on writing, sharing, and running reproducible code. It enables scientists to collaborate on projects, replicate and extend prior work, and teach students new ideas and techniques through hands-on, interactive tutorials.
+
+%The ability to write, share, and run reproducible code plays many important roles in modern scientific research:
+
+%Writing and sharing reproducible code plays an important role in modern scientific research, enabling scientists to collaborate on projects, replicate and extend previous work, and teach students concepts and techniques through
+
+%\texttt{davos} is a Python package that provides a simple, accessible, and effective way to manage and share reproducible code.
+
+%\texttt{davos} provides the \texttt{smuggle} statement
+
+%\texttt{davos} is a Python package that allows users to write, run, and share reproducible Python code without the need for containers or virtual environments.
+
+%davos is a Python package that allows users to manage and share reproducible code without the need for containers or virtual environments.
+
+%\texttt{davos} is a Python package for creating, running, and sharing reproducible workflows without the need for external containerized or virtualized environments.
+
+%\texttt{davos} is a Python package that makes writing, running, and sharing reproducible code easy. Importing \texttt{davos} in an IPython notebook enables the \textit{smuggle} statement: a drop-in replacement for the built-in \textit{import} statement that can install missing packages as needed at runtime, and may be
+
+%\texttt{davos} is a Python package for creating self-managing, reproducible workflows that specify dependencies directly within their code and install packages as needed at runtime.
+
+%\texttt{davos} works by providing the \textit{smuggle} statement: a drop-in replacement for the built-in \texttt{import} statement that  \_\_\_, and the
+
+% \texttt{davos} is a Python package for creating self-managing, reproducible workflows that specify dependencies directly in their code, and install them
+% \texttt{davos} is a Python package that enables creating and sharing reproducible workflows as a single, ready-to-run IPython notebook.
+
+%davos is a python package for creating, sharing, and running reproducible code without the need for
+
+
+% ========================================================================================================
+
+
+% Modern open science practices promote sharing code and data to enable others to explore, reproduce, learn from, and build upon existing work. Scientists, researchers, and educators produce many different forms of research-related code (e.g., experiments, data analyses, tutorials, demos) that they may seek to share with a wide range of audiences including collaborators, students, the broader scientific community, and the general public. Python has become one of the most widely used and fastest-growing scientific programming languages as it offers both a simple, accessible grammar and an extensive 
+
+%Modern scientific research frequently involves writing code for different purposes throughout the research process (e.g., experiments, data analyses, tutorials, or demos). A nearly universal (but often challenging) requirement of research-related code is that its behavior and outputs must remain consistent every time it is run, both over time and across users. This stability can be crucial to ensuring that data are collected under identical conditions (e.g., across recordings, trials, or subjects) and that statistical analyses yield accurate, consistent results. Additionally, modern open science practices encourage sharing code and data publicly to enable others to explore, reproduce, learn from, and build upon existing work. Scientists, researchers, and educators may seek to share research-related code with a wide range of audiences including collaborators, students, the broader scientific community, and the general public.
+
+%Modern scientific research often involves writing code that must behave consistently every time it is run. For example, stable code can be crucial to ensuring data are collected under the same conditions, recorded and processed in a common format, and yield reliable results when analyzed. Additionally, modern open science practices promote sharing code and data to enable others to explore, reproduce, learn from, and build upon existing work. Scientists, researchers, and educators produce many different forms of research-related code (e.g., experiments, data analyses, tutorials, demos) that they may seek to share with a wide range of audiences including collaborators, students, the broader scientific community, and the general public. 
+
+%%Modern scientific research frequently involves writing code for different purposes throughout the research process (e.g., experiments, data analyses, tutorials, or demos). An often essential (but often challenging) part of writing code for scientific research is ensuring its behavior and outputs remain consistent every time it is run, both over time and across users.
+
+% Modern scientific research often involves writing code for various uses throughout the research process (e.g., experiments, data analyses, tutorials, and demos). An often-essential feature of research-related code is that it must behave consistently 
+
+% A common challenge when writing 
+
+%A common requirement of code written for scientific research is that it must behave consistently every time it is run. This 
+
+% Modern scientific research frequently involves writing code whose behavior and outputs must be consistent every time it is run. This stability is essential for 
+
+%When writing code for scientific research, it is often essential to ensure that the code 
+
+%Modern scientific research frequently involves writing code whose behavior must be consistent every time it is run. This stability is essential to ensuring 
+
+%An essential part of modern scientific research is writing code that behaves consistently every time it is run. Stable code is crucial to ensuring data are collected under the same conditions across trials, subjects, or observations; recorded and processed in a standard format; 
+
+%For example, stable code helps ensure data are collected, recorded, and processed consistently, and that analyses performed on those data yield reliable results. 
+
+%Code that remains stable over time is essential to ensuring that data are collected under constant conditions, recorded and stored in a standard format, and yield accurate results when analyzed.
+
+%For example, stable code helps ensure all data are collected under consistent conditions, recorded and stored in a consistent format, and treated consistently during analyses.
+ 
+% An essential feature of software used in scientific research is that it behaves consistently every time it is run. For example, stable code can be crucial to ensuring constant conditions during data collection (e.g., across recordings, trials, or subjects) and that accurate, reliable outputs from statistical analyses. Additionally, modern open science practices encourage sharing code and data to enable others to explore, reproduce, learn from, and build upon existing work. Scientists, researchers, and educators produce many different forms of research-related code (e.g., experiments, analyses, tutorials, and demos) that they may seek to share with a wide range of audiences including collaborators, students, the broader scientific community, and the general public.
+
+%Modern scientific research often involves writing code for various purposes throughout the research process, from designing experiments to performing data analyses to presenting results through tutorials or demos. A nearly universal requirement of any form of research-related code is that its behavior and outputs must remain consistent every time it is run, both over time and across users. This stability can be crucial to ensuring that data are collected under identical conditions (e.g., across recordings, trials, or subjects) and that statistical analyses yield accurate, consistent results. Additionally, open science practices encourage sharing code and data publicly to enable others to explore, reproduce, learn from, and build upon existing work. Scientists, researchers, and educators may seek to share research-related code with a wide range of audiences including collaborators, students, the broader scientific community, and the general public.
+
+%Modern scientific research often involves writing code throughout the research process, from designing experiments, to analyzing data, to presenting findings via tutorials or public demos. 
+
+%Modern scientific research frequently entails writing software code for a wide range of purposes throughout the research process, from designing experiments, to processing and analyzing data, to sharing results and techniques with peers or students through tutorials, demos, workshops, and classes. One requirement common to nearly all research-related code it that its behavior and/or outputs remain consistent and predictable every time it is run, both over time and across different users. For instance, this stability can be crucial to ensuring that data are collected under the same conditions (e.g., across recordings, trials, or subjects) and that statistical analyses yield accurate, reliable results. 
+
+%However, one requirement common to virtually all forms of research-related code is that it behave consistently and predictably every time it is run, both over time and across users.
+%This stability can be crucial, for example, to carrying out studies that span multiple years or institutions
+%This stability can be crucial to ensuring, for example, that data are collected under the same conditions (e.g., across recordings, trials, or subjects) over multiple months or years, and can be accessed, processed, and analyzed consistently by collaborators across multiple institutions or countries.
+
+%For example, this stability can be crucial to ensuring that data are collected under the same conditions (e.g., across recordings, trials, or subjects), preprocessed in the same manner, stored in a standardized format, and treated equitably by statistical analyses.
+%This stability can be crucial to ensuring, for example, that data are collected under constant conditions (e.g., across recordings, trials, or subjects), are preprocessed and stored in a standard format, and that they can be easily accessed and consistently analyzed by collaborators across multiple institutions or countries.
+
+%Scientists, researchers, and educators may seek to share research code with audiences that have a broad range of scientific and technical backgrounds, including collaborators, students, 
+%stakeholders, 
+%the broader scientific community, and the general public.
+
+
+% ========================================================================================================
+
+
+%Python has become one of the most widely used and fastest-growing scientific programming languages \cite{MullEtal15}, in part due to its accessible, high-level grammar and extensive ecosystem of powerful third-party data science tools, including platforms for interactive development (e.g., Project Jupyter, \cite{KluyEtal16}; Google Colaboratory), community-maintained libraries for data manipulation (e.g., \texttt{NumPy} \cite{HarrEtal20}, \texttt{SciPy} \cite{VirtEtal20}, \texttt{Pandas} \cite{McKi10}) and visualization (e.g., \texttt{Matplotlib} \cite{Hunt07}, \texttt{seaborn} \cite{Wask21}), and myriad other software. However, this heavy use of third-party packages also presents a challenge to writing and sharing stable, reproducible scientific Python code: different versions of the same third-party library can behave differently, use different syntax, add or drop support for specific functions or other libraries, address (or introduce) bugs, and so on. While these issues are present to some extent in any language or ecosystem, they have a particular impact on the Python community due to its unusually rapid growth relative to other languages. Ensuring consistent behavior over time and across users therefore typically requires ensuring that code is shared and run with a specific set of versions for each package used. 
+
+%the availability of powerful third-party tools \cite{MullEtal15} . 
+%an extensive ecosystem of third-party tools \cite{MullEtal15}.
+
+%Python \cite{vanR95} has become one of the most widely used and fastest-growing scientific programming languages by combining an accessible, high-level syntax with the availability of powerful third-party tools that facilitate rapid development and collaboration \cite{MullEtal15}.
+
+
+%However, this heavy emphasis on third-party libraries also presents a challenge to writing and sharing stable, reproducible scientific Python code: different versions of the same library may behave differently, adopt changes in syntax, add or drop support for specific functions or other libraries, save data in (and expect loaded data to adhere to) different formats, address (or introduce) bugs, and so on.
+
+%Ensuring Python code behaves consistently over time and across users therefore typically requires making sure it is always run with the same specific set of versions for each package used.
+
+
+% ========================================================================================================
+
+
+%They can also distribute this custom environment alongside their code as a configuration file that explicitly lists required package versions, enabling others to build identical copies for themselves.
+
+% ========================================================================================================
+
+%For example, sharing an analysis or tutorial that relies on a particular Docker image to run properly not only necessitates writing and distributing extra configuration files and setup instructions, but more significantly, requires anyone hoping to run code (including the original author) to first be able to install and navigate additional software that is likely far more complex and resource-intensive than the actual analysis or tutorial code it facilitates running.
+
+%For example, sharing an analysis or tutorial that relies on a particular Docker image to run properly not only necessitates distributing extra configuration files and setup instructions, but also requires both the original author and anyone with whom the code is shared to first install and navigate additional software that is likely more complex and resource-intensive than the actual code it facilitates running.
+
+%For example, sharing an analysis or tutorial that relies on a particular Docker image to run properly necessarily requires writing and distributing extra configuration files and setup instructions. But more significantly, it also requires that anyone seeking to run the code first be able to install and navigate additional software that is likely far more complex and resource-intensive than the actual analysis or tutorial it facilitates running.
+
+%For example, sharing an analysis or tutorial that relies on a particular Docker image to run properly would, of course, necessitate writing and distributing extra configuration files (e.g., a \texttt{Dockerfile} or \texttt{docker-compose.yaml}) as well as initial setup instructions. %While this may be a mere minor inconvenience (if not a fact of everyday life) for users seasoned in DevOps tooling, the impact is far more significant in a research context, requiring both the original author and anyone else who may want to run their code (whether Z
+
+% ========================================================================================================
+
+
+% First, most tools for building a Python environment from a configuration file (e.g., ..) work by installing all requirements upfront, but typically do not enforce their presence after this initial setup. This makes it easy to inadvertently alter the environment after this initial setup. For example, suppose a researcher has implemented a series of analyses using version 1.0 of ``Package \textit{X}," and decides to perform an additional analysis that requires ``Package \textit{Y}." If Package \textit{Y} depends on version 1.1 of Package \textit{X}, then Package \textit{X} will be upgraded to accommodate this new requirement, potentially leading to inconsistent behavior of code that uses Package \textit{X}.
+
+%Second, it can add substantially to the technical knowledge, system resources, and initial setup required to share and run the actual code of interest. For example, sharing research code that relies on a particular Docker image to run properly not only necessitates additional configuration files and setup steps, but also requires both the author and all users with whom it may be shared to install and navigate additional software that is likely more complicated and resource-intensive than the actual code being shared.
+
+% While effective, such tools can add a significant degree of complexity to the process of sharing research code for both the author and 
+
+
+% ========================================================================================================
+
+
+%Second, typical environment management tools don't enforce that specified requirements \textit{remain} satisfied after they are initially installed. 
+
+%This makes it easy to inadvertently alter existing packages in a Python environment at any point in time, potentially affecting the behavior of code that depends on them. 
+
+%For instance, suppose a researcher has implemented a series of analyses using version 1.0 of ``Package \textit{X}," and decides to  perform an additional analysis that requires installing ``Package \textit{Y}." 
+
+%If Package \textit{Y} depends on version 1.1 of Package \textit{X}, then Package \textit{X} will be upgrade to accommodate this new requirement, potentially altering or breaking the previous analyses in future runs, as well as for anyone with whom  analysis code. 
+
+%Further, if earlier analyses require functionality 
+
+%install a fixed set of required Python packages in an isolated environment (e.g., from a \texttt{requirements.txt}, \texttt{pyproject.toml}, \texttt{environment.yml}, \texttt{Pipfile}, \texttt{Dockerfile RUN} instruction, etc.), few, if any, enforce that the specified requirements \textit{remain} satisfied after this initial setup.
+
+
+% ========================================================================================================
+
+
+%\texttt{davos} was created\comment{designed?} to address these issues by providing a novel, Python-native framework for creating, sharing, and running reproducible workflows. The general scheme for using \textit{davos} (described in \hyperref[sec:functionalities]{\textit{Software functionalities}}, below) was designed with \textbf{NUMBER} goals in mind: to be more accessible, intuitive, and lightweight than typical environment management tools; to guarantee the desired version
+
+%We created the davos package \comment{the davos package was created} to address these issues by providing a novel, Python-based \comment{Python-native(?)} framework for writing, sharing, and running reproducible code. davos was designed with the goal of stimultaneously being being more accessible, intuitive, and lightweight than typical environment management tools; guaranteeing that Python workflows always use the intended package versions (even beyond their initial installation); providing greater, more precise control over which package versions are used for what purposes
+
+%We created \texttt{davos} to address these issues, with the goal of providing an alternative approach to ensuring reproducibility over time and across users that is more intuitive
+
+%The \texttt{davos} package was designed to address these issues by providing an intuitive, lightweight, and Python-native way to ensure reproducibility over time and across users. With \texttt{davos} users to distribute reproducible workflows as a single, ready-to-run Jupyter notebook \cite{KluyEtal16} that specifies
+
+
+% ========================================================================================================
+
+
+%For greater control over the behavior of \texttt{smuggle} statements, \texttt{davos} defines an additional construct called the ``onion comment''. An onion comment is a special type of inline comment that may be placed on a line containing a \texttt{smuggle} statement to customize how \texttt{davos} searches for the smuggled package locally and, if necessary, downloads and installs it. Onion comments follow a simple syntax based on the ``type comment'' syntax introduced in PEP 484 \cite{vanREtal14}, and are designed to make managing packages with \texttt{davos} intuitive and familiar. To construct an onion comment, simply provide the name of the installer program (e.g., \texttt{pip}) and the same arguments one would use to manually install the package as desired via the command line\comment{(see Fig.~\ref{fig:snippets})} (see Fig.~\ref{fig:snippets}, lines 3--10 for examples).
+
+%Onion comments are useful when smuggling a package whose distribution name (i.e., the name used when installing it) is different from its top-level module name (i.e., the name used when importing it; e.g., Fig.~\ref{fig:snippets}, lines 12--13). However, the most powerful use of the onion comment is making \texttt{smuggle} statements \textit{version-sensitive}. If an onion comment includes a version specifier \cite{CoghStuf13} (e.g., Fig.~\ref{fig:snippets}, lines 15--19), \texttt{davos} will ensure that the version of the package loaded into the notebook always matches the specific version requested, or satisfies the given version constraints. If the smuggled package exists locally, \texttt{davos} will extract its version info from its metadata and compare it to the specifier provided. If the two are incompatible (or no local installation is found), \texttt{davos} will install and load a suitable version of the package instead. Onion comments can similarly be used to smuggle specific VCS references (e.g., Git \cite{TorvHama05}  branches, commits, tags, etc.; Fig.~\ref{fig:snippets}, lines 21--22).
+
+%\texttt{davos} processes onion comments internally before forwarding arguments to the installer program. In addition to preventing onion comments from being used as a vehicle for shell injection attacks, this allows \texttt{davos} take certain logical actions when particular arguments are passed (e.g., Fig.~\ref{fig:snippets}, lines 24--28). For example, the \texttt{--force-reinstall}, \texttt{-I}/\texttt{--ignore-installed}, and \texttt{-U}/\texttt{--upgrade} flags will all cause \texttt{davos} to skip searching for a smuggled package locally before installing a new copy; \texttt{--no-input} will temporarily enable \texttt{davos}'s non-interactive mode (see Section \ref{subsec:onion}); and installing a package into \texttt{<dir>} with \texttt{--target <dir>} will cause \texttt{dir} to be prepended to the module search path (\texttt{sys.path}), if necessary, so the package can be imported.
+
+%\begin{figure}[h]
+%\centering
+%\includegraphics[width=\textwidth]{figs/snippets.pdf}
+%\caption{\small Example \texttt{smuggle} statements and accompanying onion comments.}
+%\label{fig:snippets}
+%\end{figure}
+
+
+
+
+%If an onion comment includes a version specifier (\cite{CoghStuf13}; e.g., Fig.~\ref{fig:snippets}, lines \_\_\_-\_\_\_), \texttt{davos} will first check whether the smuggled package exists locally, and if so, extract its version info from its metadata to compare against the requested version. If the installed version doesn't satisfy the given constraint (or if no local version is found), \texttt{davos} will install and load a suitable version instead. This guarantees the notebook is always run with a specific (or constrained) version of a particular package. Similarly, \texttt{davos}
+%If the package exists locally, \texttt{davos} will extract its version from metadata before loading it to determine its version, and if it does not match the requested version (or if no local installation is found),
+%If an onion comment includes a version specifier \cite{CoghStuf13}, \texttt{davos} will check whether the smuggled package is installed locally (as usual), and if so, additionally parse its metadata to determine
+%Adding a version specifier \cite{CoghStuf13} to an onion comment guarantees the version loaded into the notebook will matches the version specified
+
+
+
+% ========================================================================================================
+
+
+%Functionally, importing \texttt{davos} appears to add "\texttt{smuggle}'' to Python's set of reserved keywords (like \texttt{import}, \texttt{def}, \texttt{return}, etc.) and cause comments to be parsed at runtime, when they're otherwise not. However, \texttt{davos} doesn't actually modify 
+
+%Functionally, importing \texttt{davos} appears to create a new reserved Python keyword (like \texttt{import}, \texttt{def}, or \texttt{return}) and cause comments affect runtime behavior, which they normally do not. However, \texttt{davos} doesn't actually modify the rules of Python's parser and lexical analyzer in order to do so---in fact, modifying the Python grammar isn't possible at runtime and would require rebuilding the interpreter. Instead, \texttt{davos} 
+% Functionally, importing \texttt{davos} appears to make "\texttt{smuggle}'' a valid Python keyword (like ``\texttt{import}'', ``\texttt{def}'', or ``\texttt{return}'') as well as cause Python to  
+% Functionally, importing \texttt{davos} appears to create a new reserved Python keyword, ``\texttt{smuggle}'', similar to ``\texttt{import}'', ``\texttt{def}'', or ``\texttt{return}''.
+% Functionally, importing \texttt{davos} appears to add ``\texttt{smuggle}'' to Python's reserved keywords (like ``\texttt{import}'', ``\texttt{def}'', and ``\texttt{return}''). It also appears to cause comments to be parsed, their contents to be potentially relevant to the code's behavior at runtime, which they normally are not.
+%It also appears to cause comments to be parsed and capable of affecting code behavior at runtime, which they normally are not. 
+%It also appears to cause Python to fundamentally redefine comments as potentially relevant to code behavior at runtime, which they normally are not. 
+
+
+% ========================================================================================================
+
+
+%\definecolor{backgroundgrey}{RGB}{244,246,249}
+%\definecolor{commentgrey}{RGB}{91,99,110}
+%\definecolor{keywordred}{RGB}{194,11,36}
+%
+%\lstset{
+%    backgroundcolor=\color{backgroundgrey},
+%    basicstyle=\footnotesize\fontfamily{qag}\selectfont, %\ttfamily,
+%    breaklines=true,
+%    commentstyle=\color{commentgrey},
+%    language=Python,
+%    emph={import, smuggle, from, as},
+%    emphstyle=\color{keywordred},
+%}
+%
+%\begin{lstlisting}
+%import davos
+%
+%# if numpy is not installed locally, pip-install it and display verbose output
+%smuggle numpy as np    # pip: numpy --verbose
+%
+%# pip-install pandas without using or writing to the package cache
+%smuggle pandas as pd    # pip: pandas --no-cache-dir
+%
+%# install scipy from a relative local path, in editable mode
+%from scipy.stats smuggle ttest_ind # pip: -e ../../pkgs/scipy
+%\end{lstlisting}
+
diff --git a/paper/elsarticle-num.bst b/paper/elsarticle-num.bst
new file mode 100644
index 00000000..14499bfd
--- /dev/null
+++ b/paper/elsarticle-num.bst
@@ -0,0 +1,1509 @@
+%%
+%% This is file `elsarticle-num.bst' (Version 2.1),
+%% 
+%% Copyright 2007-2020 Elsevier Ltd
+%% 
+%% This file is part of the 'Elsarticle Bundle'.
+%% ---------------------------------------------
+%% 
+%% It may be distributed under the conditions of the LaTeX Project Public
+%% License, either version 1.2 of this license or (at your option) any
+%% later version.  The latest version of this license is in
+%%    http://www.latex-project.org/lppl.txt
+%% and version 1.2 or later is part of all distributions of LaTeX
+%% version 1999/12/01 or later.
+%% 
+%% 
+%% $Id: elsarticle-num.bst 194 2020-11-23 11:29:27Z rishi $
+%% 
+%% $URL: https://lenova.river-valley.com/svn/elsarticle/trunk/elsarticle-num.bst $
+%%
+%% ----------------------------------------
+
+ENTRY
+  { address
+    author
+    booktitle
+    chapter
+    edition
+    editor
+    howpublished
+    institution
+    journal
+    key
+    month
+    note
+    number
+    organization
+    pages
+    publisher
+    school
+    series
+    title
+    type
+    volume
+    year
+    eprint % urlbst
+    doi % urlbst
+    url % urlbst
+    lastchecked % urlbst
+  }
+  {}
+  { label }
+
+INTEGERS { output.state before.all mid.sentence after.sentence after.block }
+
+STRINGS { urlintro eprinturl eprintprefix doiprefix doiurl openinlinelink closeinlinelink } % urlbst...
+INTEGERS { hrefform inlinelinks makeinlinelink addeprints adddoiresolver }
+FUNCTION {init.urlbst.variables}
+{
+  "Available from: " 'urlintro := % prefix before URL
+  "http://arxiv.org/abs/" 'eprinturl := % prefix to make URL from eprint ref
+  "arXiv:" 'eprintprefix := % text prefix printed before eprint ref
+  "https://doi.org/" 'doiurl := % prefix to make URL from DOI
+  "doi:" 'doiprefix :=      % text prefix printed before DOI ref
+  #1 'addeprints :=         % 0=no eprints; 1=include eprints
+  #1 'adddoiresolver :=     % 0=no DOI resolver; 1=include it
+  #2 'hrefform :=           % 0=no crossrefs; 1=hypertex xrefs; 2=hyperref refs
+  #1 'inlinelinks :=        % 0=URLs explicit; 1=URLs attached to titles
+  % the following are internal state variables, not config constants
+  #0 'makeinlinelink :=     % state variable managed by setup.inlinelink
+  "" 'openinlinelink :=     % ditto
+  "" 'closeinlinelink :=    % ditto
+}
+INTEGERS {
+  bracket.state
+  outside.brackets
+  open.brackets
+  within.brackets
+  close.brackets
+}
+FUNCTION {init.state.consts}
+{ #0 'outside.brackets := % urlbst
+  #1 'open.brackets :=
+  #2 'within.brackets :=
+  #3 'close.brackets :=
+
+  #0 'before.all :=
+  #1 'mid.sentence :=
+  #2 'after.sentence :=
+  #3 'after.block :=
+}
+
+STRINGS { s t }
+
+FUNCTION {output.nonnull.original}
+{ 's :=
+  output.state mid.sentence =
+    { ", " * write$ }
+    { output.state after.block =
+        { add.period$ write$
+          newline$
+          "\newblock " write$
+        }
+        { output.state before.all =
+            'write$
+            { add.period$ " " * write$ }
+          if$
+        }
+      if$
+      mid.sentence 'output.state :=
+    }
+  if$
+  s
+}
+
+FUNCTION {setup.inlinelink}
+{ makeinlinelink
+    { hrefform #1 = % hypertex
+        { "\special {html:<a href=" quote$ * url * quote$ * "> }{" * 'openinlinelink :=
+          "\special {html:</a>}" 'closeinlinelink :=
+          }
+        { hrefform #2 = % hyperref
+            { "\href{" url * "}{" * 'openinlinelink :=
+              "}" 'closeinlinelink :=
+              }
+            'skip$
+          if$ % hrefform #2 =
+        }
+      if$ % hrefform #1 =
+      #0 'makeinlinelink :=
+    }
+    'skip$
+ if$ % makeinlinelink
+}
+FUNCTION {add.inlinelink}
+{ openinlinelink empty$
+    'skip$
+    { openinlinelink swap$ * closeinlinelink *
+      "" 'openinlinelink :=
+      }
+  if$
+}
+FUNCTION {output.nonnull}
+{ % Save the thing we've been asked to output
+  's :=
+  % If the bracket-state is close.brackets, then add a close-bracket to
+  % what is currently at the top of the stack, and set bracket.state
+  % to outside.brackets
+  bracket.state close.brackets =
+    { "]" *
+      outside.brackets 'bracket.state :=
+    }
+    'skip$
+  if$
+  bracket.state outside.brackets =
+    { % We're outside all brackets -- this is the normal situation.
+      % Write out what's currently at the top of the stack, using the
+      % original output.nonnull function.
+      s
+      add.inlinelink
+      output.nonnull.original % invoke the original output.nonnull
+    }
+    { % Still in brackets.  Add open-bracket or (continuation) comma, add the
+      % new text (in s) to the top of the stack, and move to the close-brackets
+      % state, ready for next time (unless inbrackets resets it).  If we come
+      % into this branch, then output.state is carefully undisturbed.
+      bracket.state open.brackets =
+        { " [" * }
+        { ", " * } % bracket.state will be within.brackets
+      if$
+      s *
+      close.brackets 'bracket.state :=
+    }
+  if$
+}
+
+FUNCTION {inbrackets}
+{ bracket.state close.brackets =
+    { within.brackets 'bracket.state := } % reset the state: not open nor closed
+    { open.brackets 'bracket.state := }
+  if$
+}
+
+FUNCTION {format.lastchecked}
+{ lastchecked empty$
+    { "" }
+    { inbrackets "cited " lastchecked * }
+  if$
+}
+
+FUNCTION {output}
+{ duplicate$ empty$
+    'pop$
+    'output.nonnull
+  if$
+}
+
+FUNCTION {output.check}
+{ 't :=
+  duplicate$ empty$
+    { pop$ "empty " t * " in " * cite$ * warning$ }
+    'output.nonnull
+  if$
+}
+
+FUNCTION {fin.entry.original}
+{ add.period$
+  write$
+  newline$
+}
+
+FUNCTION {new.block}
+{ output.state before.all =
+    'skip$
+    { after.block 'output.state := }
+  if$
+}
+
+FUNCTION {new.sentence}
+{ output.state after.block =
+    'skip$
+    { output.state before.all =
+        'skip$
+        { after.sentence 'output.state := }
+      if$
+    }
+  if$
+}
+
+FUNCTION {add.blank}
+{  " " * before.all 'output.state :=
+}
+
+FUNCTION {date.block}
+{
+  add.blank
+}
+
+FUNCTION {not}
+{   { #0 }
+    { #1 }
+  if$
+}
+
+FUNCTION {and}
+{   'skip$
+    { pop$ #0 }
+  if$
+}
+
+FUNCTION {or}
+{   { pop$ #1 }
+    'skip$
+  if$
+}
+
+FUNCTION {new.block.checka}
+{ empty$
+    'skip$
+    'new.block
+  if$
+}
+
+FUNCTION {new.block.checkb}
+{ empty$
+  swap$ empty$
+  and
+    'skip$
+    'new.block
+  if$
+}
+
+FUNCTION {new.sentence.checka}
+{ empty$
+    'skip$
+    'new.sentence
+  if$
+}
+
+FUNCTION {new.sentence.checkb}
+{ empty$
+  swap$ empty$
+  and
+    'skip$
+    'new.sentence
+  if$
+}
+
+FUNCTION {field.or.null}
+{ duplicate$ empty$
+    { pop$ "" }
+    'skip$
+  if$
+}
+
+FUNCTION {emphasize}
+{ skip$ }
+
+FUNCTION {capitalize}
+{ "u" change.case$ "t" change.case$ }
+
+FUNCTION {space.word}
+{ " " swap$ * " " * }
+
+ % Here are the language-specific definitions for explicit words.
+ % Each function has a name bbl.xxx where xxx is the English word.
+ % The language selected here is ENGLISH
+FUNCTION {bbl.and}
+{ "and"}
+
+FUNCTION {bbl.etal}
+{ "et~al." }
+
+FUNCTION {bbl.editors}
+{ "Eds." }
+
+FUNCTION {bbl.editor}
+{ "Ed." }
+
+FUNCTION {bbl.edby}
+{ "edited by" }
+
+FUNCTION {bbl.edition}
+{ "Edition" }
+
+FUNCTION {bbl.volume}
+{ "Vol." }
+
+FUNCTION {bbl.of}
+{ "of" }
+
+FUNCTION {bbl.number}
+{ "no." }
+
+FUNCTION {bbl.nr}
+{ "no." }
+
+FUNCTION {bbl.in}
+{ "in" }
+
+FUNCTION {bbl.pages}
+{ "pp." }
+
+FUNCTION {bbl.page}
+{ "p." }
+
+FUNCTION {bbl.chapter}
+{ "Ch." }
+
+FUNCTION {bbl.techrep}
+{ "Tech. Rep." }
+
+FUNCTION {bbl.mthesis}
+{ "Master's thesis" }
+
+FUNCTION {bbl.phdthesis}
+{ "Ph.D. thesis" }
+
+FUNCTION {bbl.first}
+{ "1st" }
+
+FUNCTION {bbl.second}
+{ "2nd" }
+
+FUNCTION {bbl.third}
+{ "3rd" }
+
+FUNCTION {bbl.fourth}
+{ "4th" }
+
+FUNCTION {bbl.fifth}
+{ "5th" }
+
+FUNCTION {bbl.st}
+{ "st" }
+
+FUNCTION {bbl.nd}
+{ "nd" }
+
+FUNCTION {bbl.rd}
+{ "rd" }
+
+FUNCTION {bbl.th}
+{ "th" }
+
+MACRO {jan} {"Jan."}
+
+MACRO {feb} {"Feb."}
+
+MACRO {mar} {"Mar."}
+
+MACRO {apr} {"Apr."}
+
+MACRO {may} {"May"}
+
+MACRO {jun} {"Jun."}
+
+MACRO {jul} {"Jul."}
+
+MACRO {aug} {"Aug."}
+
+MACRO {sep} {"Sep."}
+
+MACRO {oct} {"Oct."}
+
+MACRO {nov} {"Nov."}
+
+MACRO {dec} {"Dec."}
+
+FUNCTION {eng.ord}
+{ duplicate$ "1" swap$ *
+  #-2 #1 substring$ "1" =
+     { bbl.th * }
+     { duplicate$ #-1 #1 substring$
+       duplicate$ "1" =
+         { pop$ bbl.st * }
+         { duplicate$ "2" =
+             { pop$ bbl.nd * }
+             { "3" =
+                 { bbl.rd * }
+                 { bbl.th * }
+               if$
+             }
+           if$
+          }
+       if$
+     }
+   if$
+}
+
+MACRO {acmcs} {"ACM Comput. Surv."}
+
+MACRO {acta} {"Acta Inf."}
+
+MACRO {cacm} {"Commun. ACM"}
+
+MACRO {ibmjrd} {"IBM J. Res. Dev."}
+
+MACRO {ibmsj} {"IBM Syst.~J."}
+
+MACRO {ieeese} {"IEEE Trans. Softw. Eng."}
+
+MACRO {ieeetc} {"IEEE Trans. Comput."}
+
+MACRO {ieeetcad}
+ {"IEEE Trans. Comput.-Aided Design Integrated Circuits"}
+
+MACRO {ipl} {"Inf. Process. Lett."}
+
+MACRO {jacm} {"J.~ACM"}
+
+MACRO {jcss} {"J.~Comput. Syst. Sci."}
+
+MACRO {scp} {"Sci. Comput. Programming"}
+
+MACRO {sicomp} {"SIAM J. Comput."}
+
+MACRO {tocs} {"ACM Trans. Comput. Syst."}
+
+MACRO {tods} {"ACM Trans. Database Syst."}
+
+MACRO {tog} {"ACM Trans. Gr."}
+
+MACRO {toms} {"ACM Trans. Math. Softw."}
+
+MACRO {toois} {"ACM Trans. Office Inf. Syst."}
+
+MACRO {toplas} {"ACM Trans. Prog. Lang. Syst."}
+
+MACRO {tcs} {"Theoretical Comput. Sci."}
+
+FUNCTION {write.url}
+{ url empty$
+    { skip$ }
+    { "\newline\urlprefix\url{" url * "}" * write$ newline$ }
+  if$
+}
+
+INTEGERS { nameptr namesleft numnames }
+
+FUNCTION {format.names}
+{ 's :=
+  #1 'nameptr :=
+  s num.names$ 'numnames :=
+  numnames 'namesleft :=
+    { namesleft #0 > }
+    { s nameptr
+      "{f.~}{vv~}{ll}{, jj}" format.name$
+    't :=
+      nameptr #1 >
+        {
+          namesleft #1 >
+            { ", " * t * }
+            {
+              "," *
+              s nameptr "{ll}" format.name$ duplicate$ "others" =
+                { 't := }
+                { pop$ }
+              if$
+              t "others" =
+                {
+                  " " * bbl.etal *
+                }
+                { " " * t * }
+              if$
+            }
+          if$
+        }
+        't
+      if$
+      nameptr #1 + 'nameptr :=
+      namesleft #1 - 'namesleft :=
+    }
+  while$
+}
+FUNCTION {format.names.ed}
+{ format.names }
+FUNCTION {format.authors}
+{ author empty$
+    { "" }
+    { author format.names }
+  if$
+}
+
+FUNCTION {format.editors}
+{ editor empty$
+    { "" }
+    { editor format.names
+      editor num.names$ #1 >
+        { " (" * bbl.editors * ")" * }
+        { " (" * bbl.editor * ")" * }
+      if$
+    }
+  if$
+}
+
+FUNCTION {format.in.editors}
+{ editor empty$
+    { "" }
+    { editor format.names.ed
+      editor num.names$ #1 >
+        { " (" * bbl.editors * ")" * }
+        { " (" * bbl.editor * ")" * }
+      if$
+    }
+  if$
+}
+
+FUNCTION {format.note}
+{
+ note empty$
+    { "" }
+    { note #1 #1 substring$
+      duplicate$ "{" =
+        'skip$
+        { output.state mid.sentence =
+          { "l" }
+          { "u" }
+        if$
+        change.case$
+        }
+      if$
+      note #2 global.max$ substring$ *
+    }
+  if$
+}
+
+FUNCTION {format.title}
+{ title empty$
+    { "" }
+    { title "t" change.case$
+    }
+  if$
+}
+
+FUNCTION {output.bibitem.original}
+{ newline$
+  "\bibitem{" write$
+  cite$ write$
+  "}" write$
+  newline$
+  ""
+  before.all 'output.state :=
+}
+
+FUNCTION {n.dashify}
+{
+  't :=
+  ""
+    { t empty$ not }
+    { t #1 #1 substring$ "-" =
+        { t #1 #2 substring$ "--" = not
+            { "--" *
+              t #2 global.max$ substring$ 't :=
+            }
+            {   { t #1 #1 substring$ "-" = }
+                { "-" *
+                  t #2 global.max$ substring$ 't :=
+                }
+              while$
+            }
+          if$
+        }
+        { t #1 #1 substring$ *
+          t #2 global.max$ substring$ 't :=
+        }
+      if$
+    }
+  while$
+}
+
+FUNCTION {word.in}
+{ bbl.in
+  ":" *
+  " " * }
+
+FUNCTION {format.date}
+{ year empty$
+    { month empty$
+        { "" }
+        { "there's a month but no year in " cite$ * warning$
+          month
+        }
+      if$
+    }
+    { month empty$
+        'year
+        { month " " * year * }
+      if$
+    }
+  if$
+  duplicate$ empty$
+    'skip$
+    {
+      before.all 'output.state :=
+    " (" swap$ * ")" *
+    }
+  if$
+}
+
+FUNCTION{format.year}
+{ year duplicate$ empty$
+    { "empty year in " cite$ * warning$ pop$ "" }
+    { "(" swap$ * ")" * }
+  if$
+}
+
+FUNCTION {format.btitle}
+{ title
+}
+
+FUNCTION {tie.or.space.connect}
+{ duplicate$ text.length$ #3 <
+    { "~" }
+    { " " }
+  if$
+  swap$ * *
+}
+
+FUNCTION {either.or.check}
+{ empty$
+    'pop$
+    { "can't use both " swap$ * " fields in " * cite$ * warning$ }
+  if$
+}
+
+FUNCTION {format.bvolume}
+{ volume empty$
+    { "" }
+    { bbl.volume volume tie.or.space.connect
+      series empty$
+        'skip$
+        { bbl.of space.word * series emphasize * }
+      if$
+      "volume and number" number either.or.check
+    }
+  if$
+}
+
+FUNCTION {format.number.series}
+{ volume empty$
+    { number empty$
+        { series field.or.null }
+        { output.state mid.sentence =
+            { bbl.number }
+            { bbl.number capitalize }
+          if$
+          number tie.or.space.connect
+          series empty$
+            { "there's a number but no series in " cite$ * warning$ }
+            { bbl.in space.word * series * }
+          if$
+        }
+      if$
+    }
+    { "" }
+  if$
+}
+
+FUNCTION {is.num}
+{ chr.to.int$
+  duplicate$ "0" chr.to.int$ < not
+  swap$ "9" chr.to.int$ > not and
+}
+
+FUNCTION {extract.num}
+{ duplicate$ 't :=
+  "" 's :=
+  { t empty$ not }
+  { t #1 #1 substring$
+    t #2 global.max$ substring$ 't :=
+    duplicate$ is.num
+      { s swap$ * 's := }
+      { pop$ "" 't := }
+    if$
+  }
+  while$
+  s empty$
+    'skip$
+    { pop$ s }
+  if$
+}
+
+FUNCTION {convert.edition}
+{ edition extract.num "l" change.case$ 's :=
+  s "first" = s "1" = or
+    { bbl.first 't := }
+    { s "second" = s "2" = or
+        { bbl.second 't := }
+        { s "third" = s "3" = or
+            { bbl.third 't := }
+            { s "fourth" = s "4" = or
+                { bbl.fourth 't := }
+                { s "fifth" = s "5" = or
+                    { bbl.fifth 't := }
+                    { s #1 #1 substring$ is.num
+                        { s eng.ord 't := }
+                        { edition 't := }
+                      if$
+                    }
+                  if$
+                }
+              if$
+            }
+          if$
+        }
+      if$
+    }
+  if$
+  t
+}
+
+FUNCTION {format.edition}
+{ edition empty$
+    { "" }
+    { output.state mid.sentence =
+        { convert.edition "l" change.case$ " " * bbl.edition * }
+        { convert.edition "t" change.case$ " " * bbl.edition * }
+      if$
+    }
+  if$
+}
+
+INTEGERS { multiresult }
+
+FUNCTION {multi.page.check}
+{ 't :=
+  #0 'multiresult :=
+    { multiresult not
+      t empty$ not
+      and
+    }
+    { t #1 #1 substring$
+      duplicate$ "-" =
+      swap$ duplicate$ "," =
+      swap$ "+" =
+      or or
+        { #1 'multiresult := }
+        { t #2 global.max$ substring$ 't := }
+      if$
+    }
+  while$
+  multiresult
+}
+
+FUNCTION {format.pages}
+{ pages empty$
+    { "" }
+    { pages multi.page.check
+        { bbl.pages pages n.dashify tie.or.space.connect }
+        { bbl.page pages tie.or.space.connect }
+      if$
+    }
+  if$
+}
+
+FUNCTION {format.journal.pages}
+{ pages empty$
+    'skip$
+    { duplicate$ empty$
+        { pop$ format.pages }
+        {
+          " " *
+          format.year * " " *
+          pages n.dashify *
+        }
+      if$
+    }
+  if$
+}
+
+FUNCTION {format.vol.num.pages}
+{
+  % volume field.or.null
+  " "
+  volume empty$
+    { pop$ "" }
+    { volume * }
+  if$
+  number empty$
+    'skip$
+    {
+      "~(" number * ")" * *
+      volume empty$
+        { "there's a number but no volume in " cite$ * warning$ }
+        'skip$
+      if$
+    }
+  if$
+}
+
+FUNCTION {format.chapter.pages}
+{ chapter empty$
+    { "" }
+    { type empty$
+        { bbl.chapter }
+        { type "l" change.case$ }
+      if$
+      chapter tie.or.space.connect
+    }
+  if$
+}
+
+FUNCTION {format.in.ed.booktitle}
+{ booktitle empty$
+    { "" }
+    { editor empty$
+        { word.in booktitle * }
+        { word.in format.in.editors * ", " *
+          booktitle * }
+      if$
+    }
+  if$
+}
+
+FUNCTION {empty.misc.check}
+{ author empty$ title empty$ howpublished empty$
+  month empty$ year empty$ note empty$
+  and and and and and
+    { "all relevant fields are empty in " cite$ * warning$ }
+    'skip$
+  if$
+}
+
+FUNCTION {format.thesis.type}
+{ type empty$
+    'skip$
+    { pop$
+      type "t" change.case$
+    }
+  if$
+}
+
+FUNCTION {format.tr.number}
+{ type empty$
+    { bbl.techrep }
+    'type
+  if$
+  number empty$
+    { "t" change.case$ }
+    { number tie.or.space.connect }
+  if$
+}
+
+FUNCTION {format.article.crossref}
+{
+  key empty$
+    { journal empty$
+        { "need key or journal for " cite$ * " to crossref " * crossref *
+          warning$
+          ""
+        }
+        { word.in journal emphasize * }
+      if$
+    }
+    { word.in key * " " *}
+  if$
+  " \cite{" * crossref * "}" *
+}
+
+FUNCTION {format.crossref.editor}
+{ editor #1 "{vv~}{ll}" format.name$
+  editor num.names$ duplicate$
+  #2 >
+    { pop$
+      " " * bbl.etal *
+    }
+    { #2 <
+        'skip$
+        { editor #2 "{ff }{vv }{ll}{ jj}" format.name$ "others" =
+            {
+              " " * bbl.etal *
+            }
+            { bbl.and space.word * editor #2 "{vv~}{ll}" format.name$
+              * }
+          if$
+        }
+      if$
+    }
+  if$
+}
+
+FUNCTION {format.book.crossref}
+{ volume empty$
+    { "empty volume in " cite$ * "'s crossref of " * crossref * warning$
+      word.in
+    }
+    { bbl.volume volume tie.or.space.connect
+      bbl.of space.word *
+    }
+  if$
+  editor empty$
+  editor field.or.null author field.or.null =
+  or
+    { key empty$
+        { series empty$
+            { "need editor, key, or series for " cite$ * " to crossref " *
+              crossref * warning$
+              "" *
+            }
+            { series emphasize * }
+          if$
+        }
+        { key * }
+      if$
+    }
+    { format.crossref.editor * }
+  if$
+  " \cite{" * crossref * "}" *
+}
+
+FUNCTION {format.incoll.inproc.crossref}
+{
+  editor empty$
+  editor field.or.null author field.or.null =
+  or
+    { key empty$
+        { booktitle empty$
+            { "need editor, key, or booktitle for " cite$ * " to crossref " *
+              crossref * warning$
+              ""
+            }
+            { word.in booktitle * }
+          if$
+        }
+        { word.in key * " " *}
+      if$
+    }
+    { word.in format.crossref.editor * " " *}
+  if$
+  " \cite{" * crossref * "}" *
+}
+
+FUNCTION {format.org.or.pub}
+{ 't :=
+  ""
+  year empty$
+    { "empty year in " cite$ * warning$ }
+    'skip$
+  if$
+  address empty$ t empty$ and
+  year empty$ and
+    'skip$
+    {
+      t empty$
+        { address empty$
+          'skip$
+          { address * }
+          if$
+        }
+        { t *
+          address empty$
+            'skip$
+            { ", " * address * }
+          if$
+        }
+      if$
+      year empty$
+        'skip$
+        { t empty$ address empty$ and
+            'skip$
+            { ", " * }
+          if$
+          year *
+        }
+      if$
+    }
+  if$
+}
+
+FUNCTION {format.publisher.address}
+{ publisher empty$
+    { "empty publisher in " cite$ * warning$
+      ""
+    }
+    { publisher }
+  if$
+  format.org.or.pub
+}
+
+FUNCTION {format.organization.address}
+{ organization empty$
+    { "" }
+    { organization }
+  if$
+  format.org.or.pub
+}
+
+FUNCTION {make.href.null}
+{
+  pop$
+}
+FUNCTION {make.href.hypertex}
+{
+  "\special {html:<a href=" quote$ *
+  swap$ * quote$ * "> }" * swap$ *
+  "\special {html:</a>}" *
+}
+FUNCTION {make.href.hyperref}
+{
+  "\href {" swap$ * "} {\path{" * swap$ * "}}" *
+}
+FUNCTION {make.href}
+{ hrefform #2 =
+    'make.href.hyperref      % hrefform = 2
+    { hrefform #1 =
+        'make.href.hypertex  % hrefform = 1
+        'make.href.null      % hrefform = 0 (or anything else)
+      if$
+    }
+  if$
+}
+
+FUNCTION {format.url}
+{ inlinelinks #1 = url empty$ or
+   { "" }
+   { hrefform #1 =
+       { % special case -- add HyperTeX specials
+         urlintro "\url{" url * "}" * url make.href.hypertex * }
+       { urlintro "\url{" * url * "}" * }
+     if$
+   }
+  if$
+}
+
+FUNCTION {format.eprint}
+{ eprint empty$
+    { "" }
+    { eprintprefix eprint * eprinturl eprint * make.href }
+  if$
+}
+
+FUNCTION {format.doi}
+{ doi empty$
+    { "" }
+    { doiprefix doi * doiurl doi * make.href }
+  if$
+}
+
+FUNCTION {output.url}
+{ url empty$
+    'skip$
+    { new.block
+      format.url output
+      format.lastchecked output
+    }
+  if$
+}
+
+FUNCTION {output.web.refs}
+{
+  new.block
+  output.url
+  addeprints eprint empty$ not and
+    { format.eprint output.nonnull }
+    'skip$
+  if$
+  adddoiresolver doi empty$ not and
+    { format.doi output.nonnull }
+    'skip$
+  if$
+}
+
+FUNCTION {output.bibitem}
+{ outside.brackets 'bracket.state :=
+  output.bibitem.original
+  inlinelinks url empty$ not and
+    { #1 'makeinlinelink := }
+    { #0 'makeinlinelink := }
+  if$
+}
+
+FUNCTION {fin.entry}
+{ output.web.refs  % urlbst
+  makeinlinelink       % ooops, it appears we didn't have a title for inlinelink
+    { setup.inlinelink % add some artificial link text here, as a fallback
+      "[link]" output.nonnull }
+    'skip$
+  if$
+  bracket.state close.brackets = % urlbst
+    { "]" * }
+    'skip$
+  if$
+  fin.entry.original
+}
+
+FUNCTION {webpage}
+{ output.bibitem
+  author empty$
+    { editor empty$
+        'skip$  % author and editor both optional
+        { format.editors output.nonnull }
+      if$
+    }
+    { editor empty$
+        { format.authors output.nonnull }
+        { "can't use both author and editor fields in " cite$ * warning$ }
+      if$
+    }
+  if$
+  new.block
+  title empty$ 'skip$ 'setup.inlinelink if$
+  format.title "title" output.check
+  inbrackets "online" output
+  new.block
+  year empty$
+    'skip$
+    { format.date "year" output.check }
+  if$
+  % We don't need to output the URL details ('lastchecked' and 'url'),
+  % because fin.entry does that for us, using output.web.refs.  The only
+  % reason we would want to put them here is if we were to decide that
+  % they should go in front of the rather miscellaneous information in 'note'.
+  new.block
+  note output
+  fin.entry
+}
+
+FUNCTION {article}
+{ output.bibitem
+  format.authors "author" output.check
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.title "title" output.check
+  crossref missing$
+    { journal
+      "journal" output.check
+      % add.blank
+  before.all 'output.state :=
+      format.vol.num.pages output
+    }
+    { format.article.crossref output.nonnull
+      format.pages output
+    }
+  if$
+  format.journal.pages
+  format.note output
+  pages empty$  
+	  { format.date "year" output.check }
+    'skip$ 
+  if$
+  fin.entry
+  write.url
+}
+
+FUNCTION {book}
+{ output.bibitem
+  author empty$
+    { format.editors "author and editor" output.check
+    }
+    { format.authors output.nonnull
+      crossref missing$
+        { "author and editor" editor either.or.check }
+        'skip$
+      if$
+    }
+  if$
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.btitle "title" output.check
+  crossref missing$
+    { format.edition output
+      format.bvolume output
+      format.number.series output
+      format.publisher.address output
+    }
+    {
+      format.book.crossref output.nonnull
+    }
+  if$
+  format.note output
+  fin.entry
+  write.url
+}
+
+FUNCTION {booklet}
+{ output.bibitem
+  format.authors output
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.title "title" output.check
+  howpublished output
+  address output
+  format.note output
+  format.date "year" output.check
+  fin.entry
+  write.url
+}
+
+FUNCTION {inbook}
+{ output.bibitem
+  author empty$
+    { format.editors "author and editor" output.check
+    }
+    { format.authors output.nonnull
+      crossref missing$
+        { "author and editor" editor either.or.check }
+        'skip$
+      if$
+    }
+  if$
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.btitle "title" output.check
+  crossref missing$
+    {
+      format.edition output
+      format.bvolume output
+      format.number.series output
+      format.publisher.address output
+      format.chapter.pages "chapter and pages" output.check
+    }
+    {
+      format.chapter.pages "chapter and pages" output.check
+      format.book.crossref output.nonnull
+    }
+  if$
+  format.pages "pages" output.check
+  format.note output
+  fin.entry
+  write.url
+}
+
+FUNCTION {incollection}
+{ output.bibitem
+  format.authors "author" output.check
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.title "title" output.check
+  crossref missing$
+    { format.in.ed.booktitle "booktitle" output.check
+      format.edition output
+      format.bvolume output
+      format.number.series output
+      format.publisher.address output
+      format.chapter.pages output
+    }
+    { format.incoll.inproc.crossref output.nonnull
+      format.chapter.pages output
+    }
+  if$
+  format.pages "pages" output.check
+  format.note output
+  fin.entry
+  write.url
+}
+
+FUNCTION {inproceedings}
+{ output.bibitem
+  format.authors "author" output.check
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.title "title" output.check
+  crossref missing$
+    { format.in.ed.booktitle "booktitle" output.check
+      format.edition output
+      format.bvolume output
+      format.number.series output
+      publisher empty$
+        { format.organization.address output }
+        { organization output
+          format.publisher.address output
+        }
+      if$
+    }
+    { format.incoll.inproc.crossref output.nonnull
+    }
+  if$
+  format.pages "pages" output.check
+  format.note output
+  fin.entry
+  write.url
+}
+
+FUNCTION {conference} { inproceedings }
+
+FUNCTION {manual}
+{ output.bibitem
+  author empty$
+    { organization empty$
+        'skip$
+        { organization output.nonnull
+          address output
+        }
+      if$
+    }
+    { format.authors output.nonnull }
+  if$
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.btitle "title" output.check
+  author empty$
+    { organization empty$
+    {
+          address output
+        }
+        'skip$
+      if$
+    }
+    {
+      organization output
+      address output
+    }
+  if$
+  format.edition output
+  format.note output
+  format.date "year" output.check
+  fin.entry
+  write.url
+}
+
+FUNCTION {mastersthesis}
+{ output.bibitem
+  format.authors "author" output.check
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.title "title" output.check
+  bbl.mthesis format.thesis.type output.nonnull
+  school "school" output.check
+  address output
+  format.note output
+  format.date "year" output.check
+  fin.entry
+  write.url
+}
+
+FUNCTION {misc}
+{ output.bibitem
+  format.authors output
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.title output
+  howpublished output
+  format.note output
+  format.date "year" output.check
+  fin.entry
+  write.url
+  empty.misc.check
+}
+
+FUNCTION {phdthesis}
+{ output.bibitem
+  format.authors "author" output.check
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.title "title" output.check
+  bbl.phdthesis format.thesis.type output.nonnull
+  school "school" output.check
+  address output
+  format.note output
+  format.date "year" output.check
+  fin.entry
+  write.url
+}
+
+FUNCTION {proceedings}
+{ output.bibitem
+  editor empty$
+    { organization output }
+    { format.editors output.nonnull }
+  if$
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.btitle "title" output.check
+  format.bvolume output
+  format.number.series output
+  editor empty$
+    { publisher empty$
+        'skip$
+        {
+          format.publisher.address output
+        }
+      if$
+    }
+    { publisher empty$
+        {
+          format.organization.address output }
+        {
+          organization output
+          format.publisher.address output
+        }
+      if$
+     }
+  if$
+  format.note output
+  fin.entry
+  write.url
+}
+
+FUNCTION {techreport}
+{ output.bibitem
+  format.authors "author" output.check
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.title "title" output.check
+  format.tr.number output.nonnull
+  institution "institution" output.check
+  address output
+  format.note output
+  format.date "year" output.check
+  fin.entry
+  write.url
+}
+
+FUNCTION {unpublished}
+{ output.bibitem
+  format.authors "author" output.check
+  title empty$ 'skip$ 'setup.inlinelink if$ % urlbst
+  format.title "title" output.check
+  format.note "note" output.check
+  format.date "year" output.check
+  fin.entry
+  write.url
+}
+
+FUNCTION {default.type} { misc }
+
+READ
+
+STRINGS { longest.label }
+
+INTEGERS { number.label longest.label.width }
+
+FUNCTION {initialize.longest.label}
+{ "" 'longest.label :=
+  #1 'number.label :=
+  #0 'longest.label.width :=
+}
+
+FUNCTION {longest.label.pass}
+{ number.label int.to.str$ 'label :=
+  number.label #1 + 'number.label :=
+  label width$ longest.label.width >
+    { label 'longest.label :=
+      label width$ 'longest.label.width :=
+    }
+    'skip$
+  if$
+}
+
+EXECUTE {initialize.longest.label}
+
+ITERATE {longest.label.pass}
+
+FUNCTION {begin.bib}
+{ preamble$ empty$
+    'skip$
+    { preamble$ write$ newline$ }
+  if$
+  "\begin{thebibliography}{"  longest.label  * "}" *
+  write$ newline$
+  "\expandafter\ifx\csname url\endcsname\relax"
+  write$ newline$
+  "  \def\url#1{\texttt{#1}}\fi"
+  write$ newline$
+  "\expandafter\ifx\csname urlprefix\endcsname\relax\def\urlprefix{URL }\fi"
+  write$ newline$
+  "\expandafter\ifx\csname href\endcsname\relax"
+  write$ newline$
+  "  \def\href#1#2{#2} \def\path#1{#1}\fi"
+  write$ newline$
+}
+
+EXECUTE {begin.bib}
+
+EXECUTE {init.urlbst.variables}
+EXECUTE {init.state.consts}
+
+ITERATE {call.type$}
+
+FUNCTION {end.bib}
+{ newline$
+  "\end{thebibliography}" write$ newline$
+}
+
+EXECUTE {end.bib}
+%% End of customized bst file
+%%
+%% End of file `elsarticle-num.bst'.
+
+
diff --git a/paper/elsarticle.cls b/paper/elsarticle.cls
new file mode 100644
index 00000000..b8f488cd
--- /dev/null
+++ b/paper/elsarticle.cls
@@ -0,0 +1,701 @@
+%%
+%% This is file `elsarticle.cls',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% elsarticle.dtx  (with options: `class')
+%% 
+%% Copyright 2007, 2008 Elsevier Ltd.
+%% 
+%% This file is part of the 'Elsarticle Bundle'.
+%% -------------------------------------------
+%% 
+%% It may be distributed under the conditions of the LaTeX Project Public
+%% License, either version 1.2 of this license or (at your option) any
+%% later version.  The latest version of this license is in
+%%    http://www.latex-project.org/lppl.txt
+%% and version 1.2 or later is part of all distributions of LaTeX
+%% version 1999/12/01 or later.
+%% 
+%% The list of all files belonging to the 'Elsarticle Bundle' is
+%% given in the file `manifest.txt'.
+%% 
+%% $Id: elsarticle.cls,v 1.18 2008-08-28 06:03:47 cvr Exp $
+%%
+ \def\RCSfile{elsarticle}%
+ \def\RCSversion{1.0.1}%
+ \def\RCSdate{2008/05/22}%
+ \def\@shortjnl{\relax}
+ \def\@journal{Elsevier Science} \def\@company{Elsevier Science}
+ \def\@issn{000-0000}
+ \def\@shortjid{elsarticle}
+\NeedsTeXFormat{LaTeX2e}[1995/12/01]
+\ProvidesClass{\@shortjid}[\RCSdate, \RCSversion: \@journal]
+\def\ABD{\AtBeginDocument}
+\newif\ifpreprint \preprintfalse
+\newif\iflongmktitle \longmktitlefalse
+
+\def\@blstr{1}
+\newdimen\@bls
+\@bls=\baselineskip
+
+\def\@finalWarning{%
+  *****************************************************\MessageBreak
+   This document is typeset in the CRC style which\MessageBreak
+   is not suitable for submission.\MessageBreak
+   \MessageBreak
+   Please typeset again using 'preprint' option\MessageBreak
+   for creating PDF suitable for submission.\MessageBreak
+  ******************************************************\MessageBreak
+}
+
+\DeclareOption{preprint}{\global\preprinttrue
+  \gdef\@blstr{1}\xdef\jtype{0}%
+   \AtBeginDocument{\@twosidefalse\@mparswitchfalse}}
+\DeclareOption{final}{\gdef\@blstr{1}\global\preprintfalse}
+\DeclareOption{review}{\global\preprinttrue\gdef\@blstr{1.5}}
+\DeclareOption{authoryear}{\xdef\@biboptions{authoryear}}
+\DeclareOption{number}{\xdef\@biboptions{square,numbers}}
+\DeclareOption{numbers}{\xdef\@biboptions{square,numbers}}
+\DeclareOption{longtitle}{\global\longmktitletrue}
+\DeclareOption{5p}{\xdef\jtype{5}\global\preprintfalse
+  \ExecuteOptions{twocolumn}}
+  \def\jtype{0}
+\DeclareOption{3p}{\xdef\jtype{3}\global\preprintfalse}
+\DeclareOption{1p}{\xdef\jtype{1}\global\preprintfalse
+  \AtBeginDocument{\@twocolumnfalse}}
+\DeclareOption{times}{\IfFileExists{txfonts.sty}%
+  {\AtEndOfClass{\RequirePackage{txfonts}%
+  \gdef\ttdefault{cmtt}%
+   \let\iint\relax
+  \let\iiint\relax
+  \let\iiiint\relax
+  \let\idotsint\relax
+  \let\openbox\relax}}{\RequirePackage{times}}}
+\ExecuteOptions{a4paper,10pt,oneside,onecolumn,number,preprint}
+\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}
+\ProcessOptions
+\LoadClass{article}
+\RequirePackage{graphicx}
+\let\comma\@empty
+\let\tnotesep\@empty
+\def\title#1{\gdef\@title{#1}}
+\let\@title\@empty
+
+\def\elsLabel#1{\@bsphack\protected@write\@auxout{}%
+         {\string\Newlabel{#1}{\@currentlabel}}\@esphack}
+\def\Newlabel#1#2{\expandafter\xdef\csname X@#1\endcsname{#2}}
+
+\def\Ref#1{\@ifundefined{X@#1}{0}{\csname X@#1\endcsname}%
+}
+
+\def\tnotemark[#1]{\textsuperscript{\@for\@@tmark:=#1\do{%
+      \edef\tnotenum{\@ifundefined{X@\@@tmark}{1}{\Ref{\@@tmark}}}%
+      \ifcase\tnotenum\or\ding{73}\or,\ding{73}\ding{73}\fi}}%
+}
+\let\@tnotemark\@empty
+
+\let\@tnotes\@empty
+\RequirePackage{pifont}
+\newcounter{tnote}
+\def\tnotetext[#1]#2{\g@addto@macro\@tnotes{%
+  \refstepcounter{tnote}\elsLabel{#1}%
+   \def\thefootnote{\ifcase\c@tnote\or\ding{73}\or\ding{73}\ding{73}\fi}%
+    \footnotetext{#2}}}
+
+\newcounter{fnote}
+\def\fnmark[#1]{\let\comma\@empty
+    \def\@fnmark{\@for\@@fnmark:=#1\do{%
+    \edef\fnotenum{\@ifundefined{X@\@@fnmark}{1}{\Ref{\@@fnmark}}}%
+  \unskip\comma\fnotenum\let\comma,}}%
+}
+
+\let\@fnotes\@empty\let\@fnmark\@empty
+\def\fntext[#1]#2{\g@addto@macro\@fnotes{%
+     \refstepcounter{fnote}\elsLabel{#1}%
+   \def\thefootnote{\thefnote}%
+   \global\setcounter{footnote}{\thefnote}%
+   \footnotetext{#2}}}
+
+\def\cormark[#1]{\edef\cnotenum{\Ref{#1}}\unskip%
+   \textsuperscript{\sep\ifcase\cnotenum\or
+       $\ast$\or$\ast\ast$\fi\hspace{-1pt}}\let\sep=,}
+
+\let\@cormark\@empty
+\let\@cornotes\@empty
+\newcounter{cnote}
+\def\cortext[#1]#2{\g@addto@macro\@cornotes{%
+    \refstepcounter{cnote}\elsLabel{#1}%
+    \def\thefootnote{\ifcase\thecnote\or$\ast$\or
+    $\ast\ast$\fi}%
+    \footnotetext{#2}}}
+
+\def\corref#1{\cormark[#1]}
+\def\fnref#1{\fnmark[#1]}
+\def\tnoteref#1{\tnotemark[#1]}
+
+\def\resetTitleCounters{\c@cnote=0
+   \c@fnote=0 \c@tnote=0 \c@footnote=0}
+
+\let\eadsep\@empty
+\let\@elseads\@empty
+\let\@elsuads\@empty
+\let\@cormark\@empty
+\def\ead{\@ifnextchar[{\@uad}{\@ead}}
+\gdef\@ead#1{\bgroup\def\_{\string\_}\def\{{\string\{}%
+   \def\}{\string\}}%
+   \edef\tmp{\the\@eadauthor}
+   \immediate\write\@auxout{\string\emailauthor
+     {#1}{\expandafter\strip@prefix\meaning\tmp}}%
+  \egroup
+}
+\newcounter{ead}
+\gdef\emailauthor#1#2{\stepcounter{ead}%
+     \g@addto@macro\@elseads{\raggedright%
+      \let\corref\@gobble
+      \eadsep\texttt{#1} (#2)\def\eadsep{\unskip,\space}}%
+}
+\gdef\@uad[#1]#2{\bgroup
+   \edef\tmp{\the\@eadauthor}
+   \immediate\write\@auxout{\string\urlauthor
+     {#2}{\expandafter\strip@prefix\meaning\tmp}}%
+  \egroup
+}
+\def\urlauthor#1#2{\g@addto@macro\@elsuads{\let\corref\@gobble%
+     \raggedright\eadsep\texttt{#1}\space(#2)%
+     \def\eadsep{\unskip,\space}}%
+}
+
+\def\elsauthors{}
+\def\pprinttitle{}
+\let\authorsep\@empty
+\let\sep\@empty
+\newcounter{author}
+\def\author{\@ifnextchar[{\@@author}{\@author}}
+
+\newtoks\@eadauthor
+\def\@@author[#1]#2{\g@addto@macro\elsauthors{%
+    \def\baselinestretch{1}%
+    \authorsep#2\unskip\textsuperscript{%#1%
+      \@for\@@affmark:=#1\do{%
+       \edef\affnum{\@ifundefined{X@\@@affmark}{1}{\Ref{\@@affmark}}}%
+     \unskip\sep\affnum\let\sep=,}%
+      \ifx\@fnmark\@empty\else\unskip\sep\@fnmark\let\sep=,\fi}%
+    \def\authorsep{\unskip,\space}%
+    \global\let\sep\@empty
+    \global\let\@fnmark\@empty}%
+    \@eadauthor={#2}
+}
+
+\def\@author#1{\g@addto@macro\elsauthors{\normalsize%
+    \def\baselinestretch{1}%
+    \upshape\authorsep#1\unskip\textsuperscript{%
+      \ifx\@fnmark\@empty\else\unskip\sep\@fnmark\let\sep=,\fi}%
+    \def\authorsep{\unskip,\space}%
+    \global\let\@fnmark\@empty
+    \global\let\sep\@empty}%
+    \@eadauthor={#1}
+}
+
+\def\elsaddress{}
+\def\addsep{\par\vskip6pt}
+\def\address{\@ifnextchar[{\@@address}{\@address}}
+
+\newcounter{affn}
+\renewcommand\theaffn{\alph{affn}}
+
+\long\def\@@address[#1]#2{\g@addto@macro\elsaddress{%
+    \def\baselinestretch{1}%
+     \refstepcounter{affn}\elsLabel{#1}%
+    \textsuperscript{\theaffn}#2\par}}
+
+\long\def\@address#1{\g@addto@macro\elsauthors{%
+    \def\baselinestretch{1}%
+    \addsep\footnotesize\itshape#1\def\addsep{\par\vskip6pt}%
+    \def\authorsep{\par\vskip8pt}}}
+
+\newbox\absbox
+\renewenvironment{abstract}{\global\setbox\absbox=\vbox\bgroup
+  \hsize=\textwidth\def\baselinestretch{1}%
+  \noindent\unskip\textbf{Abstract}
+ \par\medskip\noindent\unskip\ignorespaces}
+ {\egroup}
+
+\newbox\keybox
+\def\keyword{%
+  \def\sep{\unskip, }%
+ \def\MSC{\@ifnextchar[{\@MSC}{\@MSC[2000]}}
+  \def\@MSC[##1]{\par\leavevmode\hbox {\it ##1~MSC:\space}}%
+  \def\PACS{\par\leavevmode\hbox {\it PACS:\space}}%
+  \def\JEL{\par\leavevmode\hbox {\it JEL:\space}}%
+  \global\setbox\keybox=\vbox\bgroup\hsize=\textwidth
+  \normalsize\normalfont\def\baselinestretch{1}
+  \parskip\z@
+  \noindent\textit{Key words: }
+  \raggedright                         % Keywords are not justified.
+  \ignorespaces}
+\def\endkeyword{\par \egroup}
+
+\newdimen\Columnwidth
+\Columnwidth=\columnwidth
+
+\def\printFirstPageNotes{%
+  \iflongmktitle
+   \let\columnwidth=\textwidth\fi
+  \ifx\@tnotes\@empty\else\@tnotes\fi
+  \ifx\@cornotes\@empty\else\@cornotes\fi
+  \ifx\@elseads\@empty\relax\else
+   \let\thefootnote\relax
+   \footnotetext{\ifnum\theead=1\relax
+      \textit{Email address:\space}\else
+      \textit{Email addresses:\space}\fi
+     \@elseads}\fi
+  \ifx\@elsuads\@empty\relax\else
+   \let\thefootnote\relax
+   \footnotetext{\textit{URL:\space}%
+     \@elsuads}\fi
+  \ifx\@fnotes\@empty\else\@fnotes\fi
+  \iflongmktitle\if@twocolumn
+   \let\columnwidth=\Columnwidth\fi\fi
+}
+
+\long\def\pprintMaketitle{\clearpage
+  \iflongmktitle\if@twocolumn\let\columnwidth=\textwidth\fi\fi
+  \resetTitleCounters
+  \def\baselinestretch{1}%
+  \printFirstPageNotes
+  \begin{center}%
+ \thispagestyle{pprintTitle}%
+   \def\baselinestretch{1}%
+    \Large\@title\par\vskip18pt
+    \normalsize\elsauthors\par\vskip10pt
+    \footnotesize\itshape\elsaddress\par\vskip36pt
+    \hrule\vskip12pt
+    \ifvoid\absbox\else\unvbox\absbox\par\vskip10pt\fi
+    \ifvoid\keybox\else\unvbox\keybox\par\vskip10pt\fi
+    \hrule\vskip12pt
+    \end{center}%
+  \gdef\thefootnote{\arabic{footnote}}%
+  }
+
+\def\printWarning{%
+     \mbox{}\par\vfill\par\bgroup
+     \fboxsep12pt\fboxrule1pt
+     \hspace*{.18\textwidth}
+     \fcolorbox{gray50}{gray10}{\box\warnbox}
+     \egroup\par\vfill\thispagestyle{empty}
+     \setcounter{page}{0}
+     \clearpage}
+
+\long\def\finalMaketitle{%
+  \resetTitleCounters
+  \def\baselinestretch{1}%
+   \MaketitleBox
+   \thispagestyle{pprintTitle}%
+  \gdef\thefootnote{\arabic{footnote}}%
+  }
+
+\long\def\MaketitleBox{%
+  \resetTitleCounters
+  \def\baselinestretch{1}%
+  \begin{center}%
+   \def\baselinestretch{1}%
+    \Large\@title\par\vskip18pt
+    \normalsize\elsauthors\par\vskip10pt
+    \footnotesize\itshape\elsaddress\par\vskip36pt
+    \hrule\vskip12pt
+    \ifvoid\absbox\else\unvbox\absbox\par\vskip10pt\fi
+    \ifvoid\keybox\else\unvbox\keybox\par\vskip10pt\fi
+    \hrule\vskip12pt
+    \end{center}%
+  }
+
+\def\FNtext#1{\par\bgroup\footnotesize#1\egroup}
+\newdimen\space@left
+\def\alarm#1{\typeout{******************************}%
+             \typeout{#1}%
+             \typeout{******************************}%
+}
+\long\def\getSpaceLeft{%\global\@twocolumnfalse%
+   \global\setbox0=\vbox{\hsize=\textwidth\MaketitleBox}%
+   \global\setbox1=\vbox{\hsize=\textwidth
+    \let\footnotetext\FNtext
+    \printFirstPageNotes}%
+    \xdef\noteheight{\the\ht1}%
+    \xdef\titleheight{\the\ht0}%
+    \@tempdima=\vsize
+    \advance\@tempdima-\noteheight
+    \advance\@tempdima-1\baselineskip
+}
+
+  \skip\footins=24pt
+
+\newbox\els@boxa
+\newbox\els@boxb
+
+\ifpreprint
+  \def\maketitle{\pprintMaketitle}
+  \else
+   \ifnum\jtype=1
+    \def\maketitle{%
+      \iflongmktitle\getSpaceLeft
+       \global\setbox\els@boxa=\vsplit0 to \@tempdima
+       \box\els@boxa\par\resetTitleCounters
+       \thispagestyle{pprintTitle}%
+       \printFirstPageNotes
+       \box0%
+      \else
+       \finalMaketitle\printFirstPageNotes
+      \fi
+    \gdef\thefootnote{\arabic{footnote}}}%
+  \else
+ \ifnum\jtype=5
+    \def\maketitle{%
+      \iflongmktitle\getSpaceLeft
+       \global\setbox\els@boxa=\vsplit0 to \@tempdima
+       \box\els@boxa\par\resetTitleCounters
+       \thispagestyle{pprintTitle}%
+       \printFirstPageNotes
+       \twocolumn[\box0]%\printFirstPageNotes
+      \else
+       \twocolumn[\finalMaketitle]\printFirstPageNotes
+     \fi
+     \gdef\thefootnote{\arabic{footnote}}}
+  \else
+  \if@twocolumn
+    \def\maketitle{%
+      \iflongmktitle\getSpaceLeft
+       \global\setbox\els@boxa=\vsplit0 to \@tempdima
+       \box\els@boxa\par\resetTitleCounters
+       \thispagestyle{pprintTitle}%
+       \printFirstPageNotes
+       \twocolumn[\box0]%
+      \else
+       \twocolumn[\finalMaketitle]\printFirstPageNotes
+      \fi
+     \gdef\thefootnote{\arabic{footnote}}}%
+   \else
+    \def\maketitle{%
+      \iflongmktitle\getSpaceLeft
+       \global\setbox\els@boxa=\vsplit0 to \@tempdima
+       \box\els@boxa\par\resetTitleCounters
+       \thispagestyle{pprintTitle}%
+       \printFirstPageNotes
+       \box0%
+     \else
+       \finalMaketitle\printFirstPageNotes
+      \fi
+    \gdef\thefootnote{\arabic{footnote}}}%
+   \fi
+  \fi
+ \fi
+\fi
+\def\ps@pprintTitle{%
+     \let\@oddhead\@empty
+     \let\@evenhead\@empty
+     \def\@oddfoot{\footnotesize\itshape
+       Preprint submitted to \ifx\@journal\@empty Elsevier
+       \else\@journal\fi\hfill\today}%
+     \let\@evenfoot\@oddfoot}
+
+\def\@seccntDot{.}
+\def\@seccntformat#1{\csname the#1\endcsname\@seccntDot\hskip 0.5em}
+
+\renewcommand\section{\@startsection {section}{1}{\z@}%
+           {18\p@ \@plus 6\p@ \@minus 3\p@}%
+           {9\p@ \@plus 6\p@ \@minus 3\p@}%
+           {\normalsize\bfseries\boldmath}}
+\renewcommand\subsection{\@startsection{subsection}{2}{\z@}%
+           {12\p@ \@plus 6\p@ \@minus 3\p@}%
+           {3\p@ \@plus 6\p@ \@minus 3\p@}%
+           {\normalfont\normalsize\itshape}}
+\renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}%
+           {12\p@ \@plus 6\p@ \@minus 3\p@}%
+           {\p@}%
+           {\normalfont\normalsize\itshape}}
+
+\def\paragraph{\secdef{\els@aparagraph}{\els@bparagraph}}
+\def\els@aparagraph[#1]#2{\elsparagraph[#1]{#2.}}
+\def\els@bparagraph#1{\elsparagraph*{#1.}}
+
+\newcommand\elsparagraph{\@startsection{paragraph}{4}{0\z@}%
+           {10\p@ \@plus 6\p@ \@minus 3\p@}%
+           {-6\p@}%
+           {\normalfont\itshape}}
+\newdimen\leftMargin
+\leftMargin=2em
+\newtoks\@enLab  %\newtoks\@enfont
+\def\@enQmark{?}
+\def\@enLabel#1#2{%
+  \edef\@enThe{\noexpand#1{\@enumctr}}%
+  \@enLab\expandafter{\the\@enLab\csname the\@enumctr\endcsname}%
+  \@enloop}
+\def\@enSpace{\afterassignment\@enSp@ce\let\@tempa= }
+\def\@enSp@ce{\@enLab\expandafter{\the\@enLab\space}\@enloop}
+\def\@enGroup#1{\@enLab\expandafter{\the\@enLab{#1}}\@enloop}
+\def\@enOther#1{\@enLab\expandafter{\the\@enLab#1}\@enloop}
+\def\@enloop{\futurelet\@entemp\@enloop@}
+\def\@enloop@{%
+  \ifx A\@entemp         \def\@tempa{\@enLabel\Alph  }\else
+  \ifx a\@entemp         \def\@tempa{\@enLabel\alph  }\else
+  \ifx i\@entemp         \def\@tempa{\@enLabel\roman }\else
+  \ifx I\@entemp         \def\@tempa{\@enLabel\Roman }\else
+  \ifx 1\@entemp         \def\@tempa{\@enLabel\arabic}\else
+  \ifx \@sptoken\@entemp \let\@tempa\@enSpace         \else
+  \ifx \bgroup\@entemp   \let\@tempa\@enGroup         \else
+  \ifx \@enum@\@entemp   \let\@tempa\@gobble          \else
+                         \let\@tempa\@enOther
+             \fi\fi\fi\fi\fi\fi\fi\fi
+  \@tempa}
+\newlength{\@sep} \newlength{\@@sep}
+\setlength{\@sep}{.5\baselineskip plus.2\baselineskip
+            minus.2\baselineskip}
+\setlength{\@@sep}{.1\baselineskip plus.01\baselineskip
+            minus.05\baselineskip}
+\providecommand{\sfbc}{\rmfamily\upshape}
+\providecommand{\sfn}{\rmfamily\upshape}
+\def\@enfont{\ifnum \@enumdepth >1\let\@nxt\sfn \else\let\@nxt\sfbc \fi\@nxt}
+\def\enumerate{%
+   \ifnum \@enumdepth >3 \@toodeep\else
+      \advance\@enumdepth \@ne
+      \edef\@enumctr{enum\romannumeral\the\@enumdepth}\fi
+   \@ifnextchar[{\@@enum@}{\@enum@}}
+\def\@@enum@[#1]{%
+  \@enLab{}\let\@enThe\@enQmark
+  \@enloop#1\@enum@
+  \ifx\@enThe\@enQmark\@warning{The counter will not be printed.%
+   ^^J\space\@spaces\@spaces\@spaces The label is: \the\@enLab}\fi
+  \expandafter\edef\csname label\@enumctr\endcsname{\the\@enLab}%
+  \expandafter\let\csname the\@enumctr\endcsname\@enThe
+  \csname c@\@enumctr\endcsname7
+  \expandafter\settowidth
+            \csname leftmargin\romannumeral\@enumdepth\endcsname
+            {\the\@enLab\hskip\labelsep}%
+  \@enum@}
+\def\@enum@{\list{{\@enfont\csname label\@enumctr\endcsname}}%
+           {\usecounter{\@enumctr}\def\makelabel##1{\hss\llap{##1}}%
+     \ifnum \@enumdepth>1\setlength{\topsep}{\@@sep}\else
+           \setlength{\topsep}{\@sep}\fi
+     \ifnum \@enumdepth>1\setlength{\itemsep}{0pt plus1pt minus1pt}%
+      \else \setlength{\itemsep}{\@@sep}\fi
+     %\setlength\leftmargin{\leftMargin}%%%{1.8em}
+     \setlength{\parsep}{0pt plus1pt minus1pt}%
+     \setlength{\parskip}{0pt plus1pt minus1pt}
+                   }}
+
+\def\endenumerate{\par\ifnum \@enumdepth >1\addvspace{\@@sep}\else
+           \addvspace{\@sep}\fi \endlist}
+
+\def\sitem{\@noitemargtrue\@item[\@itemlabel *]}
+
+\def\itemize{\@ifnextchar[{\@Itemize}{\@Itemize[]}}
+
+\def\@Itemize[#1]{\def\next{#1}%
+  \ifnum \@itemdepth >\thr@@\@toodeep\else
+   \advance\@itemdepth\@ne
+  \ifx\next\@empty\else\expandafter\def\csname
+   labelitem\romannumeral\the\@itemdepth\endcsname{#1}\fi%
+  \edef\@itemitem{labelitem\romannumeral\the\@itemdepth}%
+  \expandafter\list\csname\@itemitem\endcsname
+  {\def\makelabel##1{\hss\llap{##1}}}%
+ \fi}
+\def\newdefinition#1{%
+  \@ifnextchar[{\@odfn{#1}}{\@ndfn{#1}}}%]
+\def\@ndfn#1#2{%
+  \@ifnextchar[{\@xndfn{#1}{#2}}{\@yndfn{#1}{#2}}}
+\def\@xndfn#1#2[#3]{%
+  \expandafter\@ifdefinable\csname #1\endcsname
+    {\@definecounter{#1}\@newctr{#1}[#3]%
+     \expandafter\xdef\csname the#1\endcsname{%
+       \expandafter\noexpand\csname the#3\endcsname \@dfncountersep
+          \@dfncounter{#1}}%
+     \global\@namedef{#1}{\@dfn{#1}{#2}}%
+     \global\@namedef{end#1}{\@enddefinition}}}
+\def\@yndfn#1#2{%
+  \expandafter\@ifdefinable\csname #1\endcsname
+    {\@definecounter{#1}%
+     \expandafter\xdef\csname the#1\endcsname{\@dfncounter{#1}}%
+     \global\@namedef{#1}{\@dfn{#1}{#2}}%
+     \global\@namedef{end#1}{\@enddefinition}}}
+\def\@odfn#1[#2]#3{%
+  \@ifundefined{c@#2}{\@nocounterr{#2}}%
+    {\expandafter\@ifdefinable\csname #1\endcsname
+    {\global\@namedef{the#1}{\@nameuse{the#2}}
+  \global\@namedef{#1}{\@dfn{#2}{#3}}%
+  \global\@namedef{end#1}{\@enddefinition}}}}
+\def\@dfn#1#2{%
+  \refstepcounter{#1}%
+  \@ifnextchar[{\@ydfn{#1}{#2}}{\@xdfn{#1}{#2}}}
+\def\@xdfn#1#2{%
+  \@begindefinition{#2}{\csname the#1\endcsname}\ignorespaces}
+\def\@ydfn#1#2[#3]{%
+  \@opargbegindefinition{#2}{\csname the#1\endcsname}{#3}\ignorespaces}
+\def\@dfncounter#1{\noexpand\arabic{#1}}
+\def\@dfncountersep{.}
+\def\@begindefinition#1#2{\trivlist
+   \item[\hskip\labelsep{\bfseries #1\ #2.}]\upshape}
+\def\@opargbegindefinition#1#2#3{\trivlist
+      \item[\hskip\labelsep{\bfseries #1\ #2\ (#3).}]\upshape}
+\def\@enddefinition{\endtrivlist}
+
+\def\@begintheorem#1#2{\trivlist
+  \let\baselinestretch\@blstr
+   \item[\hskip \labelsep{\bfseries #1\ #2.}]\itshape}
+\def\@opargbegintheorem#1#2#3{\trivlist
+  \let\baselinestretch\@blstr
+      \item[\hskip \labelsep{\bfseries #1\ #2\ (#3).}]\itshape}
+
+\def\newproof#1{%
+  \@ifnextchar[{\@oprf{#1}}{\@nprf{#1}}}
+\def\@nprf#1#2{%
+  \@ifnextchar[{\@xnprf{#1}{#2}}{\@ynprf{#1}{#2}}}
+\def\@xnprf#1#2[#3]{%
+  \expandafter\@ifdefinable\csname #1\endcsname
+    {\@definecounter{#1}\@newctr{#1}[#3]%
+     \expandafter\xdef\csname the#1\endcsname{%
+       \expandafter\noexpand\csname the#3\endcsname \@prfcountersep
+          \@prfcounter{#1}}%
+     \global\@namedef{#1}{\@prf{#1}{#2}}%
+     \global\@namedef{end#1}{\@endproof}}}
+\def\@ynprf#1#2{%
+  \expandafter\@ifdefinable\csname #1\endcsname
+    {\@definecounter{#1}%
+     \expandafter\xdef\csname the#1\endcsname{\@prfcounter{#1}}%
+     \global\@namedef{#1}{\@prf{#1}{#2}}%
+     \global\@namedef{end#1}{\@endproof}}}
+\def\@oprf#1[#2]#3{%
+  \@ifundefined{c@#2}{\@nocounterr{#2}}%
+    {\expandafter\@ifdefinable\csname #1\endcsname
+    {\global\@namedef{the#1}{\@nameuse{the#2}}%
+  \global\@namedef{#1}{\@prf{#2}{#3}}%
+  \global\@namedef{end#1}{\@endproof}}}}
+\def\@prf#1#2{%
+  \refstepcounter{#1}%
+  \@ifnextchar[{\@yprf{#1}{#2}}{\@xprf{#1}{#2}}}
+\def\@xprf#1#2{%
+  \@beginproof{#2}{\csname the#1\endcsname}\ignorespaces}
+\def\@yprf#1#2[#3]{%
+  \@opargbeginproof{#2}{\csname the#1\endcsname}{#3}\ignorespaces}
+\def\@prfcounter#1{\noexpand\arabic{#1}}
+\def\@prfcountersep{.}
+\def\@beginproof#1#2{\trivlist\let\baselinestretch\@blstr
+   \item[\hskip \labelsep{\scshape #1.}]\rmfamily}
+\def\@opargbeginproof#1#2#3{\trivlist\let\baselinestretch\@blstr
+      \item[\hskip \labelsep{\scshape #1\ (#3).}]\rmfamily}
+\def\@endproof{\endtrivlist}
+\newcommand*{\qed}{\hbox{}\hfill$\Box$}
+
+\@ifundefined{@biboptions}{\xdef\@biboptions{numbers}}{}
+\InputIfFileExists{\jobname.spl}{}{}
+\RequirePackage[\@biboptions]{natbib}
+
+\newwrite\splwrite
+\immediate\openout\splwrite=\jobname.spl
+\def\biboptions#1{\def\next{#1}\immediate\write\splwrite{%
+   \string\g@addto@macro\string\@biboptions{%
+    ,\expandafter\strip@prefix\meaning\next}}}
+
+\let\baselinestretch=\@blstr
+
+\ifnum\jtype=1
+ \RequirePackage{geometry}
+ \geometry{twoside,
+  paperwidth=210mm,
+  paperheight=297mm,
+  textheight=562pt,
+  textwidth=384pt,
+  centering,
+  headheight=50pt,
+  headsep=12pt,
+  footskip=12pt,
+  footnotesep=24pt plus 2pt minus 12pt,
+ }
+ \global\let\bibfont=\footnotesize
+ \global\bibsep=0pt
+ \if@twocolumn\global\@twocolumnfalse\fi
+\else\ifnum\jtype=3
+ \RequirePackage{geometry}
+ \geometry{twoside,
+  paperwidth=210mm,
+  paperheight=297mm,
+  textheight=622pt,
+  textwidth=468pt,
+  centering,
+ headheight=50pt,
+  headsep=12pt,
+  footskip=12pt,
+  footnotesep=24pt plus 2pt minus 12pt,
+  columnsep=2pc
+ }
+ \global\let\bibfont=\footnotesize
+ \global\bibsep=0pt
+ \if@twocolumn\input{fleqn.clo}\fi
+\else\ifnum\jtype=5
+ \RequirePackage{geometry}
+ \geometry{twoside,
+  paperwidth=210mm,
+  paperheight=297mm,
+  textheight=682pt,
+  textwidth=522pt,
+  centering,
+ headheight=50pt,
+  headsep=12pt,
+  footskip=18pt,
+  footnotesep=24pt plus 2pt minus 12pt,
+  columnsep=18pt
+ }%
+ \global\let\bibfont=\footnotesize
+ \global\bibsep=0pt
+ \input{fleqn.clo}
+ \global\@twocolumntrue
+%%
+%% End of option '5p'
+%%
+\fi\fi\fi
+
+\def\journal#1{\gdef\@journal{#1}}
+ \let\@journal\@empty
+\newenvironment{frontmatter}{}{\maketitle}
+
+\long\def\@makecaption#1#2{%
+  \vskip\abovecaptionskip\footnotesize
+  \sbox\@tempboxa{#1: #2}%
+  \ifdim \wd\@tempboxa >\hsize
+    #1: #2\par
+  \else
+    \global \@minipagefalse
+    \hb@xt@\hsize{\hfil\box\@tempboxa\hfil}%
+  \fi
+  \vskip\belowcaptionskip}
+
+\AtBeginDocument{\@ifpackageloaded{hyperref}
+  {\def\@linkcolor{blue}
+   \def\@anchorcolor{blue}
+   \def\@citecolor{blue}
+   \def\@filecolor{blue}
+   \def\@urlcolor{blue}
+   \def\@menucolor{blue}
+   \def\@pagecolor{blue}
+\begingroup
+  \@makeother\`%
+  \@makeother\=%
+  \edef\x{%
+    \edef\noexpand\x{%
+      \endgroup
+      \noexpand\toks@{%
+        \catcode 96=\noexpand\the\catcode`\noexpand\`\relax
+        \catcode 61=\noexpand\the\catcode`\noexpand\=\relax
+      }%
+    }%
+    \noexpand\x
+  }%
+\x
+\@makeother\`
+\@makeother\=
+}{}}
+%%
+
+\endinput
+%%
+%% End of file `elsarticle.cls'.
diff --git a/paper/figs/example1.pdf b/paper/figs/example1.pdf
new file mode 100644
index 00000000..96a83e39
Binary files /dev/null and b/paper/figs/example1.pdf differ
diff --git a/paper/figs/example2.pdf b/paper/figs/example2.pdf
new file mode 100644
index 00000000..195257cd
Binary files /dev/null and b/paper/figs/example2.pdf differ
diff --git a/paper/figs/example3.pdf b/paper/figs/example3.pdf
new file mode 100644
index 00000000..1159fb55
Binary files /dev/null and b/paper/figs/example3.pdf differ
diff --git a/paper/figs/example4.pdf b/paper/figs/example4.pdf
new file mode 100644
index 00000000..8e88c578
Binary files /dev/null and b/paper/figs/example4.pdf differ
diff --git a/paper/figs/example5.pdf b/paper/figs/example5.pdf
new file mode 100644
index 00000000..d9d75052
Binary files /dev/null and b/paper/figs/example5.pdf differ
diff --git a/paper/figs/example6.pdf b/paper/figs/example6.pdf
new file mode 100644
index 00000000..3015f601
Binary files /dev/null and b/paper/figs/example6.pdf differ
diff --git a/paper/figs/example7.pdf b/paper/figs/example7.pdf
new file mode 100644
index 00000000..69b02691
Binary files /dev/null and b/paper/figs/example7.pdf differ
diff --git a/paper/figs/flow_chart.pdf b/paper/figs/flow_chart.pdf
new file mode 100644
index 00000000..a2754d28
Binary files /dev/null and b/paper/figs/flow_chart.pdf differ
diff --git a/paper/figs/illustrative_example.pdf b/paper/figs/illustrative_example.pdf
new file mode 100644
index 00000000..1513081a
Binary files /dev/null and b/paper/figs/illustrative_example.pdf differ
diff --git a/paper/figs/package_structure.pdf b/paper/figs/package_structure.pdf
new file mode 100644
index 00000000..e13fe317
Binary files /dev/null and b/paper/figs/package_structure.pdf differ
diff --git a/paper/figs/shareable_code.pdf b/paper/figs/shareable_code.pdf
new file mode 100644
index 00000000..c301db88
Binary files /dev/null and b/paper/figs/shareable_code.pdf differ
diff --git a/paper/figs/snippet1.pdf b/paper/figs/snippet1.pdf
new file mode 100644
index 00000000..734da5b1
Binary files /dev/null and b/paper/figs/snippet1.pdf differ
diff --git a/paper/figs/snippet2.pdf b/paper/figs/snippet2.pdf
new file mode 100644
index 00000000..e0d1c13d
Binary files /dev/null and b/paper/figs/snippet2.pdf differ
diff --git a/paper/figs/snippet3.pdf b/paper/figs/snippet3.pdf
new file mode 100644
index 00000000..3234898a
Binary files /dev/null and b/paper/figs/snippet3.pdf differ
diff --git a/paper/figs/snippet4.pdf b/paper/figs/snippet4.pdf
new file mode 100644
index 00000000..8c67d5d4
Binary files /dev/null and b/paper/figs/snippet4.pdf differ
diff --git a/paper/figs/snippet5.pdf b/paper/figs/snippet5.pdf
new file mode 100644
index 00000000..77f8e28a
Binary files /dev/null and b/paper/figs/snippet5.pdf differ
diff --git a/paper/figs/snippet6.pdf b/paper/figs/snippet6.pdf
new file mode 100644
index 00000000..8e268211
Binary files /dev/null and b/paper/figs/snippet6.pdf differ
diff --git a/paper/figs/snippet7.pdf b/paper/figs/snippet7.pdf
new file mode 100644
index 00000000..bfab6528
Binary files /dev/null and b/paper/figs/snippet7.pdf differ
diff --git a/paper/snippets/snippet5.pdf b/paper/figs/snippets.pdf
similarity index 67%
rename from paper/snippets/snippet5.pdf
rename to paper/figs/snippets.pdf
index 42042e72..32eb0d71 100644
Binary files a/paper/snippets/snippet5.pdf and b/paper/figs/snippets.pdf differ
diff --git a/paper/main.bib b/paper/main.bib
new file mode 100644
index 00000000..fa6590c3
--- /dev/null
+++ b/paper/main.bib
@@ -0,0 +1,319 @@
+
+
+@article{vanR95,
+	author = {Guido {van Rossum}},
+	force = {True},
+	journal = {Department of Computer Science {[CS]}},
+	number = {R 9525},
+	publisher = {{CWI}},
+	title = {{Python} reference manual},
+	year = {1995}}
+
+@misc{Pyth03,
+	author = {{Python Software Foundation}},
+	force = {True},
+	howpublished = {\url{https://pypi.org}},
+	publisher = {Python Software Foundation},
+	title = {The {P}ython {P}ackage {I}ndex ({PyPI})},
+	year = {2003}}
+
+@misc{cond15,
+	author = {conda-forge community},
+	doi = {10.5281/zenodo.4774217},
+	force = {True},
+	howpublished = {\url{https://doi.org/10.5281/zenodo.4774217}},
+	month = {July},
+	publisher = {Zenodo},
+	title = {{The conda-forge Project: Community-based Software Distribution Built on the conda Package Format and Ecosystem}},
+	year = {2015}}
+
+@techreport{CoghStuf13,
+	author = {Nick Coghlan and Donald Stufft},
+	institution = {{Python} Software Foundation},
+	month = {March},
+	number = {440},
+	title = {Version {I}dentification and {D}ependency {S}pecification},
+	type = {PEP},
+	year = {2013}}
+
+@techreport{CannEtal16,
+	author = {Brett Cannon and Nathaniel Smith and Donald Stufft},
+	institution = {Python Software Foundation},
+	month = {May},
+	number = {518},
+	title = {Specifying {M}inimum {B}uild {S}ystem {R}equirements for {Python} {P}rojects},
+	type = {PEP},
+	year = {2016}}
+
+@misc{Anac12,
+	author = {{Anaconda, Inc.}},
+	force = {True},
+	howpublished = {\url{https://docs.conda.io}},
+	title = {{conda}},
+	year = {2012}}
+
+@misc{Eust19,
+	author = {S{\'{e}}bastien Eustace},
+	howpublished = {\url{https://github.com/python-poetry/poetry}},
+	month = {December},
+	title = {Poetry: {Python} packaging and dependency management made easy},
+	year = {2019}}
+
+@inproceedings{KluyEtal16,
+	address = {Netherlands},
+	author = {Thomas Kluyver and Benjamin Ragan-Kelley and Fernando P{\'e}rez and Brian Granger and Matthias Bussonnier and Jonathan Frederic and Kyle Kelley and Jessica Hamrick and Jason Grout and Sylvain Corlay and Paul Ivanov and Dami{\'a}n Avila and Safia Abdalla and Carol Willing},
+	booktitle = {Positioning and Power in Academic Publishing: Players, Agents and Agendas},
+	doi = {10.3233/978-1-61499-649-1-87},
+	editor = {Fernando Loizides and Birgit Scmidt},
+	pages = {87--90},
+	publisher = {{IOS} Press},
+	title = {Jupyter {N}otebooks -- a publishing format for reproducible computational workflows},
+	year = {2016}}
+
+@article{Gold74,
+	author = {Robert P Goldberg},
+	journal = {Computer},
+	number = {6},
+	pages = {34--45},
+	publisher = {{IEEE}},
+	title = {Survey of virtual machine research},
+	volume = {7},
+	year = {1974}}
+
+@article{AltiEtal05,
+	author = {Y Altintas and C Brecher and M Weck and S Witt},
+	doi = {https://doi.org/10.1016/S0007-8506(07)60022-5},
+	journal = {{CIRP} Annals},
+	number = {2},
+	pages = {115--138},
+	title = {Virtual {M}achine {T}ool},
+	volume = {54},
+	year = {2005}}
+
+@inproceedings{Rose99,
+	author = {Mendel Rosenblum},
+	booktitle = {{IEEE} Hot Chips Symposium},
+	force = {True},
+	pages = {185--196},
+	publisher = {{IEEE}},
+	title = {{VMware's Virtual Platform: A virtual machine monitor for commodity PCs}},
+	year = {1999}}
+
+@article{Merk14,
+	author = {Dirk Merkel},
+	journal = {Linux Journal},
+	month = {March},
+	number = {2},
+	pages = {2},
+	title = {Docker: lightweight linux containers for consistent development and deployment},
+	volume = {239},
+	year = {2014}}
+
+@article{KurtEtal17,
+	author = {Gregory M Kurtzer and Vanessa Sochat and Michael W Bauer},
+	journal = {{PLoS} One},
+	number = {5},
+	pages = {e0177459},
+	publisher = {Public Library of Science San Francisco, {CA} {USA}},
+	title = {Singularity: {S}cientific containers for mobility of compute},
+	volume = {12},
+	year = {2017}}
+
+@book{Mart98,
+	author = {G R R Martin},
+	month = {November},
+	publisher = {Voyager Books},
+	series = {A Song of Ice and Fire},
+	title = {A {C}lash of {K}ings},
+	year = {1998}}
+
+@article{PereGran07,
+	author = {F P{\'e}rez and B E Granger},
+	doi = {10.1109/MCSE.2007.53},
+	journal = {Computing in {s}cience and {e}ngineering},
+	number = {3},
+	pages = {21--29},
+	title = {I{P}ython: a system for interactive scientific computing},
+	volume = {9},
+	year = {2007}}
+
+@techreport{vanREtal14,
+	author = {Guido {van Rossum} and Jukka Lehtosalo and {\L}ukasz Langa},
+	institution = {Python Software Foundation},
+	month = {September},
+	number = {484},
+	title = {Type {Hints}},
+	type = {PEP},
+	year = {2014}}
+
+@misc{TorvHama05,
+	author = {Linus Torvalds and Junio Hamano},
+	howpublished = {\url{https://git.kernel.org/pub/scm/git/git.git}},
+	month = {April},
+	title = {Git: {F}ast version control system},
+	year = {2005}}
+
+@inproceedings{McKi10,
+	author = {Wes McKinney},
+	booktitle = {Proceedings of the 9th {Python} in Science Conference},
+	doi = {10.25080/Majora-92bf1922-00a},
+	editor = {St\'efan van der Walt and Jarrod Millman},
+	pages = {56--61},
+	title = {Data {S}tructures for {S}tatistical {C}omputing in {P}ython},
+	year = {2010}}
+
+@article{PedrEtal11,
+	author = {F Pedregosa and G Varoquaux and A Gramfort and V Michel and B Thirion and O Grisel and M Blondel and P Prettenhofer and R Weiss and V Dubourg and J Vanderplas and A Passos and D Cournapeau and M Brucher and M Perrot and E Duchesnay},
+	journal = {Journal of Machine Learning Research},
+	pages = {2825--2830},
+	title = {Scikit-learn: machine learning in {P}ython},
+	volume = {12},
+	year = {2011}}
+
+@misc{Varo10,
+	author = {Ga\"{e}l Varoquaux},
+	howpublished = {\url{https://github.com/joblib/joblib}},
+	month = {July},
+	title = {Joblib: {C}omputing with {P}ython functions},
+	year = {2010}}
+
+@article{HarrEtal20,
+	author = {Charles R Harris and K Jarrod Millman and St{\'{e}}fan J van der Walt and Ralf Gommers and Pauli Virtanen and David Cournapeau and Eric Wieser and Julian Taylor and Sebastian Berg and Nathaniel J Smith and Robert Kern and Matti Picus and Stephan Hoyer and Marten H van Kerkwijk and Matthew Brett and Allan Haldane and Jaime Fern{\'{a}}ndez del R{\'{i}}o and Mark Wiebe and Pearu Peterson and Pierre G{\'{e}}rard-Marchant and Kevin Sheppard and Tyler Reddy and Warren Weckesser and Hameer Abbasi and Christoph Gohlke and Travis E Oliphant},
+	doi = {10.1038/s41586-020-2649-2},
+	journal = {Nature},
+	number = {7825},
+	pages = {357--362},
+	title = {Array programming with {NumPy}},
+	volume = {585},
+	year = {2020}}
+
+@misc{AbadEtal15,
+	author = {Mart\'{i}n~Abadi and Ashish~Agarwal and Paul~Barham and Eugene~Brevdo and Zhifeng~Chen and Craig~Citro and Greg~S.~Corrado and Andy~Davis and Jeffrey~Dean and Matthieu~Devin and Sanjay~Ghemawat and Ian~Goodfellow and Andrew~Harp and Geoffrey~Irving and Michael~Isard and Yangqing Jia and Rafal~Jozefowicz and Lukasz~Kaiser and Manjunath~Kudlur and Josh~Levenberg and Dandelion~Man\'{e} and Rajat~Monga and Sherry~Moore and Derek~Murray and Chris~Olah and Mike~Schuster and Jonathon~Shlens and Benoit~Steiner and Ilya~Sutskever and Kunal~Talwar and Paul~Tucker and Vincent~Vanhoucke and Vijay~Vasudevan and Fernanda~Vi\'{e}gas and Oriol~Vinyals and Pete~Warden and Martin~Wattenberg and Martin~Wicke and Yuan~Yu and Xiaoqiang~Zheng},
+	force = {True},
+	note = {Software available from tensorflow.org},
+	title = {{TensorFlow: Large-Scale Machine Learning on Heterogeneous Systems}},
+	url = {https://www.tensorflow.org/},
+	year = {2015}}
+
+@article{McInEtal18b,
+	author = {L McInnes and J Healy and N Saul and L Gro{\ss}berger},
+	doi = {https://doi.org/10.21105/joss.00861},
+	journal = {Journal of Open Source Software},
+	number = {29},
+	pages = {861},
+	title = {{UMAP}: {U}niform {M}anifold {A}pproximation and {P}rojection},
+	volume = {3},
+	year = {2018}}
+
+@article{Hunt07,
+	author = {J D Hunter},
+	doi = {10.1109/MCSE.2007.55},
+	journal = {Computing in Science and Engineering},
+	number = {3},
+	pages = {90--95},
+	title = {Matplotlib: A {2D} graphics environment},
+	volume = {9},
+	year = {2007}}
+
+@article{Wask21,
+	author = {Michael L Waskom},
+	doi = {10.21105/joss.03021},
+	journal = {Journal of Open Source Software},
+	number = {60},
+	pages = {3021},
+	title = {{s}eaborn: statistical data visualization},
+	volume = {6},
+	year = {2021}}
+
+@article{HeusEtal17,
+	author = {A C Heusser and P C Fitzpatrick and C E Field and K Ziman and J R Manning},
+	journal = {Journal of Open Source Software},
+	title = {Quail: a {Python} toolbox for analyzing and plotting free recall data},
+	volume = {10.21105/joss.00424},
+	year = {2017}}
+
+@misc{FredEtal15,
+	author = {J Frederic and J Grout and {Jupyter Widgets Contributors}},
+	force = {True},
+	howpublished = {\url{https://github.com/jupyter-widgets/ipywidgets}},
+	month = {August},
+	title = {{ipywidgets: Interactive Widgets for the Jupyter Notebook}},
+	year = {2015}}
+
+@misc{daCoEtal22,
+	author = {Casper da Costa-Luis and Stephen Karl Larroque and Kyle Altendorf and Hadrien Mary and richardsheridan and Mikhail Korobov and Noam Raphael and Ivan Ivanov and Marcel Bargull and Nishant Rodrigues and Guangshuo Chen and Antony Lee and Charles Newey and CrazyPython and {JC} and Martin Zugnoni and Matthew D. Pagel and mjstevens777 and Mikhail Dektyarev and Alex Rothberg and Alexander Plavin and Daniel Panteleit and Fabian Dill and FichteFoll and Gregor Sturm and HeoHeo and Hugo van Kemenade and Jack McCracken and MapleCCC and Max Nordlund},
+	doi = {10.5281/zenodo.595120},
+	force = {True},
+	howpublished = {\url{https://github.com/tqdm/tqdm}},
+	month = {September},
+	title = {{tqdm}: A {F}ast, {E}xtensible {P}rogress {B}ar for {P}ython and {CLI}},
+	year = {2022}}
+
+@article{BleiEtal03,
+	author = {D M Blei and A Y Ng and M I Jordan},
+	journal = {Journal of Machine Learning Research},
+	pages = {993--1022},
+	title = {Latent dirichlet allocation},
+	volume = {3},
+	year = {2003}}
+
+@misc{Mann21d,
+	author = {J R Manning},
+	doi = {10.5281/zenodo.5182775},
+	howpublished = {\url{https://github.com/ContextLab/storytelling-with-data}},
+	month = {June},
+	publisher = {Zenodo},
+	title = {Storytelling with {D}ata},
+	year = {2021}}
+
+@misc{Mann22,
+	author = {Jeremy Manning},
+	doi = {10.5281/zenodo.6596762},
+	force = {True},
+	howpublished = {\url{https://github.com/ContextLab/experimental-psychology/tree/v1.0}},
+	month = {May},
+	publisher = {Zenodo},
+	title = {{ContextLab/experimental-psychology: v1.0 (Spring, 2022)}},
+	year = {2022}}
+
+@misc{Mann21e,
+	author = {J R Manning},
+	doi = {10.5281/zenodo.7261831},
+	force = {True},
+	howpublished = {\url{https://github.com/ContextLab/abstract2paper}},
+	month = {June},
+	title = {{abstract2paper}},
+	year = {2021}}
+
+@article{GaoEtal20,
+	author = {Gao, Leo and Biderman, Stella and Black, Sid and Golding, Laurence and Hoppe, Travis and Foster, Charles and Phang, Jason and He, Horace and Thite, Anish and Nabeshima, Noa and Presser, Shawn and Leahy, Connor},
+	force = {True},
+	journal = {{arXiv} preprint ar{X}iv:2101.00027},
+	title = {{The Pile: An 800GB Dataset of Diverse Text for Language Modeling}},
+	year = {2020}}
+
+@misc{BlacEtal21,
+	author = {Black, Sid and Gao, Leo and Wang, Phil and Leahy, Connor and Biderman, Stella},
+	force = {True},
+	howpublished = {\url{http://github.com/eleutherai/gpt-neo}},
+	title = {{GPT-Neo: Large Scale Autoregressive Language Modeling with Mesh-Tensorflow}},
+	version = {1.0},
+	year = {2021}}
+
+@article{HeusEtal18a,
+	author = {A C Heusser and K Ziman and L L W Owen and J R Manning},
+	journal = {Journal of Machine Learning Research},
+	number = {152},
+	pages = {1--6},
+	title = {{HyperTools}: a {Python} toolbox for gaining geometric insights into high-dimensional data},
+	volume = {18},
+	year = {2018}}
+
+@techreport{Shan20,
+	author = {M Shannon},
+	institution = {Python Software Foundation},
+	month = {September},
+	number = {638},
+	title = {Syntactic {M}acros},
+	type = {Draft PEP},
+	year = {2020}}
diff --git a/paper/main.pdf b/paper/main.pdf
new file mode 100644
index 00000000..1b77946f
Binary files /dev/null and b/paper/main.pdf differ
diff --git a/paper/main.tex b/paper/main.tex
new file mode 100644
index 00000000..b9e621a7
--- /dev/null
+++ b/paper/main.tex
@@ -0,0 +1,833 @@
+\documentclass[preprint,12pt,a4paper]{elsarticle}
+\usepackage{amssymb}
+\usepackage{lineno}
+\usepackage{float}
+\restylefloat{table}
+\usepackage[colorlinks=true, urlcolor=blue, pdfborder={0 0 0}]{hyperref}
+\usepackage{breakurl}
+\usepackage{placeins}
+\usepackage{geometry}
+\geometry{left=1in,right=1in,top=1in,bottom=1in,headsep=0pt}
+
+\journal{SoftwareX}
+
+\begin{document}
+\begin{frontmatter}
+
+\title{\texttt{davos}: a Python package ``smuggler'' for constructing
+  lightweight reproducible notebooks}
+\author{Paxton C. Fitzpatrick}
+\author{Jeremy R. Manning\corref{cor}}
+\ead{Jeremy.R.Manning@Dartmouth.edu}
+\cortext[cor]{Corresponding author}
+\address{Department of Psychological and Brain Sciences\\Dartmouth College, Hanover, NH 03755}
+
+
+\begin{abstract}
+  Reproducibility is a core requirement of modern scientific research.
+  For computational research, reproducibility means that code should
+  produce the same results, even when run on different systems.  A
+  standard approach to ensuring reproducibility entails packaging a
+  project's dependencies along with its primary code base.  Existing
+  solutions vary in how deeply these dependencies are specified,
+  ranging from virtual environments, to containers, to virtual
+  machines.  Each of these existing solutions requires installing or
+  setting up a system for running the desired code, increasing the
+  complexity and time cost of sharing or engaging with reproducible
+  science. Here, we propose a lighter-weight solution: the
+  \texttt{davos} package.  When used in combination with a
+  notebook-based Python project, \texttt{davos} provides a mechanism
+  for specifying (and automatically installing) the correct versions
+  of the project's dependencies.  The \texttt{davos} package further
+  ensures that those packages and specific versions are used every
+  time the notebook's code is executed.  This enables researchers to
+  share a complete reproducible copy of their code within a single
+  Jupyter notebook file.
+\end{abstract}
+
+
+\begin{keyword}
+  Reproducibility \sep Open science \sep Python \sep Jupyter Notebook
+  \sep Google Colaboratory \sep Package management
+\end{keyword}
+
+\end{frontmatter}
+
+
+\section*{Metadata}
+
+\section*{Current code version}
+
+\begin{table}[H]
+\begin{tabular}{|l|p{6.5cm}|p{6.5cm}|}
+\hline
+\textbf{Nr.} & \textbf{Code metadata description} & \textbf{Metadata value} \\
+\hline
+C1 & Current code version &  v0.1.1 \\
+\hline
+C2 & Permanent link to code/repository used for this code version & \url{https://github.com/ContextLab/davos/tree/v0.1.1} \\
+\hline
+C3 & Code Ocean compute capsule & \\
+\hline
+C4 & Legal Code License & MIT \\
+\hline
+C5 & Code versioning system used & git \\
+\hline
+C6 & Software code languages, tools, and services used & Python, JavaScript, PyPI/pip, IPython, Jupyter, ipykernel, PyZMQ. Additional tools used for tests: pytest, Selenium, Requests, Mypy, GitHub Actions \\
+\hline
+C7 & Compilation requirements, operating environments, and
+     dependencies & Dependencies:~Python $\geq 3.6$, packaging, setuptools.~Supported OSes: MacOS, Linux, Unix-like.~Supported IPython environments: Jupyter Notebooks, JupyterLab, Google Colaboratory, Binder, IDE-based notebook editors. \\
+\hline
+C8 & Link to developer documentation/manual & \url{https://github.com/ContextLab/davos\#readme} \\
+\hline
+C9 & Support email for questions & contextualdynamics@gmail.com \\
+\hline
+\end{tabular}
+\caption{Code metadata}
+\label{}
+\end{table}
+
+\linenumbers
+
+\section{Motivation and significance}
+
+The same computer code may not behave identically under different
+circumstances.  For example, when code depends on external packages,
+different versions of those packages may function differently.  Or
+when CPU or GPU instruction sets differ across machines, the same
+high-level code may be compiled into different machine instructions.
+Because executing identical code does not guarantee identical
+outcomes, code sharing alone is often insufficient for enabling
+researchers to reproduce each other's work, or to collaborate on
+projects involving data collection or analysis.
+
+Within the Python~\cite{vanR95} community, external packages that are
+published in the most popular repositories~\cite{Pyth03, cond15} are
+associated with version numbers and tags that allow users to guarantee
+they are installing exactly the same code across different computing
+environments~\cite{CoghStuf13}.  While it is \textit{possible} to
+manually install the intended version of every dependency of a Python
+script or package, manually tracking down those dependencies can
+impose a substantial burden on the user and create room for mistakes
+and inconsistencies. Further, when dependency versions are left
+unspecified, replicating the original computing environment becomes
+difficult or impossible.
+
+\begin{figure}[tp]
+\centering
+\includegraphics[width=\textwidth]{figs/shareable_code}
+\caption{\small \textbf{Systems for sharing code within the Python
+    ecosystem.}  From left to right: plain-text \textbf{Python
+    scripts} (\texttt{.py} files) provide the most basic ``system''
+  for sharing raw code.  Scripts may reference external packages, but
+  those packages must be manually installed on other users' systems.
+  Further, any checking needed to verify that the correct versions of
+  those packages were installed must also be performed manually.
+  \textbf{Jupyter notebooks} (\texttt{.ipynb} files) comprise embedded
+  text, executable code, and media (including rendered figures, code
+  output, etc.).  When the \textbf{\texttt{davos} package} is imported
+  into a Jupyter notebook, the notebook's functionality is extended to
+  automatically install any required external packages (at their
+  correct versions, when specified).  \textbf{Virtual environments}
+  allow users to install an isolated copy of Python and all required
+  dependencies. This typically entails distributing a configuration
+  file (e.g., a \texttt{pyproject.toml}~\cite{CannEtal16} or
+  \texttt{environment.yml} file) that specifies all project
+  dependencies (including version numbers of external packages)
+  alongside the primary code base. Users can then install a
+  third-party tool~\cite[e.g.,][]{Anac12, Eust19} to read the file and
+  build the environment.  \textbf{Containers} provide a means of
+  defining an isolated environment that includes a complete operating
+  system (independent of the user's operating system), in addition to
+  (optionally) specifying a virtual environment or other
+  configurations needed to provide the necessary computing
+  environment.  Containers are typically defined using specification
+  files (e.g., a plain-text \texttt{Dockerfile}) that instruct the
+  virtualization engine regarding how to build the containerized
+  environment.  \textbf{Virtual machines} provide a complete
+  hardware-level simulation of the computing environment.  In addition
+  to simulating specific hardware, virtual machines (typically
+  specified using binary image files) must also define operating
+  system-level properties of the computing environment.  Systems to
+  the left of the blue vertical line entail sharing individual files,
+  with no additional installation or configuration needed to run the
+  target code.  Systems to the right of the red vertical line support
+  precise control over dependencies and versioning.  Notebooks
+  enhanced using the \texttt{davos} package are easily shareable and
+  require minimal setup costs, while also facilitating high
+  reproducibility by enabling precise control over project
+  dependencies.}
+\label{fig:code-sharing}
+\end{figure}
+
+Computational researchers and other programmers have de\-vel\-oped a
+broad set of approaches and tools to facilitate code sharing and
+reproducible outcomes (Fig.~\ref{fig:code-sharing}). At one extreme,
+simply distributing a set of Python scripts (\texttt{.py} files) may
+enable others to use or gain insights into the relevant work. Because
+Python is installed by default on most modern operating systems, for
+some projects, this may be sufficient. Another popular approach
+entails creating Jupyter notebooks~\cite{KluyEtal16} that comprise a
+mix of text, executable code, and embedded media. Notebooks may call
+or import external scripts or packages---or even intersperse snippets
+of other programming or markup lang\-uages---in order to provide a
+more compact and readable experience for users. Both of these systems
+(Python scripts and notebooks) provide a convenient means of sharing
+code, with the caveat that they do not specify the computing
+environment in which the code is executed. Therefore the functionality
+of code shared using these systems cannot be guaranteed across
+different users or setups.
+
+At another extreme, virtual machines~\cite{Gold74, AltiEtal05, Rose99}
+provide a hardware-level simulation of the desired system.  Virtual
+machines are typically isolated, such that installing or running
+software on a virtual machine does not impact the user's primary
+operating system or computing environment.
+Containers~\cite[e.g.,][]{Merk14, KurtEtal17} provide a similar
+``isolated'' experience. Although containerized environments do not
+specify hardware-level operations, they are typically packaged with a
+complete operating system, in addition to a complete copy of Python
+and any relevant package dependencies. Virtual
+environments~\cite[e.g.,][]{Anac12, Eust19} also provide a computing
+environment that is largely separated from the user's main
+environment. They incorporate a copy of Python and the target
+software's dependencies, but virtual environments do not specify or
+reproduce an operating system for the runtime environment. Each of
+these systems (virtual machines, containers, and virtual environments)
+guarantees (to differing degrees---at the hardware level, operating
+system level, and Python environment level, respectively) that the
+relevant code will run similarly for different users. However, each of
+these systems also relies on additional software that can be complex
+or resource-intensive to install and use, creating potential barriers
+to both contributing to and taking advantage of open science
+resources.
+
+We designed \texttt{davos} to occupy a ``sweet spot'' between these
+extremes.  \texttt{davos} is a notebook-installable package that adds
+functionality to the default notebook experience. Like standard
+Jupyter notebooks, \texttt{davos}-enhanced notebooks allow researchers
+to include text, executable code, and media within a single file. No
+further setup or installation is required, beyond what is needed to
+run standard Jupyter notebooks. And like virtual environments,
+\texttt{davos} provides a convenient mechanism for fully specifying
+(and installing, as needed) a complete set of Python dependencies,
+including specific package versions.
+
+
+\section{Software description}
+
+The \texttt{davos} package is named after Davos Seaworth, a smuggler
+referred to as ``the Onion Knight" from the series \textit{A Song of
+  Ice and Fire} by George R. R. Martin~\cite{Mart98}.  The
+\texttt{smuggle} keyword provided by \texttt{davos}
+is a play on Python's \texttt{import} keyword: whereas importing can
+load a package into the Python workspace within the existing rules
+and frameworks provided by the Python language, ``smuggling'' provides
+an alternative that expands the scope and reach of ``importing.''
+Like the character Davos Seaworth (who became famous for smuggling
+onions through a blockade on his homeland), we use ``onion'' comments
+to precisely control how packages are smuggled into the Python
+workspace.
+
+\begin{figure}[tp]
+\centering
+\includegraphics[width=\textwidth]{figs/package_structure}
+\caption{\small \textbf{Package structure.} The \texttt{davos} package
+  comprises two interdependent subpackages.  The \texttt{davos.core}
+  subpackage includes modules for parsing \texttt{smuggle} statements
+  and onion comments, installing and validating packages, and
+  configuring \texttt{davos}'s behavior.  The
+  \texttt{davos.implementations} subpackage includes
+  environment-specific modifications and features that are needed to
+  support the core functionality across different notebook-based
+  environments.  Individual modules (i.e., \texttt{.py} files) are represented by lime
+  rounded rectangles, and arrows denote dependencies (each arrow
+  points to a module that imports objects defined in the module at the
+  arrow's source).}
+\label{fig:package-structure}
+\end{figure}
+
+\subsection{Software architecture}
+
+The \texttt{davos} package consists of two interdependent subpackages
+(see Fig.~\ref{fig:package-structure}). The first,
+\texttt{davos.core}, comprises a set of modules that
+implement the bulk of the package's core
+functionality, including pipelines for installing and validating
+packages, custom parsers for the \texttt{smuggle} statement (see
+Sec.~\ref{subsec:smuggle}) and onion comment (see
+Sec.~\ref{subsec:onion}), and a runtime interface for configuring
+\texttt{davos}'s behavior (see Sec.~\ref{subsec:config}). However,
+certain critical aspects of this
+functionality require (often substantially) different implementations
+depending on properties of the notebook environment in which
+\texttt{davos} is used (e.g., whether the frontend is provided by
+Jupyter or Google Colaboratory, or which version of
+IPython~\cite{PereGran07} is used by the notebook kernel). To deal
+with this, environment-dependent parts of core features and behaviors
+are isolated and abstracted to ``helper functions'' in the
+\texttt{davos.implementations} subpackage. This second subpackage
+defines multiple, interchangeable versions of each helper function,
+organized into modules by the conditions that trigger their use. At
+runtime, \texttt{davos} detects various features in the notebook
+environment and selectively imports a single version of each helper
+function into the top-level \texttt{davos.implementations} namespace,
+allowing \texttt{davos.core} modules to access the proper
+implementations for the current notebook environment in a single,
+consistent location. An additional benefit of this design is that it
+allows maintainers, developers, and users to extend \texttt{davos} to
+support new, updated, or custom notebook variants by creating new
+\texttt{davos.implementations} modules that define their own versions
+of each helper function, modified from existing implementations as
+needed.
+
+\subsection{Software functionalities}
+
+\subsubsection{The \texttt{smuggle} statement}\label{subsec:smuggle}
+
+Functionally, importing \texttt{davos} in an IPython notebook enables
+an additional Python keyword: ``\texttt{smuggle}'' (see
+Sec.~\ref{subsec:implementation} for details on how this works).
+The \texttt{smuggle} keyword-like object can be used as a drop-in
+replacement for Python's built-in \texttt{import} keyword to load
+packages, modules, and other objects into the current namespace.
+However, whereas \texttt{import} will fail if the requested package is
+not installed locally, \texttt{smuggle} statements can handle missing
+packages on the fly.  If a smuggled package does not exist in the
+local environment, \texttt{davos} will download and install it automatically,
+expose its contents to Python's \texttt{import} machinery, and load it
+into the namespace for immediate use.
+
+\subsubsection{The onion comment}\label{subsec:onion}
+
+For greater control over the behavior of \texttt{smuggle} statements,
+\texttt{davos} defines an additional construct called the ``onion
+comment.'' An onion comment is a special type of inline comment that
+may be placed on a line containing a \texttt{smuggle} statement to
+customize how \texttt{davos} searches for the smuggled package locally
+and, if necessary, downloads and installs it. Onion comments follow a
+simple format based on the ``type comment'' syntax introduced in PEP
+484~\cite{vanREtal14}, and are designed to make managing packages with
+\texttt{davos} intuitive and familiar. To construct an onion comment,
+users provide the name of the installer program (e.g., \texttt{pip})
+and the same arguments one would use to manually install the package
+as desired via the command line:
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/snippet1}
+\end{center}
+Occasionally, a package's distribution name (i.e., the name used when
+installing it) may differ from its top-level module name (i.e., the
+name used when importing it). In such cases, an onion comment may be
+used to ensure that \texttt{davos} installs the proper package if it
+cannot be found locally:
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/snippet2}
+\end{center}
+Because onion comments may be constructed to specify any aspect of the
+installer's behavior, they provide a mechanism for precisely
+controlling how, where, and when smuggled packages are installed.
+Critically, if an onion comment includes a version
+specifier~\cite{CoghStuf13}, \texttt{davos} will ensure that the
+version of the package loaded into the notebook matches the specific
+version requested, or satisfies the given version constraints. If the
+smuggled package exists locally, \texttt{davos} will extract its
+version information from its metadata and compare it to the specifier
+provided. If the two are incompatible (or no local installation is
+found), \texttt{davos} will download, install, and load a suitable
+version of the package instead:
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/snippet3}
+\end{center}
+Onion comments can also be used to \texttt{smuggle} specific VCS references
+(e.g., Git~\cite{TorvHama05} branches, commits, tags, etc.):
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/snippet4}
+\end{center}
+\texttt{davos} processes onion comments internally before forwarding
+arguments to the installer program. In addition to preventing onion
+comments from being used as a vehicle for shell injection attacks,
+this enables \texttt{davos} to adapt its behavior based on how particular
+flags will affect the behavior of the installer program. For example, if an
+onion comment contains either the \texttt{-I}/\texttt{--ignore-installed},
+\texttt{-U}/\texttt{--upgrade}, or \texttt{--force-reinstall} flag,
+\texttt{davos} will not bother checking for a local copy of the smuggled
+package before installing a new one:
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/snippet5}
+\end{center}
+Similarly, the \texttt{--no-input} flag will temporarily enable
+\texttt{davos}'s non-interactive mode (see
+Sec.~\ref{subsec:config}), and installing a smuggled package into a
+custom directory (\texttt{<dir>}) using the \texttt{--target <dir>}
+flag will cause \texttt{davos} to prepend \texttt{<dir>} to the module
+search path (i.e., \texttt{sys.path}), if necessary, so the package
+can be imported.
+
+\subsubsection{The \texttt{davos} config object}\label{subsec:config}
+
+The \texttt{davos} config object provides a high-level interface for
+controlling various aspects of \texttt{davos}'s behavior. After
+importing \texttt{davos}, the \texttt{davos.con\-fig} object (a
+singleton) exposes configurable options as attributes that can be
+modified, displayed in the notebook, or checked programmatically at runtime
+(see Sec.~\ref{sec:illustrative-example} for an illustrative example or
+Sec.~\ref{subsec:implementation} for implementation details and
+additional information). These include:
+
+\begin{itemize}
+\item \texttt{.active}: This attribute controls whether support for \texttt{smuggle}
+  statements and onion comments is enabled (\texttt{True}) or
+  disabled (\texttt{False}).  When \texttt{davos} is first imported,
+  the \texttt{.active} attribute is set to \texttt{True}.
+
+\item \texttt{.auto\_rerun}: This attribute controls how
+  \texttt{davos} behaves when attempting to \texttt{smuggle} a new
+  version of a package that was previously imported and cannot be
+  reloaded. This can happen if the package includes extension modules
+  that dynamically link C or C++ objects to the Python interpreter,
+  and the code that generates those objects was changed between the
+  previously imported and to-be-smuggled versions.  If this attribute
+  is set to \texttt{True}, \texttt{davos} will automatically restart
+  the notebook kernel and rerun all code up to (and including) the
+  current \texttt{smuggle} statement. If set to \texttt{False} (the default),
+  \texttt{davos} will instead issue a warning, pause execution, and
+  prompt the user to either restart and rerun the notebook, or
+  continue running with the previously imported package version until
+  the next time the kernel is restarted manually.  Note that, as of
+  this writing, the \texttt{.auto\_rerun} attribute is not supported
+  in Google Colaboratory notebooks.
+
+\item \texttt{.confirm\_install}: If set to \texttt{True} (default:
+  \texttt{False}), \texttt{davos} will require user confirmation
+  before installing a smuggled package that does not yet exist in the
+  user's environment.
+
+\item \texttt{.noninteractive}: Setting this attribute to
+  \texttt{True} (default: \texttt{False}) enables non-in\-ter\-act\-ive
+  mode, in which all user interactions (prompts and dialogues) are
+  disabled. Note that in non-interactive mode, the
+  \texttt{confirm\_install} option is set to \texttt{False}.  If
+  \texttt{auto\_rerun} is set to \texttt{False} while in non-interactive
+  mode, \texttt{davos} will raise an exception if a smuggled package
+  cannot be reloaded, rather than prompting the user.
+
+\item \texttt{.pip\_executable}: This attribute's value specifies the
+  path to the \texttt{pip} executable used to install smuggled
+  packages. The default is programmatically determined from the Python
+  environment and falls back to \texttt{sys.executable -m pip} if no
+  executable can be found.
+
+\item \texttt{.suppress\_stdout}: If this attribute is set to
+  \texttt{True} (default: \texttt{False}), \texttt{davos} suppresses
+  printed (console) outputs from both itself and the installer program.
+  This can be useful when smuggling packages that need to install many
+  dependencies and/or generate extensive output. However, if the installer
+  program throws an error, both its stdout and stderr streams will be
+  displayed alongside the Python traceback to allow for debugging.
+\end{itemize}
+
+\noindent The top-level \texttt{davos} namespace also defines
+convenience functions for setting and checking whether \texttt{davos}
+is active (\texttt{davos.activate()}; \texttt{davos.deactivate()};
+\texttt{davos.is\_active()}) as well as the \texttt{davos.configure()}
+function, which allows setting multiple configuration options
+simultaneously.
+
+\subsection{Implementation details}\label{subsec:implementation}
+
+Although \texttt{davos} is designed to \textit{appear} to add a new
+keyword to Python's vocabulary, this illusion is actually created through
+several ``hacks'' that make use of the notebook's IPython backend
+for processing and executing users' code.  Specifically, when
+\texttt{davos} is first imported, or when it is activated after having been
+set to an inactive state, two actions are triggered.  First, the
+\texttt{smuggle()} function is injected into the IPython user
+namespace.  Second, the \texttt{davos} parser is registered as a
+custom IPython input transformer.
+
+IPython preprocesses all executed code as plain text before it is sent
+to the Python compiler, in order to handle special constructs like
+\texttt{\%magic} and \texttt{!shell} commands. \texttt{davos} uses
+this process to transform \texttt{smuggle} statements into
+syntactically valid Python code. The \texttt{davos} parser uses a
+regular expression to match lines of code containing \texttt{smuggle}
+statements (and, optionally, onion comments), extract relevant
+information from their text, and replace them with equivalent calls to
+the \texttt{smuggle()} function. For example, if a user runs a
+notebook cell containing
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/snippet6}
+\end{center}
+the code that is actually executed by the Python interpreter would be
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/snippet7}
+\end{center}
+The call to the \texttt{smuggle()} function carries out
+\texttt{davos}'s central logic by determining whether the smuggled
+package must be installed, carrying out the installation if necessary,
+and subsequently loading it into the namespace. This process is
+outlined in Figure~\ref{fig:flow-chart}. Because the
+\texttt{smuggle()} function is defined in the notebook namespace, it
+is also possible (though never necessary) to call it
+directly. Deactivating \texttt{davos} will delete the name
+``\texttt{smuggle}'' from the namespace, unless its value has been
+overwritten and no longer refers to the \texttt{smuggle()}
+function. It will also deregister the \texttt{davos} parser from the
+set of input transformers run when each notebook cell is
+executed. While the overhead added by the \texttt{davos} parser is
+minimal, this may be useful, for example, when optimizing or precisely
+profiling code.
+
+\begin{figure}[tp]
+\centering
+\includegraphics[width=\textwidth]{figs/flow_chart}
+\caption{\small \textbf{\texttt{smuggle()} function algorithm.}  At a
+  high level, the \texttt{smuggle()} function may be conceptualized as
+following two basic steps.  First (left), \texttt{davos} ensures that the
+correct version of the desired package has been installed, carrying
+out the installation automatically if needed.  Second (right),
+\texttt{davos} imports the package and updates the current runtime environment.}
+\label{fig:flow-chart}
+\end{figure}
+
+
+\section{Illustrative Example}\label{sec:illustrative-example}
+
+\begin{figure}[tp]
+\centering
+\includegraphics[width=\textwidth]{figs/illustrative_example}
+\caption{\small \textbf{Example use case for \texttt{davos}.}
+  Snippets from this example are also excerpted in the main text of
+  Section~\ref{sec:illustrative-example}.}
+\label{fig:illustrative-example}
+\end{figure}
+
+Across different versions of a given package, particular modules, functions, and other objects
+may be updated, removed, renamed, or otherwise altered.  In addition to changing the
+behaviors of active computations, these changes can render saved objects created using one
+version of a package incompatible with other versions of the
+same package.  For example, the popular \texttt{pandas}~\cite{McKi10}
+library used to include the \texttt{Panel} data structure for storing 3-dimensional arrays.
+Since version 0.20.0, however, the \texttt{Panel} class has been deprecated, and in
+version 0.25.0, it was removed entirely. Suppose a user had a dataset stored in a \texttt{Panel} object
+(created using an older version of \texttt{pandas}) and had saved it to their disk (e.g., for later reuse
+or to share with other users) by serializing the \texttt{Panel} with Python's \texttt{pickle} protocol.
+The \texttt{pickle} protocol is a popular built-in method of persisting data in Python, allowing users to save, share, and load arbitrary objects.
+However, in order to successfully ``unpickle'' (i.e., load and restore) a ``pickled'' (i.e., saved) object, the object's class must be defined in and importable from the same module as when it was saved.
+Thus, because of the \texttt{Panel} class's removal, the user's dataset could not be read by any version of \texttt{pandas} from 0.25.0 or beyond. These
+incompatibilities are also not limited solely to traditional forms of data.
+For example, saved model states and other objects may reference
+modules, functions, attributes, classes, or other objects that may not be identical (or even present)
+across all versions of their associated package.
+
+The example provided in Figure~\ref{fig:illustrative-example}
+demonstrates how the \texttt{davos} package can be used to circumvent
+these incompatibilities by carefully controlling which versions of
+each package are used in different parts of the notebook.  The example
+shows how a dataset and model that require now-incompatible components of
+the \texttt{pandas} and \texttt{scikit-learn}~\cite{PedrEtal11}
+packages may be loaded in (using older versions of each package) and used
+alongside more recent versions of each package that provide new and improved functionality.
+When included at the top of a Jupyter notebook, the code in Figure~\ref{fig:illustrative-example}
+ensures that these objects will be loaded successfully and analyzed using the same set
+of package versions, no matter when or by whom the notebook is run.
+
+After installing and importing \texttt{davos} (lines 1--2), we first \texttt{smuggle} two
+utilities for interacting with local files in the code below. The
+\texttt{smuggle} statement in line 4 loads the \texttt{is\_file()}
+function from the Python standard library's \texttt{os.path}
+module. Standard library modules are included with all Python
+distributions, so this line is functionally equivalent to an
+\texttt{import} statement and does not need or benefit from an onion
+comment. Line 5 loads the \texttt{joblib} package~\cite{Varo10},
+installing it first, if necessary. Since \texttt{joblib}'s I/O
+interface has historically remained stable and backwards-compatible
+across releases, requiring that users have a particular exact version
+installed would likely be unnecessarily restrictive. However, a
+\textit{future} release might introduce some breaking change.  The
+onion comment in line 5 helps ensure the analysis notebook continues
+to run properly in the future by limiting allowable versions to those
+already released when the code was written:
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/example1}
+\end{center}
+Line 7 then uses the \texttt{davos.config} object to enable
+\texttt{davos}'s \texttt{auto\_rerun} option before smuggling the next
+two packages: \texttt{NumPy}~\cite{HarrEtal20} and
+\texttt{pandas}. Because these packages rely heavily on custom C data
+types, loading the particular versions from the onion comments may
+require restarting the notebook kernel if different versions had been previously
+imported during the same interpreter session (see
+Sec.~\ref{subsec:config}).
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/example2}
+\end{center}
+Setting the \texttt{auto\_rerun} attribute to \texttt{True} is particularly useful
+for managing the installation of \texttt{pandas} in the next
+lines:
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/example3}
+\end{center}
+If we suppose that the data contained in \texttt{data-old.pkl} is
+stored in a pickled \texttt{Panel} object, then we must use a version of
+\texttt{pandas} prior to 0.25.0 (i.e., the version in which the \texttt{Panel}
+class was removed) to be able to load it in. Line 11 ensures
+that an older version of \texttt{pandas} will be imported, enabling
+the data to be read in (and, in line 13, written to a CSV
+file, which is compatible with newer \texttt{pandas} versions).
+
+Newer versions of \texttt{pandas} have brought substantial
+improvements including better performance, bug fixes, and additional functionality.
+Although the original dataset had to be read in using an older version
+of the package, we can take advantage of these more recent updates
+by smuggling \texttt{pandas} a second time on line 15 (whose onion comment
+specifies that version 1.3.5 should be installed and loaded).  Since
+a different version of \texttt{pandas} had already been loaded by the Python interpreter
+(on line 11), the notebook kernel must be restarted in order to replace the old
+version's custom C extensions with those from the new version.  The \texttt{auto\_rerun} flag
+set on line 7 enables \texttt{davos} to trigger this process automatically so that
+the code can continue running without user intervention, and converting
+the dataset to a CSV file in lines 10--13 ensures that
+the older version of \texttt{pandas} does not need to be reinstalled.
+
+Next, line 17 uses the \texttt{davos.configure()} function to disable
+the \texttt{auto\_rerun} option and simultaneously enable two other
+options: \texttt{suppress\_stdout} and \texttt{noninteractive}. With
+these options enabled, lines 18--19 \texttt{smuggle}
+\texttt{TensorFlow}~\cite{AbadEtal15}, a powerful end-to-end platform
+for building and working with machine learning models, and
+\texttt{UMAP}~\cite{McInEtal18b}, a package that implements a family
+of related manifold learning techniques. The onion comment in line 19
+also specifies that \texttt{UMAP} should be installed with the
+optional requirements needed for its ``plot'' and ``parametric\_umap''
+features. Together, these two packages depend on 36 other unique
+packages, most of which have dependencies of their own. And if many of
+these are not already installed in the user's environment, lines
+18--19 could take several minutes to run.  Enabling the
+\texttt{noninteractive} option ensures that the installation will
+continue automatically without user input during that time.  Enabling
+\texttt{suppress\_stdout} also suppresses console outputs while installing
+these packages and their many dependencies to prevent other potentially important outputs from being buried.
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/example4}
+\end{center}
+
+After re-enabling these two options (line 20), we next \texttt{smuggle}
+specific versions of three plotting packages:
+\texttt{Matplotlib}~\cite{Hunt07}, \texttt{seaborn}~\cite{Wask21}, and
+\texttt{Quail}~\cite{HeusEtal17} (lines 22--24). Because the first two
+are requirements of \texttt{UMAP}'s optional ``plot'' feature, they
+will have already been installed by line 19, though possibly as
+different versions than those specified in the onion comments on lines
+22 and 23. If the installed and specified versions are the same, these
+\texttt{smuggle} statements will function like standard \texttt{import}
+statements to load the packages into the notebook namespace. If they
+differ, \texttt{davos} will download the requested versions in place
+of the installed versions before doing so.
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/example5}
+\end{center}
+Line 24 uses an onion comment to specify that \texttt{Quail} should be
+installed directly from a specific GitHub commit (\texttt{6c847a4}).
+This ability to load packages directly from GitHub repositories can
+enable developers to more easily use forked or modified versions of other
+packages in their notebooks, even if those versions have not been
+officially released.
+
+In lines 26--29, we demonstrate another aspect of \texttt{davos}'s
+functionality that supports more advanced installation scenarios.  The
+\texttt{ipywidgets}~\cite{FredEtal15} package provides an API for
+creating various JavaScript widgets with Python code, and the \texttt{widgetsnbextension} package provides
+the machinery needed by the notebook frontend to display them.
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/example6}
+\end{center}
+A complication is that \texttt{ipywidgets} must be installed in the
+same environment as the IPython kernel, whereas
+\texttt{widgetsnbextension} must be installed in the environment that
+houses the Jupyter notebook server. In many basic setups, these two
+environments are the same.  However, a common ``advanced'' approach
+entails running the notebook server from a base environment, with
+additional environments each providing their own separate,
+interchangeable IPython kernels.  To accomodate this multi-environment
+scenario, on lines 26 and 28, we use the \texttt{pip\_executable} option to control which environments each
+package should be installed to.  Once these two packages are installed
+and imported, line 31 smuggles \texttt{tqdm}~\cite{daCoEtal22}, which
+display progress bars to provide status updates for running code. In
+Jupyter notebooks, the \texttt{tqdm.notebook} module can be imported
+to enable more aesthetically pleasing progress bars that are displayed via
+\texttt{ipywidgets}, if that package is installed and
+importable. Therefore, to take advantage of this feature, it was
+important to \texttt{smuggle} \texttt{tqdm} after ensuring the
+\texttt{ipywidgets} package was available.
+
+Next, we load in the reformatted dataset (line 33) and pre-trained
+model (line 35) that we wish to use in our analysis.  In our
+hypothetical example, we can suppose that the model was provided as a
+\texttt{scikit-learn} \texttt{Pipeline} object that passes data
+through two pretrained models in succession.  First, a trained \texttt{CountVectorizer}
+instance converts text data to an array of word counts.  Second, the
+word counts are passed to a topic model~\cite{BleiEtal03} using a
+pretrained \texttt{LatentDirichletAllocation} instance.
+\begin{center}
+\includegraphics[width=0.9\textwidth]{figs/example7}
+\end{center}
+Let us suppose that the \texttt{Pipeline} object had been saved by its
+original creator using the \texttt{joblib} package, as
+\texttt{scikit-learn}'s documentation recommends.  Because
+\texttt{joblib} uses the \texttt{pickle} protocol internally, the
+ability to save and load pre-trained models is not guaranteed across
+different \texttt{scikit-learn} versions.  For example, suppose that
+the \texttt{Pipeline} object was created using \texttt{scikit-learn}
+v0.21.3.  In that version of \texttt{scikit-learn}, the
+\texttt{LatentDirichletAllocation} class was defined in
+\texttt{sklearn.decomposition.online\_lda}.  However, in version
+0.22.0, that module was renamed to \texttt{\_online\_lda}, and in
+version 0.22.1, it was again renamed to \texttt{\_lda}.
+
+In order to correctly load the model that includes the pre-trained
+\texttt{LatentDirichlet\-Allocation} instance, in line 34, we first
+\texttt{smuggle} a version of \texttt{scikit-learn} prior to v0.22.0 (i.e.,
+before the first time the relevant module's name was changed).  Once
+the model is loaded and reconstructed in memory from a compatible
+package version (line 35), we upgrade to a newer version of
+\texttt{scikit-learn} in line 36.  Taken together, the code in
+Figure~\ref{fig:illustrative-example} shows how \texttt{davos} can
+enable users to load in data and models that are incompatible with
+newer versions of \texttt{pandas} and \texttt{scikit-learn}, but still
+\textit{analyze} and manipulate the data and model output using the
+latest approaches and implementations.
+
+
+\section{Impact}
+
+Like virtual environments, containers, and virtual machines, the
+\texttt{davos} package (when used in conjunction with Jupyter
+notebooks) provides a light\-weight mechanism for sharing code and
+ensuring reproducibility across users and computing environments
+(Fig.~\ref{fig:code-sharing}). Further, \texttt{davos} enables users
+to fully specify (and install, as needed) any project dependencies
+within the same notebook. This provides a system whereby executable
+code (along with text and media) \textit{and} code for setting up and
+configuring the project dependencies, may be combined within a single
+notebook file.
+
+We designed \texttt{davos} for use in research applications. For
+example, in many settings, \texttt{davos} may be used as a drop-in
+replacement for more-difficult-to-set-up virtual environments,
+containers, and/or virtual machines. For researchers, this lowers
+barriers to sharing code. By eliminating most of the setup costs of
+reconstructing the original researchers' computing environment,
+\texttt{davos} also lowers barriers to entry for members of the
+scientific community and the public who seek to run shared code.
+
+Beyond research applications, \texttt{davos} is also useful in
+pedagogical settings. For example, in programming courses, instructors
+and students may use the \texttt{davos} package to ensure their
+notebooks will run correctly on others' machines. When combined with
+online notebook-based platforms like Google Colaboratory,
+\texttt{davos} provides a convenient way to manage dependencies within
+a notebook, without requiring any software (beyond a web browser) to
+be installed on the students' or instructors' systems. For the same
+reasons, \texttt{davos} also provides an elegant means of sharing
+ready-to-run notebook-based demonstrations or tutorials that install
+their dependencies automatically.
+
+Since its initial release, \texttt{davos} has found use in a variety
+of applications. In addition to managing computing environments for
+multiple ongoing research studies, \texttt{davos} is being used by
+both students and instructors in programming and methods courses such
+as Storytelling with Data~\cite{Mann21d} (an open course on data
+science, visualization, and communication) and Laboratory in
+Psychological Science~\cite{Mann22} (an open course on experimental
+and statistical methods for psychology research) to simplify
+distributing lessons and submitting assignments, as well as in online
+demos such as \texttt{abstract2paper}~\cite{Mann21e} (an example
+application of GPT-Neo~\cite{GaoEtal20, BlacEtal21}) to share
+ready-to-run code that installs dependencies automatically.
+
+Our work also has several more subtle ``advanced'' use cases and
+potential impacts. Whereas Python's built-in \texttt{import} statement
+is agnostic to packages' version information, \texttt{smuggle}
+statements (when combined with onion comments) are version-sensitive.
+And because onion comments are parsed at runtime, required packages
+and their specified versions are installed in a just-in-time
+manner. Thus, it is possible in most cases to \texttt{smuggle} a
+specific package version or revision even if a different version has
+already been loaded. This enables more complex uses that take
+advantage of multiple versions of a package within a single
+interpreter session (e.g., see
+Sec.~\ref{sec:illustrative-example} and Fig.~\ref{fig:illustrative-example}). This could be useful in cases
+where specific features are added or removed from a package across
+different versions, or in comparing the performance or functionality
+of particular features across different versions of the same package.
+
+A second more subtle impact of our work is in providing a
+proof-of-concept of how the ability to add new ``keyword-like''
+operators to the Python language could be specifically useful to
+researchers. With \texttt{davos}, we accomplish this by leveraging
+IPython notebooks' internal code parsing and execution machinery. We
+note that, while other popular packages similarly use these mechanisms
+to providing notebook-specific functionality (e.g.,
+\cite{Hunt07,HeusEtal18a}), this approach also has the potential to be
+exploited for more nefarious purposes. For example, a malicious user
+could design a Python package that, when imported, substantially
+changes the notebook's functionality by adding new \textit{unexpected}
+keyword-like objects (e.g., based around common typos). We also note
+that this implementation approach means \texttt{davos}'s functionality
+is currently restricted to IPython notebook environments. However,
+there have been early-stage discussions of providing this sort of
+syntactic customizability as a core feature of the Python language,
+including a draft proposal~\cite{Shan20}. In addition to enabling
+\texttt{davos} to be extended for use outside of notebooks, this could
+lead to exciting new tools that, like \texttt{davos}, extend the
+Python language in useful and more secure ways.
+
+
+\section{Conclusions}
+
+The \texttt{davos} package supports reproducible research by providing
+a novel, lightweight system for sharing notebook-based code. But
+perhaps the most exciting uses of the \texttt{davos} package are those
+that we have \textit{not} yet considered or imagined. We hope that the
+research and scientific Python communities will find \texttt{davos} to provide a convenient
+means of managing project dependencies to facilitate code sharing and collaboration. We
+also hope that some of the more advanced applications of our package
+might lead to new insights or discoveries.
+
+
+\section*{Author Contributions}
+
+\textbf{Paxton C. Fitzpatrick}: Conceptualization, Methodology,
+Software, Validation, Writing - Original Draft,
+Visualization. \textbf{Jeremy R. Manning}: Conceptualization,
+Resources, Validation, Writing - Review \& Editing, Visualization, Supervision,
+Funding acquisition.
+
+\section*{Funding}
+
+Our work was supported in part by NSF grant number 2145172 to JRM.
+The content is solely the responsibility of the authors and does not
+necessarily represent the official views of our supporting
+organizations.
+
+
+\section*{Declaration of Competing Interest}
+
+We wish to confirm that there are no known conflicts of interest
+associated with this publication and there has been no significant
+financial support for this work that could have influenced its
+outcome.
+
+
+\section*{Acknowledgements}
+
+We acknowledge useful feedback and discussion from the students of
+JRM's \textit{Storytelling with Data} course (Winter, 2022 offering)
+who used preliminary versions of our package in several assignments.
+
+\bibliographystyle{elsarticle-num}
+\bibliography{main}
+
+\end{document}
+
diff --git a/paper/paper.bib b/paper/paper.bib
deleted file mode 100644
index 6bf4f82a..00000000
--- a/paper/paper.bib
+++ /dev/null
@@ -1,120 +0,0 @@
-@article{GranGrou16,
-  title={JupyterLab: Building blocks for interactive computing},
-  author={Granger, B and Grout, J},
-  journal={Slides of presentation made at SciPy},
-  year={2016}
-}
-
-@article{HarrEtal20,
-	author = {Charles R. Harris and K. Jarrod Millman and St{\'{e}}fan J. van der Walt and Ralf Gommers and Pauli Virtanen and David Cournapeau and Eric Wieser and Julian Taylor and Sebastian Berg and Nathaniel J. Smith and Robert Kern and Matti Picus and Stephan Hoyer and Marten H. van Kerkwijk and Matthew Brett and Allan Haldane and Jaime Fern{\'{a}}ndez del R{\'{i}}o and Mark Wiebe and Pearu Peterson and Pierre G{\'{e}}rard-Marchant and Kevin Sheppard and Tyler Reddy and Warren Weckesser and Hameer Abbasi and Christoph Gohlke and Travis E. Oliphant},
-	doi = {10.1038/s41586-020-2649-2},
-	journal = {Nature},
-	number = {7825},
-	pages = {357--362},
-	title = {Array programming with {NumPy}},
-	volume = {585},
-	year = {2020}}
-
-@article{Hunt07,
-	author = {J D Hunter},
-	doi = {10.1109/MCSE.2007.55},
-	journal = {Computing in Science and Engineering},
-	number = {3},
-	pages = {90--95},
-	title = {Matplotlib: A {2D} graphics environment},
-	volume = {9},
-	year = {2007}}
-
-@inproceedings{KluyEtal16,
-	address = {Netherlands},
-	author = {Thomas Kluyver and Benjamin Ragan-Kelley and Fernando P{\'e}rez and Brian Granger and Matthias Bussonnier and Jonathan Frederic and Kyle Kelley and Jessica Hamrick and Jason Grout and Sylvain Corlay and Paul Ivanov and Dami{\'a}n Avila and Safia Abdalla and Carol Willing},
-	booktitle = {Positioning and Power in Academic Publishing: Players, Agents and Agendas},
-	doi = {10.3233/978-1-61499-649-1-87},
-	editor = {Fernando Loizides and Birgit Scmidt},
-	pages = {97--90},
-	publisher = {IOS Press},
-	title = {Jupyter Notebooks -- a publishing format for reproducible computational workflows.},
-	year = {2016}}
-
-@misc{Mann21a,
-    author = {J. R. Manning},
-    title = {{abstract2paper}},
-    year = {2021},
-    publisher = {GitHub},
-    journal = {GitHub repository},
-    url = {https://github.com/ContextLab/abstract2paper}}
-
-@misc{Mann21b,
-    author = {J. R. Manning},
-    title = {{Storytelling with Data}},
-    month = {June},
-    year = {2021},
-    publisher = {Zenodo},
-    version = {Spring (2021)},
-    doi = {10.5281/zenodo.5182775},
-    url = {https://doi.org/10.5281/zenodo.5182775}}
-
-@inproceedings{McKi10,
-	author = {W McKinney},
-	booktitle = {Proceedings of the {Python} in Science Conference},
-	doi = {10.25080/Majora-92bf1922-00a},
-	pages = {51--56},
-	title = {Data structures for statistical computing in {P}ython},
-	year = {2010}}
-
-@article{MullEtal15,
-	author = {Muller, Eilif and Bednar, James A and Diesmann, Markus and Gewaltig, Marc-Oliver and Hines, Michael and Davison, Andrew P},
-	doi = {10.3389/fninf.2015.00011},
-	journal = {Frontiers in Neuroinformatics},
-	pages = {11},
-	title = {Python in neuroscience},
-	volume = {9},
-	year = {2015}}
-
-@article{PereGran07,
-	author = {F P{\'e}rez and B E Granger},
-	doi = {10.1109/MCSE.2007.53},
-	journal = {Computing in science \& engineering},
-	number = {3},
-	pages = {21--29},
-	title = {IPython: a system for interactive scientific computing},
-	volume = {9},
-	year = {2007}}
-
-@inproceedings{RagaWill18,
-	author = {Ragan-Kelley, Benjamin and Willing, Carol},
-	booktitle = {Proceedings of the 17th Python in Science Conference},
-	doi = {10.25080/MAJORA-4AF1F417-011},
-	editor = {Akici, F and Lippa, D and Niederhut, D and and Pacer, M},
-	pages = {113--120},
-	title = {Binder 2.0-Reproducible, interactive, sharable environments for science at scale},
-	year = {2018}}
-
-@techreport{vanREtal14,
-	author = {Guido {van Rossum} and Jukka Lehtosalo and {\L}ukasz Langa},
-	month = {September},
-	number = {484},
-	title = {Type {Hints}},
-	type = {PEP},
-	year = {2014},
-	url = {https://www.python.org/dev/peps/pep-0484}}
-
-@article{VirtEtal20,
-	author = {Virtanen, Pauli and Gommers, Ralf and Oliphant, Travis E. and Haberland, Matt and Reddy, Tyler and Cournapeau, David and Burovski, Evgeni and Peterson, Pearu and Weckesser, Warren and Bright, Jonathan and {van der Walt}, St{\'e}fan J. and Brett, Matthew and Wilson, Joshua and Millman, K. Jarrod and Mayorov, Nikolay and Nelson, Andrew R. J. and Jones, Eric and Kern, Robert and Larson, Eric and Carey, C J and Polat, {\.I}lhan and Feng, Yu and Moore, Eric W. and {VanderPlas}, Jake and Laxalde, Denis and Perktold, Josef and Cimrman, Robert and Henriksen, Ian and Quintero, E. A. and Harris, Charles R. and Archibald, Anne M. and Ribeiro, Ant{\^o}nio H. and Pedregosa, Fabian and {van Mulbregt}, Paul and {SciPy 1.0 Contributors}},
-	doi = {10.1038/s41592-019-0686-2},
-	journal = {Nature Methods},
-	number = {3},
-	pages = {261--272},
-	title = {{{SciPy} 1.0: Fundamental Algorithms for Scientific Computing in Python}},
-	volume = {17},
-	year = {2020}}
-
-@article{Wask21,
-	author = {Michael L. Waskom},
-	doi = {10.21105/joss.03021},
-	journal = {Journal of Open Source Software},
-	number = {60},
-	pages = {3021},
-	title = {seaborn: statistical data visualization},
-	volume = {6},
-	year = {2021}}
diff --git a/paper/paper.md b/paper/paper.md
deleted file mode 100644
index 469f02bc..00000000
--- a/paper/paper.md
+++ /dev/null
@@ -1,203 +0,0 @@
----
-title: '`davos`: The Python package smuggler'
-tags:
- - Python
- - Jupyter Notebook
- - JupyterLab
- - Google Colab
- - reproducibility
- - package management
- - import
- - install
- - pip
-authors:
- - name: Paxton C. Fitzpatrick
-   orcid: 0000-0003-0205-3088
-   affiliation: 1
- - name: Jeremy R. Manning
-   orcid: 0000-0001-7613-4732
-   affiliation: 1
-affiliations:
- - name: Department of Psychological and Brain Sciences, Dartmouth College
-   index: 1
-date: 21 January 2022
-bibliography: paper.bib
-link-citations: true
----
-
-
-# Summary
-
-`davos` is a Python package for creating self-managing, reproducible workflows
-that specify dependencies directly within their code and install packages as
-needed at runtime. The `davos` package was developed to simplify sharing
-research-related code including data analyses, tutorials, and demos by allowing
-users to distribute their code and environment together in a single,
-ready-to-run Jupyter notebook [@KluyEtal16].
-
-Importing `davos` enables an additional Python keyword: `smuggle`. The `smuggle`
-statement can be used as a drop-in replacement for the built-in `import`
-statement to load libraries, modules, and other objects into the current
-namespace. However, whereas `import` will fail if the requested package is not
-installed locally, `smuggle` statements can handle missing packages on the fly.
-If a smuggled package does not exist in the local environment, `davos` will
-install it, make its contents visible to Python's `import` machinery, and load
-it into the namespace for immediate use.
-
-For greater control over the behavior of `smuggle` statements, `davos` defines
-an additional construct called the *onion comment*. An onion comment is a
-special type of inline comment that can be placed on a line containing a
-`smuggle` statement to customize how `davos` determines whether and how the
-smuggled package should be installed. Onion comments follow a simple syntax
-based on the type comment syntax introduced in PEP 484 [@vanREtal14] and are
-designed to make controlling installation via `davos` intuitive and familiar. To
-construct an onion comment, simply provide the name of the installer program
-(e.g., `pip`) and the same arguments one would use to install the package as
-desired manually via the command line:
-
-![](snippets/snippet1.pdf)
-
-Onion comments are also useful for smuggling a package whose distribution name
-(i.e., the name used when installing it) is different from its top-level module
-name (i.e., the name used when importing it):
-
-![](snippets/snippet2.pdf)
-
-However, the most powerful use of the onion comment is making `smuggle`
-statements *version-sensitive*. Adding a [version
-specifier](https://www.python.org/dev/peps/pep-0440/#version-specifiers) to an
-onion comment will cause `davos` to search for the smuggled package in the local
-environment (as usual), and if it is found, further check whether the installed
-version satisfies the given constraint(s). If either check fails, `davos` will
-install and use a suitable version of the package:
-
-![](snippets/snippet3.pdf)
-
-It is also possible to `smuggle` a specific VCS reference (e.g., a git branch,
-commit, tag, etc.):
-
-![](snippets/snippet4.pdf)
-
-Additionally, certain arguments can be added to skip searching the local
-environment altogether, and instead always install a fresh version of the
-package:
-
-![](snippets/snippet5.pdf)
-
-Because `davos` parses onion comments at runtime, required packages are
-validated and installed in a just-in-time manner. Thus, it is possible in most
-cases to `smuggle` a specific package version or revision even if a different
-version has already been loaded. This enables more complex use cases that take
-advantage of multiple versions of a package within a single interpreter session
-(e.g., loading an unstable development version only when necessary to use a new
-feature, comparing performance across versions for demos or regression testing,
-etc.).
-
-Finally, `davos` provides a simple, high-level interface that allows users to
-disable, re-enable, and configure its functionality at any point throughout
-their code. `davos` currently supports IPython [@PereGran07] notebook
-environments including Jupyter notebooks [@KluyEtal16], JupyterLab
-[@GranGrou16], Google Colaboratory, Binder [@RagaWill18], and IDE-based notebook
-editors. Potential future directions include extending `davos` for use in
-"vanilla" (i.e., non-interactive) Python scripts and adding support for
-installation via alternative package managers such as `conda`. The `davos`
-package is currently being used in a number of ongoing projects, as well as
-online demos for [*Storytelling with
-Data*](https://github.com/ContextLab/storytelling-with-data) [@Mann21b\; an open
-course on data science, visualization, and communication] and `abstract2paper`
-[@Mann21a\; a toy application of
-[GPT-Neo](https://github.com/EleutherAI/gpt-neo)]. A more extensive guide to
-using `davos`, additional examples, and implementation details are available
-[here](https://github.com/ContextLab/davos).
-
-
-# Statement of Need
-
-Modern open science practices encourage sharing code and data to enable others
-to explore, reproduce, and build on existing work. Scientists, researchers, and
-educators may seek to share research-related code with collaborators, students,
-the research community, or the general public. Python is among the most widely
-used and fastest-growing scientific programming languages [@MullEtal15]. In
-addition to the language's high-level, accessible syntax and large standard
-library, the Python ecosystem offers a powerful and extensive data science
-toolkit designed to facilitate rapid development and collaboration, including
-platforms for interactive programming [e.g., Project Jupyter, @KluyEtal16\;
-Google Colaboratory], community-maintained libraries for data manipulation
-[e.g., `NumPy`, @HarrEtal20; `SciPy`, @VirtEtal20; `Pandas`, @McKi10] and
-visualization [e.g., `Matplotlib`, @Hunt07; `seaborn`, @Wask21], and myriad
-other tools.
-
-One impediment to sharing and reproducing computational work implemented in
-Python is that different versions of a given package or library can behave
-differently, use different syntax, add or drop support for specific functions or
-other libraries, address (or introduce) bugs, and so on. While these challenges
-are present to some extent in any language or ecosystem, they have a particular
-impact on the Python community due to its unusually rapid growth relative to
-other languages. Ensuring stable and reproducible results across users typically
-requires ensuring that shared code is always run with the same set of versions
-for each package used. One common approach to solving this problem is to create
-containerized or virtualized environments (e.g., using
-[Docker](https://www.docker.com/),
-[Singularity](https://sylabs.io/singularity/), or
-[conda](https://docs.conda.io/en/latest/)) that house fully isolated Python
-installations tailored to specific projects. These environments may be shared
-publicly as configuration files from which other users may build identical
-copies themselves. While effective, one drawback to this approach is that it can
-introduce a level of complexity beyond what is warranted for many simpler use
-cases. For example, distributing research code that relies on a particular
-Docker image to run properly not only necessitates extra configuration files and
-setup steps, but requires that both the author and end user install and navigate
-additional software that is often more complicated and resource-intensive than
-the actual code being shared. These added prerequisites clash with the
-simplicity and accessibility that have helped popularize Python among
-researchers, and can create barriers to both contributing to and taking
-advantage of open science.
-
-`davos` provides an alternative way to ensure stable functionality of Jupyter
-notebooks across users and over time that is intuitive, lightweight, and
-contained entirely within the notebook file itself. Using `smuggle` statements
-and onion comments, required packages can be specified directly within the code
-that uses them and automatically installed as they are needed. This offers two
-notable advantages over typical approaches to dependency management. First, it
-simplifies and expedites the process of sharing reproducible workflows by
-eliminating the need for additional configuration files, pre-execution setup,
-and environment management software (aside from `davos` itself). With `davos`,
-analyses, tutorials, and demos can be packaged and shared as "batteries
-included" notebooks that can be downloaded and immediately run, making them more
-accessible to less technical users.
-
-Second, `smuggle` statements and onion comments continue to ensure requirements
-are satisfied after they are initially installed. Most dependency specification
-schemes follow a common strategy: required packages and package versions are
-listed in a configuration file (e.g., a `requirements.txt`, `pyproject.toml`,
-`environment.yml`, `Pipfile`, `RUN` instructions in a `Dockerfile`, etc.) which
-is used to install them in a Python environment up front. After this initial
-setup, however, this method generally does not ensure that the specified
-requirements *remain* installed, allowing them to be easily
-altered&mdash;sometimes inadvertently. This can lead to subtle issues when
-writing reproducible code in such a preconfigured environment. For instance,
-suppose a researcher has implemented a series of analyses using version 1.0 of
-"Package *X*," and later decides to perform an additional analysis that requires
-installing "Package *Y*." If Package *Y* depends on version 1.1 of Package *X*,
-then Package *X* will be upgraded to accommodate this new requirement. And if
-the researcher does not notice this change, differences between the two Package
-*X* versions risk introducing bugs into previously written code. Using `davos`,
-either in lieu of or alongside a different environment management tool, provides
-a safeguard against this situation. `smuggle` statements and onion comments
-enforce requirements every time they are executed, guaranteeing the expected
-version of each package is always used. This would not only catch and correct
-the unintentional change to Package *X*, but would also allow the researcher to
-choose whether to manually resolve the inconsistency or, if appropriate,
-`smuggle` different versions of the package as necessary.
-
-
-# Origin of the Name
-
-The package name is inspired by [Davos
-Seaworth](https://en.wikipedia.org/wiki/Davos_Seaworth), a smuggler often
-referred to as "the Onion Knight" from the series [*A song of Ice and
-Fire*](https://en.wikipedia.org/wiki/A_Song_of_Ice_and_Fire) by [George R. R.
-Martin](https://en.wikipedia.org/wiki/George_R._R._Martin).
-
-
-# References
diff --git a/paper/snippets/snippet1.pdf b/paper/snippets/snippet1.pdf
deleted file mode 100644
index 6da08d61..00000000
Binary files a/paper/snippets/snippet1.pdf and /dev/null differ
diff --git a/paper/snippets/snippet2.pdf b/paper/snippets/snippet2.pdf
deleted file mode 100644
index 2b873452..00000000
Binary files a/paper/snippets/snippet2.pdf and /dev/null differ
diff --git a/paper/snippets/snippet3.pdf b/paper/snippets/snippet3.pdf
deleted file mode 100644
index 83043c9b..00000000
Binary files a/paper/snippets/snippet3.pdf and /dev/null differ
diff --git a/paper/snippets/snippet4.pdf b/paper/snippets/snippet4.pdf
deleted file mode 100644
index 169a7b68..00000000
Binary files a/paper/snippets/snippet4.pdf and /dev/null differ
diff --git a/setup.cfg b/setup.cfg
index 51666a4f..90f289db 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = davos
-version = 0.1.0
+version = 0.1.1
 description = Install and manage Python packages at runtime using the "smuggle" statement.
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -23,6 +23,7 @@ classifiers =
     Programming Language :: Python :: 3.7
     Programming Language :: Python :: 3.8
     Programming Language :: Python :: 3.9
+    Development Status :: 5 - Production/Stable
     Topic :: System :: Filesystems
     Topic :: Utilities
     Typing :: Typed


Test patch:
diff --git a/.github/workflows/ci-tests-colab.yml b/.github/workflows/ci-tests-colab.yml
index ddf9bec4..1e7c8b2f 100644
--- a/.github/workflows/ci-tests-colab.yml
+++ b/.github/workflows/ci-tests-colab.yml
@@ -28,7 +28,7 @@ defaults:
 
 jobs:
   run-tests:
-    name: "Run Colab CI Tests (Python 3.7, IPython 5.5.0)"
+    name: "Run Colab CI Tests (Python 3.7, IPython 7.9.0)"
     runs-on: ubuntu-latest
     # run if triggered by any of the following:
     #   - a workflow_dispatch event (manual or from trigger-colab-tests-pr workflow)
@@ -46,7 +46,7 @@ jobs:
       GMAIL_ADDRESS: ${{ secrets.DAVOS_GMAIL_ADDRESS }}
       GMAIL_PASSWORD: ${{ secrets.DAVOS_GMAIL_PASSWORD }}
       HEAD_FORK: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.fork || github.repository_owner }}
-      IPYTHON_VERSION: 5.5.0
+      IPYTHON_VERSION: 7.9.0
       NOTEBOOK_TYPE: colab
       PYTHON_VERSION: 3.7
       RECOVERY_GMAIL_ADDRESS: ${{ secrets.DAVOS_RECOVERY_GMAIL_ADDRESS }}
diff --git a/.github/workflows/ci-tests-jupyter.yml b/.github/workflows/ci-tests-jupyter.yml
index f08c530b..9cc9a759 100644
--- a/.github/workflows/ci-tests-jupyter.yml
+++ b/.github/workflows/ci-tests-jupyter.yml
@@ -78,7 +78,7 @@ jobs:
           sudo apt-get install firefox
 
           # install python packages
-          pip install pytest==6.2 "selenium>=3.141" geckodriver-autoinstaller
+          pip install pytest==6.2 "selenium==3.141" geckodriver-autoinstaller
 
           # install geckodriver
           driver_path=$(python -c '
@@ -112,7 +112,7 @@ jobs:
               pip install typing-extensions
           fi
           [[ "$PYTHON_VERSION" == "3.6" ]] || pip install numpy==1.20.3	
-          pip install "ipykernel==5.0.0" ipython-genutils requests scipy fastdtw==0.3.4 tqdm==4.41.1
+          pip install "ipykernel==5.0.0" ipython-genutils requests "scipy<=1.7.3" fastdtw==0.3.4 tqdm==4.41.1
           if [[ "$IPYTHON_VERSION" == "latest" ]]; then
               pip install --upgrade IPython
           else
diff --git a/tests/utils.py b/tests/utils.py
index c12d2c1b..668c104f 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -44,7 +44,7 @@
 _E1 = TypeVar('_E1', bound=BaseException)
 _F = TypeVar('_F', bound=Callable[..., None])
 _InstallerKwargVals = Union[bool, int, str]
-_IpyVersions = Literal['5.5.0', '7.3.0', '7.15', '7.16', 'latest']
+_IpyVersions = Literal['5.5.0', '7.3.0', '7.15', '7.16', '7.31', 'latest']
 _StrOrListStr = TypeVar('_StrOrListStr', str, List[str])
 _NbTypes = Literal['colab', 'jupyter']