[med-svn] [python-cobra] 02/06: New upstream version 0.11.0

Afif Elghraoui afif at moszumanska.debian.org
Mon Feb 5 05:34:02 UTC 2018


This is an automated email from the git hooks/post-receive script.

afif pushed a commit to branch master
in repository python-cobra.

commit 362e088175c39da41e7af86c1e146d7055b66433
Author: Afif Elghraoui <afif at debian.org>
Date:   Sun Feb 4 22:10:07 2018 -0500

    New upstream version 0.11.0
---
 .gitignore                                        |   1 +
 .travis.yml                                       | 128 +++--
 MANIFEST.in                                       |   2 +-
 README.rst                                        |  17 +-
 appveyor.yml                                      |  83 +---
 appveyor/build_glpk.py                            |  60 ---
 appveyor/install.ps1                              |  85 ----
 appveyor/run_with_env.cmd                         |  88 ----
 cobra/__init__.py                                 |   4 +-
 cobra/core/arraybasedmodel.py                     | 435 ----------------
 cobra/core/dictlist.py                            |  17 +-
 cobra/core/model.py                               | 149 ++----
 cobra/core/reaction.py                            |  21 +-
 cobra/design/__init__.py                          |   5 -
 cobra/design/design_algorithms.py                 |  17 -
 cobra/exceptions.py                               |  33 +-
 cobra/flux_analysis/__init__.py                   |  14 +-
 cobra/flux_analysis/deletion.py                   | 362 ++++++++++++++
 cobra/flux_analysis/deletion_worker.py            | 177 -------
 cobra/flux_analysis/double_deletion.py            | 580 ----------------------
 cobra/flux_analysis/gapfilling.py                 |  35 +-
 cobra/flux_analysis/loopless.py                   |  44 +-
 cobra/flux_analysis/moma.py                       | 128 +----
 cobra/flux_analysis/parsimonious.py               | 173 +------
 cobra/flux_analysis/phenotype_phase_plane.py      | 303 +----------
 cobra/flux_analysis/reaction.py                   |   8 +-
 cobra/flux_analysis/sampling.py                   |  17 +-
 cobra/flux_analysis/single_deletion.py            | 383 --------------
 cobra/flux_analysis/variability.py                | 175 ++-----
 cobra/io/dict.py                                  |  25 +-
 cobra/io/json.py                                  |  16 +-
 cobra/io/sbml3.py                                 |   2 +-
 cobra/io/yaml.py                                  |  14 +-
 cobra/manipulation/__init__.py                    |   4 +-
 cobra/manipulation/modify.py                      |  75 +--
 cobra/solvers/__init__.py                         | 134 -----
 cobra/solvers/cglpk.pyx                           | 566 ---------------------
 cobra/solvers/coin.py                             | 140 ------
 cobra/solvers/cplex_solver.py                     | 347 -------------
 cobra/solvers/cplex_solver_java.py                | 326 ------------
 cobra/solvers/esolver.py                          | 161 ------
 cobra/solvers/glpk.pxd                            | 218 --------
 cobra/solvers/glpk_solver.py                      | 276 ----------
 cobra/solvers/glpk_solver_java.py                 | 372 --------------
 cobra/solvers/gurobi_solver.py                    | 305 ------------
 cobra/solvers/gurobi_solver_java.py               | 285 -----------
 cobra/solvers/mosek.py                            | 245 ---------
 cobra/solvers/parameters.py                       | 158 ------
 cobra/solvers/wrappers.py                         |  49 --
 cobra/test/conftest.py                            |  31 +-
 cobra/test/test_flux_analysis.py                  | 416 ++++++++--------
 cobra/test/test_io.py                             |  12 +-
 cobra/test/test_io_order.py                       |  92 ++++
 cobra/test/test_manipulation.py                   |  44 +-
 cobra/test/test_model.py                          |  82 +--
 cobra/test/test_solver_model.py                   | 133 ++---
 cobra/test/test_solver_utils.py                   |  18 +-
 cobra/test/test_solvers.py                        | 271 ----------
 cobra/test/test_util.py                           |   2 -
 cobra/topology/__init__.py                        |   5 -
 cobra/topology/reporter_metabolites.py            |   9 -
 cobra/util/solver.py                              |  45 +-
 config.sh                                         |  68 ---
 develop-requirements.txt                          |  20 +-
 documentation_builder/.gitignore                  |   1 +
 documentation_builder/autodoc.sh                  |   5 -
 documentation_builder/cobra.core.rst              |  94 ----
 documentation_builder/cobra.design.rst            |  22 -
 documentation_builder/cobra.flux_analysis.rst     | 110 ----
 documentation_builder/cobra.io.rst                |  46 --
 documentation_builder/cobra.manipulation.rst      |  46 --
 documentation_builder/cobra.rst                   |  43 --
 documentation_builder/cobra.topology.rst          |  22 -
 documentation_builder/cobra.util.rst              |  46 --
 documentation_builder/conf.py                     |  54 +-
 documentation_builder/index.rst                   |   3 +-
 documentation_builder/phenotype_phase_plane.ipynb | 251 ++++++----
 documentation_builder/requirements.txt            |   2 +
 make-mac-wheels.sh                                |  14 -
 manylinux_builder/Dockerfile                      |   6 -
 manylinux_builder/README.md                       |   5 -
 manylinux_builder/build_cobrapy.sh                |  11 -
 manylinux_builder/run_cobrapy_builder.sh          |   2 -
 release-notes/0.10.0.md                           |  19 +
 release-notes/0.10.1.md                           |   9 +
 release-notes/0.11.0.md                           |  14 +
 release-notes/0.9.1.md                            |  19 +
 release-notes/next-release.md                     |   1 +
 scripts/deploy.sh                                 |   7 -
 scripts/deploy_website.sh                         |  21 +
 scripts/prepare_notes.sh                          |  10 +
 scripts/publish_release.py                        | 138 +++++
 setup.cfg                                         |   2 +-
 setup.py                                          | 173 ++-----
 tox.ini                                           |  17 +-
 95 files changed, 1598 insertions(+), 8125 deletions(-)

diff --git a/.gitignore b/.gitignore
index 65f38b7..e5127bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,7 @@ htmlcov/
 .coverage
 .coverage.*
 .cache
+.pytest_cache/
 nosetests.xml
 coverage.xml
 *,cover
diff --git a/.travis.yml b/.travis.yml
index 5bb203c..2da541b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,32 +1,23 @@
 language: python
 python: 3.5
-sudo: required
-dist: trusty
-services: docker
+sudo: false
 cache:
   directories:
     - $HOME/.cache/pip
-addons:
-  apt:
-    packages:
-      - libfreetype6-dev
-      - libpng12-dev
 git:
-  depth: 3
+  depth: 2
 
 branches:
  only:
  - master
  - devel
- - devel-2
  - /^[0-9]+\.[0-9]+\.[0-9]+[.0-9ab]*$/
 
 env:
   global:
     - secure: "hkKBaGLvoDVgktSKR3BmX+mYlGzHw9EO11MRHtiH8D9BbdygOR9p9aSV/OxkaRWhnkSP5/0SXqVgBrvU1g5OsR6cc85UQSpJ5H5jVnLoWelIbTxMCikjxDSkZlseD7ZEWrKZjRo/ZN2qym0HRWpsir3qLpl8W25xHRv/sK7Z6g8="
     - secure: "DflyBz+QiyhlhBxn4wN00xu248EJUMjKTxUZQN6wq22qV55xO3ToGJTy9i4D6OBfZGAlSXxjjKCJ2+0sAjsghBSDEK56ud3EEg/08TIo7/T8ex/C58FsGoGFz3yDBATmquClEWN8vAMrLdxwniHmQVCBZCP/phdt5dct0AUuDc8="
-    - PLAT=x86_64
-    - UNICODE_WIDTH=32
+    - GITHUB_REPO=opencobra/cobrapy
 
 matrix:
   fast_finish: true
@@ -38,70 +29,73 @@ matrix:
       env:
         - TOXENV=pep8
     - os: linux
-      env:
-        - MB_PYTHON_VERSION=2.7
+      python: 2.7
+      env: TOXENV=py27
     - os: linux
-      env:
-        - MB_PYTHON_VERSION=3.4
+      python: 3.5
+      env: TOXENV=py35
     - os: linux
-      env:
-        - MB_PYTHON_VERSION=3.5
+      python: 3.6
+      env: TOXENV=py36
     - os: linux
-      env:
-        - MB_PYTHON_VERSION=3.6
-    - os: osx
-      language: objective-c
-      env:
-        - MB_PYTHON_VERSION=2.7
-    - os: osx
-      language: objective-c
-      env:
-        - MB_PYTHON_VERSION=3.4
-    - os: osx
-      language: objective-c
-      env:
-        - MB_PYTHON_VERSION=3.5
-    - os: osx
-      language: objective-c
-      env:
-        - MB_PYTHON_VERSION=3.6
+      python: 3.5
+      env: TOXENV=sbml
+    - os: linux
+      python: 3.5
+      env: TOXENV=array
+    #    - os: osx
+    #      language: generic
+    #      before_install:
+    #        - brew update
+    #        - brew install --without-readline --without-xz --without-gdbm --without-sqlite python3
+    #        - virtualenv env -p python3
+    #        - source env/bin/activate
+    #    - os: osx
+    #      language: generic
+    #      before_install:
+    #        - brew update
+    #        - brew install --without-readline --without-xz --without-gdbm --without-sqlite python2
+    #        - virtualenv env -p python2
+    #        - source env/bin/activate
 
 before_install:
-  - if [[ -n "${MB_PYTHON_VERSION}" ]]; then
-      (travis_retry git clone https://github.com/matthew-brett/multibuild.git && cd multibuild && git checkout 37040e31b1044468027bd86991c805006a92bf09);
-      TEST_DEPENDS="swiglpk optlang sympy decorator cython codecov coverage numpy scipy jsonschema six pytest pytest-cov pytest-benchmark tabulate";
-      BUILD_DEPENDS="swiglpk optlang sympy cython numpy scipy";
-      source multibuild/common_utils.sh;
-      source multibuild/travis_steps.sh;
-      before_install;
-    fi
-  - pip install -U pip setuptools wheel tox
+  - travis_retry pip install -U pip setuptools wheel tox
 
 before_cache:
   - set +e
 
-install:
-  - if [[ -n "${MB_PYTHON_VERSION}" ]]; then
-      travis_retry build_wheel . $PLAT;
-    fi
-
 script:
-  - if [[ -n "${MB_PYTHON_VERSION}" ]]; then
-      travis_retry install_run $PLAT;
-    else
-      pip install rstcheck Cython;
-      find . -name "*.rst" -exec rstcheck {} +;
-      tox -e "${TOXENV}";
-    fi
-  - ls ${TRAVIS_BUILD_DIR}/wheelhouse/ || echo "no wheelhouse"
+  - travis_wait tox
 
-deploy:
-  provider: script
-  skip_cleanup: true
-  script: scripts/deploy.sh
-  on:
-    branch: master
-    tags: true
+# N.B.: Currently, Travis mangles (escapes) the release tag body badly.
+#before_deploy:
+#  - source scripts/prepare_notes.sh
 
-after_success:
-  - if [[ $TRAVIS_OS_NAME == "linux" ]]; then pip install codecov; codecov; fi
+deploy:
+  - provider: pypi
+    skip_cleanup: true
+    distributions: sdist bdist_wheel
+    on:
+      tags: true
+      repo: $GITHUB_REPO
+      python: '3.6'
+      condition: $TRAVIS_OS_NAME == "linux"
+    user: $PYPI_USERNAME
+    password: $PYPI_PASSWORD
+  - provider: script
+    skip_cleanup: true
+    script: scripts/deploy_website.sh
+    on:
+      tags: true
+      repo: $GITHUB_REPO
+      python: '3.6'
+      condition: $TRAVIS_OS_NAME == "linux"
+  - provider: releases
+    skip_cleanup: true
+    api_key: $GITHUB_TOKEN
+    body: "Please see https://github.com/opencobra/cobrapy/tree/${TRAVIS_TAG}/release-notes/${TRAVIS_TAG}.md for the full release notes."
+    on:
+      tags: true
+      repo: $GITHUB_REPO
+      python: '3.6'
+      condition: $TRAVIS_OS_NAME == "linux"
diff --git a/MANIFEST.in b/MANIFEST.in
index a68bad7..601fa15 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,2 @@
 include README.rst INSTALL.rst LICENSE
-include cobra/solvers/cglpk.pyx cobra/solvers/glpk.pxd
+include cobra
diff --git a/README.rst b/README.rst
index f6de8fa..bbcd61c 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,7 @@
-cobrapy - constraint-based reconstruction and analysis in python
+cobrapy - Constraint-Based Reconstruction and Analysis in Python
 ================================================================
 
-|Build Status| |Coverage Status| |Build status| |PyPI| |Gitter| |Waffle|
+|Build Status| |Coverage Status| |Build status| |PyPI| |Gitter|
 
 What is cobrapy?
 ~~~~~~~~~~~~~~~~
@@ -52,8 +52,8 @@ If you use cobrapy in a scientific publication, please cite
 Installation
 ~~~~~~~~~~~~
 
-Use pip to install cobrapy from
-`PyPI <https://pypi.python.org/pypi/cameo>`_ (we recommend doing this
+Use pip to `install cobrapy from
+PyPI <https://pypi.python.org/pypi/cobra>`_ (we recommend doing this
 inside a `virtual
 environment <http://docs.python-guide.org/en/latest/dev/virtualenvs/>`_)::
 
@@ -95,12 +95,9 @@ Public License for more details.
    :target: https://travis-ci.org/opencobra/cobrapy
 .. |Coverage Status| image:: https://codecov.io/github/opencobra/cobrapy/coverage.svg?branch=master
    :target: https://codecov.io/github/opencobra/cobrapy
-.. |Build status| image:: https://ci.appveyor.com/api/projects/status/2o549lhjyukke8nd/branch/master?svg=true
-   :target: https://ci.appveyor.com/project/hredestig/cobrapy/branch/master
-.. |PyPI| image:: https://img.shields.io/pypi/v/cobra.svg
+.. |Build status| image:: https://ci.appveyor.com/api/projects/status/qmqdy67jbwbc3ds7/branch/master?svg=true
+   :target: https://ci.appveyor.com/project/cobrapy39491/cobrapy/branch/master
+.. |PyPI| image:: https://badge.fury.io/py/cobra.svg
    :target: https://pypi.python.org/pypi/cobra
 .. |Gitter| image:: https://badges.gitter.im/opencobra/cobrapy.svg
    :target: https://gitter.im/opencobra/cobrapy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
-.. |Waffle| image:: https://badge.waffle.io/opencobra/cobrapy.png?label=ready&title=Ready
-   :target: https://waffle.io/opencobra/cobrapy
-   :alt: 'Stories in Ready'
diff --git a/appveyor.yml b/appveyor.yml
index 017a3a4..5bfaa0d 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,108 +2,63 @@ branches:
  only:
  - master
  - devel
- - devel-2
  - /^[0-9]+\.[0-9]+\.[0-9]+[.0-9ab]*$/
 
 environment:
 
   global:
-    # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
-    # /E:ON and /V:ON options are not enabled in the batch script intepreter
-    # See: http://stackoverflow.com/a/13751649/163740
-    WITH_COMPILER: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd"
     PIP_CACHE_DIR: "pip_cache"
 
   matrix:
     - PYTHON: "C:\\Miniconda-x64"
-      PYTHON_VERSION: "2.7.12"
-      PYTHON_ARCH: "64"
       CONDA: true
 
     - PYTHON: "C:\\Miniconda35-x64"
-      PYTHON_VERSION: "3.5.2"
-      PYTHON_ARCH: "64"
       CONDA: true
 
-    - PYTHON: "C:\\Miniconda35-x64"
-      PYTHON_VERSION: "3.6.0"
-      PYTHON_ARCH: "64"
+    - PYTHON: "C:\\Miniconda36-x64"
       CONDA: true
 
-    # - PYTHON: "C:\\Python27"
-    #   PYTHON_VERSION: "2.7.12"
-    #   PYTHON_ARCH: "32"
-
-    # - PYTHON: "C:\\Python34"
-    #   PYTHON_VERSION: "3.4.5"
-    #   PYTHON_ARCH: "32"
-
-    # - PYTHON: "C:\\Python35"
-    #   PYTHON_VERSION: "3.5.2"
-    #   PYTHON_ARCH: "32"
-
-    # - PYTHON: "C:\\Python36"
-    #   PYTHON_VERSION: "3.6.0"
-    #   PYTHON_ARCH: "32"
-
     - PYTHON: "C:\\Python27-x64"
-      PYTHON_VERSION: "2.7.12"
-      PYTHON_ARCH: "64"
-
-    - PYTHON: "C:\\Python34-x64"
-      PYTHON_VERSION: "3.4.5"
-      PYTHON_ARCH: "64"
+      TOXENV: py27
 
     - PYTHON: "C:\\Python35-x64"
-      PYTHON_VERSION: "3.5.2"
-      PYTHON_ARCH: "64"
+      TOXENV: py35
 
     - PYTHON: "C:\\Python36-x64"
-      PYTHON_VERSION: "3.6.0"
-      PYTHON_ARCH: "64"
+      TOXENV: py36
 
 matrix:
   fast_finish: true
 
-clone_depth: 25
+clone_depth: 2
 
 init:
-  - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%bit"
+  - "ECHO %PYTHON%"
+  - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
+  - python -V
 
 cache:
-    - glpk_build -> appveyor/build_glpk.py
     - pip_cache -> appveyor.yml
 
 
 install:
-  - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
   - ps: |
       if ($env:CONDA -eq "true") {
-          conda config --set always_yes yes --set changeps1 no;
-          conda install -q pip } else {
-          python -m pip install pip setuptools>=24.0 wheel --upgrade }
-  - if not defined CONDA %WITH_COMPILER% python -m pip install --upgrade pytest
-  - if defined CONDA conda install -q setuptools pytest numpy scipy
-  - python -m pip install pytest-cov pytest-benchmark pandas swiglpk optlang python-libsbml decorator Cython jsonschema twine pypandoc
-  - "%WITH_COMPILER% python appveyor/build_glpk.py"
+        conda config --set always_yes yes --set changeps1 no;
+        conda install -q pip setuptools wheel numpy scipy;
+        pip install -r develop-requirements.txt;
+        pip install .
+      } else {
+        pip install --upgrade setuptools wheel tox
+      }
 
 build: off
 
 test_script:
-  - "%WITH_COMPILER% python setup.py develop"
-  - "%WITH_COMPILER% python -m pytest --cov=cobra --benchmark-skip"
-
-after_test:
-  - if not defined CONDA %WITH_COMPILER% python setup.py bdist_wheel bdist_wininst
-
-artifacts:
-  - path: dist\*
-
-deploy_script:
   - ps: |
-      if($env:appveyor_repo_tag -eq "True" -And $env:CONDA -ne "true") {
-          Invoke-Expression "twine upload dist/* --username $env:PYPI_USERNAME --password $env:PYPI_PASSWORD"
+      if ($env:CONDA -eq "true") {
+        pytest --benchmark-skip cobra/test
+      } else {
+        tox
       }
-
-#on_success:
-#  - TODO: upload the content of dist/*.whl to a public wheelhouse
diff --git a/appveyor/build_glpk.py b/appveyor/build_glpk.py
deleted file mode 100644
index 69a47f3..0000000
--- a/appveyor/build_glpk.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import sys
-import hashlib
-import tarfile
-import struct
-import shutil
-import setuptools.msvc
-try:
-    import urllib2
-except ImportError:  # python 3
-    import urllib.request as urllib2
-
-# these need to be set to the latest glpk version
-glpk_version = "4.60"
-glpk_md5 = "eda7965907f6919ffc69801646f13c3e"
-
-glpk_build_dir = "glpk_build/glpk-%s" % glpk_version
-url = "http://ftp.gnu.org/gnu/glpk/glpk-%s.tar.gz" % glpk_version
-bitness = struct.calcsize("P") * 8
-
-
-def md5(fname):
-    hash = hashlib.md5()
-    with open(fname, "rb") as f:
-        for chunk in iter(lambda: f.read(4096), b""):
-            hash.update(chunk)
-    return hash.hexdigest()
-
-
-def get_vcvarsall_cmd():
-    py_ver = sys.version_info
-    if py_ver.major == 3 and py_ver.minor >= 5:
-        vc_ver = 14
-    elif py_ver.major == 3 and py_ver.minor >= 3:
-        vc_ver = 10
-    else:
-        vc_ver = 9
-    vc_path = setuptools.msvc.msvc9_find_vcvarsall(vc_ver)
-    assert vc_path is not None
-    return '"%s" %s' % (vc_path, " amd64" if bitness == 64 else "")
-
-
-if not os.path.isdir("glpk_build/"):
-    os.mkdir("glpk_build")
-if not os.path.isdir(glpk_build_dir):
-    response = urllib2.urlopen(url)
-    with open("glpk-download.tar.gz", "wb") as outfile:
-        outfile.write(response.read())
-    assert md5("glpk-download.tar.gz") == glpk_md5
-    with tarfile.open("glpk-download.tar.gz") as infile:
-        infile.extractall("glpk_build")
-
-os.chdir("%s/w%d" % (glpk_build_dir, bitness))
-if not os.path.isfile("glpk.lib"):
-    shutil.copy2("config_VC", "config.h")
-    os.system(get_vcvarsall_cmd() + "& nmake /f Makefile_VC")
-shutil.copy2("glpk.lib", "../../..")
-os.chdir("../../..")
-shutil.copy2(glpk_build_dir + "/src/glpk.h", ".")
diff --git a/appveyor/install.ps1 b/appveyor/install.ps1
deleted file mode 100644
index 3f05628..0000000
--- a/appveyor/install.ps1
+++ /dev/null
@@ -1,85 +0,0 @@
-# Sample script to install Python and pip under Windows
-# Authors: Olivier Grisel and Kyle Kastner
-# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
-
-$BASE_URL = "https://www.python.org/ftp/python/"
-$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
-$GET_PIP_PATH = "C:\get-pip.py"
-
-
-function DownloadPython ($python_version, $platform_suffix) {
-    $webclient = New-Object System.Net.WebClient
-    $filename = "python-" + $python_version + $platform_suffix + ".msi"
-    $url = $BASE_URL + $python_version + "/" + $filename
-
-    $basedir = $pwd.Path + "\"
-    $filepath = $basedir + $filename
-    if (Test-Path $filename) {
-        Write-Host "Reusing" $filepath
-        return $filepath
-    }
-
-    # Download and retry up to 5 times in case of network transient errors.
-    Write-Host "Downloading" $filename "from" $url
-    $retry_attempts = 3
-    for($i=0; $i -lt $retry_attempts; $i++){
-        try {
-            $webclient.DownloadFile($url, $filepath)
-            break
-        }
-        Catch [Exception]{
-            Start-Sleep 1
-        }
-   }
-   Write-Host "File saved at" $filepath
-   return $filepath
-}
-
-
-function InstallPython ($python_version, $architecture, $python_home) {
-    Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
-    if (Test-Path $python_home) {
-        Write-Host $python_home "already exists, skipping."
-        return $false
-    }
-    if ($architecture -eq "32") {
-        $platform_suffix = ""
-    } else {
-        $platform_suffix = ".amd64"
-    }
-    $filepath = DownloadPython $python_version $platform_suffix
-    Write-Host "Installing" $filepath "to" $python_home
-    $args = "/qn /i $filepath TARGETDIR=$python_home"
-    Write-Host "msiexec.exe" $args
-    Start-Process -FilePath "msiexec.exe" -ArgumentList $args -Wait -Passthru
-    Write-Host "Python $python_version ($architecture) installation complete"
-    return $true
-}
-
-
-function InstallPip ($python_home) {
-    $pip_path = $python_home + "/Scripts/pip.exe"
-    $python_path = $python_home + "/python.exe"
-    if (-not(Test-Path $pip_path)) {
-        Write-Host "Installing pip..."
-        $webclient = New-Object System.Net.WebClient
-        $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
-        Write-Host "Executing:" $python_path $GET_PIP_PATH
-        Start-Process -FilePath "$python_path" -ArgumentList "$GET_PIP_PATH" -Wait -Passthru
-    } else {
-        Write-Host "pip already installed."
-    }
-}
-
-function InstallPackage ($python_home, $pkg) {
-    $pip_path = $python_home + "/Scripts/pip.exe"
-    & $pip_path install $pkg
-}
-
-function main () {
-    InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON
-    InstallPip $env:PYTHON
-    InstallPackage $env:PYTHON wheel
-}
-
-main
diff --git a/appveyor/run_with_env.cmd b/appveyor/run_with_env.cmd
deleted file mode 100644
index 5da547c..0000000
--- a/appveyor/run_with_env.cmd
+++ /dev/null
@@ -1,88 +0,0 @@
-:: To build extensions for 64 bit Python 3, we need to configure environment
-:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of:
-:: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1)
-::
-:: To build extensions for 64 bit Python 2, we need to configure environment
-:: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of:
-:: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0)
-::
-:: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific
-:: environment configurations.
-::
-:: Note: this script needs to be run with the /E:ON and /V:ON flags for the
-:: cmd interpreter, at least for (SDK v7.0)
-::
-:: More details at:
-:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows
-:: http://stackoverflow.com/a/13751649/163740
-::
-:: Author: Olivier Grisel
-:: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
-::
-:: Notes about batch files for Python people:
-::
-:: Quotes in values are literally part of the values:
-::      SET FOO="bar"
-:: FOO is now five characters long: " b a r "
-:: If you don't want quotes, don't include them on the right-hand side.
-::
-:: The CALL lines at the end of this file look redundant, but if you move them
-:: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y
-:: case, I don't know why.
- at ECHO OFF
-
-SET COMMAND_TO_RUN=%*
-SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
-SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf
-
-:: Extract the major and minor versions, and allow for the minor version to be
-:: more than 9.  This requires the version number to have two dots in it.
-SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1%
-IF "%PYTHON_VERSION:~3,1%" == "." (
-    SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1%
-) ELSE (
-    SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2%
-)
-
-:: Based on the Python version, determine what SDK version to use, and whether
-:: to set the SDK for 64-bit.
-IF %MAJOR_PYTHON_VERSION% == 2 (
-    SET WINDOWS_SDK_VERSION="v7.0"
-    SET SET_SDK_64=Y
-) ELSE (
-    IF %MAJOR_PYTHON_VERSION% == 3 (
-        SET WINDOWS_SDK_VERSION="v7.1"
-        IF %MINOR_PYTHON_VERSION% LEQ 4 (
-            SET SET_SDK_64=Y
-        ) ELSE (
-            SET SET_SDK_64=N
-            IF EXIST "%WIN_WDK%" (
-                :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/
-                REN "%WIN_WDK%" 0wdf
-            )
-        )
-    ) ELSE (
-        ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%"
-        EXIT 1
-    )
-)
-
-IF %PYTHON_ARCH% == 64 (
-    IF %SET_SDK_64% == Y (
-        ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture
-        SET DISTUTILS_USE_SDK=1
-        SET MSSdk=1
-        "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
-        "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release
-        ECHO Executing: %COMMAND_TO_RUN%
-        call %COMMAND_TO_RUN% || EXIT 1
-    ) ELSE (
-        ECHO Using default MSVC build environment for 64 bit architecture
-        ECHO Executing: %COMMAND_TO_RUN%
-        call %COMMAND_TO_RUN% || EXIT 1
-    )
-) ELSE (
-    ECHO Using default MSVC build environment for 32 bit architecture
-    ECHO Executing: %COMMAND_TO_RUN%
-    call %COMMAND_TO_RUN% || EXIT 1
-)
diff --git a/cobra/__init__.py b/cobra/__init__.py
index ce83d7a..d72e6d7 100644
--- a/cobra/__init__.py
+++ b/cobra/__init__.py
@@ -8,12 +8,12 @@ from os import name as _name
 from os.path import abspath as _abspath
 from os.path import dirname as _dirname
 
-from cobra import design, flux_analysis, io
+from cobra import flux_analysis, io
 from cobra.core import (
     DictList, Gene, Metabolite, Model, Object, Reaction, Species)
 from cobra.util.version_info import show_versions
 
-__version__ = "0.9.0"
+__version__ = "0.11.0"
 
 # set the warning format to be prettier and fit on one line
 _cobra_path = _dirname(_abspath(__file__))
diff --git a/cobra/core/arraybasedmodel.py b/cobra/core/arraybasedmodel.py
deleted file mode 100644
index 663ff08..0000000
--- a/cobra/core/arraybasedmodel.py
+++ /dev/null
@@ -1,435 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
-
-from sys import maxsize
-from warnings import warn
-
-from numpy import array, ndarray
-from scipy.sparse import dok_matrix, lil_matrix
-from six import iteritems
-
-from cobra.core.model import Model
-
-
-class ArrayBasedModel(Model):
-    """ArrayBasedModel is a class that adds arrays and vectors to
-    a cobra.Model to make it easier to perform linear algebra operations.
-
-    """
-
-    def __init__(self, description=None, deepcopy_model=False,
-                 matrix_type='scipy.lil_matrix'):
-        """
-        description: None | String | cobra.Model
-
-        deepcopy_model: Boolean.  If True and description is
-        a cobra.Model then make a deepcopy of the Model before
-        creating the ArrayBasedModel.
-
-        matrix_type: 'scipy.lil_matrix' or 'scipy.dok_matrix'
-            Specifies which type of backend matrix to use for S.
-
-        """
-        warn("ArrayBasedModel is deprecated, use "
-             "`cobra.util.array.create_stoichiometric_matrix` instead",
-             DeprecationWarning)
-
-        if deepcopy_model and isinstance(description, Model):
-            description = description.copy()
-        Model.__init__(self, description)
-        self._S = None
-        self.matrix_type = matrix_type
-        self.update()
-
-    # no setter for S at the moment
-    @property
-    def S(self):
-        """Stoichiometric matrix of the model
-
-        This will be formatted as either :class:`~scipy.sparse.lil_matrix`
-        or :class:`~scipy.sparse.dok_matrix`
-
-        """
-        return self._S
-
-    @property
-    def lower_bounds(self):
-        return self._lower_bounds
-
-    @lower_bounds.setter
-    def lower_bounds(self, vector):
-        self._update_from_vector("lower_bounds", vector)
-
-    @property
-    def upper_bounds(self):
-        return self._upper_bounds
-
-    @upper_bounds.setter
-    def upper_bounds(self, vector):
-        self._update_from_vector("upper_bounds", vector)
-
-    @property
-    def objective_coefficients(self):
-        return self._objective_coefficients
-
-    @objective_coefficients.setter
-    def objective_coefficients(self, vector):
-        self._update_from_vector("objective_coefficients", vector)
-
-    @property
-    def b(self):
-        """bounds for metabolites as :class:`numpy.ndarray`"""
-        return self._b
-
-    @b.setter
-    def b(self, vector):
-        self._update_from_vector("b", vector)
-
-    @property
-    def constraint_sense(self):
-        return self._constraint_sense
-
-    @constraint_sense.setter
-    def constraint_sense(self, vector):
-        self._update_from_vector("_constraint_sense", vector)
-
-    def copy(self):
-        """Provides a partial 'deepcopy' of the Model.  All of the Metabolite,
-        Gene, and Reaction objects are created anew but in a faster fashion
-        than deepcopy
-
-        """
-        the_copy = Model.copy(self)
-        the_copy.update()
-        return the_copy
-
-    def add_metabolites(self, metabolite_list,
-                        expand_stoichiometric_matrix=True):
-        """Will add a list of metabolites to the the object, if they do not
-        exist and then expand the stochiometric matrix
-
-        metabolite_list: A list of :class:`~cobra.core.Metabolite` objects
-
-        expand_stoichimetric_matrix: Boolean.  If True and self.S is
-        not None then it will add rows to self.S.  self.S must be
-        created after adding reactions and metabolites to self before
-        it can be expanded.  Trying to expand self.S when self only
-        contains metabolites is ludacris.
-
-        """
-        Model.add_metabolites(self, metabolite_list)
-        if self._S is not None and expand_stoichiometric_matrix:
-            s_expansion = len(self.metabolites) - self._S.shape[0]
-            if s_expansion > 0:
-                self._S.resize((self._S.shape[0] + s_expansion,
-                                self._S.shape[1]))
-        self._update_metabolite_vectors()
-
-    def _update_from_vector(self, attribute, vector):
-        """convert from model.reactions = v to model.reactions[:] = v"""
-        # this will fail if vector is the wrong length
-        getattr(self, attribute)[:] = vector
-
-    def _update_reaction(self, reaction):
-        """Updates everything associated with the reaction.id of reaction.
-
-        reaction: A cobra.Reaction object, or a list of these objects.
-
-        WARNING: This function is only used after the Model has been
-        converted to matrices.  It is typically faster to access the objects
-        in the Model directly.  This function will eventually moved to another
-        module for advanced users due to the potential for mistakes.
-
-
-        """
-        if not hasattr(reaction, '__iter__'):
-            reaction = [reaction]
-        Model._update_reaction(reaction)
-        for the_reaction in reaction:
-            try:
-                reaction_index = self.reactions.index(the_reaction.id)
-            except KeyError:
-                warn(the_reaction.id + ' is not in the model')
-                continue
-
-            # zero reaction stoichiometry column
-            the_column = self._S[:, reaction_index]
-            for nonzero_index in the_column.nonzero()[0]:
-                the_column[nonzero_index, 0] = 0
-            self._lower_bounds[reaction_index] = the_reaction.lower_bound
-            self._upper_bounds[reaction_index] = the_reaction.upper_bound
-            self.objective_coefficients[
-                reaction_index] = the_reaction.objective_coefficient
-            self.add_metabolites(the_reaction._metabolites)
-            # Make sure that the metabolites are the ones contained in the
-            # model
-            the_reaction._metabolites = [self.metabolite.get_by_id(x.id)
-                                         for x in the_reaction._metabolites]
-            # Update the stoichiometric matrix
-            metabolite_indices = map(
-                self.metabolites.index,
-                the_reaction._metabolites)
-            for (index, metabolite_index) in enumerate(metabolite_indices):
-                self._S[metabolite_index, reaction_index] = \
-                    the_reaction.stoichiometric_coefficients[index]
-
-    def add_reactions(self, reaction_list, update_matrices=True):
-        """Will add a cobra.Reaction object to the model, if
-        reaction.id is not in self.reactions.
-
-        reaction_list: A :class:`~cobra.core.Reaction` object or a list of them
-
-        update_matrices:  Boolean.  If true populate / update matrices
-        S, lower_bounds, upper_bounds, .... Note this is slow to run
-        for very large models and using this option with repeated calls
-        will degrade performance.  Better to call self.update() after
-        adding all reactions.
-
-
-         If the stoichiometric matrix is initially empty then initialize a 1x1
-         sparse matrix and add more rows as needed in the self.add_metabolites
-         function
-
-        """
-        Model.add_reactions(self, reaction_list)
-        if update_matrices:
-            self._update_matrices(reaction_list)
-
-    def remove_reactions(self, reactions, update_matrices=True, **kwargs):
-        """remove reactions from the model
-
-        See :func:`cobra.core.Model.Model.remove_reactions`
-
-        update_matrices:  Boolean
-            If true populate / update matrices S, lower_bounds, upper_bounds.
-            Note that this is slow to run for very large models, and using this
-            option with repeated calls will degrade performance.
-
-        """
-        Model.remove_reactions(self, reactions, **kwargs)
-        if update_matrices:
-            self._update_matrices()
-
-    def _construct_matrices(self):
-        """Large sparse matrices take time to construct and to read / write.
-        This function allows one to let the model exists without cobra_model.S
-        and then generate it at needed.
-
-        """
-        self._update_matrices()  # This does basic construction as well.
-
-    def _update_reaction_vectors(self):
-        """regenerates the lower_bounds, upper_bounds,
-        and objective_coefficients vectors.
-
-        WARNING: This function is only used after the Model has been
-        converted to matrices.  It is typically faster to access the objects
-        in the Model directly.  This function will eventually moved to another
-        module for advanced users due to the potential for mistakes.
-
-
-        """
-        self._lower_bounds = LinkedArray(self.reactions, "lower_bound")
-        self._upper_bounds = LinkedArray(self.reactions, "upper_bound")
-        self._objective_coefficients = LinkedArray(self.reactions,
-                                                   "objective_coefficient")
-
-    def _update_metabolite_vectors(self):
-        """regenerates _b and _constraint_sense
-
-        WARNING: This function is only used after the Model has been
-        converted to matrices.  It is typically faster to access the objects
-        in the Model directly.  This function will eventually moved to another
-        module for advanced users due to the potential for mistakes.
-
-        """
-        self._b = LinkedArray(self.metabolites, "_bound")
-        self._constraint_sense = LinkedArray(
-            self.metabolites,
-            "_constraint_sense")
-
-    def _update_matrices(self, reaction_list=None):
-        """
-        reaction_list: None or a list of cobra.Reaction objects that are in
-        self.reactions.  If None then reconstruct the whole matrix.
-
-        NOTE: reaction_list is assumed to be at the end of self.reactions.
-
-        In the future, we'll be able to use reactions from anywhere in the
-        list
-
-        WARNING: This function is only used after the Model has been
-        converted to matrices.  It is typically faster to access the objects
-        in the Model directly.  This function will eventually moved to another
-        module for advanced users due to the potential for mistakes.
-
-        """
-        # no need to create matrix if there are no reactions or metabolites
-        if len(self.reactions) == 0 and len(self.metabolites) == 0:
-            return
-        elif len(self.metabolites) == 0:
-            self._update_reaction_vectors()
-            return
-        elif len(self.reactions) == 0:
-            self._update_metabolite_vectors()
-            return
-        # Pretty much all of these things are unnecessary to use the objects
-        # and interact with the optimization solvers.  It might be best to move
-        # them to linear algebra modules.  If no reactions are present in the
-        # Model, initialize the arrays
-        if self._S is None or reaction_list is None:
-            reaction_list = self.reactions
-            SMatrix = SMatrix_classes[self.matrix_type]
-            self._S = SMatrix((len(self.metabolites),
-                               len(self.reactions)), model=self)
-            self._update_reaction_vectors()
-        else:  # Expand the arrays to accomodate the new reaction
-            self._S.resize((len(self.metabolites),
-                            len(self.reactions)))
-            lower_bounds = array([x.lower_bound
-                                  for x in reaction_list])
-            upper_bounds = array([x.upper_bound
-                                  for x in reaction_list])
-            objective_coefficients = array([x.objective_coefficient
-                                            for x in reaction_list])
-            self._lower_bounds._extend(lower_bounds)
-            self._upper_bounds._extend(upper_bounds)
-            self._objective_coefficients._extend(objective_coefficients)
-
-        coefficient_dictionary = {}
-        for the_reaction in reaction_list:
-            reaction_index = self.reactions.index(the_reaction.id)
-            for the_key, the_value in the_reaction._metabolites.items():
-                coefficient_dictionary[(self.metabolites.index(the_key.id),
-                                        reaction_index)] = the_value
-
-        self._S.update(coefficient_dictionary)
-
-    def update(self):
-        """Regenerates the stoichiometric matrix and vectors"""
-        self._update_matrices()
-        self._update_metabolite_vectors()
-
-
-class LinkedArray(ndarray):
-    """A :class:`numpy.ndarray` which updates an attribute from a list"""
-    def __new__(cls, list, attribute):
-        # construct a new ndarray with the values from the list
-        # For example, if the list if model.reactions and the attribute is
-        # "lower_bound" create an array of [reaction.lower_bound for ... ]
-        x = array([getattr(i, attribute) for i in list]).view(cls)
-        return x.copy()
-
-    def __init__(self, list, attribute):
-        self._list = list
-        self._attr = attribute
-
-    def __setitem__(self, index, value):
-        ndarray.__setitem__(self, index, value)
-        if isinstance(index, slice):
-            # not sure why that is here
-            if index.stop == maxsize:
-                index = slice(index.start, len(self))
-            if hasattr(value, "__getitem__"):
-                for i, entry in enumerate(self._list[index]):
-                    setattr(entry, self._attr, value[i])
-            else:
-                for i, entry in enumerate(self._list[index]):
-                    setattr(entry, self._attr, value)
-        else:
-            setattr(self._list[index], self._attr, value)
-
-    def __setslice__(self, i, j, value):
-        self.__setitem__(slice(i, j), value)
-
-    def _extend(self, other):
-        old_size = len(self)
-        new_size = old_size + len(other)
-        self.resize(new_size, refcheck=False)
-        ndarray.__setitem__(self, slice(old_size, new_size), other)
-
-
-class SMatrix_dok(dok_matrix):
-    """A 2D sparse dok matrix which maintains links to a cobra Model"""
-
-    def __init__(self, *args, **kwargs):
-        dok_matrix.__init__(self, *args)
-        self.format = "dok"
-        self._model = kwargs["model"] if "model" in kwargs else None
-
-    def __setitem__(self, index, value):
-        dok_matrix.__setitem__(self, index, value)
-        if isinstance(index[0], int) and isinstance(index[1], int):
-            reaction = self._model.reactions[index[1]]
-            if value != 0:
-                reaction.add_metabolites(
-                    {self._model.metabolites[index[0]]: value}, combine=False)
-            else:  # setting 0 means metabolites should be removed
-                metabolite = self._model.metabolites[index[0]]
-                if metabolite in reaction._metabolites:
-                    reaction.subtract_metabolites(
-                        {metabolite: reaction.get_coefficient(metabolite)})
-
-    def tolil(self):
-        new = SMatrix_lil(dok_matrix.tolil(self), model=self._model)
-        return new
-
-
-class SMatrix_lil(lil_matrix):
-    """A 2D sparse lil matrix which maintains links to a cobra Model"""
-
-    def __init__(self, *args, **kwargs):
-        lil_matrix.__init__(self, *args)
-        self.format = "lil"
-        self._model = kwargs["model"] if "model" in kwargs else None
-
-    def __setitem__(self, index, value):
-        lil_matrix.__setitem__(self, index, value)
-        if isinstance(index[0], int):
-            metabolites = [self._model.metabolites[index[0]]]
-        else:
-            metabolites = self._model.metabolites[index[0]]
-
-        if isinstance(index[1], int):
-            reactions = [self._model.reactions[index[1]]]
-        else:
-            reactions = self._model.reactions[index[1]]
-
-        if value == 0:  # remove_metabolites
-            met_set = set(metabolites)
-            for reaction in reactions:
-                to_remove = met_set.intersection(reaction._metabolites)
-                for i in to_remove:
-                    reaction.subtract_metabolites(
-                        {i: reaction.get_coefficient(i)})
-        else:  # add metabolites
-            met_dict = {met: value for met in metabolites}
-            for reaction in reactions:
-                reaction.add_metabolites(met_dict, combine=False)
-
-    def update(self, value_dict):
-        """update matrix without propagating to model"""
-        if len(value_dict) < 100:  # TODO benchmark for heuristic
-            for index, value in iteritems(value_dict):
-                lil_matrix.__setitem__(self, index, value)
-        else:
-            matrix = lil_matrix.todok(self)
-            matrix.update(value_dict)
-            self = SMatrix_lil(matrix.tolil(), model=self._model)
-            self._model._S = self
-
-    def todok(self):
-        new = SMatrix_dok(lil_matrix.todok(self), model=self._model)
-        return new
-
-    # TODO: check if implemented before using own function
-    def resize(self, shape):
-        matrix = lil_matrix.todok(self)
-        matrix.resize(shape)
-        self = SMatrix_lil(matrix.tolil(), model=self._model)
-        self._model._S = self
-
-
-SMatrix_classes = {"scipy.dok_matrix": SMatrix_dok,
-                   "scipy.lil_matrix": SMatrix_lil}
diff --git a/cobra/core/dictlist.py b/cobra/core/dictlist.py
index 364a6a6..c830dba 100644
--- a/cobra/core/dictlist.py
+++ b/cobra/core/dictlist.py
@@ -15,13 +15,17 @@ class DictList(list):
     This object behaves like a list, but has the O(1) speed
     benefits of a dict when looking up elements by their id.
 
-    Parameters
-    ----------
-    *args : iterable
-        iterable as single argument to create new DictList from
     """
 
     def __init__(self, *args):
+        """Instantiate a combined dict and list.
+
+        Parameters
+        ----------
+        args : iterable
+            iterable as single argument to create new DictList from
+
+        """
         if len(args) > 2:
             raise TypeError("takes at most 1 argument (%d given)" % len(args))
         super(DictList, self).__init__(self)
@@ -93,13 +97,12 @@ class DictList(list):
         Parameters
         ----------
         search_function : a string, regular expression or function
-            used to find the matching elements in the list.
-
+            Used to find the matching elements in the list.
             - a regular expression (possibly compiled), in which case the
             given attribute of the object should match the regular expression.
-
             - a function which takes one argument and returns True for
             desired values
+
         attribute : string or None
             the name attribute of the object to passed as argument to the
             `search_function`. If this is None, the object itself is used.
diff --git a/cobra/core/model.py b/cobra/core/model.py
index 7eabf78..3cd0805 100644
--- a/cobra/core/model.py
+++ b/cobra/core/model.py
@@ -9,21 +9,19 @@ from functools import partial
 from warnings import warn
 
 import optlang
+from optlang.symbolics import Basic, Zero
 import six
-import sympy
 from six import iteritems, string_types
-from sympy import S
 
+from cobra.exceptions import SolverNotFound
 from cobra.core.dictlist import DictList
 from cobra.core.object import Object
 from cobra.core.reaction import separate_forward_and_reverse_bounds, Reaction
 from cobra.core.solution import get_solution
-from cobra.solvers import optimize
 from cobra.util.context import HistoryManager, resettable, get_context
 from cobra.util.solver import (
-    SolverNotFound, get_solver_name, interface_to_str, set_objective, solvers,
-    add_cons_vars_to_problem, remove_cons_vars_from_problem, choose_solver,
-    check_solver_status, assert_optimal)
+    get_solver_name, interface_to_str, set_objective, solvers,
+    add_cons_vars_to_problem, remove_cons_vars_from_problem, assert_optimal)
 from cobra.util.util import AutoVivification, format_long_string
 
 LOGGER = logging.getLogger(__name__)
@@ -101,7 +99,7 @@ class Model(Object):
             # with older cobrapy pickles?
             interface = solvers[get_solver_name()]
             self._solver = interface.Model()
-            self._solver.objective = interface.Objective(S.Zero)
+            self._solver.objective = interface.Objective(Zero)
             self._populate_solver(self.reactions, self.metabolites)
 
     @property
@@ -128,9 +126,8 @@ class Model(Object):
     @resettable
     def solver(self, value):
         not_valid_interface = SolverNotFound(
-            '%s is not a valid solver interface. Pick from %s, or specify an '
-            'optlang interface (e.g. optlang.glpk_interface).' % (
-                value, list(solvers.keys())))
+            '%s is not a valid solver interface. Pick from %s.' % (
+                value, list(solvers)))
         if isinstance(value, six.string_types):
             try:
                 interface = solvers[interface_to_str(value)]
@@ -368,7 +365,7 @@ class Model(Object):
         for met in metabolite_list:
             if met.id not in self.constraints:
                 constraint = self.problem.Constraint(
-                    S.Zero, name=met.id, lb=0, ub=0)
+                    Zero, name=met.id, lb=0, ub=0)
                 to_add += [constraint]
 
         self.add_cons_vars(to_add)
@@ -456,8 +453,8 @@ class Model(Object):
 
         If you set the reaction `type` to something else, you must specify the
         desired identifier of the created reaction along with its upper and
-         lower bound. The name will be given by the metabolite name and the
-         given `type`.
+        lower bound. The name will be given by the metabolite name and the
+        given `type`.
 
         Parameters
         ----------
@@ -523,34 +520,30 @@ class Model(Object):
         reaction_list : list
             A list of `cobra.Reaction` objects
         """
+        def existing_filter(rxn):
+            if rxn.id in self.reactions:
+                LOGGER.warning(
+                    "Ignoring reaction '%s' since it already exists.", rxn.id)
+                return False
+            return True
 
-        try:
-            reaction_list = DictList(reaction_list)
-        except TypeError:
-            reaction_list = DictList([reaction_list])
-
-        # First check whether the metabolites exist in the model
-        existing = [rxn for rxn in reaction_list if rxn.id in self.reactions]
-        for rxn in existing:
-            LOGGER.info('skip adding reaction %s as already existing', rxn.id)
-        reaction_list = [rxn for rxn in reaction_list
-                         if rxn.id not in existing]
+        # First check whether the reactions exist in the model.
+        pruned = DictList(filter(existing_filter, reaction_list))
 
         context = get_context(self)
 
-        # Add reactions. Also take care of genes and metabolites in the loop
-        for reaction in reaction_list:
-            reaction._model = self  # the reaction now points to the model
-            # keys() is necessary because the dict will be modified during
-            # the loop
-            for metabolite in list(reaction._metabolites.keys()):
-                # if the metabolite is not in the model, add it
-                # should we be adding a copy instead.
+        # Add reactions. Also take care of genes and metabolites in the loop.
+        for reaction in pruned:
+            reaction._model = self
+            # Build a `list()` because the dict will be modified in the loop.
+            for metabolite in list(reaction.metabolites):
+                # TODO: Should we add a copy of the metabolite instead?
                 if metabolite not in self.metabolites:
                     self.add_metabolites(metabolite)
                 # A copy of the metabolite exists in the model, the reaction
                 # needs to point to the metabolite in the model.
                 else:
+                    # FIXME: Modifying 'private' attributes is horrible.
                     stoichiometry = reaction._metabolites.pop(metabolite)
                     model_metabolite = self.metabolites.get_by_id(
                         metabolite.id)
@@ -578,13 +571,13 @@ class Model(Object):
                         reaction._dissociate_gene(gene)
                         reaction._associate_gene(model_gene)
 
-        self.reactions += reaction_list
+        self.reactions += pruned
 
         if context:
-            context(partial(self.reactions.__isub__, reaction_list))
+            context(partial(self.reactions.__isub__, pruned))
 
         # from cameo ...
-        self._populate_solver(reaction_list)
+        self._populate_solver(pruned)
 
     def remove_reactions(self, reactions, remove_orphans=False):
         """Remove reactions from the model.
@@ -617,14 +610,16 @@ class Model(Object):
             else:
                 forward = reaction.forward_variable
                 reverse = reaction.reverse_variable
-                self.remove_cons_vars([forward, reverse])
-                self.reactions.remove(reaction)
-                reaction._model = None
 
                 if context:
+                    context(partial(self._populate_solver, [reaction]))
                     context(partial(setattr, reaction, '_model', self))
                     context(partial(self.reactions.add, reaction))
 
+                self.remove_cons_vars([forward, reverse])
+                self.reactions.remove(reaction)
+                reaction._model = None
+
                 for met in reaction._metabolites:
                     if reaction in met._reaction:
                         met._reaction.remove(reaction)
@@ -747,28 +742,35 @@ class Model(Object):
         if metabolite_list is not None:
             for met in metabolite_list:
                 to_add += [self.problem.Constraint(
-                    S.Zero, name=met.id, lb=0, ub=0)]
+                    Zero, name=met.id, lb=0, ub=0)]
         self.add_cons_vars(to_add)
 
         for reaction in reaction_list:
 
-            reverse_lb, reverse_ub, forward_lb, forward_ub = \
-                separate_forward_and_reverse_bounds(*reaction.bounds)
+            if reaction.id not in self.variables:
+
+                reverse_lb, reverse_ub, forward_lb, forward_ub = \
+                    separate_forward_and_reverse_bounds(*reaction.bounds)
 
-            forward_variable = self.problem.Variable(
-                reaction.id, lb=forward_lb, ub=forward_ub)
-            reverse_variable = self.problem.Variable(
-                reaction.reverse_id, lb=reverse_lb, ub=reverse_ub)
+                forward_variable = self.problem.Variable(
+                    reaction.id, lb=forward_lb, ub=forward_ub)
+                reverse_variable = self.problem.Variable(
+                    reaction.reverse_id, lb=reverse_lb, ub=reverse_ub)
 
-            self.add_cons_vars([forward_variable, reverse_variable])
-            self.solver.update()
+                self.add_cons_vars([forward_variable, reverse_variable])
+
+            else:
+
+                reaction = self.reactions.get_by_id(reaction.id)
+                forward_variable = reaction.forward_variable
+                reverse_variable = reaction.reverse_variable
 
             for metabolite, coeff in six.iteritems(reaction.metabolites):
                 if metabolite.id in self.constraints:
                     constraint = self.constraints[metabolite.id]
                 else:
                     constraint = self.problem.Constraint(
-                        S.Zero,
+                        Zero,
                         name=metabolite.id,
                         lb=0, ub=0)
                     self.add_cons_vars(constraint, sloppy=True)
@@ -780,26 +782,6 @@ class Model(Object):
         for constraint, terms in six.iteritems(constraint_terms):
             constraint.set_linear_coefficients(terms)
 
-    def to_array_based_model(self, deepcopy_model=False, **kwargs):
-        """Makes a `cobra.core.ArrayBasedModel` from a cobra.Model
-        which may be used to perform linear algebra operations with the
-        stoichiometric matrix.
-
-        Deprecated (0.6). Use `cobra.util.array.create_stoichiometric_matrix`
-        instead.
-
-        Parameters
-        ----------
-        deepcopy_model : bool
-            If False then the ArrayBasedModel points to the Model
-
-        """
-        warn("to_array_based_model is deprecated. "
-             "use cobra.util.array.create_stoichiometric_matrix instead",
-             DeprecationWarning)
-        from cobra.core.arraybasedmodel import ArrayBasedModel
-        return ArrayBasedModel(self, deepcopy_model=deepcopy_model, **kwargs)
-
     def slim_optimize(self, error_value=float('nan'), message=None):
         """Optimize model without creating a solution object.
 
@@ -837,7 +819,7 @@ class Model(Object):
         else:
             assert_optimal(self, message)
 
-    def optimize(self, objective_sense=None, raise_error=False, **kwargs):
+    def optimize(self, objective_sense=None, raise_error=False):
         """
         Optimize the model using flux balance analysis.
 
@@ -849,21 +831,6 @@ class Model(Object):
         raise_error : bool
             If true, raise an OptimizationError if solver status is not
              optimal.
-        solver : {None, 'glpk', 'cglpk', 'gurobi', 'cplex'}, optional
-            If unspecified will use the currently defined `self.solver`
-            otherwise it will use the given solver and update the attribute.
-        quadratic_component : {None, scipy.sparse.dok_matrix}, optional
-            The dimensions should be (n, n) where n is the number of
-            reactions. This sets the quadratic component (Q) of the
-            objective coefficient, adding :math:`\\frac{1}{2} v^T \cdot Q
-            \cdot v` to the objective. Ignored for optlang based solvers.
-        tolerance_feasibility : float
-            Solver tolerance for feasibility. Ignored for optlang based
-            solvers
-        tolerance_markowitz : float
-            Solver threshold during pivot. Ignored for optlang based solvers
-        time_limit : float
-            Maximum solver time (in seconds). Ignored for optlang based solvers
 
         Notes
         -----
@@ -872,19 +839,7 @@ class Model(Object):
         appropriate keyword argument.
 
         """
-        legacy, solver = choose_solver(self, solver=kwargs.get("solver"))
         original_direction = self.objective.direction
-
-        if legacy:
-            if objective_sense is None:
-                objective_sense = {
-                    "max": "maximize", "min": "minimize"}[original_direction]
-            solution = optimize(self, objective_sense=objective_sense,
-                                **kwargs)
-            check_solver_status(solution.status, raise_error=raise_error)
-            return solution
-
-        self.solver = solver
         self.objective.direction = \
             {"maximize": "max", "minimize": "min"}.get(
                 objective_sense, original_direction)
@@ -947,7 +902,7 @@ class Model(Object):
 
     @objective.setter
     def objective(self, value):
-        if isinstance(value, sympy.Basic):
+        if isinstance(value, Basic):
             value = self.problem.Objective(value, sloppy=False)
         if not isinstance(value, (dict, optlang.interface.Objective)):
             try:
diff --git a/cobra/core/reaction.py b/cobra/core/reaction.py
index e5c3c42..027974e 100644
--- a/cobra/core/reaction.py
+++ b/cobra/core/reaction.py
@@ -118,7 +118,7 @@ class Reaction(Object):
         Returns
         -------
         sympy expression
-            The expression represeenting the the forward flux (if associated
+            The expression representing the the forward flux (if associated
             with model), otherwise None. Representing the net flux if
             model.reversible_encoding == 'unsplit' or None if reaction is
             not associated with a model """
@@ -681,8 +681,8 @@ class Reaction(Object):
         return self._metabolites[_id_to_metabolites[metabolite_id]]
 
     def get_coefficients(self, metabolite_ids):
-        """Return the stoichiometric coefficients for a list of
-        metabolites in the reaction.
+        """
+        Return the stoichiometric coefficients for a list of metabolites.
 
         Parameters
         ----------
@@ -806,11 +806,16 @@ class Reaction(Object):
                     combine=False, reversibly=False))
 
     def subtract_metabolites(self, metabolites, combine=True, reversibly=True):
-        """This function will 'subtract' metabolites from a reaction, which
-        means add the metabolites with -1*coefficient. If the final coefficient
-        for a metabolite is 0 then the metabolite is removed from the reaction.
+        """Subtract metabolites from a reaction.
 
-        The change is reverted upon exit when using the model as a context.
+        That means add the metabolites with -1*coefficient. If the final
+        coefficient for a metabolite is 0 then the metabolite is removed from
+        the reaction.
+
+        Notes
+        -----
+        * A final coefficient < 0 implies a reactant.
+        * The change is reverted upon exit when using the model as a context.
 
         Parameters
         ----------
@@ -828,8 +833,6 @@ class Reaction(Object):
             Whether to add the change to the context to make the change
             reversibly or not (primarily intended for internal use).
 
-        .. note:: A final coefficient < 0 implies a reactant.
-
         """
         self.add_metabolites({
             k: -v for k, v in iteritems(metabolites)},
diff --git a/cobra/design/__init__.py b/cobra/design/__init__.py
deleted file mode 100644
index ef21432..0000000
--- a/cobra/design/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
-
-from cobra.design.design_algorithms import *
diff --git a/cobra/design/design_algorithms.py b/cobra/design/design_algorithms.py
deleted file mode 100644
index c3d1165..0000000
--- a/cobra/design/design_algorithms.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
-
-from cobra.exceptions import DefunctError
-
-__all__ = ("set_up_optknock", "dual_problem")
-
-
-def set_up_optknock(*args, **kwargs):
-    raise DefunctError('set_up_optknock',
-                       'cameo.strain_design.OptKnock',
-                       'https://github.com/biosustain/cameo')
-
-
-def dual_problem(*args, **kwargs):
-    raise DefunctError('dual_problem')
diff --git a/cobra/exceptions.py b/cobra/exceptions.py
index b277aef..f79752e 100644
--- a/cobra/exceptions.py
+++ b/cobra/exceptions.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-from __future__ import absolute_import, print_function
+from __future__ import absolute_import
 
 import optlang.interface
 
@@ -26,33 +26,14 @@ class UndefinedSolution(OptimizationError):
     pass
 
 
+class SolverNotFound(Exception):
+    """A simple Exception when a solver can not be found."""
+
+    pass
+
+
 OPTLANG_TO_EXCEPTIONS_DICT = dict((
     (optlang.interface.INFEASIBLE, Infeasible),
     (optlang.interface.UNBOUNDED, Unbounded),
     (optlang.interface.FEASIBLE, FeasibleButNotOptimal),
     (optlang.interface.UNDEFINED, UndefinedSolution)))
-
-
-class DefunctError(Exception):
-    """Exception for retired functionality
-
-    Parameters
-    ----------
-    what : string
-        The name of the retired object
-    alternative : string
-        Suggestion for an alternative
-    url : string
-        A url to alternative resource
-    """
-
-    def __init__(self, what, alternative=None, url=None):
-        message = "{} has been removed from cobrapy".format(what)
-        if alternative is None:
-            message += (" without replacement. Raise an issue at "
-                        "https://github.com/opencobra/cobrapy if you miss it.")
-        if alternative is not None:
-            message += ". Consider using '{}' instead".format(alternative)
-        if url is not None:
-            message += " [{}]".format(url)
-        super(DefunctError, self).__init__(message)
diff --git a/cobra/flux_analysis/__init__.py b/cobra/flux_analysis/__init__.py
index 3f6b8d7..dc4185d 100644
--- a/cobra/flux_analysis/__init__.py
+++ b/cobra/flux_analysis/__init__.py
@@ -1,21 +1,15 @@
 # -*- coding: utf-8 -*-
 
-try:
-    import scipy
-except ImportError:
-    scipy = None
-
-from cobra.flux_analysis.gapfilling import gapfill, growMatch
+from cobra.flux_analysis.gapfilling import gapfill
 from cobra.flux_analysis.loopless import (
     construct_loopless_model, loopless_solution, add_loopless)
 from cobra.flux_analysis.parsimonious import pfba
-from cobra.flux_analysis.single_deletion import (
+from cobra.flux_analysis.deletion import (
     single_gene_deletion, single_reaction_deletion)
 from cobra.flux_analysis.variability import (
     find_blocked_reactions, flux_variability_analysis, find_essential_genes,
     find_essential_reactions)
-from cobra.flux_analysis.double_deletion import (
+from cobra.flux_analysis.deletion import (
     double_reaction_deletion, double_gene_deletion)
-from cobra.flux_analysis.phenotype_phase_plane import (
-    calculate_phenotype_phase_plane, production_envelope)
+from cobra.flux_analysis.phenotype_phase_plane import production_envelope
 from cobra.flux_analysis.sampling import sample
diff --git a/cobra/flux_analysis/deletion.py b/cobra/flux_analysis/deletion.py
new file mode 100644
index 0000000..e71ea8e
--- /dev/null
+++ b/cobra/flux_analysis/deletion.py
@@ -0,0 +1,362 @@
+# -*- coding: utf-8 -*-
+
+import multiprocessing
+import logging
+import optlang
+from warnings import warn
+from itertools import product
+from functools import partial
+from builtins import (map, dict)
+
+import pandas as pd
+
+from cobra.manipulation.delete import find_gene_knockout_reactions
+import cobra.util.solver as sutil
+from cobra.flux_analysis.moma import add_moma
+
+LOGGER = logging.getLogger(__name__)
+
+
+def _reactions_knockouts_with_restore(model, reactions):
+    with model:
+        for reaction in reactions:
+            reaction.knock_out()
+        growth = _get_growth(model)
+    return [r.id for r in reactions], growth, model.solver.status
+
+
+def _get_growth(model):
+    try:
+        if 'moma_old_objective' in model.solver.variables:
+            model.slim_optimize()
+            growth = model.solver.variables.moma_old_objective.primal
+        else:
+            growth = model.slim_optimize()
+    except optlang.exceptions.SolverError:
+        growth = float('nan')
+    return growth
+
+
+def _reaction_deletion(model, ids):
+    return _reactions_knockouts_with_restore(
+        model,
+        [model.reactions.get_by_id(r_id) for r_id in ids]
+    )
+
+
+def _gene_deletion(model, ids):
+    all_reactions = []
+    for g_id in ids:
+        all_reactions.extend(
+            find_gene_knockout_reactions(
+                model, (model.genes.get_by_id(g_id),)
+            )
+        )
+    _, growth, status = _reactions_knockouts_with_restore(model, all_reactions)
+    return (ids, growth, status)
+
+
+def _reaction_deletion_worker(ids):
+    global _model
+    return _reaction_deletion(_model, ids)
+
+
+def _gene_deletion_worker(ids):
+    global _model
+    return _gene_deletion(_model, ids)
+
+
+def _init_worker(model):
+    global _model
+    _model = model
+
+
+def _multi_deletion(model, entity, element_lists, method="fba",
+                    processes=None):
+    """
+    Provide a common interface for single or multiple knockouts.
+
+    Parameters
+    ----------
+    model : cobra.Model
+        The metabolic model to perform deletions in.
+
+    entity : 'gene' or 'reaction'
+        The entity to knockout (``cobra.Gene`` or ``cobra.Reaction``).
+
+    element_lists : list
+        List of iterables ``cobra.Reaction``s or ``cobra.Gene``s (or their IDs)
+        to be deleted.
+
+    method: {"fba", "moma", "linear moma"}, optional
+        Method used to predict the growth rate.
+
+    processes : int, optional
+        The number of parallel processes to run. Can speed up the computations
+        if the number of knockouts to perform is large. If not passed,
+        will be set to the number of CPUs found.
+
+    Returns
+    -------
+    pandas.DataFrame
+        A representation of all combinations of entity deletions. The
+        columns are 'growth' and 'status', where
+
+        index : frozenset([str])
+            The gene or reaction identifiers that were knocked out.
+        growth : float
+            The growth rate of the adjusted model.
+        status : str
+            The solution's status.
+    """
+    solver = sutil.interface_to_str(model.problem.__name__)
+    if "moma" in method and solver not in sutil.qp_solvers:
+        raise RuntimeError(
+            "Cannot use MOMA since '{}' is not QP-capable."
+            "Please choose a different solver or use FBA only.".format(solver))
+
+    if processes is None:
+        try:
+            processes = multiprocessing.cpu_count()
+        except NotImplementedError:
+            warn("Number of cores could not be detected - assuming 1.")
+            processes = 1
+
+    with model:
+        if "moma" in method:
+            add_moma(model, linear="linear" in method)
+
+        args = set([frozenset(comb) for comb in product(*element_lists)])
+        processes = min(processes, len(args))
+
+        def extract_knockout_results(result_iter):
+            result = pd.DataFrame([
+                (frozenset(ids), growth, status)
+                for (ids, growth, status) in result_iter
+            ], columns=['ids', 'growth', 'status'])
+            result.set_index('ids', inplace=True)
+            return result
+
+        if processes > 1:
+            worker = dict(gene=_gene_deletion_worker,
+                          reaction=_reaction_deletion_worker)[entity]
+            chunk_size = len(args) // processes
+            pool = multiprocessing.Pool(
+                processes, initializer=_init_worker, initargs=(model,)
+            )
+            results = extract_knockout_results(pool.imap_unordered(
+                worker,
+                args,
+                chunksize=chunk_size
+            ))
+            pool.close()
+            pool.join()
+        else:
+            worker = dict(gene=_gene_deletion,
+                          reaction=_reaction_deletion)[entity]
+            results = extract_knockout_results(map(
+                partial(worker, model), args))
+        return results
+
+
+def _entities_ids(entities):
+    try:
+        return [e.id for e in entities]
+    except AttributeError:
+        return list(entities)
+
+
+def _element_lists(entities, *ids):
+    lists = list(ids)
+    if lists[0] is None:
+        lists[0] = entities
+    result = [_entities_ids(lists[0])]
+    for l in lists[1:]:
+        if l is None:
+            result.append(result[-1])
+        else:
+            result.append(_entities_ids(l))
+    return result
+
+
+def single_reaction_deletion(model, reaction_list=None, method="fba",
+                             processes=None):
+    """
+    Knock out each reaction from a given list.
+
+    Parameters
+    ----------
+    model : cobra.Model
+        The metabolic model to perform deletions in.
+
+    reaction_list : iterable
+        ``cobra.Reaction``s to be deleted. If not passed,
+        all the reactions from the model are used.
+
+    method: {"fba", "moma", "linear moma"}, optional
+        Method used to predict the growth rate.
+
+    processes : int, optional
+        The number of parallel processes to run. Can speed up the computations
+        if the number of knockouts to perform is large. If not passed,
+        will be set to the number of CPUs found.
+
+    Returns
+    -------
+    pandas.DataFrame
+        A representation of all single reaction deletions. The columns are
+        'growth' and 'status', where
+
+        index : frozenset([str])
+            The reaction identifier that was knocked out.
+        growth : float
+            The growth rate of the adjusted model.
+        status : str
+            The solution's status.
+
+    """
+    return _multi_deletion(
+        model, 'reaction',
+        element_lists=_element_lists(model.reactions, reaction_list),
+        method=method, processes=processes)
+
+
+def single_gene_deletion(model, gene_list=None, method="fba", processes=None):
+    """
+    Knock out each gene from a given list.
+
+    Parameters
+    ----------
+    model : cobra.Model
+        The metabolic model to perform deletions in.
+
+    gene_list : iterable
+        ``cobra.Gene``s to be deleted. If not passed,
+        all the genes from the model are used.
+
+    method: {"fba", "moma", "linear moma"}, optional
+        Method used to predict the growth rate.
+
+    processes : int, optional
+        The number of parallel processes to run. Can speed up the computations
+        if the number of knockouts to perform is large. If not passed,
+        will be set to the number of CPUs found.
+
+    Returns
+    -------
+    pandas.DataFrame
+        A representation of all single gene deletions. The columns are
+        'growth' and 'status', where
+
+        index : frozenset([str])
+            The gene identifier that was knocked out.
+        growth : float
+            The growth rate of the adjusted model.
+        status : str
+            The solution's status.
+
+    """
+    return _multi_deletion(
+        model, 'gene', element_lists=_element_lists(model.genes, gene_list),
+        method=method, processes=processes)
+
+
+def double_reaction_deletion(model, reaction_list1=None, reaction_list2=None,
+                             method="fba", processes=None):
+    """
+    Knock out each reaction pair from the combinations of two given lists.
+
+    We say 'pair' here but the order order does not matter.
+
+    Parameters
+    ----------
+    model : cobra.Model
+        The metabolic model to perform deletions in.
+
+    reaction_list1 : iterable, optional
+        First iterable of ``cobra.Reaction``s to be deleted. If not passed,
+        all the reactions from the model are used.
+
+    reaction_list2 : iterable, optional
+        Second iterable of ``cobra.Reaction``s to be deleted. If not passed,
+        all the reactions from the model are used.
+
+    method: {"fba", "moma", "linear moma"}, optional
+        Method used to predict the growth rate.
+
+    processes : int, optional
+        The number of parallel processes to run. Can speed up the computations
+        if the number of knockouts to perform is large. If not passed,
+        will be set to the number of CPUs found.
+
+    Returns
+    -------
+    pandas.DataFrame
+        A representation of all combinations of reaction deletions. The
+        columns are 'growth' and 'status', where
+
+        index : frozenset([str])
+            The reaction identifiers that were knocked out.
+        growth : float
+            The growth rate of the adjusted model.
+        status : str
+            The solution's status.
+
+    """
+
+    reaction_list1, reaction_list2 = _element_lists(model.reactions,
+                                                    reaction_list1,
+                                                    reaction_list2)
+    return _multi_deletion(
+        model, 'reaction', element_lists=[reaction_list1, reaction_list2],
+        method=method, processes=processes)
+
+
+def double_gene_deletion(model, gene_list1=None, gene_list2=None,
+                         method="fba", processes=None):
+    """
+    Knock out each gene pair from the combination of two given lists.
+
+    We say 'pair' here but the order order does not matter.
+
+    Parameters
+    ----------
+    model : cobra.Model
+        The metabolic model to perform deletions in.
+
+    gene_list1 : iterable, optional
+        First iterable of ``cobra.Gene``s to be deleted. If not passed,
+        all the genes from the model are used.
+
+    gene_list2 : iterable, optional
+        Second iterable of ``cobra.Gene``s to be deleted. If not passed,
+        all the genes from the model are used.
+
+    method: {"fba", "moma", "linear moma"}, optional
+        Method used to predict the growth rate.
+
+    processes : int, optional
+        The number of parallel processes to run. Can speed up the computations
+        if the number of knockouts to perform is large. If not passed,
+        will be set to the number of CPUs found.
+
+    Returns
+    -------
+    pandas.DataFrame
+        A representation of all combinations of gene deletions. The
+        columns are 'growth' and 'status', where
+
+        index : frozenset([str])
+            The gene identifiers that were knocked out.
+        growth : float
+            The growth rate of the adjusted model.
+        status : str
+            The solution's status.
+
+    """
+
+    gene_list1, gene_list2 = _element_lists(model.genes, gene_list1,
+                                            gene_list2)
+    return _multi_deletion(
+        model, 'gene', element_lists=[gene_list1, gene_list2],
+        method=method, processes=processes)
diff --git a/cobra/flux_analysis/deletion_worker.py b/cobra/flux_analysis/deletion_worker.py
deleted file mode 100644
index 96cbe6e..0000000
--- a/cobra/flux_analysis/deletion_worker.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
-
-from multiprocessing import Process, Queue, cpu_count
-
-from six import iteritems
-
-from cobra.solvers import get_solver_name, solver_dict
-
-
-def compute_fba_deletion_worker(cobra_model, solver, job_queue, output_queue,
-                                **kwargs):
-    solver = solver_dict[get_solver_name() if solver is None else solver]
-    lp = solver.create_problem(cobra_model)
-    solver_args = kwargs
-    solver.solve_problem(lp)
-    while True:
-        indexes, label = job_queue.get()
-        label = indexes if label is None else label
-        result = compute_fba_deletion(lp, solver, cobra_model, indexes,
-                                      **solver_args)
-        output_queue.put((label, result))
-
-
-def compute_fba_deletion(lp, solver_object, model, indexes, **kwargs):
-    s = solver_object
-    old_bounds = {}
-    for i in indexes:
-        reaction = model.reactions[i]
-        old_bounds[i] = (reaction.lower_bound, reaction.upper_bound)
-        s.change_variable_bounds(lp, i, 0., 0.)
-    try:
-        s.solve_problem(lp, **kwargs)
-    except Exception as e:
-        return RuntimeError("solver failure when deleting %s: %s" %
-                            (str(indexes), repr(e)))
-    status = s.get_status(lp)
-    objective = s.get_objective_value(lp) if status == "optimal" else 0.
-
-    # reset the problem, which must be done after reading the solution
-    for index, bounds in iteritems(old_bounds):
-        s.change_variable_bounds(lp, index, bounds[0], bounds[1])
-
-    if status == "infeasible" or status == "optimal":
-        return objective
-    else:
-        return RuntimeError("solver failure (status %s) for when deleting %s" %
-                            (status, str(indexes)))
-
-
-class CobraDeletionPool(object):
-    """A pool of workers for solving deletions
-
-    submit jobs to the pool using submit and recieve results using receive_all
-    """
-    # Having an existing basis makes solving an existing LP much faster. The
-    # most efficient approach is to have a worker function which modifies an LP
-    # object and reverts it back after each calculation. Each lp object stores
-    # the basis so subsequent LP's are solved more quickely, and memory does
-    # not need to be re-allocated each time to create a new problem. Because
-    # state is being saved, the workers in the deletion pool are careful about
-    # reverting the object after simulating a deletion, and are written to be
-    # flexible enough so they can be used in most applications instead of
-    # writing a custom worker each time.
-
-    def __init__(self, cobra_model, n_processes=None, solver=None, **kwargs):
-        if n_processes is None:
-            n_processes = min(cpu_count(), 4)
-        # start queues
-        self.job_queue = Queue()  # format is (indexes, job_label)
-        self.n_submitted = 0
-        self.n_complete = 0
-        self.output_queue = Queue()  # format is (job_label, growth_rate)
-        # start processes
-        self.processes = []
-        for i in range(n_processes):
-            p = Process(target=compute_fba_deletion_worker,
-                        args=[cobra_model, solver,
-                              self.job_queue, self.output_queue],
-                        kwargs=kwargs)
-            self.processes.append(p)
-
-    def start(self):
-        for p in self.processes:
-            p.start()
-
-    def terminate(self):
-        for p in self.processes:
-            p.terminate()
-
-    def __enter__(self):
-        self.start()
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        try:
-            self.terminate()
-        except:
-            pass
-
-    def submit(self, indexes, label=None):
-        self.job_queue.put((indexes, label))
-        self.n_submitted += 1
-
-    def receive_one(self):
-        """This function blocks"""
-        self.n_complete += 1
-        result = self.output_queue.get()
-        if isinstance(result[1], Exception):
-            raise result[1]
-        return result
-
-    def receive_all(self):
-        while self.n_complete < self.n_submitted:
-            self.n_complete += 1
-            result = self.output_queue.get()
-            if isinstance(result[1], Exception):
-                raise result[1]
-            yield result
-
-    @property
-    def pids(self):
-        return [p.pid for p in self.processes]
-
-    def __del__(self):
-        for process in self.processes:
-            process.terminate()
-            process.join()
-
-
-class CobraDeletionMockPool(object):
-    """Mock pool solves LP's in the same process"""
-
-    def __init__(self, cobra_model, n_processes=1, solver=None, **kwargs):
-        if n_processes != 1:
-            from warnings import warn
-            warn("Mock Pool does not do multiprocessing")
-        self.job_queue = []
-        self.solver_args = kwargs
-        solver_name = get_solver_name() if solver is None else solver
-        self.solver = solver_dict[solver_name]
-        self.lp = self.solver.create_problem(cobra_model)
-        self.solver.solve_problem(self.lp)
-        self.model = cobra_model
-
-    def submit(self, indexes, label=None):
-        self.job_queue.append((indexes, label))
-
-    def receive_one(self):
-        indexes, label = self.job_queue.pop()
-        result = compute_fba_deletion(self.lp, self.solver, self.model,
-                                      indexes, **self.solver_args)
-        if isinstance(result, Exception):
-            raise result
-        return (label, result)
-
-    def receive_all(self):
-        for i in range(len(self.job_queue)):
-            indexes, label = self.job_queue.pop()
-            result = compute_fba_deletion(self.lp, self.solver, self.model,
-                                          indexes, **self.solver_args)
-            if isinstance(result, Exception):
-                raise result
-            yield (label, result)
-
-    def start(self):
-        None
-
-    def terminate(self):
-        None
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        None
diff --git a/cobra/flux_analysis/double_deletion.py b/cobra/flux_analysis/double_deletion.py
deleted file mode 100644
index 0b21f6a..0000000
--- a/cobra/flux_analysis/double_deletion.py
+++ /dev/null
@@ -1,580 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
-
-from itertools import chain, product
-from warnings import warn
-
-import numpy
-from pandas import DataFrame
-from six import iteritems
-
-from cobra.flux_analysis.deletion_worker import (
-    CobraDeletionMockPool, CobraDeletionPool)
-from cobra.manipulation.delete import (
-    find_gene_knockout_reactions, get_compiled_gene_reaction_rules)
-from cobra.solvers import get_solver_name, solver_dict
-
-try:
-    import scipy
-except ImportError:
-    moma = None
-else:
-    from . import moma
-
-
-# Utility functions
-def generate_matrix_indexes(ids1, ids2):
-    """map an identifier to an entry in the square result matrix"""
-    return {id: index for index, id in enumerate(set(chain(ids1, ids2)))}
-
-
-def yield_upper_tria_indexes(ids1, ids2, id_to_index):
-    """gives the necessary indexes in the upper triangle
-
-    ids1 and ids2 are lists of the identifiers i.e. gene id's or reaction
-    indexes to be knocked out. id_to_index maps each identifier to its index
-    in the result matrix.
-
-    Note that this does not return indexes for the diagonal. Those have
-    to be computed separately."""
-    # sets to check for inclusion in o(1)
-    id_set1 = set(ids1)
-    id_set2 = set(ids2)
-    for id1, id2 in product(ids1, ids2):
-        # indexes in the result matrix
-        index1 = id_to_index[id1]
-        index2 = id_to_index[id2]
-        # upper triangle
-        if index2 > index1:
-            yield ((index1, index2), (id1, id2))
-        # lower triangle but would be skipped, so return in upper triangle
-        elif id2 not in id_set1 or id1 not in id_set2:
-            yield((index2, index1), (id2, id1))  # note that order flipped
-
-
-def _format_upper_triangular_matrix(row_indexes, column_indexes, matrix):
-    """reformat the square upper-triangular result matrix
-
-    For example, results may look like this
-    [[ A  B  C  D]
-     [ -  -  -  -]
-     [ -  -  E  F]
-     [ -  -  -  G]]
-    In this case, the second row was skipped. This means we have
-    row_indexes [0, 2, 3] and column_indexes [0, 1, 2, 3]
-
-    First, it will reflect the upper triangle into the lower triangle
-    [[ A  B  C  D]
-     [ B  -  -  -]
-     [ C  -  E  F]
-     [ D  -  F  G]]
-
-    Finally, it will remove the missing rows and return
-    [[ A  B  C  D]
-     [ C  -  E  F]
-     [ D  -  F  G]]
-    """
-    results = matrix.copy()
-    # Thse select the indexes for the upper triangle. However, switching
-    # the order selects the lower triangle.
-    triu1, triu2 = numpy.triu_indices(matrix.shape[0])
-    # This makes reflection pretty easy
-    results[triu2, triu1] = results[triu1, triu2]
-    # Remove the missing rows and  return.
-    return results[row_indexes, :][:, column_indexes]
-
-
-def format_results_frame(row_ids, column_ids, matrix, return_frame=False):
-    """format results as a pandas.DataFrame if desired/possible
-
-    Otherwise returns a dict of
-    {"x": row_ids, "y": column_ids", "data": result_matrx}"""
-    if return_frame:
-        return DataFrame(data=matrix, index=row_ids, columns=column_ids)
-    else:
-        return {"x": row_ids, "y": column_ids, "data": matrix}
-
-
-def double_deletion(cobra_model, element_list_1=None, element_list_2=None,
-                    element_type='gene', **kwargs):
-    """Wrapper for double_gene_deletion and double_reaction_deletion
-
-    .. deprecated :: 0.4
-        Use double_reaction_deletion and double_gene_deletion
-    """
-    warn("deprecated - use single_reaction_deletion and single_gene_deletion")
-    if element_type == "reaction":
-        return double_reaction_deletion(cobra_model, element_list_1,
-                                        element_list_2, **kwargs)
-    elif element_type == "gene":
-        return double_gene_deletion(cobra_model, element_list_1,
-                                    element_list_2, **kwargs)
-    else:
-        raise Exception("unknown element type")
-
-
-def double_reaction_deletion(cobra_model,
-                             reaction_list1=None, reaction_list2=None,
-                             method="fba", return_frame=False,
-                             solver=None, zero_cutoff=1e-12,
-                             **kwargs):
-    """sequentially knocks out pairs of reactions in a model
-
-    cobra_model : :class:`~cobra.core.Model.Model`
-        cobra model in which to perform deletions
-
-    reaction_list1 : [:class:`~cobra.core.Reaction.Reaction`:] (or their id's)
-        Reactions to be deleted. These will be the rows in the result.
-        If not provided, all reactions will be used.
-
-    reaction_list2 : [:class:`~cobra.core.Reaction`:] (or their id's)
-        Reactions to be deleted. These will be the rows in the result.
-        If not provided, reaction_list1 will be used.
-
-    method: "fba" or "moma"
-        Procedure used to predict the growth rate
-
-    solver: str for solver name
-        This must be a QP-capable solver for MOMA. If left unspecified,
-        a suitable solver will be automatically chosen.
-
-    zero_cutoff: float
-        When checking to see if a value is 0, this threshold is used.
-
-    return_frame: bool
-        If true, formats the results as a pandas.Dataframe. Otherwise
-        returns a dict of the form:
-        {"x": row_labels, "y": column_labels", "data": 2D matrix}
-    """
-    # handle arguments which need to be passed on
-    if solver is None:
-        solver = get_solver_name(qp=(method == "moma"))
-        kwargs["solver"] = solver
-    kwargs["zero_cutoff"] = zero_cutoff
-
-    # generate other arguments
-
-    # identifiers for reactions are their indexes
-    if reaction_list1 is None:
-        reaction_indexes1 = range(len(cobra_model.reactions))
-    else:
-        reaction_indexes1 = [cobra_model.reactions.index(r)
-                             for r in reaction_list1]
-    if reaction_list2 is None:
-        reaction_indexes2 = reaction_indexes1
-    else:
-        reaction_indexes2 = [cobra_model.reactions.index(r)
-                             for r in reaction_list2]
-    reaction_to_result = generate_matrix_indexes(reaction_indexes1,
-                                                 reaction_indexes2)
-
-    # Determine 0 flux reactions. If an optimal solution passes no flux
-    # through the deleted reactions, then we know removing them will
-    # not change the solution.
-    wt_solution = solver_dict[solver].solve(cobra_model)
-    if wt_solution.status == "optimal":
-        kwargs["wt_growth_rate"] = wt_solution.f
-        kwargs["no_flux_reaction_indexes"] = \
-            {i for i, v in enumerate(wt_solution.x) if abs(v) < zero_cutoff}
-    else:
-        warn("wild-type solution status is '%s'" % wt_solution.status)
-
-    # call the computing functions
-    if method == "fba":
-        results = _double_reaction_deletion_fba(
-            cobra_model, reaction_indexes1, reaction_indexes2,
-            reaction_to_result, **kwargs)
-    elif method == "moma":
-        results = _double_reaction_deletion_moma(
-            cobra_model, reaction_indexes1, reaction_indexes2,
-            reaction_to_result, **kwargs)
-    else:
-        raise ValueError("Unknown deletion method '%s'" % method)
-
-    # convert upper triangular matrix to full matrix
-    full_result = _format_upper_triangular_matrix(
-        [reaction_to_result[i] for i in reaction_indexes1],  # row indexes
-        [reaction_to_result[i] for i in reaction_indexes2],  # col indexes
-        results)
-
-    # format appropriately with labels
-    row_ids = [cobra_model.reactions[i].id for i in reaction_indexes1]
-    column_ids = [cobra_model.reactions[i].id for i in reaction_indexes2]
-    return format_results_frame(row_ids, column_ids,
-                                full_result, return_frame)
-
-
-def double_gene_deletion(cobra_model,
-                         gene_list1=None, gene_list2=None,
-                         method="fba", return_frame=False,
-                         solver=None, zero_cutoff=1e-12,
-                         **kwargs):
-    """sequentially knocks out pairs of genes in a model
-
-    cobra_model : :class:`~cobra.core.Model.Model`
-        cobra model in which to perform deletions
-
-    gene_list1 : [:class:`~cobra.core.Gene.Gene`:] (or their id's)
-        Genes to be deleted. These will be the rows in the result.
-        If not provided, all reactions will be used.
-
-    gene_list1 : [:class:`~cobra.core.Gene.Gene`:] (or their id's)
-        Genes to be deleted. These will be the rows in the result.
-        If not provided, reaction_list1 will be used.
-
-    method: "fba" or "moma"
-        Procedure used to predict the growth rate
-
-    solver: str for solver name
-        This must be a QP-capable solver for MOMA. If left unspecified,
-        a suitable solver will be automatically chosen.
-
-    zero_cutoff: float
-        When checking to see if a value is 0, this threshold is used.
-
-    number_of_processes: int for number of processes to use.
-        If unspecified, the number of parallel processes to use will be
-        automatically determined. Setting this to 1 explicitly disables used
-        of the multiprocessing library.
-
-    .. note:: multiprocessing is not supported with method=moma
-
-    return_frame: bool
-        If true, formats the results as a pandas.Dataframe. Otherwise
-        returns a dict of the form:
-        {"x": row_labels, "y": column_labels", "data": 2D matrix}
-    """
-    # handle arguments which need to be passed on
-    if solver is None:
-        solver = get_solver_name(qp=(method == "moma"))
-        kwargs["solver"] = solver
-    kwargs["zero_cutoff"] = zero_cutoff
-
-    # generate other arguments
-
-    # identifiers for genes
-    if gene_list1 is None:
-        gene_ids1 = cobra_model.genes.list_attr("id")
-    else:
-        gene_ids1 = [str(i) for i in gene_list1]
-    if gene_list2 is None:
-        gene_ids2 = gene_ids1
-    else:
-        gene_ids2 = [str(i) for i in gene_list2]
-
-    # The gene_id_to_result dict will map each gene id to the index
-    # in the result matrix.
-    gene_id_to_result = generate_matrix_indexes(gene_ids1, gene_ids2)
-
-    # Determine 0 flux reactions. If an optimal solution passes no flux
-    # through the deleted reactions, then we know removing them will
-    # not change the solution.
-    wt_solution = solver_dict[solver].solve(cobra_model)
-    if wt_solution.status == "optimal":
-        kwargs["wt_growth_rate"] = wt_solution.f
-        kwargs["no_flux_reaction_indexes"] = \
-            {i for i, v in enumerate(wt_solution.x) if abs(v) < zero_cutoff}
-    else:
-        warn("wild-type solution status is '%s'" % wt_solution.status)
-
-    if method == "fba":
-        result = _double_gene_deletion_fba(cobra_model, gene_ids1, gene_ids2,
-                                           gene_id_to_result, **kwargs)
-    elif method == "moma":
-        result = _double_gene_deletion_moma(cobra_model, gene_ids1, gene_ids2,
-                                            gene_id_to_result, **kwargs)
-    else:
-        raise ValueError("Unknown deletion method '%s'" % method)
-
-    # convert upper triangular matrix to full matrix
-    full_result = _format_upper_triangular_matrix(
-        [gene_id_to_result[id] for id in gene_ids1],  # row indexes
-        [gene_id_to_result[id] for id in gene_ids2],  # col indexes,
-        result)
-
-    # format as a Dataframe if required
-    return format_results_frame(gene_ids1, gene_ids2,
-                                full_result, return_frame)
-
-
-def _double_reaction_deletion_fba(cobra_model, reaction_indexes1,
-                                  reaction_indexes2, reaction_to_result,
-                                  solver, number_of_processes=None,
-                                  zero_cutoff=1e-15, wt_growth_rate=None,
-                                  no_flux_reaction_indexes=set(), **kwargs):
-    """compute double reaction deletions using fba
-
-    cobra_model: model
-
-    reaction_indexes1, reaction_indexes2: reaction indexes (used as unique
-        identifiers)
-
-    reaction_to_result: maps each reaction identifier to the entry in
-        the result matrix
-
-    no_flux_reaction_indexes: set of indexes for reactions in the model
-        which carry no flux in an optimal solution. For deletions only in
-        this set, the result will beset to wt_growth_rate.
-
-    returns an upper triangular square matrix
-    """
-    if solver is None:
-        solver = get_solver_name()
-
-    # generate the square result matrix
-    n_results = len(reaction_to_result)
-    results = numpy.empty((n_results, n_results))
-    results.fill(numpy.nan)
-
-    PoolClass = CobraDeletionMockPool if number_of_processes == 1 \
-        else CobraDeletionPool  # explicitly disable multiprocessing
-
-    with PoolClass(cobra_model, n_processes=number_of_processes,
-                   solver=solver, **kwargs) as pool:
-
-        # precompute all single deletions in the pool and store them along
-        # the diagonal
-        for reaction_index, result_index in iteritems(reaction_to_result):
-            pool.submit((reaction_index, ), label=result_index)
-        for result_index, value in pool.receive_all():
-            # if singly lethal, set everything in row and column to 0
-            value = value if abs(value) > zero_cutoff else 0.
-            if value == 0.:
-                results[result_index, :] = 0.
-                results[:, result_index] = 0.
-            else:  # only the diagonal needs to be set
-                results[result_index, result_index] = value
-
-        # Run double knockouts in the upper triangle
-        index_selector = yield_upper_tria_indexes(
-            reaction_indexes1, reaction_indexes2, reaction_to_result)
-        for result_index, (r1_index, r2_index) in index_selector:
-            # skip if the result was already computed to be lethal
-            if results[result_index] == 0:
-                continue
-            # reactions removed carry no flux
-            if r1_index in no_flux_reaction_indexes and \
-                    r2_index in no_flux_reaction_indexes:
-                results[result_index] = wt_growth_rate
-                continue
-            pool.submit((r1_index, r2_index), label=result_index)
-        # get results
-        for result in pool.receive_all():
-            results[result[0]] = result[1]
-
-    return results
-
-
-def _double_gene_deletion_fba(cobra_model, gene_ids1, gene_ids2,
-                              gene_id_to_result, solver,
-                              number_of_processes=None, zero_cutoff=1e-12,
-                              wt_growth_rate=None,
-                              no_flux_reaction_indexes=set(), **kwargs):
-    """compute double gene deletions using fba
-
-    cobra_model: model
-
-    gene_ids1, gene_ids2: lists of id's to be knocked out
-
-    gene_id_to_result: maps each gene identifier to the entry in
-        the result matrix
-
-    no_flux_reaction_indexes: set of indexes for reactions in the model
-        which carry no flux in an optimal solution. For deletions only in
-        this set, the result will beset to wt_growth_rate.
-
-    returns an upper triangular square matrix
-    """
-    # Because each gene reaction rule will be evaluated multiple times
-    # the reaction has multiple associated genes being deleted, compiling
-    # the gene reaction rules ahead of time increases efficiency greatly.
-    compiled_rules = get_compiled_gene_reaction_rules(cobra_model)
-
-    n_results = len(gene_id_to_result)
-    results = numpy.empty((n_results, n_results))
-    results.fill(numpy.nan)
-
-    if number_of_processes == 1:  # explicitly disable multiprocessing
-        PoolClass = CobraDeletionMockPool
-    else:
-        PoolClass = CobraDeletionPool
-    with PoolClass(cobra_model, n_processes=number_of_processes,
-                   solver=solver, **kwargs) as pool:
-        # precompute all single deletions in the pool and store them along
-        # the diagonal
-        for gene_id, gene_result_index in iteritems(gene_id_to_result):
-            ko_reactions = find_gene_knockout_reactions(
-                cobra_model, (cobra_model.genes.get_by_id(gene_id),))
-            ko_indexes = [cobra_model.reactions.index(i) for i in ko_reactions]
-            pool.submit(ko_indexes, label=gene_result_index)
-        for result_index, value in pool.receive_all():
-            # if singly lethal, set everything in row and column to 0
-            value = value if abs(value) > zero_cutoff else 0.
-            if value == 0.:
-                results[result_index, :] = 0.
-                results[:, result_index] = 0.
-            else:  # only the diagonal needs to be set
-                results[result_index, result_index] = value
-
-        # Run double knockouts in the upper triangle
-        index_selector = yield_upper_tria_indexes(gene_ids1, gene_ids2,
-                                                  gene_id_to_result)
-        for result_index, (gene1, gene2) in index_selector:
-
-            # if singly lethal the results have already been set
-            if results[result_index] == 0:
-                continue
-            ko_reactions = find_gene_knockout_reactions(
-                cobra_model, (gene1, gene2), compiled_rules)
-            ko_indexes = [cobra_model.reactions.index(i)
-                          for i in ko_reactions]
-            # if all removed gene indexes carry no flux
-            if len(set(ko_indexes) - no_flux_reaction_indexes) == 0:
-                results[result_index] = wt_growth_rate
-                continue
-            pool.submit(ko_indexes, label=result_index)
-
-        for result in pool.receive_all():
-            value = result[1]
-            if value < zero_cutoff:
-                value = 0
-            results[result[0]] = value
-
-    return results
-
-
-def _double_reaction_deletion_moma(cobra_model, reaction_indexes1,
-                                   reaction_indexes2, reaction_to_result,
-                                   solver, number_of_processes=1,
-                                   zero_cutoff=1e-15, wt_growth_rate=None,
-                                   no_flux_reaction_indexes=set(), **kwargs):
-    """compute double reaction deletions using moma
-
-    cobra_model: model
-
-    reaction_indexes1, reaction_indexes2: reaction indexes (used as unique
-        identifiers)
-
-    reaction_to_result: maps each reaction identifier to the entry in
-        the result matrix
-
-    no_flux_reaction_indexes: set of indexes for reactions in the model
-        which carry no flux in an optimal solution. For deletions only in
-        this set, the result will beset to wt_growth_rate.
-
-    number_of_processes: must be 1. Parallel MOMA not yet implmemented
-
-    returns an upper triangular square matrix
-    """
-    if number_of_processes > 1:
-        raise NotImplementedError("parallel MOMA not implemented")
-    if moma is None:
-        raise RuntimeError("scipy required for MOMA")
-
-    # generate the square result matrix
-    n_results = len(reaction_to_result)
-    results = numpy.empty((n_results, n_results))
-    results.fill(numpy.nan)
-
-    # function to compute reaction knockouts with moma
-    moma_model, moma_obj = moma.create_euclidian_moma_model(cobra_model)
-
-    def run(indexes):
-        # If all the reactions carry no flux, deletion will have no effect.
-        if no_flux_reaction_indexes.issuperset(indexes):
-            return wt_growth_rate
-        return moma.moma_knockout(moma_model, moma_obj, indexes,
-                                  solver=solver, **kwargs).f
-
-    # precompute all single deletions and store them along the diagonal
-    for reaction_index, result_index in iteritems(reaction_to_result):
-        value = run((reaction_index,))
-        value = value if abs(value) > zero_cutoff else 0.
-        results[result_index, result_index] = value
-        # if singly lethal, the entire row and column are set to 0
-        if value == 0.:
-            results[result_index, :] = 0.
-            results[:, result_index] = 0.
-
-    # Run double knockouts in the upper triangle
-    index_selector = yield_upper_tria_indexes(
-        reaction_indexes1, reaction_indexes2, reaction_to_result)
-    for result_index, (r1_index, r2_index) in index_selector:
-        # skip if the result was already computed to be lethal
-        if results[result_index] == 0:
-            continue
-        else:
-            results[result_index] = run((r1_index, r2_index))
-
-    return results
-
-
-def _double_gene_deletion_moma(cobra_model, gene_ids1, gene_ids2,
-                               gene_id_to_result, solver,
-                               number_of_processes=1,
-                               zero_cutoff=1e-12, wt_growth_rate=None,
-                               no_flux_reaction_indexes=set(), **kwargs):
-    """compute double gene deletions using moma
-
-    cobra_model: model
-
-    gene_ids1, gene_ids2: lists of id's to be knocked out
-
-    gene_id_to_result: maps each gene identifier to the entry in
-        the result matrix
-
-    number_of_processes: must be 1. Parallel MOMA not yet implemented
-
-    no_flux_reaction_indexes: set of indexes for reactions in the model
-        which carry no flux in an optimal solution. For deletions only in
-        this set, the result will beset to wt_growth_rate.
-
-    returns an upper triangular square matrix
-    """
-    if number_of_processes > 1:
-        raise NotImplementedError("parallel MOMA not implemented")
-    if moma is None:
-        raise RuntimeError("scipy required for MOMA")
-
-    # Because each gene reaction rule will be evaluated multiple times
-    # the reaction has multiple associated genes being deleted, compiling
-    # the gene reaction rules ahead of time increases efficiency greatly.
-    compiled_rules = get_compiled_gene_reaction_rules(cobra_model)
-
-    # function to compute reaction knockouts with moma
-    moma_model, moma_obj = moma.create_euclidian_moma_model(cobra_model)
-
-    def run(gene_ids):
-        ko_reactions = find_gene_knockout_reactions(cobra_model, gene_ids)
-        ko_indexes = map(cobra_model.reactions.index, ko_reactions)
-        # If all the reactions carry no flux, deletion will have no effect.
-        if no_flux_reaction_indexes.issuperset(gene_ids):
-            return wt_growth_rate
-        return moma.moma_knockout(moma_model, moma_obj, ko_indexes,
-                                  solver=solver, **kwargs).f
-
-    n_results = len(gene_id_to_result)
-    results = numpy.empty((n_results, n_results))
-    results.fill(numpy.nan)
-
-    # precompute all single deletions and store them along the diagonal
-    for gene_id, result_index in iteritems(gene_id_to_result):
-        value = run((gene_id,))
-        value = value if abs(value) > zero_cutoff else 0.
-        results[result_index, result_index] = value
-        # If singly lethal, the entire row and column are set to 0.
-        if value == 0.:
-            results[result_index, :] = 0.
-            results[:, result_index] = 0.
-
-    # Run double knockouts in the upper triangle
-    index_selector = yield_upper_tria_indexes(gene_ids1, gene_ids2,
-                                              gene_id_to_result)
-    for result_index, (gene1, gene2) in index_selector:
-        # if singly lethal the results have already been set
-        if results[result_index] == 0:
-            continue
-        results[result_index] = run((gene1, gene2))
-
-    return results
diff --git a/cobra/flux_analysis/gapfilling.py b/cobra/flux_analysis/gapfilling.py
index ba5841c..1500866 100644
--- a/cobra/flux_analysis/gapfilling.py
+++ b/cobra/flux_analysis/gapfilling.py
@@ -2,8 +2,7 @@
 
 from __future__ import absolute_import
 
-from sympy import Add
-from warnings import warn
+from optlang.symbolics import Add
 
 from optlang.interface import OPTIMAL
 from cobra.core import Model
@@ -296,35 +295,3 @@ def gapfill(model, universal=None, lower_bound=0.05,
                           demand_reactions=demand_reactions,
                           exchange_reactions=exchange_reactions)
     return gapfiller.fill(iterations=iterations)
-
-
-def growMatch(model, Universal, dm_rxns=False, ex_rxns=False,
-              penalties=None, iterations=1, **solver_parameters):
-    """runs (partial implementation of) growMatch. Legacy function,
-    to be removed in future version of cobrapy in favor of gapfill. """
-
-    warn('use gapfill instead', DeprecationWarning)
-    if 'solver' in dict(**solver_parameters):
-        raise ValueError('growMatch implementation for cobra legacy solvers'
-                         ' is defunct. Choose optlang solver with '
-                         'model.solver = "solver"')
-    return gapfill(model, universal=Universal,
-                   iterations=iterations, penalties=penalties,
-                   demand_reactions=dm_rxns, exchange_reactions=ex_rxns)
-
-
-def SMILEY(model, metabolite_id, Universal,
-           dm_rxns=False, ex_rxns=False, penalties=None, **solver_parameters):
-    """runs the SMILEY algorithm. Legacy function,
-    to be removed in future version of cobrapy in favor of gapfill. """
-
-    warn('use gapfill instead', DeprecationWarning)
-    if 'solver' in dict(**solver_parameters):
-        raise ValueError('SMILEY implementation for cobra legacy solvers'
-                         ' is defunct. Choose optlang solver with '
-                         'model.solver = "solver"')
-    with model:
-        metabolite = model.metabolites.get_by_id(metabolite_id)
-        model.objective = model.add_boundary(metabolite, type='demand')
-        return gapfill(model, universal=Universal, penalties=penalties,
-                       demand_reactions=dm_rxns, exchange_reactions=ex_rxns)
diff --git a/cobra/flux_analysis/loopless.py b/cobra/flux_analysis/loopless.py
index c7af942..7c1b474 100644
--- a/cobra/flux_analysis/loopless.py
+++ b/cobra/flux_analysis/loopless.py
@@ -6,11 +6,10 @@ from __future__ import absolute_import
 
 import numpy
 from six import iteritems
-from sympy.core.singleton import S
+from optlang.symbolics import Zero
 
 from cobra.core import Metabolite, Reaction, get_solution
-from cobra.util import (linear_reaction_coefficients,
-                        create_stoichiometric_matrix, nullspace)
+from cobra.util import create_stoichiometric_matrix, nullspace
 from cobra.manipulation.modify import convert_to_irreversible
 
 
@@ -72,7 +71,7 @@ def add_loopless(model, zero_cutoff=1e-12):
     # Add nullspace constraints for G_i
     for i, row in enumerate(n_int):
         name = "nullspace_constraint_" + str(i)
-        nullspace_constraint = prob.Constraint(S.Zero, lb=0, ub=0, name=name)
+        nullspace_constraint = prob.Constraint(Zero, lb=0, ub=0, name=name)
         model.add_cons_vars([nullspace_constraint])
         coefs = {model.variables[
                  "delta_g_" + model.reactions[ridx].id]: row[i]
@@ -83,7 +82,8 @@ def add_loopless(model, zero_cutoff=1e-12):
 
 def _add_cycle_free(model, fluxes):
     """Add constraints for CycleFreeFlux."""
-    model.objective = S.Zero
+    model.objective = Zero
+    objective_vars = []
     for rxn in model.reactions:
         flux = fluxes[rxn.id]
         if rxn.boundary:
@@ -91,14 +91,13 @@ def _add_cycle_free(model, fluxes):
             continue
         if flux >= 0:
             rxn.lower_bound = max(0, rxn.lower_bound)
-            model.objective.set_linear_coefficients(
-                {rxn.forward_variable: 1, rxn.reverse_variable: -1})
+            objective_vars.append(rxn.forward_variable)
         else:
             rxn.upper_bound = min(0, rxn.upper_bound)
-            model.objective.set_linear_coefficients(
-                {rxn.forward_variable: -1, rxn.reverse_variable: 1})
+            objective_vars.append(rxn.reverse_variable)
 
-    model.solver.objective.direction = "min"
+    model.objective.set_linear_coefficients(dict.fromkeys(objective_vars, 1.0))
+    model.objective.direction = "min"
 
 
 def loopless_solution(model, fluxes=None):
@@ -117,10 +116,6 @@ def loopless_solution(model, fluxes=None):
         A dictionary {rxn_id: flux} that assigns a flux to each reaction. If
         not None will use the provided flux values to obtain a close loopless
         solution.
-        Note that this requires a linear objective function involving
-        only the model reactions. This is the case if
-        `linear_reaction_coefficients(model)` is a correct representation of
-        the objective.
 
     Returns
     -------
@@ -131,12 +126,13 @@ def loopless_solution(model, fluxes=None):
 
     Notes
     -----
-
     The returned flux solution has the following properties:
 
     - it contains the minimal number of loops possible and no loops at all if
       all flux bounds include zero
-    - it has the same exact objective value as the previous solution
+    - it has an objective value close to the original one and the same
+      objective value id the objective expression can not form a cycle
+      (which is usually true since it consumes metabolites)
     - it has the same exact exchange fluxes as the previous solution
     - all fluxes have the same sign (flow in the same direction) as the
       previous solution
@@ -154,19 +150,17 @@ def loopless_solution(model, fluxes=None):
     if fluxes is None:
         sol = model.optimize(objective_sense=None)
         fluxes = sol.fluxes
-        obj_val = sol.objective_value
-    else:
-        coefs = linear_reaction_coefficients(model)
-        obj_val = sum(coefs[rxn] * fluxes[rxn.id] for rxn in coefs)
 
     with model:
         prob = model.problem
+        # Needs one fixed bound for cplex...
         loopless_obj_constraint = prob.Constraint(
             model.objective.expression,
-            lb=obj_val, ub=obj_val, name="loopless_obj_constraint")
+            lb=-1e32, name="loopless_obj_constraint")
         model.add_cons_vars([loopless_obj_constraint])
         _add_cycle_free(model, fluxes)
         solution = model.optimize(objective_sense=None)
+        solution.objective_value = loopless_obj_constraint.primal
 
     return solution
 
@@ -203,6 +197,7 @@ def loopless_fva_iter(model, reaction, solution=False, zero_cutoff=1e-6):
     """
     current = model.objective.value
     sol = get_solution(model)
+    objective_dir = model.objective.direction
 
     # boundary reactions can not be part of cycles
     if reaction.boundary:
@@ -212,10 +207,8 @@ def loopless_fva_iter(model, reaction, solution=False, zero_cutoff=1e-6):
             return current
 
     with model:
-        model.objective = 1.0 * model.variables.fva_old_objective
         _add_cycle_free(model, sol.fluxes)
-        model.slim_optimize()
-        flux = reaction.flux
+        flux = model.slim_optimize()
 
         # If the previous optimum is maintained in the loopless solution it was
         # loopless and we are done
@@ -242,10 +235,11 @@ def loopless_fva_iter(model, reaction, solution=False, zero_cutoff=1e-6):
                 rxn.bounds = max(0, rxn.lower_bound), min(0, rxn.upper_bound)
 
         if solution:
-            best = model.optimize(objective_sense=None)
+            best = model.optimize()
         else:
             model.slim_optimize()
             best = reaction.flux
+    model.objective.direction = objective_dir
     return best
 
 
diff --git a/cobra/flux_analysis/moma.py b/cobra/flux_analysis/moma.py
index 6b47dad..02a9338 100644
--- a/cobra/flux_analysis/moma.py
+++ b/cobra/flux_analysis/moma.py
@@ -4,11 +4,9 @@
 
 from __future__ import absolute_import
 
-from scipy.sparse import dok_matrix
-from sympy.core.singleton import S
+from optlang.symbolics import Zero
 
 import cobra.util.solver as sutil
-from cobra.solvers import get_solver_name, solver_dict
 
 
 def add_moma(model, solution=None, linear=False):
@@ -68,7 +66,7 @@ def add_moma(model, solution=None, linear=False):
 
     # Fall back to default QP solver if current one has no QP capability
     if not linear:
-        model.solver = sutil.choose_solver(model, qp=True)[1]
+        model.solver = sutil.choose_solver(model, qp=True)
 
     if solution is None:
         solution = model.optimize()
@@ -77,7 +75,7 @@ def add_moma(model, solution=None, linear=False):
     c = prob.Constraint(model.solver.objective.expression - v,
                         lb=0.0, ub=0.0, name="moma_old_objective_constraint")
     to_add = [v, c]
-    new_obj = S.Zero
+    new_obj = Zero
     for r in model.reactions:
         flux = solution.fluxes[r.id]
         if linear:
@@ -94,123 +92,3 @@ def add_moma(model, solution=None, linear=False):
             new_obj += dist**2
     model.add_cons_vars(to_add)
     model.objective = prob.Objective(new_obj, direction='min')
-
-
-def create_euclidian_moma_model(cobra_model, wt_model=None, **solver_args):
-    """Create a new moma model (legacy function)."""
-    # make the wild type copy if none was supplied
-    if wt_model is None:
-        wt_model = cobra_model.copy()
-    else:
-        wt_model = wt_model.copy()
-        # ensure single objective
-        wt_obj = sutil.linear_reaction_coefficients(wt_model)
-        if len(wt_obj) != 1:
-            raise ValueError("wt_model must have exactly 1 objective, %d found"
-                             % len(wt_obj))
-
-    obj = sutil.linear_reaction_coefficients(wt_model)
-    if len(obj) == 1:
-        objective_id = list(obj)[0].id
-    else:
-        raise ValueError("model must have exactly 1 objective, %d found" %
-                         len(obj))
-
-    wt_model.optimize(**solver_args)
-    for reaction in wt_model.reactions:
-        # we don't want delete_model_gene to remove the wt reaction as well
-        reaction.gene_reaction_rule = ''
-        if reaction.objective_coefficient != 0:
-            reaction.objective_coefficient = 0
-            reaction.upper_bound = reaction.lower_bound = reaction.x
-        reaction.id = "MOMA_wt_" + reaction.id
-    for metabolite in wt_model.metabolites:
-        metabolite.id = "MOMA_wt_" + metabolite.id
-    wt_model.repair()
-
-    # make the moma model by combining both
-    moma_model = cobra_model.copy()
-    for reaction in moma_model.reactions:
-        reaction.objective_coefficient = 0
-    moma_model.add_reactions(wt_model.reactions)
-    return moma_model, objective_id
-
-
-def create_euclidian_distance_objective(n_moma_reactions):
-    """Return a matrix which will minimize the euclidian distance (legacy).
-
-    This matrix has the structure
-    [ I  -I]
-    [-I   I]
-    where I is the identity matrix the same size as the number of
-    reactions in the original model.
-
-    Parameters
-    ----------
-    n_moma_reactions: int
-        This is the number of reactions in the MOMA model, which should
-        be twice the number of reactions in the original model
-
-    Returns
-    -------
-    scipy.sparse.dok_matrix
-        A matrix describing the distance objective.
-    """
-    if n_moma_reactions % 2 != 0:
-        raise ValueError("must be even")
-    n_reactions = n_moma_reactions // 2
-    Q = dok_matrix((n_reactions * 2, n_reactions * 2))
-    for i in range(2 * n_reactions):
-        Q[i, i] = 1
-    for i in range(n_reactions):
-        Q[i, n_reactions + i] = -1
-        Q[n_reactions + i, i] = -1
-    return Q
-
-
-def create_euclidian_distance_lp(moma_model, solver):
-    """Create the distance linear program (legacy method)."""
-    Q = create_euclidian_distance_objective(len(moma_model.reactions))
-    lp = solver.create_problem(moma_model, objective_sense="minimize",
-                               quadratic_component=Q)
-    return lp
-
-
-def solve_moma_model(moma_model, objective_id, solver=None, **solver_args):
-    """Solve the MOMA LP (legacy method)."""
-    solver = solver_dict[solver if solver and isinstance(solver, str)
-                         else get_solver_name(qp=True)]
-    lp = create_euclidian_distance_lp(moma_model, solver=solver)
-    solver.solve_problem(lp, **solver_args)
-    solution = solver.format_solution(lp, moma_model)
-    solution.f = 0. if solution.x_dict is None \
-        else solution.x_dict[objective_id]
-    return solution
-
-
-def moma(wt_model, mutant_model, solver=None, **solver_args):
-    """Run MOMA on models (legacy method)."""
-    if "norm_type" in solver_args:
-        print("only euclidian norm type supported for moma")
-        solver_args.pop("norm_type")
-    moma_model, objective_id = create_euclidian_moma_model(mutant_model,
-                                                           wt_model)
-    return solve_moma_model(moma_model, objective_id,
-                            solver=solver, **solver_args)
-
-
-def moma_knockout(moma_model, moma_objective, reaction_indexes, **moma_args):
-    """Compute result of reaction_knockouts using moma."""
-    n = len(moma_model.reactions) // 2
-    # knock out the reaction
-    for i in reaction_indexes:
-        mutant_reaction = moma_model.reactions[i]
-        mutant_reaction.lower_bound, mutant_reaction.upper_bound = (0., 0.)
-    result = solve_moma_model(moma_model, moma_objective, **moma_args)
-    # reset the knockouts
-    for i in reaction_indexes:
-        mutant_reaction = moma_model.reactions[i]
-        wt_reaction = moma_model.reactions[n + i]
-        mutant_reaction.lower_bound = wt_reaction.lower_bound
-        mutant_reaction.upper_bound = wt_reaction.upper_bound
-    return result
diff --git a/cobra/flux_analysis/parsimonious.py b/cobra/flux_analysis/parsimonious.py
index 85ac3ee..6283622 100644
--- a/cobra/flux_analysis/parsimonious.py
+++ b/cobra/flux_analysis/parsimonious.py
@@ -6,16 +6,11 @@ import logging
 from warnings import warn
 from itertools import chain
 
-import sympy
+from optlang.symbolics import Zero
 
 from cobra.util import solver as sutil
-from cobra.manipulation.modify import (
-    convert_to_irreversible, revert_to_reversible)
-from cobra.util import linear_reaction_coefficients, set_objective
 from cobra.core.solution import get_solution
 
-add = sympy.Add._from_args
-mul = sympy.Mul._from_args
 LOGGER = logging.getLogger(__name__)
 
 
@@ -24,10 +19,7 @@ def optimize_minimal_flux(*args, **kwargs):
     return pfba(*args, **kwargs)
 
 
-def pfba(model, already_irreversible=False,
-         fraction_of_optimum=1.0, solver=None,
-         desired_objective_value=None, objective=None,
-         reactions=None, **optimize_kwargs):
+def pfba(model, fraction_of_optimum=1.0, objective=None, reactions=None):
     """Perform basic pFBA (parsimonious Enzyme Usage Flux Balance Analysis)
     to minimize total flux.
 
@@ -41,35 +33,18 @@ def pfba(model, already_irreversible=False,
     ----------
     model : cobra.Model
         The model
-    already_irreversible : bool, optional
-        By default, the model is converted to an irreversible one.
-        However, if the model is already irreversible, this step can be
-        skipped. Ignored for optlang solvers as not relevant.
     fraction_of_optimum : float, optional
         Fraction of optimum which must be maintained. The original objective
         reaction is constrained to be greater than maximal_value *
         fraction_of_optimum.
-    solver : str, optional
-        Name of the solver to be used. If None it will respect the solver set
-        in the model (model.solver).
-    desired_objective_value : float, optional
-        A desired objective value for the minimal solution that bypasses the
-        initial optimization result. Ignored for optlang solvers, instead,
-        define your objective separately and pass using the `objective`
-        argument.
     objective : dict or model.problem.Objective
         A desired objective to use during optimization in addition to the
         pFBA objective. Dictionaries (reaction as key, coefficient as value)
-        can be used for linear objectives. Not used for non-optlang solvers.
+        can be used for linear objectives.
     reactions : iterable
         List of reactions or reaction identifiers. Implies `return_frame` to
         be true. Only return fluxes for the given reactions. Faster than
-        fetching all fluxes if only a few are needed. Only supported for
-        optlang solvers.
-    **optimize_kwargs : additional arguments for legacy solver, optional
-        Additional arguments passed to the legacy solver. Ignored for
-        optlang solver (those can be configured using
-         model.solver.configuration).
+        fetching all fluxes if only a few are needed.
 
     Returns
     -------
@@ -85,18 +60,14 @@ def pfba(model, already_irreversible=False,
        390. doi:10.1038/msb.2010.47
 
     """
-    legacy, solver = sutil.choose_solver(model, solver)
-    if legacy:
-        return _pfba_legacy(
-            model, already_irreversible=already_irreversible,
-            fraction_of_optimum=fraction_of_optimum, solver=solver,
-            desired_objective_value=desired_objective_value,
-            **optimize_kwargs)
-    else:
-        model.solver = solver
-        return _pfba_optlang(
-            model, objective=objective,
-            fraction_of_optimum=fraction_of_optimum, reactions=reactions)
+    reactions = model.reactions if reactions is None \
+        else model.reactions.get_by_any(reactions)
+    with model as m:
+        add_pfba(m, objective=objective,
+                 fraction_of_optimum=fraction_of_optimum)
+        m.slim_optimize(error_value=None)
+        solution = get_solution(m, reactions=reactions)
+    return solution
 
 
 def add_pfba(model, objective=None, fraction_of_optimum=1.0):
@@ -128,121 +99,7 @@ def add_pfba(model, objective=None, fraction_of_optimum=1.0):
     reaction_variables = ((rxn.forward_variable, rxn.reverse_variable)
                           for rxn in model.reactions)
     variables = chain(*reaction_variables)
-    pfba_objective = model.problem.Objective(add(
-        [mul((sympy.singleton.S.One, variable))
-         for variable in variables]), direction='min', sloppy=True,
+    model.objective = model.problem.Objective(
+        Zero, direction='min', sloppy=True,
         name="_pfba_objective")
-    set_objective(model, pfba_objective)
-
-
-def _pfba_optlang(model, objective=None, reactions=None,
-                  fraction_of_optimum=1.0):
-    """Helper function to perform pFBA with the optlang interface
-
-    Not meant to be used directly.
-
-    Parameters
-    ----------
-    model : a cobra model
-        The model to perform pFBA on
-    objective :
-        An objective to use in addition to the pFBA constraints.
-    reactions : iterable
-        List of reactions or reaction identifiers.
-
-    Returns
-    -------
-    cobra.Solution
-        The solution to the pFBA optimization.
-
-    Updates everything in-place, returns model to original state at end.
-    """
-    reactions = model.reactions if reactions is None \
-        else model.reactions.get_by_any(reactions)
-    with model as m:
-        add_pfba(m, objective=objective,
-                 fraction_of_optimum=fraction_of_optimum)
-        m.slim_optimize(error_value=None)
-        solution = get_solution(m, reactions=reactions)
-    return solution
-
-
-def _pfba_legacy(model, solver, already_irreversible=False,
-                 fraction_of_optimum=1.0,
-                 desired_objective_value=None,
-                 **optimize_kwargs):
-    """Perform basic pFBA (parsimonious FBA) and minimize total flux.
-
-    The function attempts to act as a drop-in replacement for optimize. It
-    will make the reaction reversible and perform an optimization, then
-    force the objective value to remain the same and minimize the total
-    flux. Finally, it will convert the reaction back to the irreversible
-    form it was in before. See http://dx.doi.org/10.1038/msb.2010.47
-
-    Parameters
-    ----------
-    model : cobra.Model
-        The model
-    solver : solver
-        The solver object to use
-    already_irreversible : bool, optional
-        By default, the model is converted to an irreversible one.
-        However, if the model is already irreversible, this step can be
-        skipped
-    fraction_of_optimum : float, optional
-        Fraction of optimum which must be maintained. The original objective
-        reaction is constrained to be greater than maximal_value *
-        fraction_of_optimum. By default, this option is specified to be 1.0
-    desired_objective_value : float, optional
-        A desired objective value for the minimal solution that bypasses the
-        initial optimization result.
-
-    Updates everything in-place, returns model to original state at end.
-    """
-    objective_reactions = linear_reaction_coefficients(model)
-    if len(objective_reactions) > 1:
-        raise ValueError('pfba only supports models with'
-                         ' a single objective function')
-
-    if 'objective_sense' in optimize_kwargs:
-        if optimize_kwargs['objective_sense'] == 'minimize':
-            raise ValueError(
-                'Minimization not supported in pfba')
-        optimize_kwargs.pop('objective_sense', None)
-
-    if not already_irreversible:
-        convert_to_irreversible(model)
-
-    lp = solver.create_problem(model, **optimize_kwargs)
-    if not desired_objective_value:
-        solver.solve_problem(lp, objective_sense='maximize')
-        status = solver.get_status(lp)
-        if status != "optimal":
-            revert_to_reversible(model)
-            raise ValueError(
-                "pFBA requires optimal solution status, not {}".format(status))
-        desired_objective_value = solver.get_objective_value(lp)
-
-    for i, reaction in enumerate(model.reactions):
-
-        if reaction.objective_coefficient != 0:
-            # Enforce a certain fraction of the original objective
-            target = (desired_objective_value * fraction_of_optimum /
-                      reaction.objective_coefficient)
-            solver.change_variable_bounds(lp, i, target, reaction.upper_bound)
-
-        # Minimize all reaction fluxes (including objective?)
-        solver.change_variable_objective(lp, i, 1)
-
-    solver.solve_problem(lp, objective_sense='minimize', **optimize_kwargs)
-    solution = solver.format_solution(lp, model)
-
-    # Return the model to its original state
-    #    model.solution = solution
-    revert_to_reversible(model)
-
-    #    if solution.status == "optimal":
-    #        model.solution.f = sum([coeff * reaction.x for reaction, coeff in
-    #                                iteritems(objective_reactions)])
-
-    return solution
+    model.objective.set_linear_coefficients(dict.fromkeys(variables, 1.0))
diff --git a/cobra/flux_analysis/phenotype_phase_plane.py b/cobra/flux_analysis/phenotype_phase_plane.py
index 5eacbd2..1c00520 100644
--- a/cobra/flux_analysis/phenotype_phase_plane.py
+++ b/cobra/flux_analysis/phenotype_phase_plane.py
@@ -2,311 +2,21 @@
 
 from __future__ import absolute_import, division
 
-from warnings import warn
 from six import iteritems
 from itertools import product
-from multiprocessing import Pool
 
 import pandas as pd
 from optlang.interface import OPTIMAL
 from numpy import (
-    nan, abs, arange, dtype, empty, int32, linspace, meshgrid, unravel_index,
-    zeros, full)
+    nan, abs, linspace, full)
 
-from cobra.solvers import get_solver_name, solver_dict
 import cobra.util.solver as sutil
 from cobra.flux_analysis import flux_variability_analysis as fva
 from cobra.exceptions import OptimizationError
 
-# attempt to import plotting libraries
-try:
-    from matplotlib import pyplot
-    from mpl_toolkits.mplot3d import axes3d
-except (ImportError, RuntimeError):
-    pyplot = None
-    axes3d = None
-mlab = None  # mayavi may crash python
-try:  # for prettier colors
-    from palettable.colorbrewer import get_map
-except (ImportError, RuntimeError):
-    try:
-        from brewer2mpl import get_map
-    except ImportError:
-        get_map = None
-
-
-class phenotypePhasePlaneData:
-    """class to hold results of a phenotype phase plane analysis"""
-
-    def __init__(self,
-                 reaction1_name, reaction2_name,
-                 reaction1_range_max, reaction2_range_max,
-                 reaction1_npoints, reaction2_npoints):
-        self.reaction1_name = reaction1_name
-        self.reaction2_name = reaction2_name
-        self.reaction1_range_max = reaction1_range_max
-        self.reaction2_range_max = reaction2_range_max
-        self.reaction1_npoints = reaction1_npoints
-        self.reaction2_npoints = reaction2_npoints
-        self.reaction1_fluxes = linspace(0, reaction1_range_max,
-                                         reaction1_npoints)
-        self.reaction2_fluxes = linspace(0, reaction2_range_max,
-                                         reaction2_npoints)
-        self.growth_rates = zeros((reaction1_npoints, reaction2_npoints))
-        self.shadow_prices1 = zeros((reaction1_npoints, reaction2_npoints))
-        self.shadow_prices2 = zeros((reaction1_npoints, reaction2_npoints))
-        self.segments = zeros(self.growth_rates.shape, dtype=int32)
-        self.phases = []
-
-    def plot(self):
-        """plot the phenotype phase plane in 3D using any available backend"""
-        if pyplot is not None:
-            self.plot_matplotlib()
-        elif mlab is not None:
-            self.plot_mayavi()
-        else:
-            raise ImportError("No suitable 3D plotting package found")
-
-    def plot_matplotlib(self, theme="Paired", scale_grid=False):
-        """Use matplotlib to plot a phenotype phase plane in 3D.
-
-        theme: color theme to use (requires palettable)
-
-        returns: maptlotlib 3d subplot object"""
-        if pyplot is None:
-            raise ImportError("Error importing matplotlib 3D plotting")
-        colors = empty(self.growth_rates.shape, dtype=dtype((str, 7)))
-        n_segments = self.segments.max()
-        # pick colors
-        color_list = ['#A6CEE3', '#1F78B4', '#B2DF8A', '#33A02C',
-                      '#FB9A99', '#E31A1C', '#FDBF6F', '#FF7F00',
-                      '#CAB2D6', '#6A3D9A', '#FFFF99', '#B15928']
-        if get_map is not None:
-            try:
-                color_list = get_map(theme, 'Qualitative',
-                                     n_segments).hex_colors
-            except ValueError:
-                from warnings import warn
-                warn('palettable could not be used for this number of phases')
-        if n_segments > len(color_list):
-            from warnings import warn
-            warn("not enough colors to color all detected phases")
-        if n_segments > 0 and n_segments <= len(color_list):
-            for i in range(n_segments):
-                colors[self.segments == (i + 1)] = color_list[i]
-        else:
-            colors[:, :] = 'b'
-        if scale_grid:
-            # grid wires should not have more than ~20 points
-            xgrid_scale = int(self.reaction1_npoints / 20)
-            ygrid_scale = int(self.reaction2_npoints / 20)
-        else:
-            xgrid_scale, ygrid_scale = (1, 1)
-        figure = pyplot.figure()
-        xgrid, ygrid = meshgrid(self.reaction1_fluxes, self.reaction2_fluxes)
-        axes = figure.add_subplot(111, projection="3d")
-        xgrid = xgrid.transpose()
-        ygrid = ygrid.transpose()
-        axes.plot_surface(xgrid, ygrid, self.growth_rates, rstride=1,
-                          cstride=1, facecolors=colors, linewidth=0,
-                          antialiased=False)
-        axes.plot_wireframe(xgrid, ygrid, self.growth_rates, color="black",
-                            rstride=xgrid_scale, cstride=ygrid_scale)
-        axes.set_xlabel(self.reaction1_name, size="x-large")
-        axes.set_ylabel(self.reaction2_name, size="x-large")
-        axes.set_zlabel("Growth rate", size="x-large")
-        axes.view_init(elev=30, azim=-135)
-        figure.set_tight_layout(True)
-        return axes
-
-    def plot_mayavi(self):
-        """Use mayavi to plot a phenotype phase plane in 3D.
-        The resulting figure will be quick to interact with in real time,
-        but might be difficult to save as a vector figure.
-        returns: mlab figure object"""
-        from mayavi import mlab
-        figure = mlab.figure(bgcolor=(1, 1, 1), fgcolor=(0, 0, 0))
-        figure.name = "Phenotype Phase Plane"
-        max = 10.0
-        xmax = self.reaction1_fluxes.max()
-        ymax = self.reaction2_fluxes.max()
-        zmax = self.growth_rates.max()
-        xgrid, ygrid = meshgrid(self.reaction1_fluxes, self.reaction2_fluxes)
-        xgrid = xgrid.transpose()
-        ygrid = ygrid.transpose()
-        xscale = max / xmax
-        yscale = max / ymax
-        zscale = max / zmax
-        mlab.surf(xgrid * xscale, ygrid * yscale, self.growth_rates * zscale,
-                  representation="wireframe", color=(0, 0, 0), figure=figure)
-        mlab.mesh(xgrid * xscale, ygrid * yscale, self.growth_rates * zscale,
-                  scalars=self.shadow_prices1 + self.shadow_prices2,
-                  resolution=1, representation="surface", opacity=0.75,
-                  figure=figure)
-        # draw axes
-        mlab.outline(extent=(0, max, 0, max, 0, max))
-        mlab.axes(opacity=0, ranges=[0, xmax, 0, ymax, 0, zmax])
-        mlab.xlabel(self.reaction1_name)
-        mlab.ylabel(self.reaction2_name)
-        mlab.zlabel("Growth rates")
-        return figure
-
-    def segment(self, threshold=0.01):
-        """attempt to segment the data and identify the various phases"""
-        self.segments *= 0
-        # each entry in phases will consist of the following tuple
-        # ((x, y), shadow_price1, shadow_price2)
-        self.phases = []
-        # initialize the area to be all False
-        covered_area = (self.growth_rates * 0 == 1)
-        # as long as part of the area has not been covered
-        segment_id = 0
-        while self.segments.min() == 0:
-            segment_id += 1
-            # i and j are indices for a current point which has not been
-            # assigned a segment yet
-
-            i, j = unravel_index(self.segments.argmin(), self.segments.shape)
-            # update the segment id for any point with a similar shadow price
-            # to the current point
-            d1 = abs(self.shadow_prices1 - self.shadow_prices1[i, j])
-            d2 = abs(self.shadow_prices2 - self.shadow_prices2[i, j])
-            self.segments[(d1 < threshold) * (d2 < threshold)] += segment_id
-            # add the current point as one of the phases
-            self.phases.append((
-                (self.reaction1_fluxes[i], self.reaction2_fluxes[j]),
-                self.shadow_prices1[i, j], self.shadow_prices2[i, j]))
-
-
-def _calculate_subset(arguments):
-    """Calculate a subset of the phenotype phase plane data.
-    Store each result tuple as:
-    (i, j, growth_rate, shadow_price1, shadow_price2)"""
-
-    model = arguments["model"]
-    reaction1_fluxes = arguments["reaction1_fluxes"]
-    reaction2_fluxes = arguments["reaction2_fluxes"]
-    metabolite1_name = arguments["metabolite1_name"]
-    metabolite2_name = arguments["metabolite2_name"]
-    index1 = arguments["index1"]
-    index2 = arguments["index2"]
-    i_list = arguments["i_list"]
-    j_list = arguments["j_list"]
-    tolerance = arguments["tolerance"]
-    solver = solver_dict[arguments["solver"]]
-
-    results = []
-    reaction1 = model.reactions[index1]
-    reaction2 = model.reactions[index2]
-    problem = solver.create_problem(model)
-    solver.solve_problem(problem)
-    for a, flux1 in enumerate(reaction1_fluxes):
-        i = i_list[a]
-        # flux is actually negative for uptake. Also some solvers require
-        # float instead of numpy.float64
-        flux1 = float(-1 * flux1)
-        # change bounds on reaction 1
-        solver.change_variable_bounds(problem, index1, flux1 - tolerance,
-                                      flux1 + tolerance)
-        for b, flux2 in enumerate(reaction2_fluxes):
-            j = j_list[b]
-            flux2 = float(-1 * flux2)  # same story as flux1
-            # change bounds on reaction 2
-            solver.change_variable_bounds(problem, index2, flux2 - tolerance,
-                                          flux2 + tolerance)
-            # solve the problem and save results
-            solver.solve_problem(problem)
-            solution = solver.format_solution(problem, model)
-            if solution is not None and solution.status == "optimal":
-                results.append((i, j, solution.f,
-                                solution.y_dict[metabolite1_name],
-                                solution.y_dict[metabolite2_name]))
-            else:
-                results.append((i, j, 0, 0, 0))
-            # reset reaction 2 bounds
-            solver.change_variable_bounds(problem, index2,
-                                          float(reaction2.lower_bound),
-                                          float(reaction2.upper_bound))
-        # reset reaction 1 bounds
-        solver.change_variable_bounds(problem, index1,
-                                      float(reaction1.lower_bound),
-                                      float(reaction1.upper_bound))
-    return results
-
-
-def calculate_phenotype_phase_plane(
-        model, reaction1_name, reaction2_name,
-        reaction1_range_max=20, reaction2_range_max=20,
-        reaction1_npoints=50, reaction2_npoints=50,
-        solver=None, n_processes=1, tolerance=1e-6):
-    """calculates the growth rates while varying the uptake rates for two
-    reactions.
-
-    :returns: a `phenotypePhasePlaneData` object containing the growth rates
-    for the uptake rates. To plot the
-    result, call the plot function of the returned object.
-
-    :Example:
-    >>> import cobra.test
-    >>> model = cobra.test.create_test_model("textbook")
-    >>> ppp = calculate_phenotype_phase_plane(model, "EX_glc__D_e", "EX_o2_e")
-    >>> ppp.plot()
-    """
-    warn('calculate_phenotype_phase_plane is deprecated, consider using '
-         'production_envelope instead', DeprecationWarning)
-    if solver is None:
-        solver = get_solver_name()
-    data = phenotypePhasePlaneData(
-        str(reaction1_name), str(reaction2_name),
-        reaction1_range_max, reaction2_range_max,
-        reaction1_npoints, reaction2_npoints)
-    # find the objects for the reactions and metabolites
-    index1 = model.reactions.index(data.reaction1_name)
-    index2 = model.reactions.index(data.reaction2_name)
-    metabolite1_name = list(model.reactions[index1]._metabolites)[0].id
-    metabolite2_name = list(model.reactions[index2]._metabolites)[0].id
-    if n_processes > reaction1_npoints:  # limit the number of processes
-        n_processes = reaction1_npoints
-    range_add = reaction1_npoints // n_processes
-    # prepare the list of arguments for each _calculate_subset call
-    arguments_list = []
-    i = arange(reaction1_npoints)
-    j = arange(reaction2_npoints)
-    for n in range(n_processes):
-        start = n * range_add
-        if n != n_processes - 1:
-            r1_range = data.reaction1_fluxes[start:start + range_add]
-            i_list = i[start:start + range_add]
-        else:
-            r1_range = data.reaction1_fluxes[start:]
-            i_list = i[start:]
-        arguments_list.append({
-            "model": model,
-            "index1": index1, "index2": index2,
-            "metabolite1_name": metabolite1_name,
-            "metabolite2_name": metabolite2_name,
-            "reaction1_fluxes": r1_range,
-            "reaction2_fluxes": data.reaction2_fluxes.copy(),
-            "i_list": i_list, "j_list": j.copy(),
-            "tolerance": tolerance, "solver": solver})
-    if n_processes > 1:
-        p = Pool(n_processes)
-        results = list(p.map(_calculate_subset, arguments_list))
-    else:
-        results = [_calculate_subset(arguments_list[0])]
-    for result_list in results:
-        for result in result_list:
-            i = result[0]
-            j = result[1]
-            data.growth_rates[i, j] = result[2]
-            data.shadow_prices1[i, j] = result[3]
-            data.shadow_prices2[i, j] = result[4]
-    data.segment()
-    return data
-
 
 def production_envelope(model, reactions, objective=None, carbon_sources=None,
-                        points=20, threshold=1e-7, solver=None):
+                        points=20, threshold=1e-7):
     """Calculate the objective value conditioned on all combinations of
     fluxes for a set of chosen reactions
 
@@ -338,11 +48,6 @@ def production_envelope(model, reactions, objective=None, carbon_sources=None,
        The number of points to calculate production for.
     threshold : float, optional
         A cut-off under which flux values will be considered to be zero.
-    solver : string, optional
-       The solver to use - only here for consistency with older
-       implementations (this argument will be removed in the future). The
-       solver should be set using ``model.solver`` directly. Only optlang
-       based solvers are supported.
 
     Returns
     -------
@@ -368,10 +73,6 @@ def production_envelope(model, reactions, objective=None, carbon_sources=None,
     >>> model = cobra.test.create_test_model("textbook")
     >>> production_envelope(model, ["EX_glc__D_e", "EX_o2_e"])
     """
-    legacy, solver = sutil.choose_solver(model, solver)
-    if legacy:
-        raise ValueError('production_envelope is only implemented for optlang '
-                         'based solver interfaces.')
     reactions = model.reactions.get_by_any(reactions)
     objective = model.solver.objective if objective is None else objective
     data = dict()
diff --git a/cobra/flux_analysis/reaction.py b/cobra/flux_analysis/reaction.py
index 659aba9..d98f37f 100644
--- a/cobra/flux_analysis/reaction.py
+++ b/cobra/flux_analysis/reaction.py
@@ -8,8 +8,6 @@ from warnings import warn
 from operator import attrgetter
 
 from cobra.core import Reaction
-from cobra.util import assert_optimal, choose_solver
-from cobra.exceptions import OptimizationError
 
 
 def assess(model, reaction, flux_coefficient_cutoff=0.001, solver=None):
@@ -137,11 +135,7 @@ def assess_component(model, reaction, side, flux_coefficient_cutoff=0.001,
 
 
 def _optimize_or_value(model, value=0., solver=None):
-    legacy, _ = choose_solver(model, solver=solver)
-    if legacy:
-        return model.optimize(solver=solver).f
-    else:
-        return model.slim_optimize(error_value=value)
+    return model.slim_optimize(error_value=value)
 
 
 def assess_precursors(model, reaction, flux_coefficient_cutoff=0.001,
diff --git a/cobra/flux_analysis/sampling.py b/cobra/flux_analysis/sampling.py
index a5fa2a2..ef9da5b 100644
--- a/cobra/flux_analysis/sampling.py
+++ b/cobra/flux_analysis/sampling.py
@@ -17,7 +17,7 @@ from time import time
 import numpy as np
 import pandas
 from optlang.interface import OPTIMAL
-from sympy.core.singleton import S
+from optlang.symbolics import Zero
 from cobra.util import (create_stoichiometric_matrix, constraint_matrices,
                         nullspace)
 
@@ -254,7 +254,7 @@ class HRSampler(object):
         self.n_warmup = 0
         idx = np.hstack([self.fwd_idx, self.rev_idx])
         self.warmup = np.zeros((len(idx), len(self.model.variables)))
-        self.model.objective = S.Zero
+        self.model.objective = Zero
         self.model.objective.direction = "max"
         variables = self.model.variables
         for i in idx:
@@ -676,7 +676,7 @@ class OptGPSampler(HRSampler):
         """Initialize a new OptGPSampler."""
         super(OptGPSampler, self).__init__(model, thinning, seed=seed)
         self.generate_fva_warmup()
-        self.np = processes
+        self.processes = processes
 
         # This maps our saved center into shared memory,
         # meaning they are synchronized across processes
@@ -715,15 +715,16 @@ class OptGPSampler(HRSampler):
         we recommend to calculate large numbers of samples at once
         (`n` > 1000).
         """
-        if self.np > 1:
-            n_process = np.ceil(n / self.np).astype(int)
-            n = n_process * self.np
+        if self.processes > 1:
+            n_process = np.ceil(n / self.processes).astype(int)
+            n = n_process * self.processes
             # The cast to list is weird but not doing it gives recursion
             # limit errors, something weird going on with multiprocessing
-            args = list(zip([n_process] * self.np, range(self.np)))
+            args = list(zip(
+                [n_process] * self.processes, range(self.processes)))
             # No with statement or starmap here since Python 2.x
             # does not support it :(
-            mp = Pool(self.np, initializer=mp_init, initargs=(self,))
+            mp = Pool(self.processes, initializer=mp_init, initargs=(self,))
             chains = mp.map(_sample_chain, args, chunksize=1)
             mp.close()
             mp.join()
diff --git a/cobra/flux_analysis/single_deletion.py b/cobra/flux_analysis/single_deletion.py
deleted file mode 100644
index b4b06f4..0000000
--- a/cobra/flux_analysis/single_deletion.py
+++ /dev/null
@@ -1,383 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""Bundles functions for successively deleting a set of genes or reactions."""
-
-from __future__ import absolute_import
-
-import pandas
-from six import iteritems, string_types
-from optlang.interface import OPTIMAL
-
-import cobra.solvers as legacy_solvers
-import cobra.util.solver as solvers
-from cobra.manipulation import delete_model_genes, undelete_model_genes
-from cobra.manipulation.delete import find_gene_knockout_reactions
-
-# this can be removed after deprecation of the old solver interface
-# since the optlang vrsion requires neither numpy nor scipy
-try:
-    import scipy  # noqa
-except ImportError:
-    moma = None
-else:
-    from cobra.flux_analysis import moma
-
-
-def single_reaction_deletion(cobra_model, reaction_list=None, solver=None,
-                             method="fba", **solver_args):
-    """Sequentially knocks out each reaction from a given reaction list.
-
-    Parameters
-    ----------
-    cobra_model : cobra.Model
-        The model from which to delete the reactions. The model will not be
-        modified.
-    reaction_list : iterable
-        List of reaction IDs or cobra.Reaction. If None (default) will use all
-        reactions in the model.
-    method : str, optional
-        The method used to obtain fluxes. Must be one of "fba", "moma" or
-        "linear moma".
-    solver : str, optional
-        Name of the solver to be used.
-    solver_args : optional
-        Additional arguments for the solver. Ignored for optlang solver, please
-        use `model.solver.configuration` instead.
-
-    Returns
-    -------
-    pandas.DataFrame
-        Data frame with two column and reaction id as index:
-        - flux: the value of the objective after the knockout
-        - status: the solution's status, (for instance "optimal" for each
-          knockout)
-    """
-    if reaction_list is None:
-        reaction_list = cobra_model.reactions
-    else:
-        reaction_list = [cobra_model.reactions.get_by_id(i)
-                         if isinstance(i, string_types) else i
-                         for i in reaction_list]
-    if method == "fba":
-        result = single_reaction_deletion_fba(cobra_model, reaction_list,
-                                              solver=solver, **solver_args)
-    elif method == "moma":
-        result = single_reaction_deletion_moma(cobra_model, reaction_list,
-                                               solver=solver, **solver_args)
-    elif method == "linear moma":
-        result = single_reaction_deletion_moma(cobra_model, reaction_list,
-                                               linear=True, solver=solver,
-                                               **solver_args)
-    else:
-        raise ValueError("Unknown deletion method '%s'" % method)
-    return pandas.DataFrame({'flux': result[0], 'status': result[1]})
-
-
-def single_reaction_deletion_fba(cobra_model, reaction_list, solver=None,
-                                 **solver_args):
-    """Sequentially knocks out each reaction in a model using FBA.
-
-    Not supposed to be called directly use
-    `single_reactions_deletion(..., method="fba")` instead.
-
-    Parameters
-    ----------
-    cobra_model : cobra.Model
-        The model from which to delete the reactions. The model will not be
-        modified.
-    reaction_list : iterable
-        List of reaction Ids or cobra.Reaction.
-    solver: str, optional
-        The name of the solver to be used.
-
-    Returns
-    -------
-    tuple of dicts
-        A tuple ({reaction_id: growth_rate}, {reaction_id: status})
-    """
-    legacy = False
-    if solver is None:
-        solver = cobra_model.solver
-    elif "optlang-" in solver:
-        solver = solvers.interface_to_str(solver)
-        solver = solvers.solvers[solver]
-    else:
-        legacy = True
-        solver = legacy_solvers.solver_dict[solver]
-        lp = solver.create_problem(cobra_model)
-
-    growth_rate_dict = {}
-    status_dict = {}
-
-    if not legacy:
-        with cobra_model as m:
-            m.solver = solver
-            for reaction in reaction_list:
-                with m:
-                    reaction.knock_out()
-                    obj_value = m.slim_optimize()
-                    status_dict[reaction.id] = m.solver.status
-                    growth_rate_dict[reaction.id] = obj_value
-    else:
-        # This entire block can be removed once the legacy solvers are
-        # deprecated
-        for reaction in reaction_list:
-            old_bounds = (reaction.lower_bound, reaction.upper_bound)
-            index = cobra_model.reactions.index(reaction)
-            solver.change_variable_bounds(lp, index, 0., 0.)
-            solver.solve_problem(lp, **solver_args)
-            # get the status and growth rate
-            status = solver.get_status(lp)
-            status_dict[reaction.id] = status
-            growth_rate_dict[reaction.id] = solver.get_objective_value(lp) \
-                if status == "optimal" else 0.
-            # reset the problem
-            solver.change_variable_bounds(lp, index, old_bounds[0],
-                                          old_bounds[1])
-    return growth_rate_dict, status_dict
-
-
-def single_reaction_deletion_moma(cobra_model, reaction_list, linear=False,
-                                  solver=None, **solver_args):
-    """Sequentially knocks out each reaction in a model using MOMA.
-
-    Not supposed to be called directly use
-    `single_reactions_deletion(..., method="moma")` instead.
-
-    Parameters
-    ----------
-    cobra_model : cobra.Model
-        The model from which to delete the reactions. The model will not be
-        modified.
-    reaction_list : iterable
-        List of reaction IDs or cobra.Reaction.
-    linear : bool
-        Whether to use linear MOMA.
-    solver: str, optional
-        The name of the solver to be used.
-
-    Returns
-    -------
-    tuple of dicts
-        A tuple ({reaction_id: growth_rate}, {reaction_id: status})
-    """
-    # The same function can not be used because MOMA can not re-use the
-    # same LP object. Problem re-use leads to incorrect solutions.
-    # This is *not* true for optlang solvers!
-    if moma is None:
-        raise RuntimeError("scipy required for moma")
-
-    legacy = False
-    if solver is None:
-        solver = cobra_model.solver
-    elif "optlang-" in solver:
-        solver = solvers.interface_to_str(solver)
-        solver = solvers.solvers[solver]
-    else:
-        legacy = True
-        solver = legacy_solvers.solver_dict[solver]
-        moma_model, moma_objective = moma.create_euclidian_moma_model(
-            cobra_model)
-
-    growth_rate_dict = {}
-    status_dict = {}
-
-    if not legacy:
-        solution = cobra_model.optimize()
-        with cobra_model as m:
-            m.solver = solver
-            moma.add_moma(m, solution=solution, linear=linear)
-            for reaction in reaction_list:
-                with m:
-                    reaction.knock_out()
-                    status = m.solver.optimize()
-                    status_dict[reaction.id] = status
-                    if status == OPTIMAL:
-                        growth = m.variables.moma_old_objective.primal
-                    else:
-                        growth = float("nan")
-                    growth_rate_dict[reaction.id] = growth
-    else:
-        for reaction in reaction_list:
-            index = cobra_model.reactions.index(reaction)
-            solution = moma.moma_knockout(moma_model, moma_objective, (index,),
-                                          solver=solver, **solver_args)
-            status_dict[reaction.id] = solution.status
-            growth_rate_dict[reaction.id] = solution.f
-    return growth_rate_dict, status_dict
-
-
-def single_gene_deletion(cobra_model, gene_list=None, solver=None,
-                         method="fba", **solver_args):
-    """Sequentially knocks out each gene from a given gene list.
-
-    Parameters
-    ----------
-    cobra_model : a cobra model
-        The model from which to delete the genes. The model will not be
-        modified.
-    gene_list : iterable
-        List of gene IDs or cobra.Gene. If None (default) will use all genes in
-        the model.
-    method : str, optional
-        The method used to obtain fluxes. Must be one of "fba", "moma" or
-        "linear moma".
-    solver : str, optional
-        Name of the solver to be used.
-    solver_args : optional
-        Additional arguments for the solver. Ignored for optlang solver, please
-        use `model.solver.configuration` instead.
-
-    Returns
-    -------
-    pandas.DataFrame
-        Data frame with two column and reaction id as index:
-        - flux: the value of the objective after the knockout
-        - status: the solution's status, (for instance "optimal" for each
-          knockout)
-    """
-    if gene_list is None:
-        gene_list = cobra_model.genes
-    else:
-        gene_list = [cobra_model.genes.get_by_id(i)
-                     if isinstance(i, string_types) else i for i in gene_list]
-
-    if method == "fba":
-        result = single_gene_deletion_fba(cobra_model, gene_list,
-                                          solver=solver, **solver_args)
-    elif method == "moma":
-        result = single_gene_deletion_moma(cobra_model, gene_list,
-                                           solver=solver, **solver_args)
-    elif method == "linear moma":
-        result = single_gene_deletion_moma(cobra_model, gene_list, linear=True,
-                                           solver=solver, **solver_args)
-    else:
-        raise ValueError("Unknown deletion method '%s'" % method)
-    return pandas.DataFrame({'flux': result[0], 'status': result[1]})
-
-
-def single_gene_deletion_fba(cobra_model, gene_list, solver=None,
-                             **solver_args):
-    """Sequentially knocks out each gene in a model using FBA.
-
-    Not supposed to be called directly use
-    `single_reactions_deletion(..., method="fba")` instead.
-
-    Parameters
-    ----------
-    gene_list : iterable
-        List of gene IDs or cobra.Reaction.
-    solver: str, optional
-        The name of the solver to be used.
-
-    Returns
-    -------
-    tuple of dicts
-        A tuple ({reaction_id: growth_rate}, {reaction_id: status})
-    """
-    legacy = False
-    if solver is None:
-        solver = cobra_model.solver
-    elif "optlang-" in solver:
-        solver = solvers.interface_to_str(solver)
-        solver = solvers.solvers[solver]
-    else:
-        legacy = True
-        solver = legacy_solvers.solver_dict[solver]
-        lp = solver.create_problem(cobra_model)
-
-    growth_rate_dict = {}
-    status_dict = {}
-
-    if not legacy:
-        with cobra_model as m:
-            m.solver = solver
-            for gene in gene_list:
-                with m:
-                    gene.knock_out()
-                    obj_value = m.slim_optimize()
-                    status_dict[gene.id] = m.solver.status
-                    growth_rate_dict[gene.id] = obj_value
-    else:
-        for gene in gene_list:
-            old_bounds = {}
-            for reaction in find_gene_knockout_reactions(cobra_model, [gene]):
-                index = cobra_model.reactions.index(reaction)
-                old_bounds[index] = reaction.bounds
-                solver.change_variable_bounds(lp, index, 0., 0.)
-            solver.solve_problem(lp, **solver_args)
-            # get the status and growth rate
-            status = solver.get_status(lp)
-            status_dict[gene.id] = status
-            growth_rate = solver.get_objective_value(lp) \
-                if status == "optimal" else 0.
-            growth_rate_dict[gene.id] = growth_rate
-            # reset the problem
-            for index, bounds in iteritems(old_bounds):
-                solver.change_variable_bounds(lp, index, bounds[0], bounds[1])
-    return growth_rate_dict, status_dict
-
-
-def single_gene_deletion_moma(cobra_model, gene_list, linear=False,
-                              solver=None, **solver_args):
-    """Sequentially knocks out each gene in a model using MOMA.
-
-    Not supposed to be called directly use
-    `single_reactions_deletion(..., method="moma")` instead.
-
-    Parameters
-    ----------
-    gene_list : iterable
-        List of gene IDs or cobra.Reaction.
-    linear : bool
-        Whether to use linear MOMA.
-    solver : str, optional
-        The name of the solver to be used.
-
-    Returns
-    -------
-    tuple of dicts
-        A tuple ({reaction_id: growth_rate}, {reaction_id: status})
-    """
-    if moma is None:
-        raise RuntimeError("scipy required for moma")
-
-    legacy = False
-    if solver is None:
-        solver = cobra_model.solver
-    elif "optlang-" in solver:
-        solver = solvers.interface_to_str(solver)
-        solver = solvers.solvers[solver]
-    else:
-        legacy = True
-        solver = legacy_solvers.solver_dict[solver]
-        moma_model, moma_objective = moma.create_euclidian_moma_model(
-            cobra_model)
-
-    growth_rate_dict = {}
-    status_dict = {}
-
-    if not legacy:
-        solution = cobra_model.optimize()
-        with cobra_model as m:
-            m.solver = solver
-            moma.add_moma(m, solution=solution, linear=linear)
-            for gene in gene_list:
-                with m:
-                    gene.knock_out()
-                    status = m.solver.optimize()
-                    status_dict[gene.id] = status
-                    if status == OPTIMAL:
-                        growth = m.variables.moma_old_objective.primal
-                    else:
-                        growth = float("nan")
-                    growth_rate_dict[gene.id] = growth
-    else:
-        for gene in gene_list:
-            delete_model_genes(moma_model, [gene.id])
-            solution = moma.solve_moma_model(moma_model, moma_objective,
-                                             solver=solver, **solver_args)
-            status_dict[gene.id] = solution.status
-            growth_rate_dict[gene.id] = solution.f
-            undelete_model_genes(moma_model)
-    return growth_rate_dict, status_dict
diff --git a/cobra/flux_analysis/variability.py b/cobra/flux_analysis/variability.py
index f3e21b2..1b91771 100644
--- a/cobra/flux_analysis/variability.py
+++ b/cobra/flux_analysis/variability.py
@@ -2,22 +2,24 @@
 
 from __future__ import absolute_import
 
-import pandas
-from sympy.core.singleton import S
+import math
 from warnings import warn
 from itertools import chain
 
+import pandas
+from optlang.symbolics import Zero
+from six import iteritems
+
 from cobra.flux_analysis.loopless import loopless_fva_iter
 from cobra.flux_analysis.parsimonious import add_pfba
-from cobra.flux_analysis.single_deletion import (single_gene_deletion,
-                                                 single_reaction_deletion)
+from cobra.flux_analysis.deletion import (
+    single_gene_deletion, single_reaction_deletion)
 from cobra.core import get_solution
 from cobra.util import solver as sutil
 
 
 def flux_variability_analysis(model, reaction_list=None, loopless=False,
-                              fraction_of_optimum=1.0, pfba_factor=None,
-                              solver=None, **solver_args):
+                              fraction_of_optimum=1.0, pfba_factor=None):
     """Runs flux variability analysis to find the min/max flux values for each
     each reaction in `reaction_list`.
 
@@ -44,13 +46,6 @@ def flux_variability_analysis(model, reaction_list=None, loopless=False,
         one that optimally minimizes the total flux sum, the pfba_factor
         should, if set, be larger than one. Setting this value may lead to
         more realistic predictions of the effective flux bounds.
-    solver : str, optional
-        Name of the solver to be used. If None it will respect the solver set
-        in the model (model.solver).
-    **solver_args : additional arguments for legacy solver, optional
-        Additional arguments passed to the legacy solver. Ignored for
-        optlang solver (those can be configured using
-        model.solver.configuration).
 
     Returns
     -------
@@ -88,75 +83,14 @@ def flux_variability_analysis(model, reaction_list=None, loopless=False,
        Bioinformatics. 2015 Jul 1;31(13):2159-65.
        doi: 10.1093/bioinformatics/btv096.
     """
-    legacy, solver = sutil.choose_solver(model, solver)
-
     if reaction_list is None:
         reaction_list = model.reactions
 
-    if not legacy:
-        fva_result = _fva_optlang(model, reaction_list, fraction_of_optimum,
-                                  loopless, pfba_factor)
-    else:
-        if pfba_factor is not None:
-            ValueError('pfba_factor only supported for optlang interfaces')
-        fva_result = _fva_legacy(model, reaction_list, fraction_of_optimum,
-                                 "maximize", solver, **solver_args)
+    fva_result = _fva_optlang(model, reaction_list, fraction_of_optimum,
+                              loopless, pfba_factor)
     return pandas.DataFrame(fva_result).T
 
 
-def _fva_legacy(cobra_model, reaction_list, fraction_of_optimum,
-                objective_sense, solver, **solver_args):
-    """Runs flux variability analysis to find max/min flux values
-
-    cobra_model : :class:`~cobra.core.Model`:
-
-    reaction_list : list of :class:`~cobra.core.Reaction`: or their id's
-        The id's for which FVA should be run. If this is None, the bounds
-        will be computed for all reactions in the model.
-
-    fraction_of_optimum : fraction of optimum which must be maintained.
-        The original objective reaction is constrained to be greater than
-        maximal_value * fraction_of_optimum
-
-    solver : string of solver name
-        If None is given, the default solver will be used.
-
-    """
-    lp = solver.create_problem(cobra_model)
-    solver.solve_problem(lp, objective_sense=objective_sense)
-    solution = solver.format_solution(lp, cobra_model)
-    if solution.status != "optimal":
-        raise ValueError("FVA requires the solution status to be optimal, "
-                         "not " + solution.status)
-    # set all objective coefficients to 0
-    for i, r in enumerate(cobra_model.reactions):
-        if r.objective_coefficient != 0:
-            f = solution.x_dict[r.id]
-            new_bounds = (f * fraction_of_optimum, f)
-            solver.change_variable_bounds(lp, i,
-                                          min(new_bounds), max(new_bounds))
-            solver.change_variable_objective(lp, i, 0.)
-    return calculate_lp_variability(lp, solver, cobra_model, reaction_list,
-                                    **solver_args)
-
-
-def calculate_lp_variability(lp, solver, cobra_model, reaction_list,
-                             **solver_args):
-    """calculate max and min of selected variables in an LP"""
-    fva_results = {r.id: {} for r in reaction_list}
-    for what in ("minimum", "maximum"):
-        sense = "minimize" if what == "minimum" else "maximize"
-        for r in reaction_list:
-            r_id = r.id
-            i = cobra_model.reactions.index(r_id)
-            solver.change_variable_objective(lp, i, 1.)
-            solver.solve_problem(lp, objective_sense=sense, **solver_args)
-            fva_results[r_id][what] = solver.get_objective_value(lp)
-            # revert the problem to how it was before
-            solver.change_variable_objective(lp, i, 0.)
-    return fva_results
-
-
 def _fva_optlang(model, reaction_list, fraction, loopless, pfba_factor):
     """Helper function to perform FVA with the optlang interface.
 
@@ -211,7 +145,7 @@ def _fva_optlang(model, reaction_list, fraction, loopless, pfba_factor):
                     name="flux_sum_constraint")
             m.add_cons_vars([flux_sum, flux_sum_constraint])
 
-        m.objective = S.Zero  # This will trigger the reset as well
+        m.objective = Zero  # This will trigger the reset as well
         for what in ("minimum", "maximum"):
             sense = "min" if what == "minimum" else "max"
             for rxn in reaction_list:
@@ -238,8 +172,8 @@ def _fva_optlang(model, reaction_list, fraction, loopless, pfba_factor):
 
 
 def find_blocked_reactions(model, reaction_list=None,
-                           solver=None, zero_cutoff=1e-9,
-                           open_exchanges=False, **solver_args):
+                           zero_cutoff=1e-9,
+                           open_exchanges=False):
     """Finds reactions that cannot carry a flux with the current
     exchange reaction settings for a cobra model, using flux variability
     analysis.
@@ -250,22 +184,17 @@ def find_blocked_reactions(model, reaction_list=None,
         The model to analyze
     reaction_list : list
         List of reactions to consider, use all if left missing
-    solver : string
-        The name of the solver to use
     zero_cutoff : float
         Flux value which is considered to effectively be zero.
     open_exchanges : bool
         If true, set bounds on exchange reactions to very high values to
         avoid that being the bottle-neck.
-    **solver_args :
-        Additional arguments to the solver. Ignored for optlang based solvers.
 
     Returns
     -------
     list
         List with the blocked reactions
     """
-    legacy, solver_interface = sutil.choose_solver(model, solver)
     with model:
         if open_exchanges:
             for reaction in model.exchanges:
@@ -275,25 +204,18 @@ def find_blocked_reactions(model, reaction_list=None,
             reaction_list = model.reactions
         # limit to reactions which are already 0. If the reactions already
         # carry flux in this solution, then they can not be blocked.
-        if legacy:
-            solution = solver_interface.solve(model, **solver_args)
-            reaction_list = [i for i in reaction_list
-                             if abs(solution.x_dict[i.id]) < zero_cutoff]
-        else:
-            model.solver = solver_interface
-            model.slim_optimize()
-            solution = get_solution(model, reactions=reaction_list)
-            reaction_list = [rxn for rxn in reaction_list if
-                             abs(solution.fluxes[rxn.id]) < zero_cutoff]
+        model.slim_optimize()
+        solution = get_solution(model, reactions=reaction_list)
+        reaction_list = [rxn for rxn in reaction_list if
+                         abs(solution.fluxes[rxn.id]) < zero_cutoff]
         # run fva to find reactions where both max and min are 0
         flux_span = flux_variability_analysis(
-            model, fraction_of_optimum=0., reaction_list=reaction_list,
-            solver=solver, **solver_args)
+            model, fraction_of_optimum=0., reaction_list=reaction_list)
         return [rxn_id for rxn_id, min_max in flux_span.iterrows() if
                 max(abs(min_max)) < zero_cutoff]
 
 
-def find_essential_genes(model, threshold=0.01):
+def find_essential_genes(model, threshold=None, processes=None):
     """Return a set of essential genes.
 
     A gene is considered essential if restricting the flux of all reactions
@@ -304,29 +226,28 @@ def find_essential_genes(model, threshold=0.01):
     ----------
     model : cobra.Model
         The model to find the essential genes for.
-    threshold : float (default 0.01)
-        Minimal objective flux to be considered viable.
+    threshold : float, optional
+        Minimal objective flux to be considered viable. By default this is
+        0.01 times the growth rate.
+    processes : int, optional
+        The number of parallel processes to run. Can speed up the computations
+        if the number of knockouts to perform is large. If not passed,
+        will be set to the number of CPUs found.
 
     Returns
     -------
     set
         Set of essential genes
     """
-    solution = model.optimize(raise_error=True)
-    tolerance = model.solver.configuration.tolerances.feasibility
-    non_zero_flux_reactions = list(
-        solution[solution.fluxes.abs() > tolerance].index)
-    genes_to_check = set(chain.from_iterable(
-        model.reactions.get_by_id(rid).genes for rid in
-        non_zero_flux_reactions))
-    deletions = single_gene_deletion(model, gene_list=genes_to_check,
-                                     method='fba')
-    gene_ids = list(deletions[(pandas.isnull(deletions.flux)) |
-                              (deletions.flux < threshold)].index)
-    return set(model.genes.get_by_any(gene_ids))
-
-
-def find_essential_reactions(model, threshold=0.01):
+    if threshold is None:
+        threshold = model.slim_optimize(error_value=None) * 1E-02
+    deletions = single_gene_deletion(model, method='fba', processes=processes)
+    essential = deletions.loc[deletions['growth'].isna() |
+                              (deletions['growth'] < threshold), :].index
+    return set(model.genes.get_by_id(g) for ids in essential for g in ids)
+
+
+def find_essential_reactions(model, threshold=None, processes=None):
     """Return a set of essential reactions.
 
     A reaction is considered essential if restricting its flux to zero
@@ -336,21 +257,23 @@ def find_essential_reactions(model, threshold=0.01):
     ----------
     model : cobra.Model
         The model to find the essential reactions for.
-    threshold : float (default 0.01)
-        Minimal objective flux to be considered viable.
+    threshold : float, optional
+        Minimal objective flux to be considered viable. By default this is
+        0.01 times the growth rate.
+    processes : int, optional
+        The number of parallel processes to run. Can speed up the computations
+        if the number of knockouts to perform is large. If not passed,
+        will be set to the number of CPUs found.
 
     Returns
     -------
     set
         Set of essential reactions
     """
-    solution = model.optimize(raise_error=True)
-    tolerance = model.solver.configuration.tolerances.feasibility
-    non_zero_flux_reactions = list(
-        solution[solution.fluxes.abs() > tolerance].index)
-    deletions = single_reaction_deletion(model,
-                                         reaction_list=non_zero_flux_reactions,
-                                         method='fba')
-    reaction_ids = list(deletions[(pandas.isnull(deletions.flux)) |
-                                  (deletions.flux < threshold)].index)
-    return set(model.reactions.get_by_any(reaction_ids))
+    if threshold is None:
+        threshold = model.slim_optimize(error_value=None) * 1E-02
+    deletions = single_reaction_deletion(model, method='fba',
+                                         processes=processes)
+    essential = deletions.loc[deletions['growth'].isna() |
+                              (deletions['growth'] < threshold), :].index
+    return set(model.reactions.get_by_id(r) for ids in essential for r in ids)
diff --git a/cobra/io/dict.py b/cobra/io/dict.py
index 8670252..da4369a 100644
--- a/cobra/io/dict.py
+++ b/cobra/io/dict.py
@@ -65,6 +65,8 @@ def _fix_type(value):
         return bool(value)
     if isinstance(value, set):
         return list(value)
+    if isinstance(value, dict):
+        return OrderedDict((key, value[key]) for key in sorted(value))
     # handle legacy Formula type
     if value.__class__.__name__ == "Formula":
         return str(value)
@@ -145,13 +147,16 @@ def reaction_from_dict(reaction, model):
     return new_reaction
 
 
-def model_to_dict(model):
+def model_to_dict(model, sort=False):
     """Convert model to a dict.
 
     Parameters
     ----------
     model : cobra.Model
-        The model to reformulate as a dict
+        The model to reformulate as a dict.
+    sort : bool, optional
+        Whether to sort the metabolites, reactions, and genes or maintain the
+        order defined in the model.
 
     Returns
     -------
@@ -166,17 +171,17 @@ def model_to_dict(model):
     cobra.io.model_from_dict
     """
     obj = OrderedDict()
-    obj["reactions"] = sorted(
-        (reaction_to_dict(reaction) for reaction in model.reactions),
-        key=itemgetter("id"))
-    obj["metabolites"] = sorted(
-        (metabolite_to_dict(metabolite) for metabolite in model.metabolites),
-        key=itemgetter("id"))
-    obj["genes"] = sorted(
-        (gene_to_dict(gene) for gene in model.genes), key=itemgetter("id"))
+    obj["metabolites"] = list(map(metabolite_to_dict, model.metabolites))
+    obj["reactions"] = list(map(reaction_to_dict, model.reactions))
+    obj["genes"] = list(map(gene_to_dict, model.genes))
     obj["id"] = model.id
     _update_optional(model, obj, _OPTIONAL_MODEL_ATTRIBUTES,
                      _ORDERED_OPTIONAL_MODEL_KEYS)
+    if sort:
+        get_id = itemgetter("id")
+        obj["metabolites"].sort(key=get_id)
+        obj["reactions"].sort(key=get_id)
+        obj["genes"].sort(key=get_id)
     return obj
 
 
diff --git a/cobra/io/json.py b/cobra/io/json.py
index 12ecee6..410f96c 100644
--- a/cobra/io/json.py
+++ b/cobra/io/json.py
@@ -2,8 +2,6 @@
 
 from __future__ import absolute_import
 
-import io
-
 try:
     import simplejson as json
 except ImportError:
@@ -15,7 +13,7 @@ from cobra.io.dict import model_to_dict, model_from_dict
 JSON_SPEC = "1"
 
 
-def to_json(model, **kwargs):
+def to_json(model, sort=False, **kwargs):
     """
     Return the model as a JSON document.
 
@@ -25,6 +23,9 @@ def to_json(model, **kwargs):
     ----------
     model : cobra.Model
         The cobra model to represent.
+    sort : bool, optional
+        Whether to sort the metabolites, reactions, and genes or maintain the
+        order defined in the model.
 
     Returns
     -------
@@ -36,7 +37,7 @@ def to_json(model, **kwargs):
     save_json_model : Write directly to a file.
     json.dumps : Base function.
     """
-    obj = model_to_dict(model)
+    obj = model_to_dict(model, sort=sort)
     obj[u"version"] = JSON_SPEC
     return json.dumps(obj, allow_nan=False, **kwargs)
 
@@ -62,7 +63,7 @@ def from_json(document):
     return model_from_dict(json.loads(document))
 
 
-def save_json_model(model, filename, pretty=False, **kwargs):
+def save_json_model(model, filename, sort=False, pretty=False, **kwargs):
     """
     Write the cobra model to a file in JSON format.
 
@@ -75,6 +76,9 @@ def save_json_model(model, filename, pretty=False, **kwargs):
     filename : str or file-like
         File path or descriptor that the JSON representation should be
         written to.
+    sort : bool, optional
+        Whether to sort the metabolites, reactions, and genes or maintain the
+        order defined in the model.
     pretty : bool, optional
         Whether to format the JSON more compactly (default) or in a more
         verbose but easier to read fashion. Can be partially overwritten by the
@@ -85,7 +89,7 @@ def save_json_model(model, filename, pretty=False, **kwargs):
     to_json : Return a string representation.
     json.dump : Base function.
     """
-    obj = model_to_dict(model)
+    obj = model_to_dict(model, sort=sort)
     obj[u"version"] = JSON_SPEC
 
     if pretty:
diff --git a/cobra/io/sbml3.py b/cobra/io/sbml3.py
index d2c1c07..8f6e7e8 100644
--- a/cobra/io/sbml3.py
+++ b/cobra/io/sbml3.py
@@ -48,7 +48,7 @@ else:
     from cobra.io.sbml import write_cobra_model_to_sbml_file as write_sbml2
 
 try:
-    from sympy import Basic
+    from optlang.symbolics import Basic
 except ImportError:
     class Basic:
         pass
diff --git a/cobra/io/yaml.py b/cobra/io/yaml.py
index 040d32e..1098a89 100644
--- a/cobra/io/yaml.py
+++ b/cobra/io/yaml.py
@@ -12,7 +12,7 @@ from cobra.io.dict import model_to_dict, model_from_dict
 YAML_SPEC = "1"
 
 
-def to_yaml(model, **kwargs):
+def to_yaml(model, sort=False, **kwargs):
     """
     Return the model as a YAML document.
 
@@ -22,6 +22,9 @@ def to_yaml(model, **kwargs):
     ----------
     model : cobra.Model
         The cobra model to represent.
+    sort : bool, optional
+        Whether to sort the metabolites, reactions, and genes or maintain the
+        order defined in the model.
 
     Returns
     -------
@@ -33,7 +36,7 @@ def to_yaml(model, **kwargs):
     save_yaml_model : Write directly to a file.
     ruamel.yaml.dump : Base function.
     """
-    obj = model_to_dict(model)
+    obj = model_to_dict(model, sort=sort)
     obj["version"] = YAML_SPEC
     return yaml.dump(obj, Dumper=yaml.RoundTripDumper, **kwargs)
 
@@ -59,7 +62,7 @@ def from_yaml(document):
     return model_from_dict(yaml.load(document, yaml.RoundTripLoader))
 
 
-def save_yaml_model(model, filename, **kwargs):
+def save_yaml_model(model, filename, sort=False, **kwargs):
     """
     Write the cobra model to a file in YAML format.
 
@@ -72,13 +75,16 @@ def save_yaml_model(model, filename, **kwargs):
     filename : str or file-like
         File path or descriptor that the YAML representation should be
         written to.
+    sort : bool, optional
+        Whether to sort the metabolites, reactions, and genes or maintain the
+        order defined in the model.
 
     See Also
     --------
     to_yaml : Return a string representation.
     ruamel.yaml.dump : Base function.
     """
-    obj = model_to_dict(model)
+    obj = model_to_dict(model, sort=sort)
     obj["version"] = YAML_SPEC
     if isinstance(filename, string_types):
         with io.open(filename, "w") as file_handle:
diff --git a/cobra/manipulation/__init__.py b/cobra/manipulation/__init__.py
index 649153d..9bbcdd8 100644
--- a/cobra/manipulation/__init__.py
+++ b/cobra/manipulation/__init__.py
@@ -7,8 +7,8 @@ from cobra.manipulation.delete import (
     delete_model_genes, find_gene_knockout_reactions, remove_genes,
     undelete_model_genes)
 from cobra.manipulation.modify import (
-    canonical_form, convert_to_irreversible, escape_ID,
-    get_compiled_gene_reaction_rules, revert_to_reversible)
+    escape_ID, get_compiled_gene_reaction_rules, convert_to_irreversible,
+    revert_to_reversible)
 from cobra.manipulation.validate import (
     check_mass_balance, check_metabolite_compartment_formula,
     check_reaction_bounds)
diff --git a/cobra/manipulation/modify.py b/cobra/manipulation/modify.py
index fcfe681..c980dba 100644
--- a/cobra/manipulation/modify.py
+++ b/cobra/manipulation/modify.py
@@ -8,7 +8,7 @@ from itertools import chain
 from six import iteritems
 from warnings import warn
 
-from cobra.core import Gene, Metabolite, Reaction
+from cobra.core import Gene, Reaction
 from cobra.core.gene import ast2str
 from cobra.manipulation.delete import get_compiled_gene_reaction_rules
 from cobra.util.solver import set_objective
@@ -185,76 +185,3 @@ def revert_to_reversible(cobra_model, update_solution=True):
     # use we can do this faster removal step.  We can
     # probably speed things up here.
     cobra_model.remove_reactions(reverse_reactions)
-
-
-def canonical_form(model, objective_sense='maximize',
-                   already_irreversible=False, copy=True):
-    """Return a model (problem in canonical_form).
-
-    Converts a minimization problem to a maximization, makes all variables
-    positive by making reactions irreversible, and converts all constraints to
-    <= constraints.
-
-
-    model: class:`~cobra.core.Model`. The model/problem to convert.
-
-    objective_sense: str. The objective sense of the starting problem, either
-    'maximize' or 'minimize'. A minimization problems will be converted to a
-    maximization.
-
-    already_irreversible: bool. If the model is already irreversible, then pass
-    True.
-
-    copy: bool. Copy the model before making any modifications.
-
-    """
-    warn("deprecated, not applicable for optlang solvers", DeprecationWarning)
-    if copy:
-        model = model.copy()
-
-    if not already_irreversible:
-        convert_to_irreversible(model)
-
-    if objective_sense == "minimize":
-        # if converting min to max, reverse all the objective coefficients
-        for reaction in model.reactions:
-            reaction.objective_coefficient = - reaction.objective_coefficient
-    elif objective_sense != "maximize":
-        raise Exception("Invalid objective sense '%s'. "
-                        "Must be 'minimize' or 'maximize'." % objective_sense)
-
-    # convert G and E constraints to L constraints
-    for metabolite in model.metabolites:
-        if metabolite._constraint_sense == "G":
-            metabolite._constraint_sense = "L"
-            metabolite._bound = - metabolite._bound
-            for reaction in metabolite.reactions:
-                coeff = reaction.get_coefficient(metabolite)
-                # reverse the coefficient
-                reaction.add_metabolites({metabolite: -2 * coeff})
-        elif metabolite._constraint_sense == "E":
-            # change existing constraint to L
-            metabolite._constraint_sense = "L"
-            # add new constraint
-            new_constr = Metabolite("%s__GE_constraint" % metabolite.id)
-            new_constr._constraint_sense = "L"
-            new_constr._bound = - metabolite._bound
-            for reaction in metabolite.reactions:
-                coeff = reaction.get_coefficient(metabolite)
-                reaction.add_metabolites({new_constr: -coeff})
-
-    # convert lower bounds to LE constraints
-    for reaction in model.reactions:
-        if reaction.lower_bound < 0:
-            raise Exception("Bounds of irreversible reactions should be >= 0,"
-                            " for %s" % reaction.id)
-        elif reaction.lower_bound == 0:
-            continue
-        # new constraint for lower bound
-        lb_constr = Metabolite("%s__LB_constraint" % reaction.id)
-        lb_constr._constraint_sense = "L"
-        lb_constr._bound = - reaction.lower_bound
-        reaction.add_metabolites({lb_constr: -1})
-        reaction.lower_bound = 0
-
-    return model
diff --git a/cobra/solvers/__init__.py b/cobra/solvers/__init__.py
deleted file mode 100644
index e2dd02d..0000000
--- a/cobra/solvers/__init__.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Solvers are expected to follow the following interface
-# create_problem: makes a solver problem object from a cobra.model and
-# sets parameters (if possible)
-
-# format_solution: Returns a cobra.Solution object.  This is where one
-# should dress the cobra.model with results if desired.
-
-# get_status: converts a solver specific status flag to a cobra pie flag.
-
-# set_parameter: takes solver specific parameter strings and sets them.
-
-# solve: solves the optimization problem.  this is where one should put
-# in logic on what to try if the problem
-# isn't optimal
-
-# solve_problem: dumb and fast which will set parameters, if provided
-
-# update_problem: changes bounds and linear objective coefficient of the
-# solver specific problem file, given the complementary cobra.model
-
-# This attempts to import all working solvers in this directory
-
-from __future__ import absolute_import
-
-import logging
-from os import listdir, path
-from warnings import warn
-
-LOGGER = logging.getLogger(__name__)
-
-solver_dict = {}
-possible_solvers = set()
-
-
-def add_solver(solver_name, use_name=None):
-    """add a solver module to the solvers"""
-    exec("from . import " + solver_name)
-    solver = eval(solver_name)
-    if use_name is None:
-        if hasattr(solver, "solver_name"):
-            use_name = solver.solver_name
-        else:
-            use_name = solver_name
-    solver_dict[use_name] = eval(solver_name)
-
-
-for i in listdir(path.dirname(path.abspath(__file__))):
-    if i.startswith("_") or i.startswith(".") or i.startswith('legacy'):
-        continue
-    if i.startswith("parameters"):
-        continue
-    if i.endswith(".py") or i.endswith(".so") or i.endswith(".pyc") \
-            or i.endswith(".pyd"):
-        possible_solvers.add(i.split(".")[0])
-
-if "wrappers" in possible_solvers:
-    possible_solvers.remove("wrappers")
-
-for solver in possible_solvers:
-    LOGGER.debug("adding '%s'...", solver)
-    try:
-        add_solver(solver)
-    except Exception as err:
-        LOGGER.debug("addition failed: %s", str(err))
-        pass
-    else:
-        LOGGER.debug("success!")
-    del solver
-
-if len(solver_dict) == 0:
-    warn("No LP solvers found")
-
-# clean up the namespace
-del path, listdir, warn, i, possible_solvers
-
-
-class SolverNotFound(Exception):
-    None
-
-
-def get_solver_name(mip=False, qp=False):
-    """returns a solver name
-
-    raises SolverNotFound if a suitable solver is not found
-    """
-    if len(solver_dict) == 0:
-        raise SolverNotFound("no solvers installed")
-    # glpk only does lp, not qp. Gurobi and cplex are better at mip
-    mip_order = ["gurobi", "cplex", "mosek", "coin", "cglpk", "glpk"]
-    lp_order = ["cglpk", "cplex",  "gurobi", "mosek", "coin", "glpk"]
-    qp_order = ["gurobi", "cplex", "mosek"]
-
-    if mip is False and qp is False:
-        for solver_name in lp_order:
-            if solver_name in solver_dict:
-                return solver_name
-        # none of them are in the list order - so return the first one
-        return list(solver_dict)[0]
-    elif qp:  # mip does not yet matter for this determination
-        for solver_name in qp_order:
-            if solver_name in solver_dict:
-                return solver_name
-        # see if any solver defines set_quadratic_objective
-        for solver_name in solver_dict:
-            if hasattr(solver_dict[solver_name], "set_quadratic_objective"):
-                return solver_name
-        raise SolverNotFound("no qp-capable solver found")
-    else:
-        for solver_name in mip_order:
-            if solver_name in solver_dict:
-                return solver_name
-        for solver_name in solver_dict:
-            if hasattr(solver_dict[solver_name], "_SUPPORTS_MIP"):
-                return solver_name
-    raise SolverNotFound("no mip-capable solver found")
-
-
-def optimize(cobra_model, solver=None, **kwargs):
-    """Wrapper to optimization solvers
-
-    solver : str
-        Name of the LP solver from solver_dict to use. If None is given, the
-        default one will be used
-
-    """
-    # If the default solver is not installed then use one of the others
-    if solver is None:
-        qp = "quadratic_component" in kwargs and \
-            kwargs["quadratic_component"] is not None
-        solver = get_solver_name(qp=qp)
-
-    return solver_dict[solver].solve(cobra_model, **kwargs)
diff --git a/cobra/solvers/cglpk.pyx b/cobra/solvers/cglpk.pyx
deleted file mode 100644
index 80ca79f..0000000
--- a/cobra/solvers/cglpk.pyx
+++ /dev/null
@@ -1,566 +0,0 @@
-# distutils: libraries=glpk
-# cython: embedsignature=True
-
-from glpk cimport *
-from libc.stdlib cimport malloc, free
-from cpython cimport bool
-from cpython.version cimport PY_MAJOR_VERSION
-
-import sys
-from tempfile import NamedTemporaryFile as _NamedTemporaryFile  # for pickling
-from os import unlink as _unlink
-from contextlib import contextmanager as _contextmanager
-from warnings import warn as _warn
-
-from six import StringIO, iteritems
-try:
-    from sympy import Basic, Number
-except:
-    class Basic:
-        pass
-    Number = Basic
-
-from cobra.core.solution import LegacySolution
-
-__glpk_version__ = str(glp_version())
-_SUPPORTS_MILP = True
-solver_name = "cglpk"
-
-__doc__ = """Bindings to GLPK
-
-The GNU Linear Programming Kit (GLPK) is released under the GPL.
-The source can be downloaded from http://www.gnu.org/software/glpk/
-
-The GNU Multiple Precision Arithmetic Library (GMP) is released under the GPL.
-The source can be downloaded from https://gmplib.org/
-
-"""
-
-# this is to stop errors in glpk, where "constructing initial basis" messages
-# are printed, even when the message level is set to off.
- at _contextmanager
-def quiet(verbose):
-    if verbose:
-        yield
-    else:
-        new_out, new_err = StringIO(), StringIO()
-        old_out, old_err = sys.stdout, sys.stderr
-        try:
-            sys.stdout, sys.stderr = new_out, new_err
-            yield
-        finally:
-            sys.stdout, sys.stderr = old_out, old_err
-
-
-cdef dict ERROR_CODES = {
-    GLP_EBADB: "GLP_EBADB",
-    GLP_ESING: "GLP_ESING",
-    GLP_ECOND: "GLP_ECOND",
-    GLP_EBOUND: "GLP_EBOUND",
-    GLP_EFAIL: "GLP_EFAIL",
-    GLP_EOBJLL: "GLP_EOBJLL",
-    GLP_EOBJUL: "GLP_EOBJUL",
-    GLP_EITLIM: "GLP_EITLIM",
-    GLP_ETMLIM: "GLP_ETMLIM",
-    GLP_ENOPFS: "GLP_ENOPFS",
-    GLP_ENODFS: "GLP_ENODFS",
-    GLP_EROOT: "GLP_EROOT",
-    GLP_ESTOP: "GLP_ESTOP",
-    GLP_EMIPGAP: "GLP_EMIPGAP",
-    GLP_ENOFEAS: "GLP_ENOFEAS",
-    GLP_ENOCVG: "GLP_ENOCVG",
-    GLP_EINSTAB: "GLP_EINSTAB",
-    GLP_EDATA: "GLP_EDATA",
-    GLP_ERANGE: "GLP_ERANGE"
-}
-
-cdef dict ERROR_MESSAGES = {
-    GLP_EBADB: "invalid basis",
-    GLP_ESING: "singular matrix",
-    GLP_ECOND: "ill-conditioned matrix",
-    GLP_EBOUND: "invalid bounds",
-    GLP_EFAIL: "solver failed",
-    GLP_EOBJLL: "objective lower limit reached",
-    GLP_EOBJUL: "objective upper limit reached",
-    GLP_EITLIM: "iteration limit exceeded",
-    GLP_ETMLIM: "time limit exceeded",
-    GLP_ENOPFS: "no primal feasible solution",
-    GLP_ENODFS: "no dual feasible solution",
-    GLP_EROOT: "root LP optimum not provided",
-    GLP_ESTOP: "search terminated by application",
-    GLP_EMIPGAP: "relative mip gap tolerance reached",
-    GLP_ENOFEAS: "no primal/dual feasible solution",
-    GLP_ENOCVG: "no convergence",
-    GLP_EINSTAB: "numerical instability",
-    GLP_EDATA: "invalid data",
-    GLP_ERANGE: "result out of range"
-}
-
-cdef dict METHODS = {
-    "auto": GLP_DUALP,
-    "primal": GLP_PRIMAL,
-    "dual": GLP_DUAL
-}
-
-cdef dict PRICINGS = {
-    "std": GLP_PT_STD,
-    "pse": GLP_PT_PSE
-}
-
-cdef dict RATIOS = {
-    "std": GLP_RT_STD,
-    "har": GLP_RT_HAR
-}
-
-cdef dict SCALINGS = {
-    "auto": GLP_SF_AUTO,
-    "gm": GLP_SF_GM,
-    "eq": GLP_SF_EQ,
-    "2n": GLP_SF_2N,
-    "skip": GLP_SF_SKIP
-}
-
-cdef int downcast_pos_size(Py_ssize_t size):
-    if size > INT_MAX:
-        raise ValueError("Integer overflow %d > %d" % (size, INT_MAX))
-    else:
-        return <int> size
-
-
-cdef check_error(int result):
-    if result == 0:
-        return
-    if result not in ERROR_CODES:
-        raise RuntimeError("glp_simplex failed with unknown error code 0x%x" %
-                           result)
-    raise RuntimeError("glp_simplex failed with error code %s: %s" %
-                       (ERROR_CODES[result], ERROR_MESSAGES[result]))
-
-
-
-cdef int hook(void *info, const char *s):
-    """function to redirect sdout to python stdout"""
-    print(s)
-    return 1
-
-
-cdef int silent_hook(void *info, const char *s):
-    """function to print nothing but trick GLPK into thinking we did"""
-    return 1
-
-cdef double _to_double(value):
-    if isinstance(value, Basic) and not isinstance(value, Number):
-        return 0.
-    else:
-        return <double>value
-
-
-cdef class GLP:
-    cdef glp_prob *glp
-    cdef glp_smcp parameters
-    cdef glp_iocp integer_parameters
-    cdef public bool exact
-
-    # cython related allocation/dellocation functions
-    def __cinit__(self):
-        self.glp = glp_create_prob()
-        # initialize parameters
-        glp_set_obj_dir(self.glp, GLP_MAX)  # default is maximize
-        glp_init_smcp(&self.parameters)
-        glp_init_iocp(&self.integer_parameters)
-        self.exact = False
-        glp_term_hook(hook, NULL)
-        self.parameters.msg_lev = GLP_MSG_OFF
-        self.integer_parameters.tol_int = 1e-9
-
-    def __dealloc__(self):
-        glp_delete_prob(self.glp)
-
-    def __init__(self, cobra_model=None):
-        cdef int bound_type, index, n, n_values
-        cdef glp_prob *glp
-        cdef int *c_rows
-        cdef int *c_cols
-        cdef double *c_values
-        cdef double b
-
-        if cobra_model is None:
-            return
-        glp = self.glp
-        glp_add_rows(glp, downcast_pos_size(len(cobra_model.metabolites)))
-        glp_add_cols(glp, downcast_pos_size(len(cobra_model.reactions)))
-
-        metabolite_id_to_index = {r.id: index for index, r
-                                  in enumerate(cobra_model.metabolites, 1)}
-
-        linear_constraint_rows = []
-        linear_constraint_cols = []
-        linear_constraint_values = []
-
-        # set metabolite/consraint bounds
-        for index, metabolite in enumerate(cobra_model.metabolites, 1):
-            b = _to_double(metabolite._bound)
-            c = metabolite._constraint_sense
-            if c == 'E':
-                bound_type = GLP_FX  # Set metabolite to steady state levels
-            elif c == 'L':
-                bound_type = GLP_UP  # x < 2 <==> x has an upper bound of 2
-            elif c == 'G':
-                bound_type = GLP_LO  # x > 2 <==> x has a lower bound of 2
-            else:
-                raise ValueError("unsupported bound type: %s" % c)
-            glp_set_row_bnds(glp, index, bound_type, b, b)
-
-        # set reaction/varaiable bounds
-        for index, reaction in enumerate(cobra_model.reactions, 1):
-            if reaction.variable_kind == "integer":
-                if reaction.lower_bound == 0 and reaction.upper_bound == 1:
-                    glp_set_col_kind(self.glp, index, GLP_BV)  # binary
-                else:
-                    glp_set_col_kind(self.glp, index, GLP_IV)
-            if reaction.lower_bound == reaction.upper_bound:
-                bound_type = GLP_FX
-            else:
-                bound_type = GLP_DB
-            glp_set_col_bnds(glp, index, bound_type,
-                             _to_double(reaction.lower_bound),
-                             _to_double(reaction.upper_bound))
-            glp_set_obj_coef(glp, index,
-                             _to_double(reaction.objective_coefficient))
-
-            for metabolite, coefficient in iteritems(reaction.metabolites):
-                linear_constraint_rows.append(
-                    metabolite_id_to_index[metabolite.id])
-                linear_constraint_cols.append(index)
-                linear_constraint_values.append(coefficient)
-
-        # set constraint marix
-        # first copy the python lists to c arrays
-        n_values = downcast_pos_size(len(linear_constraint_rows))
-        c_cols = <int *> malloc((n_values + 1) * sizeof(int))
-        c_rows = <int *> malloc((n_values + 1) * sizeof(int))
-        c_values = <double *> malloc((n_values + 1) * sizeof(double))
-        if c_rows is NULL or c_rows is NULL or c_values is NULL:
-            raise MemoryError()
-        for index in range(n_values):
-            c_rows[index + 1] = linear_constraint_rows[index]
-            c_cols[index + 1] = linear_constraint_cols[index]
-            c_values[index + 1] = _to_double(linear_constraint_values[index])
-        # actually set the values
-        glp_load_matrix(glp, n_values, c_rows, c_cols, c_values)
-        # free the c arrays
-        free(c_rows)
-        free(c_cols)
-        free(c_values)
-
-    # problem creation and modification
-    @classmethod
-    def create_problem(cls, cobra_model, objective_sense="maximize"):
-        problem = cls(cobra_model)
-        problem.set_objective_sense(objective_sense)
-        return problem
-
-    cpdef change_variable_bounds(self, int index, double lower_bound,
-                                 double upper_bound):
-        cdef int bound_type = GLP_FX if lower_bound == upper_bound else GLP_DB
-        assert index >= 0
-        glp_set_col_bnds(self.glp, index + 1, bound_type, lower_bound, upper_bound)
-
-    def change_coefficient(self, int met_index, int rxn_index, double value):
-        cdef int col_length, i
-        cdef int *indexes
-        cdef double *values
-        # glpk uses 1 indexing
-        met_index += 1
-        rxn_index += 1
-        # we first have to get the old column
-        col_length = glp_get_mat_col(self.glp, rxn_index, NULL, NULL)
-        indexes = <int *> malloc((col_length + 2) * sizeof(int))
-        values = <double *> malloc((col_length + 2) * sizeof(double))
-        if indexes == NULL or values == NULL:
-            raise MemoryError()
-        glp_get_mat_col(self.glp, rxn_index, indexes, values)
-        # search for duplicate
-        for i in range(col_length):
-            # if a duplicate exists replace that value and exit
-            if indexes[i + 1] == met_index:
-                values[i + 1] = value
-                glp_set_mat_col(self.glp, rxn_index, col_length, indexes, values)
-                free(indexes)
-                free(values)
-                return
-        # need to add a new entry
-        indexes[col_length + 1] = met_index
-        values[col_length + 1] = value
-        glp_set_mat_col(self.glp, rxn_index, col_length + 1, indexes, values)
-        free(indexes)
-        free(values)
-
-    def solve_problem(self, **solver_parameters):
-        cdef int result
-        cdef int time_limit
-        cdef glp_prob *glp = self.glp
-
-        for key, value in solver_parameters.items():
-            self.set_parameter(key, value)
-
-        # suspend the gil to allow multithreading
-        # multithreading must occur with DIFFERENT glp objects
-        # calling solve_problem on the same object from 2 different
-        # threads at the same time will probably cause problems
-        # because glpk itself is not thread safe
-
-        #with nogil:  # we can use this if glpk ever gets thread-safe malloc
-        # Try to solve the problem with the existing basis, but with
-        # a time limit in case it gets stuck.
-        time_limit = self.parameters.tm_lim  # save time limit
-        self.parameters.tm_lim = min(500, time_limit)
-        fast_status = glp_simplex(glp, &self.parameters)
-        self.parameters.tm_lim = time_limit
-
-        if fast_status != 0:
-            with quiet(self.parameters.msg_lev):
-                glp_adv_basis(glp, 0)
-            check_error(glp_simplex(glp, &self.parameters))
-        self.parameters.tm_lim = time_limit
-        if self.exact:
-            # sigh... it looks like the exact routine doesn't fully respect
-            # the verbosity parameter
-            if self.parameters.msg_lev == GLP_MSG_OFF:
-                glp_term_hook(silent_hook, NULL)
-            try:
-                check_error(glp_exact(glp, &self.parameters))
-            finally:
-                glp_term_hook(hook, NULL)
-
-        if self.is_mip():
-            self.integer_parameters.tm_lim = self.parameters.tm_lim
-            self.integer_parameters.msg_lev = self.parameters.msg_lev
-            #with nogil:
-            check_error(glp_intopt(glp, &self.integer_parameters))
-        return self.get_status()
-
-    @classmethod
-    def solve(cls, cobra_model, **kwargs):
-        problem = cls.create_problem(cobra_model)
-        problem.solve_problem(**kwargs)
-        solution = problem.format_solution(cobra_model)
-        return solution
-
-    def get_status(self):
-        cdef int result = glp_mip_status(self.glp) if self.is_mip() \
-            else glp_get_status(self.glp)
-        if result == GLP_OPT:
-            return "optimal"
-        if result == GLP_FEAS:
-            return glp_get_status(self.glp)
-        if result == GLP_UNDEF:
-            return "undefined"
-        if result == GLP_UNBND:
-            return "unbounded"
-        if result == GLP_NOFEAS:
-            return "infeasible"
-        return "failed"
-
-    cpdef set_objective_sense(self, objective_sense):
-        objective_sense = objective_sense.lower()
-        if objective_sense == "maximize":
-            glp_set_obj_dir(self.glp, GLP_MAX)
-        elif objective_sense == "minimize":
-            glp_set_obj_dir(self.glp, GLP_MIN)
-        else:
-            raise ValueError("%s is not a valid objective sense" % objective_sense)
-
-    cpdef set_parameter(self, parameter_name, value):
-        """set a solver parameter"""
-        if parameter_name == "objective_sense":
-            self.set_objective_sense(value)
-        elif parameter_name in {"time_limit", "tm_lim"}:
-            self.parameters.tm_lim = int(1000 * value)
-            # Setting a value less than 0.001 would cause us to not
-            # set a time limit at all. It's better to set a time limit
-            # of 1 ms in this case.
-            #if value > 0 and self.parameters.tm_lim == 0:
-            #    self.parameters.tm_lim = 1
-        elif parameter_name == "tolerance_feasibility":
-            self.parameters.tol_bnd = float(value)
-            self.parameters.tol_dj = float(value)
-        elif parameter_name == "tol_bnd":
-            self.parameters.tol_bnd = float(value)
-        elif parameter_name == "tol_dj":
-            self.parameters.tol_dj = float(value)
-        elif parameter_name in {"tolerance_markowitz", "tol_piv"}:
-            self.parameters.tol_piv = float(value)
-        elif parameter_name in {"tolerance_integer", "tol_int"}:
-            self.integer_parameters.tol_int = float(value)
-        elif parameter_name in {"mip_gap", "MIP_gap"}:
-            self.integer_parameters.mip_gap = float(value)
-        elif parameter_name == "verbose":
-            if not value:  # suppress all output
-                self.parameters.msg_lev = GLP_MSG_OFF
-                return
-            if value == "err":
-                self.parameters.msg_lev = GLP_MSG_ERR
-            elif value is True or value == "all":
-                self.parameters.msg_lev = GLP_MSG_ALL
-            elif value == "normal":
-                self.parameters.msg_lev = GLP_MSG_ON
-        elif parameter_name == "iteration_limit":
-            self.parameters.it_lim = value
-        elif parameter_name == "lp_method":
-            self.parameters.meth = METHODS[value]
-        elif parameter_name == "exact":
-            self.exact = value
-        elif parameter_name == "threads":
-            _warn("multiple threads not supported")
-        elif parameter_name == "MIP_gap_abs":
-            _warn("setting aboslute mip gap not supported")
-        elif parameter_name == "presolve":
-            self.parameters.presolve = GLP_ON if value else GLP_OFF
-        elif parameter_name == "pricing":
-            self.parameters.pricing = PRICINGS[value]
-        elif parameter_name == "r_test":
-            self.parameters.r_test = RATIOS[value]
-        elif parameter_name == "scale":
-            if value:
-                if isinstance(value, int):
-                    glp_scale_prob(self.glp, value)
-                else:
-                    glp_scale_prob(self.glp, SCALINGS[value])
-            else:
-                glp_unscale_prob(self.glp)
-        elif parameter_name == "quadratic_component":
-            if value is not None:
-                raise ValueError("quadratic component must be None for glpk")
-        else:
-            raise ValueError("unknown parameter " + str(parameter_name))
-
-    cpdef get_objective_value(self):
-        if self.is_mip():
-            return glp_mip_obj_val(self.glp)
-        return glp_get_obj_val(self.glp)
-
-    cpdef change_variable_objective(self, int index, double value):
-        assert index >= 0
-        glp_set_obj_coef(self.glp, index + 1, value)
-
-    cpdef is_mip(self):
-        return glp_get_num_int(self.glp) > 0
-
-    def format_solution(self, cobra_model):
-        cdef int i, m, n
-        cdef glp_prob *glp = self.glp
-        status = self.get_status()
-        if status != "optimal":  # todo handle other possible
-            return LegacySolution(None, status=status)
-        solution = LegacySolution(self.get_objective_value(), status=status)
-        m = glp_get_num_rows(glp)
-        n = glp_get_num_cols(glp)
-        x = [0] * n
-        if self.is_mip():
-            for i in range(1, n + 1):
-                    x[i - 1] = glp_mip_col_val(glp, i)
-            solution.x_dict = {rxn.id: x[i] for i, rxn
-                               in enumerate(cobra_model.reactions)}
-            solution.x = x
-        else:
-            for i in range(1, n + 1):
-                x[i - 1] = glp_get_col_prim(glp, i)
-            solution.x_dict = {rxn.id: x[i] for i, rxn
-                               in enumerate(cobra_model.reactions)}
-            solution.x = x
-            y = [0] * m
-            for i in range(1, m + 1):
-                y[i - 1] = glp_get_row_dual(glp, i)
-            solution.y_dict = {met.id: y[i] for i, met
-                               in enumerate(cobra_model.metabolites)}
-            solution.y = y
-        return solution
-
-    # make serializable and copyable
-    def __getstate__(self):
-        cdef int result
-        cdef char *name
-        tempfile = _NamedTemporaryFile(mode="r", delete=False)
-        name = tempfile.name
-        tempfile.close()
-        result = glp_write_prob(self.glp, 0, name)
-        assert result == 0
-        with open(name, "r") as infile:
-            state = infile.read()
-        _unlink(name)
-        return state
-
-    def __reduce__(self):
-        return (GLP, (), self.__getstate__())
-
-    def __setstate__(self, state):
-        cdef int result
-        cdef char *name = NULL
-        with _NamedTemporaryFile(mode="w", delete=False) as tempfile:
-            name = tempfile.name
-            tempfile.write(state)
-        result = glp_read_prob(self.glp, 0, name)
-        assert result == 0
-        _unlink(name)
-
-    def __copy__(self):
-        other = GLP()
-        glp_copy_prob(other.glp, self.glp, GLP_ON)
-        other.parameters = self.parameters
-        other.integer_parameters = self.integer_parameters
-        other.exact = self.exact
-        return other
-
-    def write(self, filename):
-        """Write the problem to a file
-
-        The format will be determined by the file extension.
-            .lp: LP file
-            .mps: MPS file
-        """
-        if PY_MAJOR_VERSION == 2:
-            b_name = bytes(filename)
-        elif PY_MAJOR_VERSION == 3:
-            b_name = bytes(filename, "latin-1")
-        else:
-            raise RuntimeError("Unknown python version")
-        cdef char *c_name = <bytes> b_name
-        cdef int res
-        # no other way to silence this function
-        glp_term_hook(silent_hook, NULL)
-        try:
-            if b_name.endswith(".lp"):
-                res = glp_write_lp(self.glp, NULL, c_name)
-            elif b_name.endswith(".mps"):
-                res = glp_write_mps(self.glp, GLP_MPS_FILE, NULL, c_name)
-            else:
-                raise ValueError("Unknown file format for %s" % str(filename))
-            if res != 0:
-                raise IOError("failed to write LP to file %s" % str(filename))
-        finally:
-            glp_term_hook(hook, NULL)
-
-
-# wrappers for all the functions at the module level
-create_problem = GLP.create_problem
-def set_objective_sense(lp, objective_sense="maximize"):
-    return lp.set_objective_sense(lp, objective_sense=objective_sense)
-cpdef change_variable_bounds(lp, int index,
-                             double lower_bound, double upper_bound):
-    return lp.change_variable_bounds(index, lower_bound, upper_bound)
-cpdef change_variable_objective(lp, int index, double value):
-    return lp.change_variable_objective(index, value)
-cpdef change_coefficient(lp, int met_index, int rxn_index, double value):
-    return lp.change_coefficient(met_index, rxn_index, value)
-cpdef set_parameter(lp, parameter_name, value):
-    return lp.set_parameter(parameter_name, value)
-def solve_problem(lp, **kwargs):
-    return lp.solve_problem(**kwargs)
-cpdef get_status(lp):
-    return lp.get_status()
-cpdef get_objective_value(lp):
-    return lp.get_objective_value()
-cpdef format_solution(lp, cobra_model):
-    return lp.format_solution(cobra_model)
-solve = GLP.solve
diff --git a/cobra/solvers/coin.py b/cobra/solvers/coin.py
deleted file mode 100644
index c9ff9c3..0000000
--- a/cobra/solvers/coin.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-
-from cobra.core.solution import LegacySolution
-
-from cylp.cy import CyClpSimplex
-from cylp.cy.CyCoinPackedMatrix import CyCoinPackedMatrix
-from cylp.py.modeling.CyLPModel import CyLPArray
-
-solver_name = "coin"
-_status_translation = {"primal infeasible": "infeasible",
-                       "solution": "optimal"}
-
-_SUPPORTS_MILP = True
-
-
-class Coin(CyClpSimplex):
-    cbc = None
-
-    @property
-    def status_(self):
-        return self.cbc.status if self.cbc else self.getStatusString()
-
-    @property
-    def primalVariableSolution_(self):
-        return self.cbc.primalVariableSolution if self.cbc \
-            else self.primalVariableSolution
-
-    @property
-    def objectiveValue_(self):
-        return self.cbc.objectiveValue if self.cbc else self.objectiveValue
-
-
-def create_problem(cobra_model, objective_sense="maximize", **kwargs):
-    m = cobra_model.to_array_based_model(deepcopy_model=True)
-    lp = Coin()
-    v = lp.addVariable("v", len(m.reactions))
-    for i, rxn in enumerate(m.reactions):
-        if rxn.variable_kind == "integer":
-            lp.setInteger(v[i])
-    S = m.S
-    v.lower = CyLPArray(m.lower_bounds)
-    v.upper = CyLPArray(m.upper_bounds)
-    inf = float("inf")
-    cons = zip(m.b, m.constraint_sense)
-    b_l = CyLPArray([-inf if s == "L" else b for b, s in cons])
-    b_u = CyLPArray([inf if s == "G" else b for b, s in cons])
-    lp.addConstraint(b_u >= S * v >= b_l, "b")
-    lp.objectiveCoefficients = CyLPArray(m.objective_coefficients)
-    set_parameter(lp, "objective_sense", objective_sense)
-    set_parameter(lp, "tolerance_feasibility", 1e-9)
-    lp.logLevel = 0
-    for key, value in kwargs.items():
-        set_parameter(lp, key, value)
-    return lp
-
-
-def solve(cobra_model, **kwargs):
-    lp = create_problem(cobra_model)
-    for key, value in kwargs.items():
-        set_parameter(lp, key, value)
-    solve_problem(lp)
-    return format_solution(lp, cobra_model)
-
-
-def set_parameter(lp, parameter_name, value):
-    if parameter_name == "objective_sense":
-        v = str(value).lower()
-        if v == "maximize":
-            lp.optimizationDirection = "max"
-        elif v == "minimize":
-            lp.optimizationDirection = "min"
-        else:
-            raise ValueError("unknown objective sense '%s'" % value)
-    elif parameter_name == "tolerance_feasibility":
-        lp.primalTolerance = value
-    elif parameter_name == "verbose":
-        lp.logLevel = value
-    elif parameter_name == "quadratic_component":
-        set_quadratic_objective(lp, value)
-    else:
-        setattr(lp, parameter_name, value)
-
-
-def solve_problem(lp, **kwargs):
-    for key, value in kwargs.items():
-        set_parameter(lp, key, value)
-    if max(lp.integerInformation):
-        lp.cbc = lp.getCbcModel()
-        lp.cbc.logLevel = lp.logLevel
-        return lp.cbc.branchAndBound()
-    else:
-        lp.cbc = None
-        return lp.primal()
-
-
-def format_solution(lp, cobra_model):
-    status = get_status(lp)
-    if status != "optimal":  # todo handle other possible
-        return LegacySolution(None, status=status)
-    solution = LegacySolution(lp.objectiveValue_, status=status)
-    x = lp.primalVariableSolution_["v"].tolist()
-    solution.x_dict = {r.id: x[i] for i, r in enumerate(cobra_model.reactions)}
-    solution.x = x
-    # TODO handle y
-
-    return solution
-
-
-def get_status(lp):
-    status = lp.status_
-    return _status_translation.get(status, status)
-
-
-def get_objective_value(lp):
-    return lp.objectiveValue_
-
-
-def change_variable_bounds(lp, index, lower_bound, upper_bound):
-    lp.variablesLower[index] = lower_bound
-    lp.variablesUpper[index] = upper_bound
-
-
-def change_coefficient(lp, met_index, rxn_index, value):
-    S = lp.coefMatrix
-    S[met_index, rxn_index] = value
-    lp.coefMatrix = S
-
-
-def change_variable_objective(lp, index, value):
-    lp.setObjectiveCoefficient(index, value)
-
-
-def _set_quadratic_objective(lp, quadratic_objective):
-    """The quadratic routines in CLP do not yet work for GEMs"""
-    if not hasattr(quadratic_objective, "tocoo"):
-        raise Exception('quadratic component must have method tocoo')
-    coo = quadratic_objective.tocoo()
-    matrix = CyCoinPackedMatrix(True, coo.row, coo.col, coo.data)
-    lp.loadQuadraticObjective(matrix)
diff --git a/cobra/solvers/cplex_solver.py b/cobra/solvers/cplex_solver.py
deleted file mode 100644
index 5d60dc1..0000000
--- a/cobra/solvers/cplex_solver.py
+++ /dev/null
@@ -1,347 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Interface to ilog/cplex 12.4 python interface
-
-from __future__ import absolute_import
-
-import sys
-from warnings import warn
-
-from cplex import Cplex, SparsePair
-from cplex.exceptions import CplexError
-from six import iteritems, string_types
-from six.moves import zip
-
-from cobra.core.solution import LegacySolution
-
-try:
-    from sympy import Basic, Number
-except:
-    class Basic:
-        pass
-
-def _float(value):
-    if isinstance(value, Basic) and not isinstance(value, Number):
-        return 0.
-    else:
-        return float(value)
-
-solver_name = 'cplex'
-_SUPPORTS_MILP = True
-
-# solver specific parameters
-parameter_defaults = {'objective_sense': 'maximize',
-                      'tolerance_optimality': 1e-6,
-                      'tolerance_feasibility': 1e-6,
-                      'tolerance_integer': 1e-9,
-                      'lp_method': 1,
-                      'tolerance_barrier': 1e-8,
-                      'verbose': False,
-                      'qpmethod': 1}
-parameter_mappings = {'lp_method': 'lpmethod',
-                      'lp_parallel': 'threads',
-                      'threads': 'threads',
-                      'objective_sense': 'objective_sense',
-                      'time_limit': 'timelimit',
-                      'iteration_limit': 'simplex.limits.iterations',
-                      'tolerance_barrier': 'barrier.convergetol',
-                      'tolerance_feasibility': 'simplex.tolerances.feasibility',
-                      'tolerance_markowitz': 'simplex.tolerances.markowitz',
-                      'tolerance_optimality': 'simplex.tolerances.optimality',
-                      'tolerance_integer': 'mip.tolerances.integrality',
-                      'MIP_gap_abs': 'mip.tolerances.absmipgap',
-                      'MIP_gap': 'mip.tolerances.mipgap'}
-variable_kind_dict = {'continuous': Cplex.variables.type.continuous,
-                      'integer': Cplex.variables.type.integer}
-status_dict = {'MIP_infeasible': 'infeasible',
-               'integer optimal solution': 'optimal',
-               'MIP_optimal': 'optimal',
-               'MIP_optimal_tolerance': 'optimal',
-               'MIP_unbounded':  'unbounded',
-               'infeasible': 'infeasible',
-               'integer infeasible': 'infeasible',
-               'optimal': 'optimal',
-               'optimal_tolerance': 'optimal',
-               'unbounded': 'unbounded',
-               'integer optimal, tolerance': 'optimal',
-               'time limit exceeded': 'time_limit'}
-
-
-def get_status(lp):
-    status = lp.solution.get_status_string().lower()
-    return status_dict.get(status, status)
-
-
-def get_objective_value(lp):
-    return lp.solution.get_objective_value()
-
-
-def format_solution(lp, cobra_model, **kwargs):
-    status = get_status(lp)
-    if status in ('optimal', 'time_limit', 'non-optimal'):
-        objective_value = lp.solution.get_objective_value()
-        x_dict = dict(zip(lp.variables.get_names(),
-                          lp.solution.get_values()))
-        x = lp.solution.get_values()
-        # MIP's don't have duals
-        if lp.get_problem_type() in (Cplex.problem_type.MIQP,
-                                     Cplex.problem_type.MILP):
-
-            y = y_dict = None
-        else:
-            y_dict = dict(zip(lp.linear_constraints.get_names(),
-                              lp.solution.get_dual_values()))
-            y = lp.solution.get_dual_values()
-    else:
-        x = y = x_dict = y_dict = objective_value = None
-
-    return LegacySolution(objective_value, x=x, x_dict=x_dict, status=status,
-                    y=y, y_dict=y_dict)
-
-
-def set_parameter(lp, parameter_name, parameter_value):
-    if parameter_name == 'objective_sense':
-        parameter_value = getattr(lp.objective.sense, parameter_value)
-        lp.objective.set_sense(parameter_value)
-        return
-    elif parameter_name == 'the_problem':
-        warn('option the_problem removed')
-        return
-    elif parameter_name == 'verbose':
-        if parameter_value:
-            lp.set_log_stream(sys.stdout)
-            lp.set_results_stream(sys.stdout)
-            lp.set_warning_stream(sys.stderr)
-            # If the value passed in is True, it shold be 1. MIP display can
-            # be as high as 5, but the others only go up to 2.
-            value = int(parameter_value)
-            set_parameter(lp, 'mip.display', value)
-            set_parameter(lp, 'simplex.display', min(value, 2))
-            set_parameter(lp, 'barrier.display', min(value, 2))
-        else:
-            lp.set_log_stream(None)
-            lp.set_results_stream(None)
-            lp.set_warning_stream(None)
-            set_parameter(lp, 'mip.display', 0)
-            set_parameter(lp, 'simplex.display', 0)
-            set_parameter(lp, 'barrier.display', 0)
-        return
-    try:
-        cplex_name = parameter_mappings.get(parameter_name, parameter_name)
-        cplex_value = parameter_value
-        # This will iteratively get parameters. For example
-        # "simplex.tolerances.feasibility" will evaluate to
-        # lp.parameters.simplex.tolerances.feasibility.
-        param = lp.parameters
-        for i in cplex_name.split("."):
-            param = getattr(param, i)
-        # For example, this next part will allow setting the parameter
-        # lpmethod to "auto", "primal", "dual" or any other string in
-        # parameters.lp.method.values
-        if isinstance(cplex_value, string_types) and \
-                hasattr(param.values, cplex_value):
-            cplex_value = getattr(param.values, cplex_value)
-        param.set(cplex_value)
-    except (CplexError, AttributeError) as e:
-        raise ValueError("Failed to set %s to %s: %s" %
-                         (parameter_name, str(parameter_value), repr(e)))
-
-
-def create_problem(cobra_model, quadratic_component=None, **kwargs):
-    """Solver-specific method for constructing a solver problem from
-    a cobra.Model.  This can be tuned for performance using kwargs
-
-
-    """
-    # Process parameter defaults
-    the_parameters = parameter_defaults
-    if kwargs:
-        the_parameters = parameter_defaults.copy()
-        the_parameters.update(kwargs)
-    if 'relax_b' in the_parameters:
-        relax_b = the_parameters.pop("relax_b")
-        warn('need to reimplement relax_b')
-        relax_b = False
-    else:
-        relax_b = False
-
-    # Begin problem creation
-    lp = Cplex()
-    for k, v in iteritems(the_parameters):
-        set_parameter(lp, k, v)
-    objective_coefficients = [float(x.objective_coefficient)
-                              for x in cobra_model.reactions]
-    lower_bounds = [_float(x.lower_bound) for x in cobra_model.reactions]
-    upper_bounds = [_float(x.upper_bound) for x in cobra_model.reactions]
-    variable_names = cobra_model.reactions.list_attr("id")
-    variable_kinds = [variable_kind_dict[x.variable_kind] for x
-                      in cobra_model.reactions]
-    # Cplex decides that the problem is a MIP if variable_kinds are supplied
-    # even if there aren't any integers.
-    if variable_kind_dict['integer'] in variable_kinds:
-        lp.variables.add(obj=objective_coefficients,
-                         lb=lower_bounds,
-                         ub=upper_bounds,
-                         names=variable_names,
-                         types=variable_kinds)
-    else:
-        lp.variables.add(obj=objective_coefficients,
-                         lb=lower_bounds,
-                         ub=upper_bounds,
-                         names=variable_names)
-
-    constraint_sense = []
-    constraint_names = []
-    constraint_limits = []
-
-    for x in cobra_model.metabolites:
-        constraint_sense.append(x._constraint_sense)
-        constraint_names.append(x.id)
-        constraint_limits.append(float(x._bound))
-
-    the_linear_expressions = []
-    # NOTE: This won't work with metabolites that aren't in any reaction
-    for the_metabolite in cobra_model.metabolites:
-        variable_list = []
-        coefficient_list = []
-        for the_reaction in the_metabolite._reaction:
-            variable_list.append(the_reaction.id)
-            coefficient_list.append(_float(the_reaction._metabolites[the_metabolite]))
-        the_linear_expressions.append(SparsePair(ind=variable_list,
-                                                 val=coefficient_list))
-    # Set objective to quadratic program
-    if quadratic_component is not None:
-        set_quadratic_objective(lp, quadratic_component)
-
-    if relax_b:
-        lp.linear_constraints.add(lin_expr=the_linear_expressions,
-                                  rhs=constraint_limits,
-                                  range_values=list(range_values),
-                                  senses=constraint_sense,
-                                  names=constraint_names)
-
-    else:
-        lp.linear_constraints.add(lin_expr=the_linear_expressions,
-                                  rhs=constraint_limits,
-                                  senses=constraint_sense,
-                                  names=constraint_names)
-
-    # Set the problem type as cplex doesn't appear to do this correctly
-    problem_type = Cplex.problem_type.LP
-    if Cplex.variables.type.integer in variable_kinds:
-        if quadratic_component is not None:
-            problem_type = Cplex.problem_type.MIQP
-        else:
-            problem_type = Cplex.problem_type.MILP
-    elif quadratic_component is not None:
-        problem_type = Cplex.problem_type.QP
-    lp.set_problem_type(problem_type)
-    return(lp)
-
-
-def set_quadratic_objective(lp, quadratic_objective):
-    if not hasattr(quadratic_objective, 'todok'):
-        raise Exception('quadratic component must have method todok')
-    # ensure the matrix is properly read in
-    nnz = quadratic_objective.nnz
-    if lp.parameters.read.qpnonzeros.get() < nnz:
-        lp.parameters.read.qpnonzeros.set(nnz + 1)
-    # Reset the quadratic coefficient if it exists
-    if lp.objective.get_num_quadratic_nonzeros() > 0:
-        lp.objective.set_quadratic((0.,) * lp.variables.get_num())
-
-    quadratic_component_scaled = quadratic_objective.todok()
-
-    lp.parameters.emphasis.numerical.set(1)
-    for k, v in quadratic_component_scaled.items():
-        lp.objective.set_quadratic_coefficients(int(k[0]), int(k[1]), v)
-
-
-def change_variable_bounds(lp, index, lower_bound, upper_bound):
-    lp.variables.set_lower_bounds(index, lower_bound)
-    lp.variables.set_upper_bounds(index, upper_bound)
-
-
-def change_variable_objective(lp, index, objective):
-    lp.objective.set_linear(index, objective)
-
-
-def change_coefficient(lp, met_index, rxn_index, value):
-    lp.linear_constraints.set_coefficients(met_index, rxn_index, value)
-
-
-def update_problem(lp, cobra_model, new_objective=None, **kwargs):
-    """A performance tunable method for updating a model problem file
-
-    lp: A cplex problem object
-
-    cobra_model: the cobra.Model corresponding to 'lp'
-
-    """
-    #When reusing the basis only assume that the objective coefficients or bounds can change
-    #BUG with changing / unchanging the basis
-
-    try:
-        update_problem_reaction_bounds = kwargs['update_problem_reaction_bounds']
-    except:
-        update_problem_reaction_bounds = True
-    try:
-        quadratic_component = kwargs['quadratic_component']
-        if quadratic_component is not None:
-            warn("update_problem does not yet take quadratic_component as a parameter")
-    except:
-        quadratic_component = None
-
-    if new_objective is not None:
-        lp.objective.set_linear([(x.id, float(x.objective_coefficient))
-                                 for x in cobra_model.reactions])
-    if update_problem_reaction_bounds:
-        lp.variables.set_upper_bounds([(x.id, float(x.upper_bound))
-                                        for x in cobra_model.reactions])
-        lp.variables.set_lower_bounds([(x.id, float(x.lower_bound))
-                                        for x in cobra_model.reactions])
-
-
-def solve_problem(lp, **kwargs):
-    # Update parameter settings if provided
-    for k, v in iteritems(kwargs):
-        set_parameter(lp, k, v)
-    lp.solve()
-    # If the solver takes more than 0.1 s with a hot start it is likely stuck
-    return get_status(lp)
-
-
-def solve(cobra_model, **kwargs):
-    """
-
-    """
-    # Start out with default parameters and then modify if
-    # new onese are provided
-    for i in ["new_objective", "update_problem", "the_problem"]:
-        if i in kwargs:
-            raise Exception("Option %s removed" % i)
-    if 'error_reporting' in kwargs:
-        kwargs.pop('error_erporting')
-        warn("error_reporting deprecated")
-
-    # Create problem will get parameter defaults
-    lp = create_problem(cobra_model, **kwargs)
-
-    # Try to solve the problem using other methods if first method doesn't work
-    try:
-        lp_method = the_parameters['lp_method']
-    except:
-        lp_method = 1
-    the_methods = [1, 2, 3, 4, 5, 6]
-    if lp_method in the_methods:
-        the_methods.remove(lp_method)
-    # Start with the user specified method
-    the_methods.insert(0, lp_method)
-    for the_method in the_methods:
-        try:
-            status = solve_problem(lp, lp_method=the_method)
-        except:
-            status = 'failed'
-        if status == 'optimal':
-            break
-    return format_solution(lp, cobra_model)
diff --git a/cobra/solvers/cplex_solver_java.py b/cobra/solvers/cplex_solver_java.py
deleted file mode 100644
index 59a75ab..0000000
--- a/cobra/solvers/cplex_solver_java.py
+++ /dev/null
@@ -1,326 +0,0 @@
-# -*- coding: utf-8 -*-
-# PLEASE NOTE THAT JYTHON SUPPORT (and this jython-only-solver) is deprecated
-#Interface to ilog/cplex 12.4 python / jython interfaces
-#QPs are not yet supported under jython
-from __future__ import absolute_import, print_function
-
-from copy import deepcopy
-from os import name as __name
-from time import time
-from warnings import warn
-
-from six import iteritems
-
-from ilog.concert import IloNumVarType, IloObjectiveSense
-from ilog.cplex import IloCplex
-from ilog.cplex.IloCplex import DoubleParam, IntParam, StringParam
-
-from ..core.solution import Solution
-###solver specific parameters
-from .parameters import (
-    default_objective_sense, objective_senses, parameter_defaults,
-    parameter_mappings, sense_dict, status_dict, variable_kind_dict)
-
-solver_name = 'cplex'
-parameter_defaults = parameter_defaults[solver_name]
-sense_dict = eval(sense_dict[solver_name])
-
-#__solver_class = IloCplex
-status_dict = eval(status_dict[solver_name])
-class Problem(IloCplex):
-    def __init__(self):
-        IloCplex.__init__(self)
-        self._lp_matrix = self.addLPMatrix()
-        self.objective_value = None
-        self._objective_sense = 'maximize'
-    def add_linear_expression(self, linear_expression, metabolite):
-        b = metabolite._bound
-        c = metabolite._constraint_sense
-        the_id = metabolite.id
-        if c == 'E':
-            p = self.addEq(linear_expression, b, the_id)
-        elif c == 'L':
-            p = self.addLe(linear_expression, b, the_id)
-        elif c == 'G':
-            p = self.addGe(linear_expression, b, the_id)
-        else:
-            raise Exception("Constraint sense '%s' for metabolite %s is not valid"%(c,
-                                                                                    the_id))
-        return(p)
-
-__solver_class = Problem
-parameter_mappings = parameter_mappings['%s_%s'%(solver_name,
-                                                 __name)]
-variable_kind_dict = eval(variable_kind_dict['%s_%s'%(solver_name,
-                                                      __name)])
-objective_senses = objective_senses['%s_%s'%(solver_name,
-                                             __name)]
-## from jarray import array as j_array
-## def array(x, variable_type='d'):
-##     return j_array(x, variable_type)
-
-
-def get_status(lp):
-    status = repr(lp.status).lower()
-    if status in status_dict:
-        status = status_dict[status]
-    else:
-        status = 'failed'
-    return status
-
-def set_parameter(lp, parameter_name, parameter_value):
-    if parameter_name == 'objective_sense':
-        objective = lp.getObjective()
-        if objective is not None:
-            objective.setSense(eval(objective_senses[parameter_value]))
-
-    else:
-        if hasattr(DoubleParam, parameter_name):
-            parameter_type = 'DoubleParam'
-        elif hasattr(IntParam, parameter_name):
-            parameter_type = 'IntParam'
-        elif hasattr(StringParam, parameter_name):
-            parameter_type = 'StringParam'
-        else:
-            raise Exception("%s is not a DoubleParam, IntParam, or StringParam"%parameter_name)
-        lp.setParam(eval('%s.%s'%(parameter_type, parameter_name)),
-                    parameter_value)
-
-def get_objective_value(lp):
-    return lp.getObjValue()
-
-def format_solution(lp, cobra_model, **kwargs):
-    """
-    TODO
-    """
-    status = get_status(lp)
-    try:
-        x = lp.getValues(lp.variables)
-        x_dict = dict(zip(cobra_model.reactions, x))
-        objective_value = lp.getObjValue()
-    except:
-        x = x_dict = objective_value = None
-        #print status
-
-    try:
-        y = lp.getDuals(lp.variables)
-        y_dict = dict(zip(cobra_model.metabolites, y))
-    except:
-        y = y_dict = None
-    return(Solution(objective_value, x=x, x_dict=x_dict, y=y,
-                    y_dict=y_dict, status=status))
-
-def create_problem(cobra_model,  **kwargs):
-    """Solver-specific method for constructing a solver problem from
-    a cobra.Model.  This can be tuned for performance using kwargs
-
-    TODO: This will need to be specific for python / jython
-    """
-    the_parameters = parameter_defaults
-    if kwargs:
-        the_parameters = deepcopy(parameter_defaults)
-        the_parameters.update(kwargs)
-
-    lp = Problem()
-
-    if 'log_file' not in the_parameters:
-        lp.setWarning(None)
-        lp.setOut(None)
-    [set_parameter(lp, parameter_mappings[k], v)
-     for k, v in iteritems(the_parameters) if k in parameter_mappings]
-    quadratic_component = the_parameters['quadratic_component']
-    new_objective = the_parameters['new_objective']
-    error_reporting = the_parameters['error_reporting']
-    lp._objective_sense  = the_parameters['objective_sense']
-    if 'relax_b' in the_parameters:
-        warn('need to reimplement relax_b')
-        relax_b = False
-    else:
-        relax_b = False
-
-    #Using the new objects
-    #NOTE: This might be slow
-    objective_coefficients = []
-    lower_bounds = []
-    upper_bounds = []
-    variable_names = []
-    variable_kinds = []
-    [(objective_coefficients.append(x.objective_coefficient),
-      lower_bounds.append(x.lower_bound),
-      upper_bounds.append(x.upper_bound),
-      variable_names.append(x.id),
-      variable_kinds.append(variable_kind_dict[x.variable_kind]))
-     for x in cobra_model.reactions]
-
-    #Only add the variable types if one or more variables is an integer, just
-    #in case the java interface has the same bug as the python interface where
-    #the problem type switches to integer if variable types are supplied even
-    #if all are continuous
-    if variable_kind_dict['integer'] in variable_kinds:
-        lp.variables = lp.numVarArray(len(cobra_model.reactions), lower_bounds,
-                                      upper_bounds, variable_kinds, variable_names)
-    else:
-        lp.variables = lp.numVarArray(len(cobra_model.reactions), lower_bounds, upper_bounds,
-                                      variable_names)
-    
-    lp.variable_dict = dict(zip(cobra_model.reactions, lp.variables))
-    if lp._objective_sense == 'maximize':
-        __lp_add_objective = lp.addMaximize
-    else:
-        __lp_add_objective = lp.addMinimize
-
-    __lp_add_objective(lp.scalProd(lp.variables, objective_coefficients))
-    
-
-    
-    lp.constraints = []
-    lp.constraint_dict = {}
-    for the_metabolite in cobra_model.metabolites:
-        linear_expression = lp.sum([lp.prod(k._metabolites[the_metabolite],
-                                            lp.variable_dict[k])
-                                    for k in the_metabolite._reaction])
-        expression_pointer = lp.add_linear_expression(linear_expression, the_metabolite)
-        lp.constraints.append(expression_pointer)
-        lp.constraint_dict[the_metabolite] = expression_pointer
-    
-    if quadratic_component is not None:
-        raise Exception("cplex through java isn't configured for QPs yet")
-        if not hasattr(quadratic_component, 'todok'):
-            raise Exception('quadratic component must have method todok')
-        quadratic_component_scaled = quadratic_component.todok()
-
-        lp.parameters.emphasis.numerical.set(1)
-        for k, v in quadratic_component_scaled.items():
-            lp.objective.set_quadratic_coefficients(int(k[0]), int(k[1]), v)
-
-    ## #Set the problem type as cplex doesn't appear to do this correctly
-    ## problem_type = Cplex.problem_type.LP
-    ## if Cplex.variables.type.integer in variable_kinds:
-    ##     if quadratic_component is not None:
-    ##         problem_type = Cplex.problem_type.MIQP
-    ##     else:
-    ##         problem_type = Cplex.problem_type.MILP
-    ## elif quadratic_component is not None:
-    ##     problem_type = Cplex.problem_type.QP
-    ## lp.set_problem_type(problem_type)
-    return(lp)
-
-
-def update_problem(lp, cobra_model, **kwargs):
-    """A performance tunable method for updating a model problem file
-
-    lp: A gurobi problem object
-
-    cobra_model: the cobra.Model corresponding to 'lp'
-
-    """
-    #When reusing the basis only assume that the objective coefficients or bounds can change
-    #BUG with changing / unchanging the basis
-    try:
-        new_objective = kwargs['new_objective']
-    except:
-        new_objective = None
-    try:
-        update_problem_reaction_bounds = kwargs['update_problem_reaction_bounds']
-    except:
-        update_problem_reaction_bounds = True
-    try:
-        quadratic_component = kwargs['quadratic_component']
-        if quadratic_component is not None:
-            warn("update_problem does not yet take quadratic_component as a parameter")
-    except:
-        quadratic_component = None
-
-    the_objective = lp.getObjective()
-    for the_variable, the_reaction in zip(lp.variables, cobra_model.reactions):
-        the_variable.setUB(float(the_reaction.upper_bound))
-        the_variable.setLB(float(the_reaction.lower_bound))
-        the_objective.setLinearCoef(the_variable, the_reaction.objective_coefficient)
-            
-
-
-###
-def solve_problem(lp, **kwargs):
-    """A performance tunable method for solving a problem
-
-    """
-    #Update parameter settings if provided
-    if kwargs:
-        [set_parameter(lp, parameter_mappings[k], v)
-         for k, v in iteritems(kwargs) if k in parameter_mappings]
-    try:
-        the_problem = kwargs['the_problem']
-    except:
-        the_problem = False
-    if isinstance(the_problem, __solver_class):
-        try:
-            the_basis = the_problem.solution.basis.get_basis()
-            lp.start.set_basis(the_basis[0],the_basis[1])
-            lp.parameters.preprocessing.presolve.set(0)
-        except:
-            warn("cplex_java isn't yet configured to reuse the basis")
-    
-    lp.solve()
-    #If the solver takes more than 0.1 s with a hot start it is likely stuck
-    status = get_status(lp)
-    return status
-
-    
-def solve(cobra_model, **kwargs):
-    """
-
-    """
-    #Start out with default parameters and then modify if
-    #new onese are provided
-    the_parameters = deepcopy(parameter_defaults)
-    if kwargs:
-        the_parameters.update(kwargs)
-    #Update objectives if they are new.
-    if 'new_objective' in the_parameters:
-        raise ValueError("new_objective option removed")
-
-    if 'the_problem' in the_parameters:
-        the_problem = the_parameters['the_problem']
-    else:
-        the_problem = None
-    if 'error_reporting' in the_parameters:
-        error_reporting = the_parameters['error_reporting']
-    else:
-        error_reporting = False
-    if isinstance(the_problem, __solver_class):
-        #Update the problem with the current cobra_model
-        lp = the_problem
-        update_problem(lp, cobra_model, **the_parameters)
-    else:
-        #Create a new problem
-        lp = create_problem(cobra_model, **the_parameters)
-    #Deprecated way for returning a solver problem created from a cobra_model
-    #without performing optimization
-    if the_problem == 'setup':
-        return lp
-
-    ###Try to solve the problem using other methods if the first method doesn't work
-    try:
-        lp_method = the_parameters['lp_method']
-    except:
-        lp_method = 1
-    the_methods = [1, 2, 3, 4, 5, 6]
-    if lp_method in the_methods:
-        the_methods.remove(lp_method)
-    #Start with the user specified method
-    the_methods.insert(0, lp_method)
-    for the_method in the_methods:
-        the_parameters['lp_method'] = the_method
-        try:
-            status = solve_problem(lp, **the_parameters)
-        except:
-            status = 'failed'
-        if status == 'optimal':
-            break
-
-    the_solution = format_solution(lp, cobra_model)
-    if status != 'optimal' and error_reporting:
-        print('{:s} failed: {:s}'.format(solver_name, status))
-    cobra_model.solution = the_solution
-    solution = {'the_problem': lp, 'the_solution': the_solution}
-    return solution
diff --git a/cobra/solvers/esolver.py b/cobra/solvers/esolver.py
deleted file mode 100644
index 12a08c5..0000000
--- a/cobra/solvers/esolver.py
+++ /dev/null
@@ -1,161 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
-
-from fractions import Fraction
-from os import devnull, unlink
-from os.path import isfile
-from subprocess import CalledProcessError, check_call, check_output
-from tempfile import NamedTemporaryFile
-
-from six.moves import zip
-
-from cobra.core.solution import LegacySolution
-from cobra.solvers import cglpk
-from cobra.solvers.wrappers import *
-
-# detect paths to system calls for esolver and gzip
-with open(devnull, "w") as DEVNULL:
-    try:
-        ESOLVER_COMMAND = check_output(["which", "esolver"],
-                                       stderr=DEVNULL).strip()
-        __esolver_version__ = check_output(["esolver", "-v"], stderr=DEVNULL)
-    except CalledProcessError:
-        raise RuntimeError("esolver command not found")
-    try:
-        GZIP_COMMAND = check_output(["which", "gzip"], stderr=DEVNULL).strip()
-    except CalledProcessError:
-        raise RuntimeError("gzip command not found")
-del DEVNULL
-
-solver_name = "esolver"
-
-
-class Esolver(cglpk.GLP):
-    """contain an LP which will be solved through the QSopt_ex
-
-    The LP is stored using a GLPK object, and written out to an
-    LP file which is then solved by the esolver command."""
-
-    def __init__(self, cobra_model=None):
-        cglpk.GLP.__init__(self, cobra_model)
-        self.solution_filepath = None
-        self.basis_filepath = None
-        self.rational_solution = False
-        self.verbose = False
-        self.clean_up = True  # clean up files
-
-    def _clean(self, filename):
-        """remove old files"""
-        if self.clean_up and filename is not None and isfile(filename):
-            unlink(filename)
-
-    def set_parameter(self, parameter_name, value):
-        if parameter_name == "GLP":
-            raise Exception("can not be set this way")
-        if parameter_name == "objective_sense":
-            self.set_objective_sense(value)
-        if not hasattr(self, parameter_name):
-            raise ValueError("Unkonwn parameter '%s'" % parameter_name)
-        setattr(self, parameter_name, value)
-
-    def solve_problem(self, **solver_parameters):
-        if "objective_sense" in solver_parameters:
-            self.set_objective_sense(solver_parameters.pop("objective_sense"))
-        for key, value in solver_parameters.items():
-            self.set_parameter(key, value)
-        # remove the old solution file
-        self._clean(self.solution_filepath)
-        with NamedTemporaryFile(suffix=".lp", delete=False) as f:
-            lp_filepath = f.name
-        self.write(lp_filepath)
-        existing_basis = self.basis_filepath
-        with NamedTemporaryFile(suffix=".bas", delete=False) as f:
-            self.basis_filepath = f.name
-        with NamedTemporaryFile(suffix=".sol") as f:
-            self.solution_filepath = f.name
-        command = [ESOLVER_COMMAND, "-b", self.basis_filepath,
-                   "-O", self.solution_filepath[:-4]]
-        if existing_basis is not None and isfile(existing_basis):
-            command.extend(["-B", existing_basis])
-        command.extend(["-L", lp_filepath])
-        command_kwargs = {}
-        if self.verbose:
-            print(" ".join(command))
-            DEVNULL = None
-        else:
-            DEVNULL = open(devnull, 'wb')
-            command_kwargs["stdout"] = DEVNULL
-            command_kwargs["stderr"] = DEVNULL
-        try:
-            check_call(command, **command_kwargs)
-            failed = False
-        except CalledProcessError as e:
-            failed = True
-        if failed:
-            self.basis_filepath = existing_basis
-            existing_basis = None
-            # Sometimes on failure a solution isn't written out
-            if not isfile(self.solution_filepath):
-                with open(self.solution_filepath, "w") as outfile:
-                    outfile.write("=infeasible\n")
-        elif isfile(self.solution_filepath + ".gz"):
-            # the solution may be written out compressed
-            check_call([GZIP_COMMAND, "-d", self.solution_filepath + ".gz"])
-        if DEVNULL is not None:
-            DEVNULL.close()
-        self._clean(lp_filepath)
-        self._clean(existing_basis)  # replaced with the new basis
-
-    def get_status(self):
-        with open(self.solution_filepath) as infile:
-            return infile.readline().split("=")[1].strip().lower()
-
-    def _format(self, value):
-        """convert a string value into either a fraction or float"""
-        value = Fraction(value)
-        return value if self.rational_solution else float(value)
-
-    def get_objective_value(self):
-        with open(self.solution_filepath) as infile:
-            status = infile.readline().split("=")[1].strip().lower()
-            if status != "optimal":
-                raise RuntimeError("status not optimal")
-            infile.readline()
-            return self._format(infile.readline().split("=")[1].strip())
-
-    def format_solution(self, cobra_model):
-        m = cobra_model
-        solution = LegacySolution(None)
-        with open(self.solution_filepath) as infile:
-            solution.status = infile.readline().split("=")[1].strip().lower()
-            if solution.status != "optimal":
-                return solution
-            infile.readline()
-            solution.f = self._format(Fraction(infile.readline()
-                                               .split("=")[1].strip()))
-            infile.readline()
-            value_dict = {}
-            for line in infile:
-                if line.endswith(":\n"):
-                    break
-                varname, value = line.split("=")
-                value_dict[varname.strip()] = self._format(value.strip())
-            dual_dict = {}
-            for line in infile:
-                if line.endswith(":\n"):
-                    break
-                varname, value = line.split("=")
-                dual_dict[varname.strip()] = self._format(value.strip())
-        solution.x = [value_dict.get("x_%d" % (i + 1), 0)
-                      for i in range(len(m.reactions))]
-        solution.x_dict = {r.id: v for r, v in zip(m.reactions, solution.x)}
-        solution.y = [dual_dict.get("r_%d" % (i + 1), 0)
-                      for i in range(len(m.metabolites))]
-        solution.y_dict = {m.id: v for m, v in zip(m.metabolites, solution.y)}
-        return solution
-
-
-# wrappers for the classmethods at the module level
-create_problem = Esolver.create_problem
-solve = Esolver.solve
diff --git a/cobra/solvers/glpk.pxd b/cobra/solvers/glpk.pxd
deleted file mode 100644
index 91993b2..0000000
--- a/cobra/solvers/glpk.pxd
+++ /dev/null
@@ -1,218 +0,0 @@
-#inspired by sage/src/sage/numerical/backends/glpk_backend.pxd
-
-cdef extern from "glpk.h":
-    ctypedef struct glp_prob "glp_prob":
-        pass
-    ctypedef struct glp_iocp "glp_iocp":
-        int msg_lev
-        int br_tech
-        int bt_tech
-        int pp_tech
-        int fp_heur
-        int gmi_cuts
-        int mir_cuts
-        int cov_cuts
-        int clq_cuts
-        double tol_int
-        double tol_obj
-        double mip_gap
-        int tm_lim
-        int out_frq
-        int out_dly
-        int presolve
-        int binarize
-    ctypedef struct glp_smcp "glp_smcp":
-        int msg_lev
-        int meth
-        int pricing
-        int r_test
-        double tol_bnd
-        double tol_dj
-        double tol_piv
-        double obj_ll
-        double obj_ul
-        int it_lim
-        int tm_lim
-        int out_frq
-        int out_dly
-        int presolve
-    glp_iocp * new_glp_iocp "new glp_iocp" ()
-    void glp_init_iocp(glp_iocp *)
-    void glp_init_smcp(glp_smcp *)
-    glp_prob * glp_create_prob()
-    void glp_set_prob_name(glp_prob *, char *)
-    void glp_set_obj_dir(glp_prob *, int)
-    void glp_add_rows(glp_prob *, int)
-    void glp_add_cols(glp_prob *, int)
-    void glp_del_rows(glp_prob *, int, int *)
-    void glp_set_row_name(glp_prob *, int, char *)
-    void glp_set_col_name(glp_prob *, int, char *)
-    void glp_set_row_bnds(glp_prob *, int, int, double, double)
-    void glp_set_col_bnds(glp_prob *, int, int, double, double)
-    void glp_set_obj_coef(glp_prob *, int, double)
-    void glp_load_matrix(glp_prob *, int, int *, int *, double *)
-    int glp_simplex(glp_prob *, glp_smcp *)
-    int glp_exact(glp_prob *, glp_smcp *)  # requires gmp
-    int glp_intopt(glp_prob *, glp_iocp *)
-    void glp_std_basis(glp_prob *)
-    void glp_delete_prob(glp_prob *)
-    double glp_get_col_prim(glp_prob *, int)
-    double glp_get_obj_val(glp_prob *)
-    double glp_get_col_dual(glp_prob *, int)
-    double glp_get_row_dual(glp_prob *, int)
-    int glp_print_ranges(glp_prob *lp, int,int, int, char *fname)
-    int glp_get_num_rows(glp_prob *)
-    int glp_get_num_cols(glp_prob *)
-    int glp_get_num_int(glp_prob *)
-    double glp_mip_col_val(glp_prob *, int)
-    double glp_mip_obj_val(glp_prob *)
-    void glp_set_col_kind(glp_prob *, int, int)
-    int glp_write_mps(glp_prob *lp, int fmt, void *parm, char *fname)
-    int glp_write_lp(glp_prob *lp, void *parm, char *fname)
-    int glp_write_prob(glp_prob *P, int flags, char *fname)
-    int glp_read_prob(glp_prob *P, int flags, char *fname)
-
-    void glp_set_prob_name(glp_prob *lp, char *name)
-    void glp_set_obj_name(glp_prob *lp, char *name)
-    void glp_set_row_name(glp_prob *lp, int i, char *name)
-    void glp_set_col_name(glp_prob *lp, int i, char *name)
-
-    double glp_get_row_ub(glp_prob *lp, int i)
-    double glp_get_row_lb(glp_prob *lp, int i)
-
-    double glp_get_col_ub(glp_prob *lp, int i)
-    double glp_get_col_lb(glp_prob *lp, int i)
-    void glp_set_col_ub(glp_prob *lp, int i, double value)
-    void glp_set_col_lb(glp_prob *lp, int i, double value)
-
-
-    void glp_create_index(glp_prob *P)
-    int glp_find_row(glp_prob *P, const char *name)
-    int glp_find_col(glp_prob *P, const char *name)
-    void glp_delete_index(glp_prob *P)
-
-    double glp_get_col_lb(glp_prob *lp, int i)
-    double glp_get_col_ub(glp_prob *lp, int i)
-
-    void glp_scale_prob(glp_prob *lp, int flags)
-    void glp_unscale_prob(glp_prob *lp)
-
-    int glp_get_prim_stat(glp_prob *lp)
-    int glp_get_status(glp_prob *lp)
-    int glp_mip_status(glp_prob *lp)
-    int glp_get_num_nz(glp_prob *lp)
-    int glp_set_mat_row(glp_prob *lp, int, int, int *, double * )
-    int glp_set_mat_col(glp_prob *lp, int, int, int *, double * )
-    int glp_get_mat_row(glp_prob *lp, int, int *, double * )
-    int glp_get_mat_col(glp_prob *lp, int, int *, double * )
-    double glp_get_row_ub(glp_prob *lp, int)
-    double glp_get_row_lb(glp_prob *lp, int)
-    int glp_get_col_kind(glp_prob *lp, int)
-    double glp_get_obj_coef(glp_prob *lp, int)
-    int glp_get_obj_dir(glp_prob *lp)
-    void glp_copy_prob(glp_prob *dst, glp_prob *src, int names)
-
-    const char *glp_version()
-
-    # output redirection
-    int glp_term_out(int flag)
-    void glp_term_hook(int (*func)(void *info, const char *s), void *info)
-
-    int glp_warm_up(glp_prob *P)
-    void glp_adv_basis(glp_prob *P, int flags)
-
-    # constants
-
-    # constants for smcp control
-
-    int GLP_MSG_OFF
-    int GLP_MSG_ERR
-    int GLP_MSG_ON
-    int GLP_MSG_ALL
-
-    int GLP_PRIMAL
-    int GLP_DUALP
-    int GLP_DUAL
-
-    int GLP_PT_STD
-    int GLP_PT_PSE
-
-    int GLP_RT_STD
-    int GLP_RT_HAR
-
-    double DBL_MAX
-
-    int INT_MAX
-
-    int GLP_ON
-    int GLP_OFF
-
-    # constants for scaling the problem
-    int GLP_SF_AUTO
-    int GLP_SF_GM
-    int GLP_SF_EQ
-    int GLP_SF_2N
-    int GLP_SF_SKIP
-
-    # constants for iocp control, not already in simplex
-
-    int GLP_BR_FFV
-    int GLP_BR_LFV
-    int GLP_BR_MFV
-    int GLP_BR_DTH
-    int GLP_BR_PCH
-
-    int GLP_BT_DFS
-    int GLP_BT_BFS
-    int GLP_BT_BLB
-    int GLP_BT_BPH
-
-    int GLP_PP_NONE
-    int GLP_PP_ROOT
-    int GLP_PP_ALL
-
-    # error codes
-    int GLP_EBADB
-    int GLP_ESING
-    int GLP_ECOND
-    int GLP_EBOUND
-    int GLP_EFAIL
-    int GLP_EOBJLL
-    int GLP_EOBJUL
-    int GLP_EITLIM
-    int GLP_ETMLIM
-    int GLP_ENOPFS
-    int GLP_ENODFS
-    int GLP_EROOT
-    int GLP_ESTOP
-    int GLP_EMIPGAP
-    int GLP_ENOFEAS
-    int GLP_ENOCVG
-    int GLP_EINSTAB
-    int GLP_EDATA
-    int GLP_ERANGE
-
-
-    int GLP_UNDEF
-    int GLP_OPT
-    int GLP_FEAS
-    int GLP_NOFEAS
-    int GLP_INFEAS
-    int GLP_UNBND
-
-    # other constants
-
-    int GLP_MAX
-    int GLP_MIN
-    int GLP_UP
-    int GLP_FR
-    int GLP_DB
-    int GLP_FX
-    int GLP_LO
-    int GLP_CV
-    int GLP_IV
-    int GLP_BV
-    int GLP_MPS_DECK
-    int GLP_MPS_FILE
-
-    int GLP_MSG_DBG
diff --git a/cobra/solvers/glpk_solver.py b/cobra/solvers/glpk_solver.py
deleted file mode 100644
index e30f08a..0000000
--- a/cobra/solvers/glpk_solver.py
+++ /dev/null
@@ -1,276 +0,0 @@
-# -*- coding: utf-8 -*-
-##cobra.solvers.glpk_solver
-#This script provides wrappers for pyglpk 0.3
-from __future__ import absolute_import
-
-from copy import deepcopy
-from warnings import warn
-
-from six import iteritems
-
-from glpk import LPX
-
-from cobra.core.solution import LegacySolution
-
-try:
-    # Import izip for python versions < 3.x
-    from itertools import izip as zip
-except ImportError:
-    pass
-
-
-
-solver_name = 'glpk'
-_SUPPORTS_MILP = True
-
-# solver specific parameters
-variable_kind_dict = {'continuous': float, 'integer': int}
-status_dict = {'opt': 'optimal', 'nofeas': 'infeasible', 'unbnd': 'unbounded'}
-parameter_defaults = {
-    'tolerance_feasibility': 1e-6,
-    'tolerance_integer': 1e-9,
-    'lp_method': 1
-}
-
-METHOD_TYPES = {"auto": 2, "primal": 1, "dual": 3}
-
-def get_status(lp):
-    status = lp.status
-    if status in status_dict:
-        status = status_dict[status]
-    else:
-        status = 'failed'
-    return status
-
-def get_objective_value(lp):
-    return lp.obj.value
-
-def format_solution(lp, cobra_model, **kwargs):
-    status = get_status(lp)
-    if status == 'optimal':
-        sol = LegacySolution(lp.obj.value, status=status)
-        sol.x = [float(c.primal) for c in lp.cols]
-        sol.x_dict = {c.name: c.primal for c in lp.cols}
-
-        # return the duals as well as the primals for LPs
-        if lp.kind == float:
-            sol.y = [float(c.dual) for c in lp.rows]
-            sol.y_dict = {c.name: c.dual for c in lp.rows}
-        return sol
-
-    return LegacySolution(None, status=status)
-
-def set_parameter(lp, parameter_name, parameter_value):
-    """with pyglpk the parameters are set during the solve phase, with
-    the exception of objective sense.
-
-    """
-    if parameter_name == 'objective_sense':
-        if parameter_value.lower() == 'maximize':
-            lp.obj.maximize = True
-        elif parameter_value.lower() == 'minimize':
-            lp.obj.maximize = False
-        else:
-            raise ValueError("objective_sense should be "
-                             "'maximize' or 'minimize'")
-    else:
-        # This will be made into an exception in the future
-        warn("pyglpk parameters (other than objective_sense) are set "
-             "during solve_problem")
-
-
-def create_problem(cobra_model, **kwargs):
-    """Solver-specific method for constructing a solver problem from
-    a cobra.Model.  This can be tuned for performance using kwargs
-
-    """
-    metabolite_to_index = {r: i for i, r in enumerate(cobra_model.metabolites)}
-
-    lp = LPX()        # Create empty problem instance
-    lp.name = 'cobra'     # Assign symbolic name to problem
-    lp.rows.add(len(cobra_model.metabolites))
-    lp.cols.add(len(cobra_model.reactions))
-
-    for r, the_metabolite in zip(lp.rows, cobra_model.metabolites):
-        r.name = the_metabolite.id
-        b = float(the_metabolite._bound)
-        c = the_metabolite._constraint_sense
-        if c == 'E':
-            r.bounds = b, b     # Set metabolite to steady state levels
-        elif c == 'L':
-            r.bounds = None, b
-        elif c == 'G':
-            r.bounds = b, None
-        else:
-            raise ValueError("invalid constraint sense")
-
-    objective_coefficients = []
-    linear_constraints = []
-    for c, the_reaction in zip(lp.cols, cobra_model.reactions):
-        c.name = the_reaction.id
-        c.kind = variable_kind_dict[the_reaction.variable_kind]
-        c.bounds = the_reaction.lower_bound, the_reaction.upper_bound
-        objective_coefficients.append(float(the_reaction.objective_coefficient))
-        for metabolite, coefficient in iteritems(the_reaction._metabolites):
-            metabolite_index = metabolite_to_index[metabolite]
-            linear_constraints.append((metabolite_index, c.index, coefficient))
-
-    #Add the new objective coefficients to the problem
-    lp.obj[:] = objective_coefficients
-    #Need to assign lp.matrix after constructing the whole list
-    #linear_constraints.sort()  # if we wanted to be 100% deterministic
-    lp.matrix = linear_constraints
-
-    # make sure the objective sense is set in create_problem
-    objective_sense = kwargs.get("objective_sense", "maximize")
-    set_parameter(lp, "objective_sense", objective_sense)
-
-    return lp
-
-
-def change_variable_bounds(lp, index, lower_bound, upper_bound):
-    lp.cols[index].bounds = (lower_bound, upper_bound)
-
-
-def change_variable_objective(lp, index, objective):
-    lp.obj[index] = objective
-
-
-def update_problem(lp, cobra_model, **kwargs):
-    """A performance tunable method for updating a model problem file
-
-    lp: A gurobi problem object
-
-    cobra_model: the cobra.Model corresponding to 'lp'
-
-    """
-    #When reusing the basis only assume that the objective coefficients or bounds can change
-    #BUG with changing / unchanging the basis
-    index_to_metabolite = dict(zip(range(len(cobra_model.metabolites)),
-                                   cobra_model.metabolites))
-    index_to_reaction = dict(zip(range(len(cobra_model.reactions)),
-                                 cobra_model.reactions))
-    reaction_to_index = dict(zip(index_to_reaction.values(),
-                                 index_to_reaction.keys()))
-
-    try:
-        new_objective = kwargs['new_objective']
-    except:
-        new_objective = None
-    if new_objective is not None:
-        objective_coefficients = []
-        for c in lp.cols:      # Iterate over all rows
-            the_reaction = index_to_reaction[c.index]
-            c.name = the_reaction.id
-            c.bounds = the_reaction.lower_bound, the_reaction.upper_bound
-            objective_coefficients.append(float(the_reaction.objective_coefficient))
-            c.kind = variable_kind_dict[the_reaction.variable_kind]
-        #Add the new objective coefficients to the problem
-        lp.obj[:] = objective_coefficients
-    else:
-        for c in lp.cols:      # Iterate over all rows
-            the_reaction = index_to_reaction[c.index]
-            c.name = the_reaction.id
-            c.bounds = the_reaction.lower_bound, the_reaction.upper_bound
-            c.kind = variable_kind_dict[the_reaction.variable_kind]
-
-def change_coefficient(lp, met_index, rxn_index, value):
-    col = lp.cols[rxn_index]
-    mat = col.matrix
-    for i, entry in enumerate(mat):
-        if entry[0] == met_index:
-            mat[i] = (met_index, value)
-            col.matrix = mat
-            return
-    # need to append
-    mat.append((met_index, value))
-    col.matrix = mat
-
-
-def solve_problem(lp, **kwargs):
-    """A performance tunable method for updating a model problem file
-
-    lp: a pyGLPK 0.3 problem
-
-    For pyGLPK it is necessary to provide the following parameters, if they
-    are not provided then the default settings will be used: tolerance_feasibility,
-    tolerance_integer, lp_method, and objective_sense
-
-    """
-    parameters = parameter_defaults.copy()
-    parameters.update(kwargs)
-    if "quadratic_component" in parameters:
-        if parameters.pop('quadratic_component') is not None:
-            raise Exception('glpk cannot solve QPs')
-    lp_args = {}  # only for lp
-    extra_args = {}  # added to both lp and milp
-    lp_args["tol_bnd"] = parameters.pop("tolerance_feasibility")
-    lp_args["tol_dj"] = lp_args["tol_bnd"]
-    method = parameters.pop("lp_method")
-    if isinstance(method, int):
-        lp_args["meth"] = method
-    else:
-        lp_args["meth"] = METHOD_TYPES[method]
-    if "time_limit" in parameters:
-        extra_args["tm_lim"] = int(parameters.pop("time_limit") * 1000)
-    if "iteration_limit" in parameters:
-        extra_args["it_lim"] = parameters.pop("iteration_limit")
-    if "objective_sense" in parameters:
-        set_parameter(lp, "objective_sense", parameters.pop("objective_sense"))
-    tol_int = parameters.pop("tolerance_integer")
-    if len(parameters) > 0:
-        raise ValueError("Unknown parameters: " + ", ".join(parameters))
-    lp_args.update(extra_args)
-    # solve the problem
-    lp.simplex(**lp_args)
-    if lp.kind == int:
-        # For MILPs, it is faster to solve LP then move to MILP
-        lp.integer(tol_int=tol_int, **extra_args)
-    return get_status(lp)
-
-
-def solve(cobra_model, **kwargs):
-    """Smart interface to optimization solver functions that will convert
-    the cobra_model to a solver object, set the parameters, and try multiple
-    methods to get an optimal solution before returning the solver object and
-    a cobra.LegacySolution
-
-    cobra_model: a cobra.Model
-
-    returns a dict: {'the_problem': solver specific object, 'the_solution':
-    cobra.solution for the optimization problem'}
-
-
-    """
-    #Start out with default parameters and then modify if
-    #new onese are provided
-    the_parameters = deepcopy(parameter_defaults)
-    if kwargs:
-        the_parameters.update(kwargs)
-
-    for i in ["new_objective", "update_problem", "the_problem"]:
-        if i in the_parameters:
-            raise Exception("Option %s removed" % i)
-    if 'error_reporting' in the_parameters:
-        warn("error_reporting deprecated")
-
-    #Create a new problem
-    lp = create_problem(cobra_model, **the_parameters)
-
-    ###Try to solve the problem using other methods if the first method doesn't work
-    lp_method = the_parameters['lp_method']
-    the_methods = [1, 2, 3]
-    if lp_method in the_methods:
-        the_methods.remove(lp_method)
-    #Start with the user specified method
-    the_methods.insert(0, lp_method)
-    for the_method in the_methods:
-        the_parameters['lp_method'] = the_method
-        try:
-            status = solve_problem(lp, **the_parameters)
-        except Exception as e:
-            status = 'failed'
-        if status == 'optimal':
-            break
-
-    return format_solution(lp, cobra_model)
diff --git a/cobra/solvers/glpk_solver_java.py b/cobra/solvers/glpk_solver_java.py
deleted file mode 100644
index 6aec7d6..0000000
--- a/cobra/solvers/glpk_solver_java.py
+++ /dev/null
@@ -1,372 +0,0 @@
-# -*- coding: utf-8 -*-
-# PLEASE NOTE THAT JYTHON SUPPORT (and this jython-only-solver) is deprecated
-#This script provides wrappers for libglpk-java 1.0.22 and pyglpk 0.3
-from __future__ import absolute_import, print_function
-
-from copy import deepcopy
-from os import name
-from time import time
-from warnings import warn
-
-from six import iteritems
-
-from org.gnu.glpk import GLPK, GLPKConstants, glp_iocp, glp_smcp
-
-from ..core.solution import Solution
-###solver specific parameters
-from .parameters import (
-    default_objective_sense, objective_senses, parameter_defaults,
-    parameter_mappings, sense_dict, status_dict, variable_kind_dict)
-
-solver_name = 'glpk'
-sense_dict = eval(sense_dict[solver_name])
-#Functions that are different for java implementation of a solver
-
-if name != "java":
-    raise Exception("jython only")
-
-warn("cobra.solvers.glpk_solver isn't mature.  consider using gurobi or cplex")
-variable_kind_dict = eval(variable_kind_dict['%s_%s'%(solver_name,
-                                            __name)])
-status_dict = eval(status_dict['%s_%s'%(solver_name,
-                                            __name)])
-objective_senses = objective_senses['%s_%s'%(solver_name,
-                                            __name)]
-parameter_mappings = parameter_mappings['%s_%s'%(solver_name,
-                                                 __name)]
-parameter_defaults = parameter_defaults['%s_%s'%(solver_name,
-                                                 __name)]
-
-class Problem():
-    """Create a more pythonesqe class to wrap the key
-    features of the libglpk-java functions.
-    
-    """
-    def __init__(self):
-        """the attributes g, lp, mip should be made private
-        """
-        self._g = GLPK
-        self._lp= GLPK.glp_create_prob()
-        self._simplex_parameters = glp_smcp()
-        self._mip_parameters = None
-        self._g.glp_init_smcp(self._simplex_parameters)
-        self.status = self.objective_value = None
-        self._mip = False
-    def set_name(self, name=''):
-        self._g.glp_set_prob_name(self._lp, name)
-
-    def solve(self):
-        try:
-            self._g.glp_simplex(self._lp,
-                               self._simplex_parameters)
-            if self._mip:
-                #perform the MIP
-                setattr(self._mip_parameters, 'msg_lev',
-                         self._simplex_parameters.msg_lev)
-                self._g.glp_intopt(self._lp, self._mip_parameters)
-            self.status = self.get_status()
-            self.objective_value = self.get_objective_value()
-        except:
-            self.status = 'failed'
-        return self.status
-
-    def get_status(self):
-        if self._mip:
-            status = self._g.glp_mip_status(self._lp)
-        else:
-            status = self._g.glp_get_status(self._lp)
-        return status_dict[status]
-    
-    def set_objective_sense(self, parameter_value='maximize'):
-        self._g.glp_set_obj_dir(self._lp,
-                               eval(objective_senses[parameter_value]))
-
-    def set_parameter(self, parameter_name, parameter_value, warning=False):
-        if parameter_name == 'objective_sense':
-            self.set_objective_sense(parameter_value)
-        else:
-            if parameter_name == 'meth' and parameter_value not in [1,2,3]:
-                parameter_value = 1
-            try:
-                setattr(self._simplex_parameters, parameter_name,
-                        parameter_value)
-            except Exception as e1:
-                try:
-                    setattr(self._mip_parameters, parameter_name,
-                            parameter_value)
-                except Exception as e2:
-                    if warning:
-                        print("Could not set simplex parameter " +\
-                              "{:s}: {:s}".format(parameter_name, repr(e1)))
-                        
-                        if self._mip_parameters is not None:
-                            print("Could not set mip parameter " +\
-                                  "{:s}: {:s}".format(parameter_name, repr(e2)))
-    def get_objective_value(self):
-        if self._mip:
-            tmp_value = self._g.glp_mip_obj_val(self._lp)
-        else:
-            tmp_value = self._g.glp_get_obj_val(self._lp)
-        return tmp_value
-
-    def create_problem(self, cobra_model):
-        g = self._g
-        lp = self._lp
-        number_of_reactions = len(cobra_model.reactions)
-        number_of_metabolites = len(cobra_model.metabolites)
-        g.glp_add_cols(lp, number_of_reactions)
-        reaction_to_index = {}
-        objective_dict = {}
-        #Add in the variables
-        tmp_kinds = []
-        for i, the_reaction in enumerate(cobra_model.reactions):
-            i_offset = i + 1
-            reaction_to_index[the_reaction] = i_offset
-            if the_reaction.objective_coefficient != 0:
-                objective_dict[i_offset] = the_reaction.objective_coefficient
-            g.glp_set_col_name(lp, i_offset, the_reaction.id)
-            tmp_kinds.append(the_reaction.variable_kind)
-            the_kind = variable_kind_dict[the_reaction.variable_kind]
-            lower_bound = the_reaction.lower_bound
-            upper_bound = the_reaction.upper_bound
-            #Note. It is possible to have unbounded or one-bound variables
-            if lower_bound == upper_bound:
-                bound_kind = GLPKConstants.GLP_FX
-            else:
-                bound_kind = GLPKConstants.GLP_DB
-            g.glp_set_col_kind(lp, i_offset, the_kind)
-            g.glp_set_col_bnds(lp, i_offset,
-                               bound_kind, the_reaction.lower_bound,
-                               the_reaction.upper_bound)
-        tmp_kinds = set(tmp_kinds)
-        if 'integer' in tmp_kinds or 'binary' in tmp_kinds:
-            self._mip = True
-            self._mip_parameters = glp_iocp()
-            g.glp_init_iocp(self._mip_parameters)
-        #create constraints
-        g.glp_add_rows(lp, number_of_metabolites)
-        row_indices = []
-        column_indices = []
-        constraint_values = []
-        for i, the_metabolite in enumerate(cobra_model.metabolites):
-            i_offset = i + 1
-            g.glp_set_row_name(lp, i_offset, the_metabolite.id)
-
-            lower_bound = upper_bound = the_metabolite._bound
-            constraint_sense = sense_dict[the_metabolite._constraint_sense]
-            if constraint_sense == 'E':
-                bound_type = GLPKConstants.GLP_FX
-            elif constraint_sense == 'L':
-                bound_type = GLPKConstants.GLP_UP
-            elif constraint_sense == 'G':
-                bound_type = GLPKConstants.GLP_LO
-            elif constraint_sense == 'U':
-                bound_type = GLPKConstants.GLP_FR
-            elif hasattr(lower_bound, '__iter__'):
-                lower_bound, upper_bound = lower_bound[:2]
-                bound_type = GLPKConstants.GLP_DB
-                
-
-            g.glp_set_row_bnds(lp, i_offset, bound_type,
-                               lower_bound, upper_bound)
-
-            [(row_indices.append(i_offset),
-              column_indices.append(reaction_to_index[k]),
-              constraint_values.append(k._metabolites[the_metabolite]))
-             for k in the_metabolite._reaction]
-
-        #Load the constraints into the lp.  Need to use
-        #typed arrays.
-        number_of_constraints = len(row_indices)
-        i_array = g.new_intArray(number_of_constraints)
-        j_array = g.new_intArray(number_of_constraints)
-        v_array = g.new_doubleArray(number_of_constraints)
-        for a, (i, j, v) in enumerate(zip(row_indices,
-                                          column_indices,
-                                          constraint_values)):
-            g.intArray_setitem(i_array, a+1, i)
-            g.intArray_setitem(j_array, a+1, j)
-            g.doubleArray_setitem(v_array, a+1, v)
-        g.glp_load_matrix(lp, number_of_constraints, i_array,
-                          j_array, v_array)
-        # the following lines often cause memory crashes
-        g.delete_intArray(i_array)
-        g.delete_intArray(j_array)
-        g.delete_doubleArray(v_array)
-        
-
-        g.glp_set_obj_name(lp, "z")
-        [g.glp_set_obj_coef(lp, k, v)
-          for k, v in iteritems(objective_dict)]
-
-        
-
-
-
-
-__solver_class = Problem
-
-def set_parameter(lp, parameter_name, parameter_value):
-    lp.set_parameter(parameter_name, parameter_value)
-
-
-def get_status(lp):
-    return lp.get_status()
-
-def format_solution(lp, cobra_model, **kwargs):
-    """
-
-    """
-    status = get_status(lp)
-    if not lp._mip:
-        try:
-            x = [lp._g.glp_get_col_prim(lp._lp, i + 1)
-                 for i in range(len(cobra_model.reactions))]
-            x_dict = dict(zip(cobra_model.reactions, x))
-
-            y = [lp._g.glp_get_row_dual(lp._lp, i + 1)
-                 for i in range(len(cobra_model.metabolites))]
-            y_dict = dict(zip(cobra_model.metabolites, y))
-        
-            objective_value = lp.objective_value
-        except Exception as e:
-            print(repr(e))
-            y = y_dict = x = x_dict = objective_value = None
-            #print status
-    else:
-        try:
-            x = [lp._g.glp_mip_col_val(lp._lp, i + 1)
-                 for i in range(len(cobra_model.reactions))]
-            x_dict = dict(zip(cobra_model.reactions, x))
-            y = y_dict = None
-            objective_value = lp.objective_value
-        except:
-            y = y_dict = x = x_dict = objective_value = None
-
-    return(Solution(objective_value, x=x, x_dict=x_dict, y=y,
-                    y_dict=y_dict, status=status))
-def create_problem(cobra_model,  **kwargs):
-    """Solver-specific method for constructing a solver problem from
-    a cobra.Model.  This can be tuned for performance using kwargs
-
-
-    """
-    the_parameters = parameter_defaults
-    if kwargs:
-        the_parameters = deepcopy(parameter_defaults)
-        the_parameters.update(kwargs)
-    quadratic_component = the_parameters['quadratic_component']
-    new_objective = the_parameters['new_objective']
-    if quadratic_component is not None:
-        raise Exception('%s cannot solve QPs, try a different solver'%solver_name)
-    lp = Problem()        # Create empty problem instance
-    lp.create_problem(cobra_model)
-    [set_parameter(lp, parameter_mappings[k], v)
-     for k, v in iteritems(the_parameters) if k in parameter_mappings]
-    return(lp)
-
-def update_problem(lp, cobra_model, **kwargs):
-    """
-    Assumes that neither Metabolites nor Reaction have been
-    added or removed.
-
-    Currently only deals with reaction bounds and objective
-    coefficients.
-
-    """
-    g = lp._g
-    l = lp._lp
-    for i, the_reaction in enumerate(cobra_model.reactions):
-        lower_bound = float(the_reaction.lower_bound)
-        upper_bound = float(the_reaction.upper_bound)
-        objective_coefficient = float(the_reaction.objective_coefficient)
-        if lower_bound == upper_bound:
-            bound_type = GLPKConstants.GLP_FX
-        else:
-            bound_type = GLPKConstants.GLP_DB
-        g.glp_set_col_bnds(l, i + 1, bound_type, lower_bound, upper_bound)
-        g.glp_set_obj_coef(l, i + 1, objective_coefficient)
-
-    
-def solve_problem(lp, **kwargs):
-    """A performance tunable method for updating a model problem file
-
-    """
-    #Update parameter settings if provided
-    if kwargs:
-        [set_parameter(lp, parameter_mappings[k], v)
-         for k, v in iteritems(kwargs) if k in parameter_mappings]
-    try:
-        print_solver_time = kwargs['print_solver_time']
-        start_time = time()
-    except:
-        print_solver_time = False
-    lp_method = lp._simplex_parameters.meth
-    lp.solve()
-    status = get_status(lp)
-    if print_solver_time:
-        print('optimize time: {:f}'.format(time() - start_time))
-    return status
-
-    
-def solve(cobra_model, **kwargs):
-    """Smart interface to optimization solver functions that will convert
-    the cobra_model to a solver object, set the parameters, and try multiple
-    methods to get an optimal solution before returning the solver object and
-    a cobra.solution (which is attached to cobra_model.solution)
-
-    cobra_model: a cobra.Model
-
-    returns a dict: {'the_problem': solver specific object, 'the_solution':
-    cobra.solution for the optimization problem'}
-    
-
-    """
-    #Start out with default parameters and then modify if
-    #new onese are provided
-    the_parameters = deepcopy(parameter_defaults)
-    if kwargs:
-        the_parameters.update(kwargs)
-    #Update objectives if they are new.
-    error_reporting = the_parameters['error_reporting']
-    if 'new_objective' in the_parameters and \
-           the_parameters['new_objective'] not in ['update problem', None]:
-       from ..flux_analysis.objective import update_objective
-       update_objective(cobra_model, the_parameters['new_objective'])
-    if 'the_problem' in the_parameters:
-        the_problem = the_parameters['the_problem']
-    else:
-        the_problem = None
-    if isinstance(the_problem, __solver_class):
-        #Update the problem with the current cobra_model
-        lp = the_problem
-        update_problem(lp, cobra_model, **the_parameters)
-    else:
-        #Create a new problem
-        lp = create_problem(cobra_model, **the_parameters)
-    #Deprecated way for returning a solver problem created from a cobra_model
-    #without performing optimization
-    if the_problem == 'setup':
-        return lp
-    ###Try to solve the problem using other methods if the first method doesn't work
-    lp_method = the_parameters['lp_method']
-    the_methods = [1, 2, 3]
-    if lp_method in the_methods:
-        the_methods.remove(lp_method)
-    #Start with the user specified method
-    the_methods.insert(0, lp_method)
-    for the_method in the_methods:
-        the_parameters['lp_method'] = the_method
-        try:
-            status = solve_problem(lp, **the_parameters)
-        except:
-            status = 'failed'
-        if status == 'optimal':
-            break
-    
-    the_solution = format_solution(lp, cobra_model)
-    if status != 'optimal' and error_reporting:
-        print('{:s} failed: {:s}'.format(solver_name, status))
-    cobra_model.solution = the_solution
-    solution = {'the_problem': lp, 'the_solution': the_solution}
-    return solution
diff --git a/cobra/solvers/gurobi_solver.py b/cobra/solvers/gurobi_solver.py
deleted file mode 100644
index 0e6d478..0000000
--- a/cobra/solvers/gurobi_solver.py
+++ /dev/null
@@ -1,305 +0,0 @@
-# -*- coding: utf-8 -*-
-# Interface to gurobipy
-
-from __future__ import absolute_import
-
-import platform
-from multiprocessing import Process
-from warnings import warn
-
-from gurobipy import GRB, LinExpr, Model, QuadExpr
-from six import iteritems, string_types
-
-from ..core.solution import LegacySolution
-
-try:
-    # Import izip for python versions < 3.x
-    from itertools import izip as zip
-except ImportError:
-    pass
-
-
-def test_import():
-    """Sometimes trying to import gurobipy can segfault. To prevent this from
-    crashing everything, ensure it can be imported in a separate process."""
-    try:
-        import gurobipy
-    except ImportError:
-        pass
-
-if platform.system() != "Windows":
-    # https://github.com/opencobra/cobrapy/issues/207
-    p = Process(target=test_import)
-    p.start()
-    p.join()
-    if p.exitcode != 0:
-        raise RuntimeError("importing gurobi causes a crash (exitcode %d)" %
-                           p.exitcode)
-
-
-
-
-try:
-    from sympy import Basic, Number
-except:
-    class Basic:
-        pass
-    Number = Basic
-
-
-def _float(value):
-    if isinstance(value, Basic) and not isinstance(value, Number):
-        return 0.
-    else:
-        return float(value)
-
-solver_name = 'gurobi'
-_SUPPORTS_MILP = True
-
-
-# set solver-specific parameters
-parameter_defaults = {'objective_sense': 'maximize',
-                      'tolerance_optimality': 1e-6,
-                      'tolerance_feasibility': 1e-6,
-                      'tolerance_integer': 1e-9,
-                      # This is primal simplex, default is -1 (automatic)
-                      'lp_method': 0,
-                      'verbose': False,
-                      'log_file': ''}
-parameter_mappings = {'log_file': 'LogFile',
-                      'lp_method': 'Method',
-                      'threads': 'Threads',
-                      'objective_sense': 'ModelSense',
-                      'output_verbosity': 'OutputFlag',
-                      'verbose': 'OutputFlag',
-                      'quadratic_precision': 'Quad',
-                      'time_limit': 'TimeLimit',
-                      'tolerance_feasibility': 'FeasibilityTol',
-                      'tolerance_markowitz': 'MarkowitzTol',
-                      'tolerance_optimality': 'OptimalityTol',
-                      'iteration_limit': 'IterationLimit',
-                      'tolerance_barrier': 'BarConvTol',
-                      'tolerance_integer': 'IntFeasTol',
-                      'MIP_gap_abs': 'MIPGapAbs',
-                      'MIP_gap': 'MIPGap'}
-# http://www.gurobi.com/documentation/5./6/reference-manual/method
-METHODS = {"auto": -1, "primal": 0, "dual": 1, "barrier": 2,
-           "concurrent": 3, "deterministic concurrent": 4}
-variable_kind_dict = {'continuous': GRB.CONTINUOUS, 'integer': GRB.INTEGER}
-sense_dict = {'E': GRB.EQUAL, 'L': GRB.LESS_EQUAL, 'G': GRB.GREATER_EQUAL}
-objective_senses = {'maximize': GRB.MAXIMIZE, 'minimize': GRB.MINIMIZE}
-status_dict = {GRB.OPTIMAL: 'optimal', GRB.INFEASIBLE: 'infeasible',
-               GRB.UNBOUNDED: 'unbounded', GRB.TIME_LIMIT: 'time_limit'}
-
-
-def get_status(lp):
-    status = lp.status
-    if status in status_dict:
-        status = status_dict[status]
-    else:
-        status = 'failed'
-    return status
-
-
-def get_objective_value(lp):
-    return lp.ObjVal
-
-
-def format_solution(lp, cobra_model, **kwargs):
-    status = get_status(lp)
-    if status not in ('optimal', 'time_limit'):
-        the_solution = LegacySolution(None, status=status)
-    else:
-        objective_value = lp.ObjVal
-        x = [v.X for v in lp.getVars()]
-        x_dict = {r.id: value for r, value in zip(cobra_model.reactions, x)}
-        if lp.isMIP:
-            y = y_dict = None  # MIP's don't have duals
-        else:
-            y = [c.Pi for c in lp.getConstrs()]
-            y_dict = {m.id: value for m, value
-                      in zip(cobra_model.metabolites, y)}
-        the_solution = LegacySolution(objective_value, x=x, x_dict=x_dict, y=y,
-                                y_dict=y_dict, status=status)
-    return(the_solution)
-
-
-def set_parameter(lp, parameter_name, parameter_value):
-    if parameter_name == 'ModelSense' or parameter_name == "objective_sense":
-        lp.setAttr('ModelSense', objective_senses[parameter_value])
-    elif parameter_name == 'reuse_basis' and not parameter_value:
-        lp.reset()
-    else:
-        parameter_name = parameter_mappings.get(parameter_name, parameter_name)
-        if parameter_name == "Method" and isinstance(parameter_value,
-                                                     string_types):
-            parameter_value = METHODS[parameter_value]
-        lp.setParam(parameter_name, parameter_value)
-
-
-def change_variable_bounds(lp, index, lower_bound, upper_bound):
-    variable = lp.getVarByName(str(index))
-    variable.lb = lower_bound
-    variable.ub = upper_bound
-
-
-def change_variable_objective(lp, index, objective):
-    variable = lp.getVarByName(str(index))
-    variable.obj = objective
-
-
-def change_coefficient(lp, met_index, rxn_index, value):
-    met = lp.getConstrByName(str(met_index))
-    rxn = lp.getVarByName(str(rxn_index))
-    lp.chgCoeff(met, rxn, value)
-
-
-def update_problem(lp, cobra_model, **kwargs):
-    """A performance tunable method for updating a model problem file
-
-    lp: A gurobi problem object
-
-    cobra_model: the cobra.Model corresponding to 'lp'
-
-    """
-    #When reusing the basis only assume that the objective coefficients or bounds can change
-    try:
-        quadratic_component = kwargs['quadratic_component']
-        if quadratic_component is not None:
-            warn("update_problem does not yet take quadratic_component as a parameter")
-    except:
-        quadratic_component = None
-
-    if 'copy_problem' in kwargs and kwargs['copy_problem']:
-        lp = lp.copy()
-    if 'reuse_basis' in kwargs and not kwargs['reuse_basis']:
-        lp.reset()
-    for the_variable, the_reaction in zip(lp.getVars(),
-                                          cobra_model.reactions):
-        the_variable.lb = float(the_reaction.lower_bound)
-        the_variable.ub = float(the_reaction.upper_bound)
-        the_variable.obj = float(the_reaction.objective_coefficient)
-
-
-def create_problem(cobra_model, quadratic_component=None, **kwargs):
-    """Solver-specific method for constructing a solver problem from
-    a cobra.Model.  This can be tuned for performance using kwargs
-
-
-    """
-    lp = Model("")
-
-    the_parameters = parameter_defaults
-    if kwargs:
-        the_parameters = parameter_defaults.copy()
-        the_parameters.update(kwargs)
-
-    # Set verbosity first to quiet infos on parameter changes
-    if "verbose" in the_parameters:
-        set_parameter(lp, "verbose", the_parameters["verbose"])
-    for k, v in iteritems(the_parameters):
-        set_parameter(lp, k, v)
-
-
-    # Create variables
-    #TODO:  Speed this up
-    variable_list = [lp.addVar(_float(x.lower_bound),
-                               _float(x.upper_bound),
-                               float(x.objective_coefficient),
-                               variable_kind_dict[x.variable_kind],
-                               str(i))
-                     for i, x in enumerate(cobra_model.reactions)]
-    reaction_to_variable = dict(zip(cobra_model.reactions,
-                                    variable_list))
-    # Integrate new variables
-    lp.update()
-
-    #Constraints are based on mass balance
-    #Construct the lin expression lists and then add
-    #TODO: Speed this up as it takes about .18 seconds
-    #HERE
-    for i, the_metabolite in enumerate(cobra_model.metabolites):
-        constraint_coefficients = []
-        constraint_variables = []
-        for the_reaction in the_metabolite._reaction:
-            constraint_coefficients.append(_float(the_reaction._metabolites[the_metabolite]))
-            constraint_variables.append(reaction_to_variable[the_reaction])
-        #Add the metabolite to the problem
-        lp.addConstr(LinExpr(constraint_coefficients, constraint_variables),
-                     sense_dict[the_metabolite._constraint_sense.upper()],
-                     the_metabolite._bound,
-                     str(i))
-
-    # Set objective to quadratic program
-    if quadratic_component is not None:
-        set_quadratic_objective(lp, quadratic_component)
-
-    lp.update()
-    return(lp)
-
-
-def set_quadratic_objective(lp, quadratic_objective):
-    if not hasattr(quadratic_objective, 'todok'):
-        raise Exception('quadratic component must have method todok')
-    variable_list = lp.getVars()
-    linear_objective = lp.getObjective()
-    # If there already was a quadratic expression set, this will be quadratic
-    # and we need to extract the linear component
-    if hasattr(linear_objective, "getLinExpr"):  # duck typing
-        linear_objective = linear_objective.getLinExpr()
-    gur_quadratic_objective = QuadExpr()
-    for (index_0, index_1), the_value in quadratic_objective.todok().items():
-        # gurobi does not multiply by 1/2 (only does v^T Q v)
-        gur_quadratic_objective.addTerms(the_value * 0.5,
-                                         variable_list[index_0],
-                                         variable_list[index_1])
-    # this adds to the existing quadratic objectives
-    lp.setObjective(gur_quadratic_objective + linear_objective)
-
-def solve_problem(lp, **kwargs):
-    """A performance tunable method for updating a model problem file
-
-    """
-    #Update parameter settings if provided
-    for k, v in iteritems(kwargs):
-        set_parameter(lp, k, v)
-
-    lp.update()
-    lp.optimize()
-    status = get_status(lp)
-    return status
-
-
-def solve(cobra_model, **kwargs):
-    """
-
-    """
-    for i in ["new_objective", "update_problem", "the_problem"]:
-        if i in kwargs:
-            raise Exception("Option %s removed" % i)
-    if 'error_reporting' in kwargs:
-        warn("error_reporting deprecated")
-        kwargs.pop('error_reporting')
-
-    #Create a new problem
-    lp = create_problem(cobra_model, **kwargs)
-
-    ###Try to solve the problem using other methods if the first method doesn't work
-    try:
-        lp_method = kwargs['lp_method']
-    except:
-        lp_method = 0
-    the_methods = [0, 2, 1]
-    if lp_method in the_methods:
-        the_methods.remove(lp_method)
-    #Start with the user specified method
-    the_methods.insert(0, lp_method)
-    for the_method in the_methods:
-        try:
-            status = solve_problem(lp, lp_method=the_method)
-        except:
-            status = 'failed'
-        if status == 'optimal':
-            break
-
-    return format_solution(lp, cobra_model)
diff --git a/cobra/solvers/gurobi_solver_java.py b/cobra/solvers/gurobi_solver_java.py
deleted file mode 100644
index 1b91d59..0000000
--- a/cobra/solvers/gurobi_solver_java.py
+++ /dev/null
@@ -1,285 +0,0 @@
-# -*- coding: utf-8 -*-
-# PLEASE NOTE THAT JYTHON SUPPORT (and this jython-only-solver) is deprecated
-#Interface to the gurobi 5.0.1 python and java solvers
-#QPs are not yet supported on java
-from __future__ import absolute_import, print_function
-
-from copy import deepcopy
-from os import name as __name
-from time import time
-from warnings import warn
-
-from six import iteritems
-
-from gurobi import GRBQuadExpr as QuadExpr
-from gurobi import GRB, GRBEnv, GRBLinExpr, GRBModel
-
-from ..core.solution import Solution
-###solver specific parameters
-from .parameters import (
-    default_objective_sense, objective_senses, parameter_defaults,
-    parameter_mappings, sense_dict, status_dict, variable_kind_dict)
-
-solver_name = 'gurobi'
-objective_senses = objective_senses[solver_name]
-parameter_mappings = parameter_mappings[solver_name]
-parameter_defaults = parameter_defaults[solver_name]
-#Functions that are different for java implementation of a solver
-## from jarray import array as j_array
-## def array(x, variable_type='d'):
-##     return j_array(x, variable_type)
-
-variable_kind_dict = eval(variable_kind_dict[solver_name])
-status_dict = eval(status_dict[solver_name])
-
-__solver_class = GRBModel
-#TODO: Create a pythonesqe class similar to in glpk_solver
-def Model(name=''):
-    grb_environment = GRBEnv(name)
-    tmp_model = GRBModel(grb_environment)
-    return tmp_model
-def LinExpr(coefficients, variables):
-    coefficients, variables = map(list, [coefficients, variables])
-    tmp_expression = GRBLinExpr()
-    tmp_expression.addTerms(coefficients, variables)
-    return tmp_expression
-
-def get_status(lp):
-    status = lp.get(GRB.IntAttr.Status)
-    if status in status_dict:
-        status = status_dict[status]
-    else:
-        status = 'failed'
-    return status
-
-def set_parameter(lp, parameter_name, parameter_value):
-    """Sets model parameters and attributes.
-    
-    """
-    grb_environment = lp.getEnv()
-    try:
-        if hasattr(GRB.DoubleParam, parameter_name):
-            grb_environment.set(eval('GRB.DoubleParam.%s'%parameter_name),
-                                     parameter_value)
-        elif hasattr(GRB.IntParam, parameter_name):
-            grb_environment.set(eval('GRB.IntParam.%s'%parameter_name),
-                                     parameter_value)
-        elif hasattr(GRB.StringParam, parameter_name):
-            grb_environment.set(eval('GRB.StringParam.%s'%parameter_name),
-                                parameter_value)
-        elif hasattr(GRB.IntAttr, parameter_name):
-            if parameter_name == 'ModelSense':
-                parameter_value = objective_senses[parameter_value]
-            lp.set(eval('GRB.IntAttr.%s'%parameter_name),
-                                parameter_value)
-        else:
-            warn("%s is not a DoubleParam, IntParam, StringParam, IntAttr"%parameter_name)
-            ## raise Exception("%s is not a DoubleParam, IntParam, StringParam, IntAttr"%parameter_name)
-    except Exception as e:
-        warn("%s %s didn't work %s"%(parameter_name, parameter_value, e))
-
-def get_objective_value(lp):
-    return lp.get(GRB.DoubleAttr.ObjVal)
-
-def format_solution(lp, cobra_model, **kwargs):
-    """
-    """
-    status = get_status(lp)
-    if status not in ('optimal', 'time_limit'):
-        the_solution = Solution(None, status=status)
-    else:
-        x_dict = dict(((v.get(GRB.StringAttr.VarName),
-                        v.get(GRB.DoubleAttr.X))
-                       for v in lp.getVars()))
-        x = [x_dict[v.id] for v in cobra_model.reactions]
-        objective_value = lp.get(GRB.DoubleAttr.ObjVal)
-        if lp.get(GRB.IntAttr.IsMIP) != 0:
-            y = y_dict = None #MIP's don't have duals
-        else:
-            y_dict = dict(((c.get(GRB.StringAttr.ConstrName), c.get(GRB.DoubleAttr.Pi))
-                          for c in lp.getConstrs()))
-            y = list([y_dict[v.id] for v in cobra_model.metabolites])
-        the_solution = Solution(objective_value, x=x, x_dict=x_dict, y=y,
-                                y_dict=y_dict, status=status)
-    return(the_solution)
-
-def update_problem(lp, cobra_model, **kwargs):
-    """A performance tunable method for updating a model problem file
-
-    lp: A gurobi problem object
-
-    cobra_model: the cobra.Model corresponding to 'lp'
-
-    """
-    #When reusing the basis only assume that the objective coefficients or bounds can change
-    try:
-        quadratic_component = kwargs['quadratic_component']
-        if quadratic_component is not None:
-            warn("update_problem does not yet take quadratic_component as a parameter")
-    except:
-        quadratic_component = None
-
-    if 'reuse_basis' in kwargs and not kwargs['reuse_basis']:
-        lp.reset()
-    for the_variable, the_reaction in zip(lp.getVars(),
-                                          cobra_model.reactions):
-        the_variable.set(GRB.DoubleAttr.LB, float(the_reaction.lower_bound))
-        the_variable.set(GRB.DoubleAttr.UB, float(the_reaction.upper_bound))
-        the_variable.set(GRB.DoubleAttr.Obj, float(the_reaction.objective_coefficient))
-
-
-
-
-###
-sense_dict = eval(sense_dict[solver_name])
-def create_problem(cobra_model,  **kwargs):
-    """Solver-specific method for constructing a solver problem from
-    a cobra.Model.  This can be tuned for performance using kwargs
-
-
-    """
-    lp = Model("")
-    #Silence the solver
-    set_parameter(lp, 'OutputFlag', 0)
-
-    the_parameters = parameter_defaults
-    if kwargs:
-        the_parameters = deepcopy(parameter_defaults)
-        the_parameters.update(kwargs)
-
-    [set_parameter(lp, parameter_mappings[k], v)
-         for k, v in iteritems(the_parameters) if k in parameter_mappings]
-    quadratic_component = the_parameters['quadratic_component']
-    objective_sense = objective_senses[the_parameters['objective_sense']]
-
-
-    # Create variables
-    #TODO:  Speed this up
-    variable_list = [lp.addVar(float(x.lower_bound),
-                               float(x.upper_bound),
-                               float(x.objective_coefficient),
-                               variable_kind_dict[x.variable_kind],
-                               x.id)
-                     for x in cobra_model.reactions]
-    reaction_to_variable = dict(zip(cobra_model.reactions,
-                                    variable_list))
-    # Integrate new variables
-    lp.update()
-    #Set objective to quadratic program
-    if quadratic_component is not None:
-        if not hasattr(quadratic_component, 'todok'):
-            raise Exception('quadratic component must have method todok')
-
-        quadratic_objective = QuadExpr()
-        for (index_0, index_1), the_value in quadratic_component.todok().items():
-            quadratic_objective.addTerms(the_value,
-                                   variable_list[index_0],
-                                   variable_list[index_1])
-        #Does this override the linear objective coefficients or integrate with them?
-        lp.setObjective(quadratic_objective, sense=objective_sense)
-    #Constraints are based on mass balance
-    #Construct the lin expression lists and then add
-    #TODO: Speed this up as it takes about .18 seconds
-    #HERE
-    for the_metabolite in cobra_model.metabolites:
-        constraint_coefficients = []
-        constraint_variables = []
-        for the_reaction in the_metabolite._reaction:
-            constraint_coefficients.append(the_reaction._metabolites[the_metabolite])
-            constraint_variables.append(reaction_to_variable[the_reaction])
-        #Add the metabolite to the problem
-        lp.addConstr(LinExpr(constraint_coefficients, constraint_variables),
-                     sense_dict[the_metabolite._constraint_sense.upper()],
-                     the_metabolite._bound,
-                     the_metabolite.id)
-
-
-
-    return(lp)
-###
-
-###
-def solve_problem(lp, **kwargs):
-    """A performance tunable method for updating a model problem file
-
-    """
-    #Update parameter settings if provided
-    if kwargs:
-        [set_parameter(lp, parameter_mappings[k], v)
-         for k, v in iteritems(kwargs) if k in parameter_mappings]
-
-    try:
-        print_solver_time = kwargs['print_solver_time']
-        start_time = time()
-    except:
-        print_solver_time = False
-    lp.update()
-    #Different methods to try if lp_method fails
-    lp.optimize()
-    status = get_status(lp)
-    if print_solver_time:
-        print('optimize time: {:f}'.format(time() - start_time))
-    return status
-
-    
-def solve(cobra_model, **kwargs):
-    """
-
-    """
-    #Start out with default parameters and then modify if
-    #new onese are provided
-    the_parameters = deepcopy(parameter_defaults)
-    if kwargs:
-        the_parameters.update(kwargs)
-    #Update objectives if they are new.
-    if 'new_objective' in the_parameters and \
-           the_parameters['new_objective'] not in ['update problem', None]:
-       from ..flux_analysis.objective import update_objective
-       update_objective(cobra_model, the_parameters['new_objective'])
-
-    if 'the_problem' in the_parameters:
-        the_problem = the_parameters['the_problem']
-    else:
-        the_problem = None
-    if 'error_reporting' in the_parameters:
-        error_reporting = the_parameters['error_reporting']
-    else:
-        error_reporting = False
-
-    if isinstance(the_problem, __solver_class):
-        #Update the problem with the current cobra_model
-        lp = the_problem
-        update_problem(lp, cobra_model, **the_parameters)
-    else:
-        #Create a new problem
-        lp = create_problem(cobra_model, **the_parameters)
-    #Deprecated way for returning a solver problem created from a cobra_model
-    #without performing optimization
-    if the_problem == 'setup':
-            return lp
-
-    ###Try to solve the problem using other methods if the first method doesn't work
-    try:
-        lp_method = the_parameters['lp_method']
-    except:
-        lp_method = 0
-    the_methods = [0, 2, 1]
-    if lp_method in the_methods:
-        the_methods.remove(lp_method)
-    #Start with the user specified method
-    the_methods.insert(0, lp_method)
-    for the_method in the_methods:
-        the_parameters['lp_method'] = the_method
-        try:
-            status = solve_problem(lp, **the_parameters)
-        except:
-            status = 'failed'
-        if status == 'optimal':
-            break
-    status = solve_problem(lp, **the_parameters)
-    the_solution = format_solution(lp, cobra_model)
-    if status != 'optimal' and error_reporting:
-        print('{:s} failed: {:s}'.format(solver_name, status))
-    cobra_model.solution = the_solution
-    solution = {'the_problem': lp, 'the_solution': the_solution}
-    return solution
diff --git a/cobra/solvers/mosek.py b/cobra/solvers/mosek.py
deleted file mode 100644
index d19dcf0..0000000
--- a/cobra/solvers/mosek.py
+++ /dev/null
@@ -1,245 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, print_function
-
-import mosek
-from six import iteritems, string_types
-from six.moves import zip
-
-from cobra.core.solution import LegacySolution
-
-env = mosek.Env()
-
-# make sure the mosek environment works
-test = env.Task(0, 0)
-test.optimize()
-del test
-
-
-solver_name = "mosek"
-__mosek_version__ = ".".join(str(i) for i in mosek.getversion())
-_SUPPORTS_MILP = True
-
-status_dict = {
-    mosek.solsta.dual_infeas_cer: 'infeasible',
-    mosek.solsta.prim_infeas_cer: 'infeasible',
-    mosek.solsta.optimal: 'optimal',
-    mosek.solsta.integer_optimal: 'optimal'}
-
-param_enums = {
-    "bi_clean_optimizer": mosek.optimizertype,
-    "cache_license": mosek.onoffkey,
-    "check_convexity": mosek.checkconvexitytype,
-    "compress_statfile": mosek.onoffkey,
-    "feasrepair_optimize": mosek.feasrepairtype,
-    "infeas_generic_names": mosek.onoffkey,
-    "infeas_prefer_primal": mosek.onoffkey,
-    "infeas_report_auto": mosek.onoffkey,
-    "license_allow_overuse": mosek.onoffkey,
-    "license_wait": mosek.onoffkey,
-    "mio_branch_dir": mosek.branchdir,
-    "mio_branch_priorities_use": mosek.onoffkey,
-    "mio_construct_sol": mosek.onoffkey,
-    "mio_cont_sol": mosek.miocontsoltype,
-    "mio_use_multithreaded_optimizer": mosek.onoffkey,
-    "presolve_lindep_use": mosek.onoffkey,
-    "presolve_use": mosek.presolvemode,
-    "sim_basis_factor_use": mosek.onoffkey,
-    "sim_degen": mosek.simdegen
-}
-
-param_aliases = {
-    "tolerance_feasibility": "basis_tol_x",
-    "time_limit": "optimizer_max_time",
-    "mip_gap_abs": "mio_tol_abs_gap",
-    "mip_gap": "mio_tol_rel_gap",
-    "tolerance_integer": "mio_tol_abs_relax_int"
-    }
-
-
-def _verbose_printer(text):
-    print(text)
-
-
-def create_problem(cobra_model, objective_sense="maximize",
-                   **solver_parameters):
-    n_rxns = len(cobra_model.reactions)
-    n_mets = len(cobra_model.metabolites)
-    rxn_indexes = range(n_rxns)
-    met_indexes = range(n_mets)
-    # create lp object and set default parameters
-    lp = env.Task(0, 0)
-    set_objective_sense(lp, objective_sense)
-    lp.putdouparam(mosek.dparam.basis_tol_x, 1e-9)
-    lp.putdouparam(mosek.dparam.basis_tol_s, 1e-9)
-    lp.putdouparam(mosek.dparam.basis_rel_tol_s, 0.)
-    lp.putdouparam(mosek.dparam.simplex_abs_tol_piv, 1e-12)
-    lp.putdouparam(mosek.dparam.intpnt_tol_rel_gap, 1e-6)
-    lp.putdouparam(mosek.dparam.presolve_tol_aij, 1e-15)
-    lp.putdouparam(mosek.dparam.presolve_tol_abs_lindep, 0.)
-    lp.putdouparam(mosek.dparam.presolve_tol_s, 0.)
-    lp.putdouparam(mosek.dparam.presolve_tol_x, 1e-10)
-    lp.putintparam(mosek.iparam.concurrent_priority_intpnt, 0)
-    lp.putintparam(mosek.iparam.concurrent_num_optimizers, 1)
-    # add reactions/variables
-    lp.appendvars(len(cobra_model.reactions))
-    lp.putvarboundlist(
-        rxn_indexes,
-        (mosek.boundkey.ra,) * n_rxns,
-        [float(i.lower_bound) for i in cobra_model.reactions],
-        [float(i.upper_bound) for i in cobra_model.reactions],
-    )
-    lp.putclist(
-        rxn_indexes,
-        [float(i.objective_coefficient) for i in cobra_model.reactions])
-    integer_variables = [i for i, r in enumerate(cobra_model.reactions)
-                         if r.variable_kind == "integer"]
-    lp.putvartypelist(
-        integer_variables,
-        (mosek.variabletype.type_int,) * len(integer_variables))
-
-    # add metabolites/constraints
-    c_sense_dict = {"E": mosek.boundkey.fx, "L": mosek.boundkey.up,
-                    "G": mosek.boundkey.lo}
-    c_senses = [c_sense_dict[met._constraint_sense]
-                for met in cobra_model.metabolites]
-    bounds = cobra_model.metabolites.list_attr("_bound")
-    lp.appendcons(len(cobra_model.metabolites))
-    lp.putconboundlist(met_indexes, c_senses, bounds, bounds)
-
-    # add in the S matrix
-    for i, reaction in enumerate(cobra_model.reactions):
-        for metabolite, stoichiometry in iteritems(reaction._metabolites):
-            lp.putaij(cobra_model.metabolites.index(metabolite),
-                      i, stoichiometry)
-    # set user-supplied parameters
-    for key, value in iteritems(solver_parameters):
-        set_parameter(lp, key, value)
-    return lp
-
-
-def set_objective_sense(lp, objective_sense):
-    if objective_sense == "maximize":
-        lp.putobjsense(mosek.objsense.maximize)
-    elif objective_sense == "minimize":
-        lp.putobjsense(mosek.objsense.minimize)
-    else:
-        raise ValueError("unknown objective sense '%s'" % objective_sense)
-
-
-def set_parameter(lp, parameter_name, parameter_value):
-    parameter_name = parameter_name.lower()
-    parameter_name = param_aliases.get(parameter_name, parameter_name)
-    if parameter_name == "verbose":
-        if parameter_value:
-            for streamtype in mosek.streamtype.values:
-                lp.set_Stream(streamtype, _verbose_printer)
-        else:
-            for streamtype in mosek.streamtype.values:
-                lp.set_Stream(streamtype, lambda x: None)
-    elif parameter_name == "objective_sense":
-        set_objective_sense(lp, parameter_value)
-    elif parameter_name == "quadratic_component":
-        if parameter_value is not None:
-            set_quadratic_objective(lp, parameter_value)
-    elif hasattr(mosek.dparam, parameter_name):
-        lp.putdouparam(getattr(mosek.dparam, parameter_name), parameter_value)
-    # Integer parameter section
-    # Many of these are enumerations. Therefore the value should be converted
-    # to the appropriate enum one.
-    elif isinstance(parameter_value, string_types) and \
-            parameter_name in param_enums:
-        lp.putintparam(getattr(mosek.iparam, parameter_name),
-                       getattr(param_enums[parameter_name], parameter_value))
-    elif isinstance(parameter_value, bool) and parameter_name in param_enums:
-        if parameter_value:
-            set_parameter(lp, parameter_name, "on")
-        else:
-            set_parameter(lp, parameter_name, "off")
-    elif hasattr(mosek.iparam, parameter_name):
-        lp.putintparam(getattr(mosek.iparam, parameter_name), parameter_value)
-    else:
-        raise ValueError("unknown parameter '%s'" % parameter_name)
-
-
-def solve_problem(lp, **solver_parameters):
-    for key, value in iteritems(solver_parameters):
-        set_parameter(lp, key, value)
-    lp.optimize()
-    return get_status(lp)
-
-
-def solve(cobra_model, **solver_parameters):
-    lp = create_problem(cobra_model, **solver_parameters)
-    solve_problem(lp)
-    return format_solution(lp, cobra_model)
-
-
-def _get_soltype(lp):
-    """get the solution type
-
-    This is bas for LP and itg for MIP and QP
-
-    """
-    if lp.getnumintvar() > 0:
-        return mosek.soltype.itg
-    if lp.getnumqobjnz() > 0:
-        return mosek.soltype.itr
-    return mosek.soltype.bas
-
-
-def get_status(lp):
-    mosek_status = lp.getsolsta(_get_soltype(lp))
-    return status_dict.get(mosek_status, str(mosek_status))
-
-
-def get_objective_value(lp):
-    return lp.getprimalobj(_get_soltype(lp))
-
-
-def format_solution(lp, cobra_model):
-    soltype = _get_soltype(lp)
-    mosek_status = lp.getsolsta(soltype)
-    status = status_dict.get(mosek_status, str(mosek_status))
-    if status != "optimal":
-        return LegacySolution(None, status=status)
-    solution = LegacySolution(get_objective_value(lp))
-    solution.status = status
-    x = [0] * len(cobra_model.reactions)
-    lp.getxx(soltype, x)
-    solution.x = x
-    solution.x_dict = {rxn.id: value for rxn, value
-                       in zip(cobra_model.reactions, x)}
-    if soltype == mosek.soltype.bas:
-        y = [0] * len(cobra_model.metabolites)
-        lp.gety(mosek.soltype.bas, y)
-        solution.y = y
-        solution.y_dict = {met.id: value for met, value
-                           in zip(cobra_model.metabolites, y)}
-    return solution
-
-
-def change_variable_objective(lp, index, value):
-    lp.putcj(index, value)
-
-
-def change_variable_bounds(lp, index, lower_bound, upper_bound):
-    lp.putvarbound(index, mosek.boundkey.ra, lower_bound, upper_bound)
-
-
-def change_coefficient(lp, met_index, rxn_index, value):
-    lp.putaij(met_index, rxn_index, value)
-
-
-def set_quadratic_objective(lp, quadratic_objective):
-    if not hasattr(quadratic_objective, 'todok'):
-        raise Exception('quadratic component must be a sparse matrix')
-    row_indexes = []
-    col_indexes = []
-    values = []
-    for (index_0, index_1), value in iteritems(quadratic_objective.todok()):
-        # specify lower triangular only
-        if index_0 >= index_1:
-            row_indexes.append(index_0)
-            col_indexes.append(index_1)
-            values.append(value)
-    lp.putqobj(row_indexes, col_indexes, values)
diff --git a/cobra/solvers/parameters.py b/cobra/solvers/parameters.py
deleted file mode 100644
index 0a5e797..0000000
--- a/cobra/solvers/parameters.py
+++ /dev/null
@@ -1,158 +0,0 @@
-# -*- coding: utf-8 -*-
-#This centralizes some of the common elements that are differently named across solvers.
-#These are stored as strings here to prevent problems associated with calling
-#solver objects for solver packages that aren't available
-from __future__ import absolute_import
-
-from copy import deepcopy
-
-__objective_sense_cplex = {'maximize': 'Cplex.objective.sense.maximize',
-                           'minimize': 'Cplex.objective.sense.minimize'}
-__objective_sense_cplex_java = {'maximize': 'IloObjectiveSense.Maximize',
-                                'minimize': 'IloObjectiveSense.Minimize'}
-
-__objective_sense_glpk = {'maximize': True,
-                          'minimize': False}
-__objective_sense_glpk_java = {'maximize': 'GLPKConstants.GLP_MAX',
-                               'minimize': 'GLPKConstants.GLP_MIN'}
-__objective_sense_gurobi = {'maximize': -1,
-                            'minimize': 1}
-objective_senses = {'cplex': __objective_sense_cplex,
-                    'cplex_java': __objective_sense_cplex_java,
-                    'glpk': __objective_sense_glpk,
-                    'glpk_java': __objective_sense_glpk_java,
-                    'gurobi': __objective_sense_gurobi}
-default_objective_sense = 'maximize'
-#Mappings from solver-specific status values to cobra pie status values
-## __status_cplex = "{Cplex.solution.status.MIP_infeasible: 'infeasible', " +\
-##                  "Cplex.solution.status.MIP_optimal: 'optimal', " +\
-##                  "Cplex.solution.status.MIP_optimal_tolerance: 'optimal'," +\
-##                  "Cplex.solution.status.MIP_unbounded:  'unbounded', "+\
-##                  "Cplex.solution.status.infeasible: 'infeasible', " +\
-##                  "Cplex.solution.status.optimal: 'optimal',  " +\
-##                  "Cplex.solution.status.optimal_tolerance: 'optimal', " +\
-##                  "Cplex.solution.status.unbounded: 'unbounded', }"
-__status_cplex = """{
-    'MIP_infeasible': 'infeasible',
-    'integer optimal solution': 'optimal',
-    'MIP_optimal': 'optimal',
-    'MIP_optimal_tolerance': 'optimal',
-    'MIP_unbounded':  'unbounded',
-    'infeasible': 'infeasible',
-    'optimal': 'optimal',
-    'optimal_tolerance': 'optimal',
-    'unbounded': 'unbounded',
-    'integer optimal, tolerance': 'optimal',
-    'time limit exceeded': 'time_limit'
-}"""
-
-__status_glpk = """{
-    'opt': 'optimal',
-    'nofeas': 'infeasible',
-    'unbnd': 'unbounded'
-}"""
-__status_glpk_java = "{GLPKConstants.GLP_OPT: 'optimal', GLPKConstants.GLP_FEAS: 'feasible', GLPKConstants.GLP_INFEAS: 'infeasible', GLPKConstants.GLP_NOFEAS: 'infeasible', GLPKConstants.GLP_UNBND: 'unbounded', GLPKConstants.GLP_UNDEF: 'undefined'}"
-__status_gurobi = "{GRB.OPTIMAL: 'optimal', GRB.INFEASIBLE: 'infeasible', GRB.UNBOUNDED: 'unbounded', GRB.TIME_LIMIT: 'time_limit'}"
-
-status_dict = {'cplex': __status_cplex,
-               'glpk': __status_glpk,
-               'glpk_java': __status_glpk_java,
-               'gurobi': __status_gurobi}
-
-#Mappings from solver-specific variable kinds to cobra pie
-__kind_cplex = "{'continuous': Cplex.variables.type.continuous, 'integer': Cplex.variables.type.integer}"
-__kind_cplex_java = "{'continuous':  IloNumVarType.Float, 'integer': IloNumVarType.Int}"
-__kind_glpk = "{'continuous': float, 'integer': int}"
-__kind_glpk_java = "{'binary': GLPKConstants.GLP_BV, 'continuous': GLPKConstants.GLP_CV, 'integer': GLPKConstants.GLP_IV}"
-__kind_gurobi = "{'continuous': GRB.CONTINUOUS, 'integer': GRB.INTEGER}"
-
-variable_kind_dict = {'cplex': __kind_cplex,
-                      'cplex_java': __kind_cplex_java,
-                      'glpk': __kind_glpk,
-                      'glpk_java': __kind_glpk_java, 
-                      'gurobi': __kind_gurobi}
-
-#Mappings from solver-specific constraint senses to cobra pie
-sense_dict = {'cplex': "{'E': 'E', 'L': 'L', 'G': 'G'}",
-              'glpk': "{'E': 'E', 'L': 'L', 'G': 'G'}",
-              'gurobi': "{'E': GRB.EQUAL, 'L': GRB.LESS_EQUAL, 'G': GRB.GREATER_EQUAL}"}
-
-
-#Mappings from cobra pie parameters names to solver specific parameter names
-__mappings_cplex = {'lp_method': 'lpmethod',
-                    'lp_parallel': 'threads',
-                    'threads': 'threads',
-                    'objective_sense': 'objective_sense',
-                    'time_limit': 'timelimit',
-                    'iteration_limit': 'simplex.limits.iterations',
-                    'tolerance_barrier': 'barrier.convergetol',
-                    'tolerance_feasibility': 'simplex.tolerances.feasibility',
-                    'tolerance_markowitz': 'simplex.tolerances.markowitz',
-                    'tolerance_optimality': 'simplex.tolerances.optimality',
-                    'MIP_gap_abs': 'mip.tolerances.absmipgap',
-                    'MIP_gap': 'mip.tolerances.mipgap'}
-__mappings_cplex_java = {'lp_method': 'RootAlg',
-                         'lp_parallel': 'ParallelMode',
-                         'objective_sense': 'objective_sense',
-                         'time_limit': 'TiLim',
-                         'tolerance_barrier': 'BarEpComp',
-                         'tolerance_feasibility': 'EpRHS',
-                         'tolerance_markowitz': 'EpMrk',
-                         'tolerance_optimality': 'EpOpt'}
-__mappings_glpk = {}
-__mappings_glpk_java = {'objective_sense': 'objective_sense',
-                        'lp_method': 'meth',
-                        'output_verbosity': 'msg_lev',
-                        'tolerance_dual': 'tol_dj',
-                        'tolerance_integer': 'tol_int',
-                        'tolerance_optimality': 'tol_bnd'
-                        }
-__mappings_gurobi = {'log_file': 'LogFile',
-                     'lp_method': 'Method',
-                     'threads': 'Threads',
-                     'objective_sense': 'ModelSense',
-                     'output_verbosity': 'OutputFlag',
-                     'quadratic_precision': 'Quad',
-                     'time_limit': 'TimeLimit',
-                     'tolerance_feasibility': 'FeasibilityTol',
-                     'tolerance_markowitz': 'MarkowitzTol',
-                     'tolerance_optimality': 'OptimalityTol',
-                     'iteration_limit': 'IterationLimit',
-                     'MIP_gap_abs': 'MIPGapAbs',
-                     'MIP_gap': 'MIPGap'}
-parameter_mappings = {'cplex': __mappings_cplex,
-                      'cplex_java': __mappings_cplex_java,
-                      'glpk': __mappings_glpk,
-                      'glpk_java': __mappings_glpk_java,
-                      'gurobi': __mappings_gurobi}
-
-
-#Default solver parameters
-__common_defaults = {'objective_sense': 'maximize',
-                      'tolerance_optimality': 1e-6, 'tolerance_feasibility': 1e-6,
-                      'tolerance_integer': 1e-9, 
-                      'quadratic_component': None}
-                      
-
-
-__parameters_cplex = deepcopy(__common_defaults)
-__parameters_cplex.update({'lp_method': 1,
-                           'lp_parallel': 0,
-                           'tolerance_barrier': 1.e-8})
-__parameters_glpk = deepcopy(__common_defaults)
-__parameters_glpk.update({'lp_method': 1})
-__parameters_glpk_java = deepcopy(__common_defaults)
-__parameters_glpk_java.update({'lp_method': 1,
-                               'output_verbosity': 0,
-                               'tolerance_dual': 1e-8})
-__parameters_gurobi = deepcopy(__common_defaults)
-__parameters_gurobi.update({'output_verbosity': 0,
-                          'lp_method': 0,
-                          'log_file': '',
-                          'tolerance_barrier': 1e-8})
-
-
-parameter_defaults = {'cplex': __parameters_cplex,
-                      'glpk': __parameters_glpk,
-                      'glpk_java': __parameters_glpk_java,
-                      'gurobi': __parameters_gurobi}
diff --git a/cobra/solvers/wrappers.py b/cobra/solvers/wrappers.py
deleted file mode 100644
index c8c0933..0000000
--- a/cobra/solvers/wrappers.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-Wrappers for solvers with an object oriented interface. This creates
-functions to call the objects' functions.
-
-The create_problem and solve functions are not included because they
-are classmethods. They should be included by specifying
-create_problem = PROBLEM_CLASS.create_problem
-where PROBLEM_CLASS is the solver class (i.e. GLP, esolver, etc.)
-"""
-
-from __future__ import absolute_import
-
-
-def set_objective_sense(lp, objective_sense="maximize"):
-    return lp.set_objective_sense(lp, objective_sense=objective_sense)
-
-
-def change_variable_bounds(lp, *args):
-    return lp.change_variable_bounds(*args)
-
-
-def change_variable_objective(lp, *args):
-    return lp.change_variable_objective(*args)
-
-
-def change_coefficient(lp, *args):
-    return lp.change_coefficient(*args)
-
-
-def set_parameter(lp, parameter_name, value):
-    return lp.change_parameter(parameter_name, value)
-
-
-def solve_problem(lp, **kwargs):
-    return lp.solve_problem(**kwargs)
-
-
-def get_status(lp):
-    return lp.get_status()
-
-
-def get_objective_value(lp):
-    return lp.get_objective_value()
-
-
-def format_solution(lp, cobra_model):
-    return lp.format_solution(cobra_model)
diff --git a/cobra/test/conftest.py b/cobra/test/conftest.py
index 3794ef5..8bbd94f 100644
--- a/cobra/test/conftest.py
+++ b/cobra/test/conftest.py
@@ -7,6 +7,7 @@ from os.path import join
 
 import pytest
 
+import cobra.util.solver as sutil
 from cobra.test import create_test_model, data_dir
 
 try:
@@ -14,8 +15,6 @@ try:
 except ImportError:
     from pickle import load as _load
 
-import cobra.util.solver as sutil
-from cobra.solvers import solver_dict
 from cobra import Model, Metabolite, Reaction
 
 
@@ -44,17 +43,27 @@ def model(small_model):
     return small_model.copy()
 
 
- at pytest.fixture(scope="function")
-def large_model():
+ at pytest.fixture(scope="session")
+def large_once():
     return create_test_model("ecoli")
 
 
+ at pytest.fixture(scope="function")
+def large_model(large_once):
+    return large_once.copy()
+
+
 @pytest.fixture(scope="session")
-def salmonella():
+def medium_model():
     return create_test_model("salmonella")
 
 
 @pytest.fixture(scope="function")
+def salmonella(medium_model):
+    return medium_model.copy()
+
+
+ at pytest.fixture(scope="function")
 def solved_model(data_directory):
     model = create_test_model("textbook")
     with open(join(data_directory, "textbook_solution.pickle"),
@@ -89,21 +98,15 @@ def pfba_fva_results(data_directory):
 
 
 stable_optlang = ["glpk", "cplex", "gurobi"]
-optlang_solvers = ["optlang-" + s for s in stable_optlang if s in
-                   sutil.solvers]
-all_solvers = optlang_solvers + list(solver_dict)
+all_solvers = ["optlang-" + s for s in stable_optlang if
+               s in sutil.solvers]
 
 
- at pytest.fixture(params=optlang_solvers, scope="session")
+ at pytest.fixture(params=all_solvers, scope="session")
 def opt_solver(request):
     return request.param
 
 
- at pytest.fixture(params=list(solver_dict), scope="session")
-def legacy_solver(request):
-    return request.param
-
-
 @pytest.fixture(scope="function")
 def metabolites(model, request):
     if request.param == "exchange":
diff --git a/cobra/test/test_flux_analysis.py b/cobra/test/test_flux_analysis.py
index aef830e..04b6839 100644
--- a/cobra/test/test_flux_analysis.py
+++ b/cobra/test/test_flux_analysis.py
@@ -1,14 +1,14 @@
 # -*- coding: utf-8 -*-
+
 from __future__ import absolute_import
 
 import re
 import sys
 import warnings
-from contextlib import contextmanager
-from os import name
-
+import math
 import pytest
 import numpy
+from contextlib import contextmanager
 from optlang.interface import OPTIMAL, INFEASIBLE
 from six import StringIO, iteritems
 
@@ -18,33 +18,17 @@ from cobra.flux_analysis import *
 from cobra.flux_analysis.parsimonious import add_pfba
 from cobra.flux_analysis.sampling import ACHRSampler, OptGPSampler
 from cobra.flux_analysis.reaction import assess
-from cobra.manipulation import convert_to_irreversible
-from cobra.solvers import SolverNotFound, get_solver_name, solver_dict
 from cobra.exceptions import Infeasible
+from cobra.flux_analysis.moma import add_moma
 
-try:
-    import scipy
-    from cobra.flux_analysis.moma import add_moma
-except ImportError:
-    scipy = None
-    add_moma = None
-try:
-    import matplotlib
-except (ImportError, RuntimeError):
-    matplotlib = None
-try:
-    from matplotlib import pyplot
-    from mpl_toolkits.mplot3d import axes3d
-except (ImportError, RuntimeError):
-    pyplot = None
-    axes3d = None
-
-# The scipt interface is currently unstable and may yield errors or infeasible
-# solutions
+# The scipy interface is currently unstable and may yield errors or infeasible
+# solutions.
 stable_optlang = ["glpk", "cplex", "gurobi"]
 optlang_solvers = ["optlang-" + s for s in stable_optlang if s in
                    sutil.solvers]
-all_solvers = optlang_solvers + list(solver_dict)
+all_solvers = ["optlang-" + s for s in stable_optlang if
+               s in sutil.solvers]
+qp_solvers = ["cplex"]
 
 
 def construct_ll_test_model():
@@ -94,26 +78,20 @@ class TestCobraFluxAnalysis:
 
     @pytest.mark.parametrize("solver", all_solvers)
     def test_pfba_benchmark(self, large_model, benchmark, solver):
-        convert_to_irreversible(large_model)
-
-        def do_pfba(solver):
-            pfba(large_model, solver=solver,
-                 already_irreversible=True)
-
-        benchmark(do_pfba, solver)
+        large_model.solver = solver
+        benchmark(pfba, large_model)
 
     @pytest.mark.parametrize("solver", all_solvers)
     def test_pfba(self, model, solver):
+        model.solver = solver
         with model:
             add_pfba(model)
             with pytest.raises(ValueError):
                 add_pfba(model)
 
-        if solver in optlang_solvers:
-            model.solver = solver
         expression = model.objective.expression
         n_constraints = len(model.constraints)
-        solution = pfba(model, solver=solver)
+        solution = pfba(model)
         assert solution.status == "optimal"
         assert numpy.isclose(solution.x_dict["Biomass_Ecoli_core"],
                              0.8739, atol=1e-4, rtol=0.0)
@@ -135,8 +113,7 @@ class TestCobraFluxAnalysis:
 
         # TODO: parametrize fraction (DRY it up)
         # Test fraction_of_optimum
-        solution = pfba(model, solver=solver,
-                        fraction_of_optimum=0.95)
+        solution = pfba(model, fraction_of_optimum=0.95)
         assert solution.status == "optimal"
         assert numpy.isclose(solution.x_dict["Biomass_Ecoli_core"],
                              0.95 * 0.8739, atol=1e-4, rtol=0.0)
@@ -148,48 +125,53 @@ class TestCobraFluxAnalysis:
         with warnings.catch_warnings():
             warnings.simplefilter("error", UserWarning)
             with pytest.raises((UserWarning, Infeasible, ValueError)):
-                pfba(model, solver=solver)
+                pfba(model)
 
-    @pytest.mark.parametrize("solver", all_solvers)
+    @pytest.mark.parametrize("solver", optlang_solvers)
     def test_single_gene_deletion_fba_benchmark(self, model, benchmark,
                                                 solver):
-        benchmark(single_gene_deletion, model, solver=solver)
+        model.solver = solver
+        benchmark(single_gene_deletion, model)
 
-    @pytest.mark.parametrize("solver", all_solvers)
+    @pytest.mark.parametrize("solver", optlang_solvers)
     def test_single_gene_deletion_fba(self, model, solver):
         # expected knockouts for textbook model
+        model.solver = solver
         growth_dict = {"b0008": 0.87, "b0114": 0.80, "b0116": 0.78,
                        "b2276": 0.21, "b1779": 0.00}
-        df = single_gene_deletion(model, gene_list=growth_dict.keys(),
-                                  method="fba", solver=solver)
-        assert numpy.all([df.status == 'optimal'])
-        assert all(abs(df.flux[gene] - expected) < 0.01 for
-                   gene, expected in iteritems(growth_dict))
-
-    @pytest.mark.skipif(scipy is None,
-                        reason="moma gene deletion requires scipy")
+        rates = single_gene_deletion(model,
+                                     gene_list=growth_dict.keys(),
+                                     method="fba")["growth"]
+        for gene, expected_value in iteritems(growth_dict):
+            assert abs(rates[frozenset([gene])] - expected_value) < 0.01
+
     def test_single_gene_deletion_moma_benchmark(self, model, benchmark):
         try:
-            sutil.get_solver_name(qp=True)
+            solver = sutil.get_solver_name(qp=True)
+            model.solver = solver
         except sutil.SolverNotFound:
             pytest.skip("no qp support")
 
         genes = ['b0008', 'b0114', 'b2276', 'b1779']
-        benchmark(single_gene_deletion, model, gene_list=genes, method="moma")
+        benchmark(single_gene_deletion, model, gene_list=genes,
+                  method="moma")
 
-    @pytest.mark.skipif(scipy is None,
-                        reason="moma gene deletion requires scipy")
     def test_single_deletion_linear_moma_benchmark(self, model, benchmark):
+        try:
+            solver = sutil.get_solver_name(qp=True)
+            model.solver = solver
+        except sutil.SolverNotFound:
+            pytest.skip("no qp support")
+
         genes = ['b0008', 'b0114', 'b2276', 'b1779']
         benchmark(single_gene_deletion, model, gene_list=genes,
                   method="linear moma")
 
-    @pytest.mark.skipif(scipy is None,
-                        reason="moma gene deletion requires scipy")
     def test_moma_sanity(self, model):
         """Test optimization criterion and optimality."""
         try:
-            sutil.get_solver_name(qp=True)
+            solver = sutil.get_solver_name(qp=True)
+            model.solver = solver
         except sutil.SolverNotFound:
             pytest.skip("no qp support")
 
@@ -208,11 +190,10 @@ class TestCobraFluxAnalysis:
         assert numpy.allclose(moma_sol.objective_value, moma_ssq)
         assert moma_ssq < ssq
 
-    @pytest.mark.skipif(scipy is None,
-                        reason="moma gene deletion requires scipy")
     def test_single_gene_deletion_moma(self, model):
         try:
-            sutil.get_solver_name(qp=True)
+            solver = sutil.get_solver_name(qp=True)
+            model.solver = solver
         except sutil.SolverNotFound:
             pytest.skip("no qp support")
 
@@ -220,20 +201,19 @@ class TestCobraFluxAnalysis:
         growth_dict = {"b0008": 0.87, "b0114": 0.71, "b0116": 0.56,
                        "b2276": 0.11, "b1779": 0.00}
 
-        df = single_gene_deletion(model, gene_list=growth_dict.keys(),
-                                  method="moma")
-        assert (df.status == 'optimal').all()
-        assert all(abs(df.flux[gene] - expected) < 0.01
-                   for gene, expected in iteritems(growth_dict))
-        with model:
-            add_moma(model)
-            with pytest.raises(ValueError):
-                add_moma(model)
+        result = single_gene_deletion(
+            model, gene_list=growth_dict.keys(), method="moma")["growth"]
+        for gene, expected_value in iteritems(growth_dict):
+            assert abs(result[frozenset([gene])] - expected_value) < 0.01
 
-    @pytest.mark.skipif(scipy is None,
-                        reason="moma gene deletion requires scipy")
     def test_linear_moma_sanity(self, model):
         """Test optimization criterion and optimality."""
+        try:
+            solver = sutil.get_solver_name(qp=True)
+            model.solver = solver
+        except sutil.SolverNotFound:
+            pytest.skip("no qp support")
+
         sol = model.optimize()
         with model:
             model.reactions.PFK.knock_out()
@@ -249,39 +229,43 @@ class TestCobraFluxAnalysis:
         assert numpy.allclose(moma_sol.objective_value, moma_sabs)
         assert moma_sabs < sabs
 
-    @pytest.mark.skipif(scipy is None,
-                        reason="moma gene deletion requires scipy")
     def test_single_gene_deletion_linear_moma(self, model):
+        try:
+            solver = sutil.get_solver_name(qp=True)
+            model.solver = solver
+        except sutil.SolverNotFound:
+            pytest.skip("no qp support")
+
         # expected knockouts for textbook model
         growth_dict = {"b0008": 0.87, "b0114": 0.76, "b0116": 0.65,
                        "b2276": 0.08, "b1779": 0.00}
 
-        df = single_gene_deletion(model, gene_list=growth_dict.keys(),
-                                  method="linear moma")
-        assert numpy.all([df.status == 'optimal'])
-        assert all(abs(df.flux[gene] - expected) < 0.01
+        result = single_gene_deletion(
+            model, gene_list=growth_dict.keys(),
+            method="linear moma")['growth']
+        assert all(abs(result[frozenset([gene])] - expected) < 0.01
                    for gene, expected in iteritems(growth_dict))
         with model:
             add_moma(model, linear=True)
             with pytest.raises(ValueError):
                 add_moma(model)
 
-    @pytest.mark.parametrize("solver", all_solvers)
+    @pytest.mark.parametrize("solver", optlang_solvers)
     def test_single_gene_deletion_benchmark(self, model, benchmark,
                                             solver):
-        benchmark(single_reaction_deletion, model, solver=solver)
+        model.solver = solver
+        benchmark(single_reaction_deletion, model)
 
-    @pytest.mark.parametrize("solver", all_solvers)
+    @pytest.mark.parametrize("solver", optlang_solvers)
     def test_single_reaction_deletion(self, model, solver):
         expected_results = {'FBA': 0.70404, 'FBP': 0.87392, 'CS': 0,
                             'FUM': 0.81430, 'GAPD': 0, 'GLUDy': 0.85139}
 
-        df = single_reaction_deletion(
-            model, reaction_list=expected_results.keys(), solver=solver)
-        assert len(df) == 6
-        assert numpy.all([df.status == 'optimal'])
-        assert all(abs(df.flux[gene] - expected) < 0.00001 for
-                   gene, expected in iteritems(expected_results))
+        model.solver = solver
+        results = single_reaction_deletion(
+            model, reaction_list=expected_results.keys()).to_dict()['growth']
+        for reaction, value in iteritems(expected_results):
+            assert abs(results[frozenset([reaction])] - value) < 1E-05
 
     @classmethod
     def compare_matrices(cls, matrix1, matrix2, places=3):
@@ -294,70 +278,136 @@ class TestCobraFluxAnalysis:
                 assert abs(matrix1[i][j] - matrix2[i][j]) < 10 ** -places
 
     def test_double_gene_deletion_benchmark(self, large_model, benchmark):
-        genes = ["b0726", "b4025", "b0724", "b0720", "b2935", "b2935", "b1276",
+        genes = ["b0726", "b4025", "b0724", "b0720", "b2935", "b2935",
+                 "b1276",
                  "b1241"]
         benchmark(double_gene_deletion, large_model, gene_list1=genes)
 
     def test_double_gene_deletion(self, model):
-        genes = ["b0726", "b4025", "b0724", "b0720", "b2935", "b2935", "b1276",
+        genes = ["b0726", "b4025", "b0724", "b0720", "b2935", "b2935",
+                 "b1276",
                  "b1241"]
-        growth_list = [
-            [0.858, 0.857, 0.814, 0.000, 0.858, 0.858, 0.858, 0.858],
-            [0.857, 0.863, 0.739, 0.000, 0.863, 0.863, 0.863, 0.863],
-            [0.814, 0.739, 0.814, 0.000, 0.814, 0.814, 0.814, 0.814],
-            [0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],
-            [0.858, 0.863, 0.814, 0.000, 0.874, 0.874, 0.874, 0.874],
-            [0.858, 0.863, 0.814, 0.000, 0.874, 0.874, 0.874, 0.874],
-            [0.858, 0.863, 0.814, 0.000, 0.874, 0.874, 0.874, 0.874],
-            [0.858, 0.863, 0.814, 0.000, 0.874, 0.874, 0.874, 0.874]]
-        opts = {"number_of_processes": 1} if name == "nt" else {}
-        solution = double_gene_deletion(model, gene_list1=genes, **opts)
-        assert solution["x"] == genes
-        assert solution["y"] == genes
-        self.compare_matrices(growth_list, solution["data"])
-        # test when lists differ slightly
-        solution = double_gene_deletion(model, gene_list1=genes[:-1],
-                                        gene_list2=genes,
-                                        number_of_processes=1)
-        assert solution["x"] == genes[:-1]
-        assert solution["y"] == genes
-        self.compare_matrices(growth_list[:-1], solution["data"])
+        growth_dict = {'b0720': {'b0720': 0.0,
+                                 'b0724': 0.0,
+                                 'b0726': 0.0,
+                                 'b1241': 0.0,
+                                 'b1276': 0.0,
+                                 'b2935': 0.0,
+                                 'b4025': 0.0},
+                       'b0724': {'b0720': 0.0,
+                                 'b0724': 0.814,
+                                 'b0726': 0.814,
+                                 'b1241': 0.814,
+                                 'b1276': 0.814,
+                                 'b2935': 0.814,
+                                 'b4025': 0.739},
+                       'b0726': {'b0720': 0.0,
+                                 'b0724': 0.814,
+                                 'b0726': 0.858,
+                                 'b1241': 0.858,
+                                 'b1276': 0.858,
+                                 'b2935': 0.858,
+                                 'b4025': 0.857},
+                       'b1241': {'b0720': 0.0,
+                                 'b0724': 0.814,
+                                 'b0726': 0.858,
+                                 'b1241': 0.874,
+                                 'b1276': 0.874,
+                                 'b2935': 0.874,
+                                 'b4025': 0.863},
+                       'b1276': {'b0720': 0.0,
+                                 'b0724': 0.814,
+                                 'b0726': 0.858,
+                                 'b1241': 0.874,
+                                 'b1276': 0.874,
+                                 'b2935': 0.874,
+                                 'b4025': 0.863},
+                       'b2935': {'b0720': 0.0,
+                                 'b0724': 0.814,
+                                 'b0726': 0.858,
+                                 'b1241': 0.874,
+                                 'b1276': 0.874,
+                                 'b2935': 0.874,
+                                 'b4025': 0.863},
+                       'b4025': {'b0720': 0.0,
+                                 'b0724': 0.739,
+                                 'b0726': 0.857,
+                                 'b1241': 0.863,
+                                 'b1276': 0.863,
+                                 'b2935': 0.863,
+                                 'b4025': 0.863}}
+        solution = double_gene_deletion(
+            model, gene_list1=genes, processes=4)['growth']
+        solution_one_process = double_gene_deletion(
+            model, gene_list1=genes, processes=1)['growth']
+        for (rxn_a, sub) in iteritems(growth_dict):
+            for rxn_b, growth in iteritems(sub):
+                sol = solution[frozenset([rxn_a, rxn_b])]
+                sol_one = solution_one_process[frozenset([rxn_a, rxn_b])]
+                assert round(sol, 3) == growth
+                assert round(sol_one, 3) == growth
 
     def test_double_reaction_deletion(self, model):
         reactions = ['FBA', 'ATPS4r', 'ENO', 'FRUpts2']
-        growth_list = [[0.704, 0.135, 0.000, 0.704],
-                       [0.135, 0.374, 0.000, 0.374],
-                       [0.000, 0.000, 0.000, 0.000],
-                       [0.704, 0.374, 0.000, 0.874]]
-
-        solution = double_reaction_deletion(model,
-                                            reaction_list1=reactions,
-                                            number_of_processes=1)
-        assert solution["x"] == reactions
-        assert solution["y"] == reactions
-        self.compare_matrices(growth_list, solution["data"])
+        growth_dict = {
+            "FBA": {
+                "ATPS4r": 0.135,
+                "ENO": float('nan'),
+                "FRUpts2": 0.704
+            },
+            "ATPS4r": {
+                "ENO": float('nan'),
+                "FRUpts2": 0.374
+            },
+            "ENO": {
+                "FRUpts2": 0.0
+            },
+        }
+
+        solution = double_reaction_deletion(
+            model, reaction_list1=reactions, processes=3)['growth']
+        solution_one_process = double_reaction_deletion(
+            model, reaction_list1=reactions, processes=1)['growth']
+        for (rxn_a, sub) in iteritems(growth_dict):
+            for rxn_b, growth in iteritems(sub):
+                sol = solution[frozenset([rxn_a, rxn_b])]
+                sol_one = solution_one_process[frozenset([rxn_a, rxn_b])]
+                if math.isnan(growth):
+                    assert math.isnan(sol)
+                    assert math.isnan(sol_one)
+                else:
+                    assert round(sol, 3) == growth
+                    assert round(sol_one, 3) == growth
+
+    def test_double_reaction_deletion_benchmark(self, large_model,
+                                                benchmark):
+        reactions = large_model.reactions[1::100]
+        benchmark(double_reaction_deletion, large_model,
+                  reaction_list1=reactions)
 
     @pytest.mark.parametrize("solver", all_solvers)
-    def test_flux_variability_benchmark(self, large_model, benchmark, solver):
-        benchmark(flux_variability_analysis, large_model, solver=solver,
+    def test_flux_variability_benchmark(self, large_model, benchmark,
+                                        solver):
+        large_model.solver = solver
+        benchmark(flux_variability_analysis, large_model,
                   reaction_list=large_model.reactions[1::3])
 
     @pytest.mark.parametrize("solver", optlang_solvers)
     def test_flux_variability_loopless_benchmark(self, model, benchmark,
                                                  solver):
+        model.solver = solver
         benchmark(flux_variability_analysis, model, loopless=True,
-                  solver=solver, reaction_list=model.reactions[1::3])
+                  reaction_list=model.reactions[1::3])
 
     @pytest.mark.parametrize("solver", optlang_solvers)
     def test_pfba_flux_variability(self, model, pfba_fva_results,
                                    fva_results, solver):
+        model.solver = solver
         with pytest.warns(UserWarning):
             flux_variability_analysis(
-                model, pfba_factor=0.1, solver=solver,
-                reaction_list=model.reactions[1::3])
+                model, pfba_factor=0.1, reaction_list=model.reactions[1::3])
         fva_out = flux_variability_analysis(
-            model, pfba_factor=1.1, solver=solver,
-            reaction_list=model.reactions)
+            model, pfba_factor=1.1, reaction_list=model.reactions)
         for name, result in iteritems(fva_out.T):
             for k, v in iteritems(result):
                 assert abs(pfba_fva_results[k][name] - v) < 0.00001
@@ -366,34 +416,37 @@ class TestCobraFluxAnalysis:
         loop_reactions = [model.reactions.get_by_id(rid)
                           for rid in ("FRD7", "SUCDi")]
         fva_loopless = flux_variability_analysis(
-            model, solver=solver, pfba_factor=1.1,
-            reaction_list=loop_reactions, loopless=True)
-        assert numpy.allclose(fva_loopless["maximum"], fva_loopless["minimum"])
+            model, pfba_factor=1.1, reaction_list=loop_reactions,
+            loopless=True)
+        assert numpy.allclose(fva_loopless["maximum"],
+                              fva_loopless["minimum"])
 
     @pytest.mark.parametrize("solver", all_solvers)
     def test_flux_variability(self, model, fva_results, solver):
-        if solver == "esolver":
-            pytest.skip("esolver too slow...")
+        model.solver = solver
         fva_out = flux_variability_analysis(
-            model, solver=solver, reaction_list=model.reactions)
+            model, reaction_list=model.reactions)
         for name, result in iteritems(fva_out.T):
             for k, v in iteritems(result):
                 assert abs(fva_results[k][name] - v) < 0.00001
 
     @pytest.mark.parametrize("solver", optlang_solvers)
     def test_flux_variability_loopless(self, model, solver):
+        model.solver = solver
         loop_reactions = [model.reactions.get_by_id(rid)
                           for rid in ("FRD7", "SUCDi")]
         fva_normal = flux_variability_analysis(
-            model, solver=solver, reaction_list=loop_reactions)
+            model, reaction_list=loop_reactions)
         fva_loopless = flux_variability_analysis(
-            model, solver=solver, reaction_list=loop_reactions, loopless=True)
+            model, reaction_list=loop_reactions, loopless=True)
 
-        assert not numpy.allclose(fva_normal["maximum"], fva_normal["minimum"])
-        assert numpy.allclose(fva_loopless["maximum"], fva_loopless["minimum"])
+        assert not numpy.allclose(fva_normal["maximum"],
+                                  fva_normal["minimum"])
+        assert numpy.allclose(fva_loopless["maximum"],
+                              fva_loopless["minimum"])
 
     def test_fva_data_frame(self, model):
-        df = flux_variability_analysis(model, return_frame=True)
+        df = flux_variability_analysis(model)
         assert numpy.all([df.columns.values == ['maximum', 'minimum']])
 
     def test_fva_infeasible(self, model):
@@ -410,13 +463,16 @@ class TestCobraFluxAnalysis:
     def test_essential_genes(self, model):
         essential_genes = {'b2779', 'b1779', 'b0720', 'b2416',
                            'b2926', 'b1136', 'b2415'}
-        observed_essential_genes = {g.id for g in find_essential_genes(model)}
+        observed_essential_genes = {g.id for g in
+                                    find_essential_genes(model)}
         assert observed_essential_genes == essential_genes
 
     def test_essential_reactions(self, model):
-        essential_reactions = {'GLNS', 'Biomass_Ecoli_core', 'PIt2r', 'GAPD',
+        essential_reactions = {'GLNS', 'Biomass_Ecoli_core', 'PIt2r',
+                               'GAPD',
                                'ACONTb', 'EX_nh4_e', 'ENO', 'EX_h_e',
-                               'EX_glc__D_e', 'ICDHyr', 'CS', 'NH4t', 'GLCpts',
+                               'EX_glc__D_e', 'ICDHyr', 'CS', 'NH4t',
+                               'GLCpts',
                                'PGM', 'EX_pi_e', 'PGK', 'RPI', 'ACONTa'}
         observed_essential_reactions = {r.id for r in
                                         find_essential_reactions(model)}
@@ -424,31 +480,24 @@ class TestCobraFluxAnalysis:
 
     @pytest.mark.parametrize("solver", all_solvers)
     def test_find_blocked_reactions(self, model, solver):
-        result = find_blocked_reactions(model, model.reactions[40:46],
-                                        solver=solver)
+        model.solver = solver
+        result = find_blocked_reactions(model, model.reactions[40:46])
         assert result == ['FRUpts2']
 
-        result = find_blocked_reactions(model, model.reactions[42:48],
-                                        solver=solver)
+        result = find_blocked_reactions(model, model.reactions[42:48])
         assert set(result) == {'FUMt2_2', 'FRUpts2'}
 
         result = find_blocked_reactions(model, model.reactions[30:50],
-                                        solver=solver,
                                         open_exchanges=True)
         assert result == []
 
-    def test_legacy_loopless_benchmark(self, benchmark):
-        test_model = construct_ll_test_model()
-        benchmark(lambda: construct_loopless_model(test_model).optimize(
-            solver="cglpk"))
-
     def test_loopless_benchmark_before(self, benchmark):
         test_model = construct_ll_test_model()
 
         def _():
             with test_model:
                 add_loopless(test_model)
-                test_model.optimize(solver="optlang-glpk")
+                test_model.optimize()
 
         benchmark(_)
 
@@ -456,23 +505,6 @@ class TestCobraFluxAnalysis:
         test_model = construct_ll_test_model()
         benchmark(loopless_solution, test_model)
 
-    def test_legacy_loopless(self):
-        try:
-            get_solver_name(mip=True)
-        except SolverNotFound:
-            pytest.skip("no MILP solver found")
-        test_model = construct_ll_test_model()
-        feasible_sol = construct_loopless_model(test_model).optimize(
-            solver="cglpk")
-        assert feasible_sol.status == "optimal"
-        test_model.reactions.v3.lower_bound = 1
-        infeasible_mod = construct_loopless_model(test_model)
-
-        with warnings.catch_warnings():
-            warnings.simplefilter("error", UserWarning)
-            with pytest.raises(UserWarning):
-                infeasible_mod.optimize(solver="cglpk")
-
     def test_loopless_solution(self, ll_test_model):
         solution_feasible = loopless_solution(ll_test_model)
         ll_test_model.reactions.v3.lower_bound = 1
@@ -485,11 +517,6 @@ class TestCobraFluxAnalysis:
         fluxes = model.optimize().fluxes
         ll_solution = loopless_solution(model, fluxes=fluxes)
         assert len(ll_solution.fluxes) == len(model.reactions)
-        fluxes["Biomass_Ecoli_core"] = 1
-        with warnings.catch_warnings():
-            warnings.simplefilter("error", UserWarning)
-            with pytest.raises(UserWarning):
-                loopless_solution(model, fluxes=fluxes)
 
     def test_add_loopless(self, ll_test_model):
         add_loopless(ll_test_model)
@@ -556,32 +583,19 @@ class TestCobraFluxAnalysis:
             assert 'TKT2' not in {r.id for r in solution[0]}
             assert gf.validate(solution[0])
 
-    def test_phenotype_phase_plane_benchmark(self, model, benchmark):
-        benchmark(calculate_phenotype_phase_plane,
-                  model, "EX_glc__D_e", "EX_o2_e",
-                  reaction1_npoints=20, reaction2_npoints=20)
-
-    def test_phenotype_phase_plane(self, model):
-        data = calculate_phenotype_phase_plane(
-            model, "EX_glc__D_e", "EX_o2_e",
-            reaction1_npoints=20, reaction2_npoints=20)
-        assert data.growth_rates.shape == (20, 20)
-        assert abs(data.growth_rates.max() - 1.20898) < 0.0001
-        assert abs(data.growth_rates[0, :].max()) < 0.0001
-        if pyplot is None or axes3d is None:
-            pytest.skip("can't test plots without 3D plotting")
-        data.plot()
-
-    def check_line(self, output, expected_entries, pattern=re.compile(r"\s")):
+    def check_line(self, output, expected_entries,
+                   pattern=re.compile(r"\s")):
         """Ensure each expected entry is in the output."""
-        output_set = set(pattern.sub("", line) for line in output.splitlines())
+        output_set = set(
+            pattern.sub("", line) for line in output.splitlines())
         for elem in expected_entries:
             assert pattern.sub("", elem) in output_set
 
     def check_in_line(self, output, expected_entries,
                       pattern=re.compile(r"\s")):
         """Ensure each expected entry is contained in the output."""
-        output_strip = [pattern.sub("", line) for line in output.splitlines()]
+        output_strip = [pattern.sub("", line) for line in
+                        output.splitlines()]
         for elem in expected_entries:
             assert any(
                 pattern.sub("", elem) in line for line in output_strip), \
@@ -687,7 +701,8 @@ class TestCobraFluxAnalysis:
     @pytest.mark.parametrize("fraction, met", [(0.99, "fdp_c")])
     def test_metabolite_summary_with_fva(self, model, opt_solver, fraction,
                                          met):
-        if opt_solver in ("optlang-glpk", "optlang-cplex", "optlang-gurobi"):
+        if opt_solver in (
+                "optlang-glpk", "optlang-cplex", "optlang-gurobi"):
             pytest.xfail("FVA currently buggy")
 
         model.solver = opt_solver
@@ -814,14 +829,14 @@ class TestCobraFluxSampling:
         s_inhom = sample(model, 64)
         model.reactions.ACALD.bounds = (-1.5 - 1e-3, -1.5 + 1e-3)
         s_hom = sample(model, 64)
-        relative_diff = (s_inhom.std() + 1e-12)/(s_hom.std() + 1e-12)
+        relative_diff = (s_inhom.std() + 1e-12) / (s_hom.std() + 1e-12)
         assert 0.5 < relative_diff.abs().mean() < 2
 
         model.reactions.ACALD.bounds = (-1.5, -1.5)
         s_inhom = sample(model, 64, method="achr")
         model.reactions.ACALD.bounds = (-1.5 - 1e-3, -1.5 + 1e-3)
         s_hom = sample(model, 64, method="achr")
-        relative_diff = (s_inhom.std() + 1e-12)/(s_hom.std() + 1e-12)
+        relative_diff = (s_inhom.std() + 1e-12) / (s_hom.std() + 1e-12)
         assert 0.5 < relative_diff.abs().mean() < 2
 
     def test_reproject(self):
@@ -861,7 +876,8 @@ class TestProductionEnvelope:
         assert numpy.isclose(df["flux_maximum"].sum(), 1737.466, atol=1e-3)
         assert numpy.isclose(df["carbon_yield_maximum"].sum(), 83.579,
                              atol=1e-3)
-        assert numpy.isclose(df["mass_yield_maximum"].sum(), 82.176, atol=1e-3)
+        assert numpy.isclose(df["mass_yield_maximum"].sum(), 82.176,
+                             atol=1e-3)
 
 
 class TestReactionUtils:
@@ -870,7 +886,8 @@ class TestReactionUtils:
     @pytest.mark.parametrize("solver", all_solvers)
     def test_assess(self, model, solver):
         with model:
-            assert assess(model, model.reactions.GLCpts, solver=solver) is True
+            assert assess(model, model.reactions.GLCpts,
+                          solver=solver) is True
             pyr = model.metabolites.pyr_c
             a = Metabolite('a')
             b = Metabolite('b')
@@ -879,6 +896,7 @@ class TestReactionUtils:
             pyr_a2b.add_metabolites({pyr: -1, a: -1, b: 1})
             model.add_reactions([pyr_a2b])
             res = assess(model, pyr_a2b, 0.01, solver=solver)
-            expected = {'precursors': {a: {'required': 0.01, 'produced': 0.0}},
-                        'products': {b: {'required': 0.01, 'capacity': 0.0}}}
+            expected = {
+                'precursors': {a: {'required': 0.01, 'produced': 0.0}},
+                'products': {b: {'required': 0.01, 'capacity': 0.0}}}
             assert res == expected
diff --git a/cobra/test/test_io.py b/cobra/test/test_io.py
index 8c03df3..08a46a6 100644
--- a/cobra/test/test_io.py
+++ b/cobra/test/test_io.py
@@ -14,8 +14,6 @@ from six import iteritems
 
 from cobra import io
 
-from .conftest import data_directory
-
 
 def write_legacy_sbml_placeholder():
     pass
@@ -185,10 +183,12 @@ class TestCobraIO:
     @classmethod
     def extra_comparisons(cls, name, model1, model2):
         assert model1.compartments == model2.compartments
-        assert model1.metabolites[4].annotation == model2.metabolites[
-            4].annotation
-        assert model1.reactions[4].annotation == model2.reactions[4].annotation
-        assert model1.genes[5].annotation == model2.genes[5].annotation
+        assert dict(model1.metabolites[4].annotation) == dict(
+            model2.metabolites[4].annotation)
+        assert dict(model1.reactions[4].annotation) == dict(
+            model2.reactions[4].annotation)
+        assert dict(model1.genes[5].annotation) == dict(
+            model2.genes[5].annotation)
         for attr in ("id", "name"):
             assert getattr(model1.genes[0], attr) == getattr(model2.genes[0],
                                                              attr)
diff --git a/cobra/test/test_io_order.py b/cobra/test/test_io_order.py
new file mode 100644
index 0000000..5d9016b
--- /dev/null
+++ b/cobra/test/test_io_order.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
+
+import logging
+from operator import attrgetter
+from os.path import join
+from random import sample
+
+import pytest
+
+import cobra.io as cio
+from cobra import DictList, Model
+
+LOGGER = logging.getLogger(__name__)
+
+
+ at pytest.fixture(scope="module")
+def tmp_path(tmpdir_factory):
+    return str(tmpdir_factory.mktemp("model_order"))
+
+
+ at pytest.fixture(scope="module")
+def minimized_shuffle(small_model):
+    model = small_model.copy()
+    chosen = sample(list(set(model.reactions) - set(model.exchanges)), 10)
+    new = Model("minimized_shuffle")
+    new.add_reactions(chosen)
+    LOGGER.debug("'%s' has %d metabolites, %d reactions, and %d genes.",
+                 new.id, new.metabolites, new.reactions, new.genes)
+    return new
+
+
+ at pytest.fixture(scope="module")
+def minimized_sorted(minimized_shuffle):
+    model = minimized_shuffle.copy()
+    model.id = "minimized_sorted"
+    model.metabolites = DictList(
+        sorted(model.metabolites, key=attrgetter("id")))
+    model.genes = DictList(sorted(model.genes, key=attrgetter("id")))
+    model.reactions = DictList(sorted(model.reactions, key=attrgetter("id")))
+    return model
+
+
+ at pytest.fixture(scope="module")
+def minimized_reverse(minimized_shuffle):
+    model = minimized_shuffle.copy()
+    model.id = "minimized_reverse"
+    model.metabolites = DictList(
+        sorted(model.metabolites, key=attrgetter("id"), reverse=True))
+    model.genes = DictList(
+        sorted(model.genes, key=attrgetter("id"), reverse=True))
+    model.reactions = DictList(
+        sorted(model.reactions, key=attrgetter("id"), reverse=True))
+    return model
+
+
+ at pytest.fixture(scope="module", params=[
+    "minimized_shuffle", "minimized_reverse", "minimized_sorted"])
+def template(request, minimized_shuffle, minimized_reverse, minimized_sorted):
+    return locals()[request.param]
+
+
+ at pytest.fixture(scope="module", params=["metabolites", "reactions", "genes"])
+def attribute(request):
+    return request.param
+
+
+def get_ids(iterable):
+    return [x.id for x in iterable]
+
+
+ at pytest.mark.parametrize("read, write, ext", [
+    ("read_sbml_model", "write_sbml_model", ".xml"),
+    pytest.mark.skip(("read_legacy_sbml", "write_legacy_sbml", ".xml"),
+                     reason="Order for legacy SBML I/O is uninteresting."),
+    pytest.mark.skip(("load_matlab_model", "save_matlab_model", ".mat"),
+                     reason="Order for Matlab model I/O is uninteresting."),
+    ("load_json_model", "save_json_model", ".json"),
+    ("load_yaml_model", "save_yaml_model", ".yml"),
+])
+def test_io_order(attribute, read, write, ext, template, tmp_path):
+    read = getattr(cio, read)
+    write = getattr(cio, write)
+    filename = join(tmp_path, "template" + ext)
+    write(template, filename)
+    model = read(filename)
+    model_elements = get_ids(getattr(model, attribute))
+    template_elements = get_ids(getattr(template, attribute))
+    assert len(model_elements) == len(template_elements)
+    assert set(model_elements) == set(template_elements)
+    assert model_elements == template_elements
diff --git a/cobra/test/test_manipulation.py b/cobra/test/test_manipulation.py
index a7d37f3..7032637 100644
--- a/cobra/test/test_manipulation.py
+++ b/cobra/test/test_manipulation.py
@@ -6,56 +6,26 @@ import pytest
 from cobra.core import Metabolite, Model, Reaction
 from cobra.manipulation import *
 
-from .conftest import model, salmonella
-
 
 class TestManipulation:
     """Test functions in cobra.manipulation"""
 
-    def test_canonical_form(self, model):
-        solver = 'cglpk'
-        # add G constraint to test
-        g_constr = Metabolite("SUCCt2_2__test_G_constraint")
-        g_constr._constraint_sense = "G"
-        g_constr._bound = 5.0
-        model.reactions.get_by_id("SUCCt2_2").add_metabolites({g_constr: 1})
-        assert abs(model.optimize("maximize", solver=solver).f - 0.855) < 0.001
-        # convert to canonical form
-        model = canonical_form(model)
-        assert abs(
-            model.optimize("maximize", solver=solver).f - 0.855) < 10 ** -3
-
-    def test_canonical_form_minimize(self, model):
-        solver = 'cglpk'
-        # make a minimization problem
-        model.reactions.get_by_id("Biomass_Ecoli_core").lower_bound = 0.5
-        for reaction in model.reactions:
-            reaction.objective_coefficient = reaction.id == "GAPD"
-        assert abs(
-            model.optimize("minimize", solver=solver).f - 6.27) < 10 ** -3
-        # convert to canonical form. Convert minimize to maximize
-        model = canonical_form(model, objective_sense="minimize")
-        assert abs(
-            model.optimize("maximize", solver=solver).f + 6.27) < 10 ** -3
-        # lower bounds should now be <= constraints
-        assert model.reactions.get_by_id(
-            "Biomass_Ecoli_core").lower_bound == 0.0
-
     def test_modify_reversible(self, model):
-        solver = 'cglpk'
+        solver = 'glpk'
+        model.solver = solver
         model1 = model.copy()
-        solution1 = model1.optimize(solver=solver)
+        solution1 = model1.optimize()
         model2 = model.copy()
         convert_to_irreversible(model2)
-        solution2 = model2.optimize(solver=solver)
+        solution2 = model2.optimize()
         assert abs(solution1.f - solution2.f) < 10 ** -3
         revert_to_reversible(model2)
-        solution2_rev = model2.optimize(solver=solver)
+        solution2_rev = model2.optimize()
         assert abs(solution1.f - solution2_rev.f) < 10 ** -3
         # Ensure revert_to_reversible is robust to solutions generated both
         # before and after reversibility conversion, or not solved at all.
         model3 = model.copy()
-        solution3 = model3.optimize(solver=solver)
+        solution3 = model3.optimize()
         convert_to_irreversible(model3)
         revert_to_reversible(model3)
         assert abs(solution1.f - solution3.f) < 10 ** -3
@@ -64,7 +34,7 @@ class TestManipulation:
         glc = model4.reactions.get_by_id("EX_glc__D_e")
         glc.upper_bound = -1
         convert_to_irreversible(model4)
-        solution4 = model4.optimize(solver=solver)
+        solution4 = model4.optimize()
         assert abs(solution1.f - solution4.f) < 10 ** -3
         glc_rev = model4.reactions.get_by_id(glc.notes["reflection"])
         assert glc_rev.lower_bound == 1
diff --git a/cobra/test/test_model.py b/cobra/test/test_model.py
index b272264..6605674 100644
--- a/cobra/test/test_model.py
+++ b/cobra/test/test_model.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+
 from __future__ import absolute_import
 
 import warnings
@@ -8,16 +9,16 @@ import numpy
 from math import isnan
 import pytest
 import pandas as pd
-from sympy import S
+from optlang.symbolics import Zero
 
 import cobra.util.solver as su
 from cobra.core import Metabolite, Model, Reaction
-from cobra.solvers import solver_dict
 from cobra.util import create_stoichiometric_matrix
 from cobra.exceptions import OptimizationError
 
 stable_optlang = ["glpk", "cplex", "gurobi"]
 optlang_solvers = ["optlang-" + s for s in stable_optlang if s in su.solvers]
+solver_dict = stable_optlang
 
 try:
     import scipy
@@ -754,8 +755,9 @@ class TestCobraModel:
                 assert 'constraint' in model.constraints
                 assert 'foo' in model.variables
                 assert 'tiny_EX_glc__D_e' in model.reactions
-                assert model.objective.expression == model.reactions.get_by_id(
-                    'Biomass_Ecoli_core').flux_expression
+                assert (model.objective.expression.simplify() ==
+                        model.reactions.get_by_id(
+                            'Biomass_Ecoli_core').flux_expression.simplify())
             assert 'ex1' not in model.reactions
             assert 'constraint' not in model.constraints
             assert 'foo' not in model.variables
@@ -853,7 +855,7 @@ class TestCobraModel:
 
     def test_problem_properties(self, model):
         new_variable = model.problem.Variable("test_variable")
-        new_constraint = model.problem.Constraint(S.Zero,
+        new_constraint = model.problem.Constraint(Zero,
                                                   name="test_constraint")
         model.add_cons_vars([new_variable, new_constraint])
         assert "test_variable" in model.variables.keys()
@@ -931,76 +933,6 @@ class TestCobraModel:
 class TestStoichiometricMatrix:
     """Test the simple replacement for ArrayBasedModel"""
 
-    @pytest.mark.skipif(not scipy, reason='Sparse array methods require scipy')
-    def test_array_model(self, model):
-        """ legacy test """
-        for matrix_type in ["scipy.dok_matrix", "scipy.lil_matrix"]:
-            array_model = model.to_array_based_model(matrix_type=matrix_type)
-            assert array_model.S[7, 0] == -1
-            assert array_model.S[43, 0] == 0
-            array_model.S[43, 0] = 1
-            assert array_model.S[43, 0] == 1
-            assert array_model.reactions[0]._metabolites[
-                       array_model.metabolites[43]] == 1
-            array_model.S[43, 0] = 0
-            assert array_model.lower_bounds[0] == array_model.reactions[
-                0].lower_bound
-            assert array_model.lower_bounds[5] == array_model.reactions[
-                5].lower_bound
-            assert array_model.upper_bounds[0] == array_model.reactions[
-                0].upper_bound
-            assert array_model.upper_bounds[5] == array_model.reactions[
-                5].upper_bound
-            array_model.lower_bounds[6] = 2
-            assert array_model.lower_bounds[6] == 2
-            assert array_model.reactions[6].lower_bound == 2
-            # this should fail because it is the wrong size
-            with pytest.raises(Exception):
-                array_model.upper_bounds = [0, 1]
-            array_model.upper_bounds = [0] * len(array_model.reactions)
-            assert max(array_model.upper_bounds) == 0
-            # test something for all the attributes
-            array_model.lower_bounds[2] = -1
-            assert array_model.reactions[2].lower_bound == -1
-            assert array_model.lower_bounds[2] == -1
-            array_model.objective_coefficients[2] = 1
-            assert array_model.reactions[2].objective_coefficient == 1
-            assert array_model.objective_coefficients[2] == 1
-            array_model.b[2] = 1
-            assert array_model.metabolites[2]._bound == 1
-            assert array_model.b[2] == 1
-            array_model.constraint_sense[2] = "L"
-            assert array_model.metabolites[2]._constraint_sense == "L"
-            assert array_model.constraint_sense[2] == "L"
-            # test resize matrix on reaction removal
-            m, n = array_model.S.shape
-            array_model.remove_reactions([array_model.reactions[2]],
-                                         remove_orphans=False)
-            assert len(array_model.metabolites) == array_model.S.shape[0]
-            assert len(array_model.reactions) == array_model.S.shape[1]
-            assert array_model.S.shape == (m, n - 1)
-
-    @pytest.mark.skipif(not scipy, reason='Sparse array methods require scipy')
-    def test_array_based_model_add(self, model):
-        """ legacy test """
-        array_model = model.to_array_based_model()
-        m = len(array_model.metabolites)
-        n = len(array_model.reactions)
-        for matrix_type in ["scipy.dok_matrix", "scipy.lil_matrix"]:
-            test_model = model.copy().to_array_based_model(
-                matrix_type=matrix_type)
-            test_reaction = Reaction("test")
-            test_reaction.add_metabolites({test_model.metabolites[0]: 4})
-            test_reaction.lower_bound = -3.14
-            test_model.add_reaction(test_reaction)
-            assert len(test_model.reactions) == n + 1
-            assert test_model.S.shape == (m, n + 1)
-            assert len(test_model.lower_bounds) == n + 1
-            assert len(test_model.upper_bounds) == n + 1
-            assert test_model.S[0, n] == 4
-            assert test_model.S[7, 0] == -1
-            assert test_model.lower_bounds[n] == -3.14
-
     def test_dense_matrix(self, model):
         S = create_stoichiometric_matrix(model, array_type='dense', dtype=int)
         assert S.dtype == int
diff --git a/cobra/test/test_solver_model.py b/cobra/test/test_solver_model.py
index 37ca3b3..e0d506d 100644
--- a/cobra/test/test_solver_model.py
+++ b/cobra/test/test_solver_model.py
@@ -14,12 +14,6 @@ import cobra
 from cobra.core import Metabolite, Model, Reaction, Solution
 from cobra.util.solver import SolverNotFound, set_objective, solvers
 
-try:
-    import scipy
-except ImportError:
-    scipy = None
-
-
 solver_trials = ['glpk',
                  pytest.mark.skipif('cplex' not in solvers,
                                     reason='no cplex')]
@@ -28,10 +22,15 @@ solver_trials = ['glpk',
 @pytest.fixture(scope="function", params=solver_trials)
 def solved_model(request, model):
     model.solver = request.param
-    solution = model.optimize(solution_type=Solution)
+    solution = model.optimize()
     return solution, model
 
 
+def same_ex(ex1, ex2):
+    """Compare to expressions for mathematical equality."""
+    return ex1.simplify() == ex2.simplify()
+
+
 class TestSolution:
     def test_solution_contains_only_reaction_specific_values(self,
                                                              solved_model):
@@ -54,22 +53,22 @@ class TestReaction:
         pgi_reaction = model.reactions.PGI
         test_met = model.metabolites[0]
         pgi_reaction.add_metabolites({test_met: 42}, combine=False)
-        assert pgi_reaction.metabolites[test_met] == 42
+        assert pgi_reaction.metabolites[test_met] == 42.0
         assert model.constraints[
                    test_met.id].expression.as_coefficients_dict()[
-                   pgi_reaction.forward_variable] == 42
+                   pgi_reaction.forward_variable] == 42.0
         assert model.constraints[
                    test_met.id].expression.as_coefficients_dict()[
-                   pgi_reaction.reverse_variable] == -42
+                   pgi_reaction.reverse_variable] == -42.0
 
         pgi_reaction.add_metabolites({test_met: -10}, combine=True)
-        assert pgi_reaction.metabolites[test_met] == 32
+        assert pgi_reaction.metabolites[test_met] == 32.0
         assert model.constraints[
                    test_met.id].expression.as_coefficients_dict()[
-                   pgi_reaction.forward_variable] == 32
+                   pgi_reaction.forward_variable] == 32.0
         assert model.constraints[
                    test_met.id].expression.as_coefficients_dict()[
-                   pgi_reaction.reverse_variable] == -32
+                   pgi_reaction.reverse_variable] == -32.0
 
         pgi_reaction.add_metabolites({test_met: 0}, combine=False)
         with pytest.raises(KeyError):
@@ -290,7 +289,7 @@ class TestReaction:
         assert rxn.reverse_variable.ub == 1000.
 
     def test_model_less_reaction(self, model):
-        model.optimize(solution_type=Solution)
+        model.slim_optimize()
         for reaction in model.reactions:
             assert isinstance(reaction.flux, float)
             assert isinstance(reaction.reduced_cost, float)
@@ -420,10 +419,10 @@ class TestReaction:
         for reaction in model.reactions:
             reaction.add_metabolites({test_metabolite: -66}, combine=True)
             assert reaction.metabolites[test_metabolite] == -66
-            assert model.constraints['test'].expression.has(
-                -66. * reaction.forward_variable)
-            assert model.constraints['test'].expression.has(
-                66. * reaction.reverse_variable)
+            assert model.constraints['test'].get_linear_coefficients(
+                [reaction.forward_variable])[reaction.forward_variable] == -66
+            assert model.constraints['test'].get_linear_coefficients(
+                [reaction.reverse_variable])[reaction.reverse_variable] == 66
             already_included_metabolite = \
                 list(reaction.metabolites.keys())[0]
             previous_coefficient = reaction.get_coefficient(
@@ -433,12 +432,14 @@ class TestReaction:
             new_coefficient = previous_coefficient + 10
             assert reaction.metabolites[
                        already_included_metabolite] == new_coefficient
-            assert model.constraints[
-                already_included_metabolite.id].expression.has(
-                new_coefficient * reaction.forward_variable)
-            assert model.constraints[
-                already_included_metabolite.id].expression.has(
-                -1 * new_coefficient * reaction.reverse_variable)
+            assert (model.constraints[
+                    already_included_metabolite.id].get_linear_coefficients(
+                    [reaction.forward_variable])[reaction.forward_variable] ==
+                    new_coefficient)
+            assert (model.constraints[
+                    already_included_metabolite.id].get_linear_coefficients(
+                    [reaction.reverse_variable])[
+                        reaction.reverse_variable] == -new_coefficient)
 
     @pytest.mark.xfail(reason='non-deterministic test')
     def test_add_metabolites_combine_false(self, model):
@@ -487,6 +488,8 @@ class TestReaction:
     def test_remove_from_model(self, model):
         pgi = model.reactions.PGI
         g6p = model.metabolites.g6p_c
+        pgi_flux = model.optimize().fluxes['PGI']
+        assert abs(pgi_flux) > 1E-6
 
         with model:
             pgi.remove_from_model()
@@ -495,6 +498,7 @@ class TestReaction:
             assert pgi.id not in model.variables
             assert pgi.reverse_id not in model.variables
             assert pgi not in g6p.reactions
+            model.optimize()
 
         assert "PGI" in model.reactions
         assert pgi.id in model.variables
@@ -502,6 +506,7 @@ class TestReaction:
         assert pgi.forward_variable.problem is model.solver
         assert pgi in g6p.reactions
         assert g6p in pgi.metabolites
+        assert numpy.isclose(pgi_flux, model.optimize().fluxes['PGI'])
 
     def test_change_id_is_reflected_in_solver(self, model):
         for i, reaction in enumerate(model.reactions):
@@ -533,11 +538,11 @@ class TestSolverBasedModel:
         pgi.objective_coefficient = 2
         coef_dict = model.objective.expression.as_coefficients_dict()
         # Check that objective has been updated
-        assert coef_dict[pgi.forward_variable] == 2
-        assert coef_dict[pgi.reverse_variable] == -2
+        assert coef_dict[pgi.forward_variable] == 2.0
+        assert coef_dict[pgi.reverse_variable] == -2.0
         # Check that original objective is still in there
-        assert coef_dict[biomass_r.forward_variable] == 1
-        assert coef_dict[biomass_r.reverse_variable] == -1
+        assert coef_dict[biomass_r.forward_variable] == 1.0
+        assert coef_dict[biomass_r.reverse_variable] == -1.0
 
     def test_transfer_objective(self, model):
         new_mod = Model("new model")
@@ -578,6 +583,28 @@ class TestSolverBasedModel:
         assert coefficients_dict[
                    model.reactions.r2.reverse_variable] == -3.
 
+    def test_add_reactions_single_existing(self, model):
+        rxn = model.reactions[0]
+        r1 = Reaction(rxn.id)
+        r1.add_metabolites({Metabolite('A'): -1, Metabolite('B'): 1})
+        r1.lower_bound, r1.upper_bound = -999999., 999999.
+        model.add_reactions([r1])
+        assert rxn in model.reactions
+        assert r1 is not model.reactions.get_by_id(rxn.id)
+
+    def test_add_reactions_duplicate(self, model):
+        rxn = model.reactions[0]
+        r1 = Reaction('r1')
+        r1.add_metabolites({Metabolite('A'): -1, Metabolite('B'): 1})
+        r1.lower_bound, r1.upper_bound = -999999., 999999.
+        r2 = Reaction(rxn.id)
+        r2.add_metabolites(
+            {Metabolite('A'): -1, Metabolite('C'): 1, Metabolite('D'): 1})
+        model.add_reactions([r1, r2])
+        assert r1 in model.reactions
+        assert rxn in model.reactions
+        assert r2 is not model.reactions.get_by_id(rxn.id)
+
     def test_add_cobra_reaction(self, model):
         r = cobra.Reaction(id="c1")
         model.add_reaction(r)
@@ -644,10 +671,9 @@ class TestSolverBasedModel:
 
     def test_objective(self, model):
         obj = model.objective
-        assert {var.name: coef for var, coef in
-                obj.expression.as_coefficients_dict().items()} == {
-                   'Biomass_Ecoli_core_reverse_2cdba': -1,
-                   'Biomass_Ecoli_core': 1}
+        assert obj.get_linear_coefficients(obj.variables) == {
+                   model.variables["Biomass_Ecoli_core_reverse_2cdba"]: -1,
+                   model.variables["Biomass_Ecoli_core"]: 1}
         assert obj.direction == "max"
 
     def test_change_objective(self, model):
@@ -655,43 +681,43 @@ class TestSolverBasedModel:
                      1.0 * model.variables['PFK']
         model.objective = model.problem.Objective(
             expression)
-        assert model.objective.expression == expression
+        assert same_ex(model.objective.expression, expression)
         model.objective = "ENO"
         eno_obj = model.problem.Objective(
             model.reactions.ENO.flux_expression, direction="max")
         pfk_obj = model.problem.Objective(
             model.reactions.PFK.flux_expression, direction="max")
-        assert model.objective == eno_obj
+        assert same_ex(model.objective.expression, eno_obj.expression)
 
         with model:
             model.objective = "PFK"
-            assert model.objective == pfk_obj
-        assert model.objective == eno_obj
+            assert same_ex(model.objective.expression, pfk_obj.expression)
+        assert same_ex(model.objective.expression, eno_obj.expression)
         expression = model.objective.expression
         atpm = model.reactions.get_by_id("ATPM")
         biomass = model.reactions.get_by_id("Biomass_Ecoli_core")
         with model:
             model.objective = atpm
-        assert model.objective.expression == expression
+        assert same_ex(model.objective.expression, expression)
         with model:
             atpm.objective_coefficient = 1
             biomass.objective_coefficient = 2
-        assert model.objective.expression == expression
+        assert same_ex(model.objective.expression, expression)
 
         with model:
             set_objective(model, model.problem.Objective(
                 atpm.flux_expression))
-            assert model.objective.expression == atpm.flux_expression
-        assert model.objective.expression == expression
+            assert same_ex(model.objective.expression, atpm.flux_expression)
+        assert same_ex(model.objective.expression, expression)
 
         expression = model.objective.expression
         with model:
             with model:  # Test to make sure nested contexts are OK
                 set_objective(model, atpm.flux_expression,
                               additive=True)
-                assert (model.objective.expression ==
-                        expression + atpm.flux_expression)
-        assert model.objective.expression == expression
+                assert same_ex(model.objective.expression,
+                               expression + atpm.flux_expression)
+        assert same_ex(model.objective.expression, expression)
 
     def test_set_reaction_objective(self, model):
         model.objective = model.reactions.ACALD
@@ -715,27 +741,12 @@ class TestSolverBasedModel:
     def test_solver_change(self, model):
         solver_id = id(model.solver)
         problem_id = id(model.solver.problem)
-        solution = model.optimize(solution_type=Solution).fluxes
+        solution = model.optimize().fluxes
         model.solver = "cplex"
         assert id(model.solver) != solver_id
         assert id(model.solver.problem) != problem_id
-        new_solution = model.optimize(solution_type=Solution).fluxes
-        for key in list(solution.keys()):
-            assert round(abs(new_solution[key] - solution[key]),
-                         7) == 0
-
-    @pytest.mark.skipif("cplex" not in solvers, reason="need cplex")
-    def test_solver_change_with_optlang_interface(self, model):
-        solver_id = id(model.solver)
-        problem_id = id(model.solver.problem)
-        solution = model.optimize(solution_type=Solution).fluxes
-        model.solver = optlang.cplex_interface
-        assert id(model.solver) != solver_id
-        assert id(model.solver.problem) != problem_id
-        new_solution = model.optimize(solution_type=Solution).fluxes
-        for key in list(solution.keys()):
-            assert round(abs(new_solution[key] - solution[key]),
-                         7) == 0
+        new_solution = model.optimize().fluxes
+        assert numpy.allclose(solution, new_solution, rtol=0, atol=1E-06)
 
     def test_no_change_for_same_solver(self, model):
         solver_id = id(model.solver)
diff --git a/cobra/test/test_solver_utils.py b/cobra/test/test_solver_utils.py
index 52710a8..a99a987 100644
--- a/cobra/test/test_solver_utils.py
+++ b/cobra/test/test_solver_utils.py
@@ -1,11 +1,10 @@
 # -*- coding: utf-8 -*-
+
 from __future__ import absolute_import
 
-import optlang
 import pytest
 
 import cobra.util.solver as su
-from cobra.test.conftest import model
 
 stable_optlang = ["glpk", "cplex", "gurobi"]
 optlang_solvers = ["optlang-" + s for s in stable_optlang if s in su.solvers]
@@ -25,17 +24,13 @@ class TestHelpers:
         assert su.get_solver_name() == "glpk"
 
     def test_choose_solver(self, model):
-        legacy, so = su.choose_solver(model)
-        assert not legacy
+        so = su.choose_solver(model)
         assert su.interface_to_str(so) == "glpk"
-        legacy, so = su.choose_solver(model, "optlang-glpk")
-        assert not legacy
+        so = su.choose_solver(model, "glpk")
         assert su.interface_to_str(so) == "glpk"
-        assert su.choose_solver(model, "cglpk")[0]
 
         if any(s in su.solvers for s in su.qp_solvers):
-            legacy, qp_choice = su.choose_solver(model, qp=True)
-            assert not legacy
+            qp_choice = su.choose_solver(model, qp=True)
             assert su.interface_to_str(qp_choice) in su.qp_solvers
         else:
             with pytest.raises(su.SolverNotFound):
@@ -120,7 +115,8 @@ class TestSolverMods:
         assert constraint_name in model.constraints
 
     @pytest.mark.parametrize("solver", optlang_solvers)
-    def test_fix_objective_as_constraint_minimize(self, solver, model):
+    def test_fix_objective_as_constraint_minimize(self, model, solver):
+        model.solver = solver
         model.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1)
         minimize_glucose = model.problem.Objective(
             model.reactions.EX_glc__D_e.flux_expression,
@@ -129,5 +125,7 @@ class TestSolverMods:
         su.fix_objective_as_constraint(model)
         fx_name = 'fixed_objective_{}'.format(model.objective.name)
         constr = model.constraints
+        # Ensure that a solution exists on non-GLPK solvers.
+        model.slim_optimize()
         assert (constr[fx_name].lb, constr[fx_name].ub) == (
             None, model.solver.objective.value)
diff --git a/cobra/test/test_solvers.py b/cobra/test/test_solvers.py
deleted file mode 100644
index d0e2d24..0000000
--- a/cobra/test/test_solvers.py
+++ /dev/null
@@ -1,271 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-
-import pytest
-
-from cobra import solvers
-from cobra.core import Metabolite, Model, Reaction
-
-from .conftest import model
-
-try:
-    import scipy
-except ImportError:
-    scipy = None
-
-
- at pytest.fixture(scope="class", params=list(solvers.solver_dict))
-def solver_test(request):
-    solver = solvers.solver_dict[request.param]
-    old_solution = 0.8739215
-    infeasible_model = Model()
-    metabolite_1 = Metabolite("met1")
-    reaction_1 = Reaction("rxn1")
-    reaction_2 = Reaction("rxn2")
-    reaction_1.add_metabolites({metabolite_1: 1})
-    reaction_2.add_metabolites({metabolite_1: 1})
-    reaction_1.lower_bound = 1
-    reaction_2.upper_bound = 2
-    infeasible_model.add_reactions([reaction_1, reaction_2])
-    return solver, old_solution, infeasible_model
-
-
-class TestCobraSolver:
-    def test_attributes(self, solver_test):
-        solver, old_solution, infeasible_model = solver_test
-        assert hasattr(solver, "create_problem")
-        assert hasattr(solver, "solve_problem")
-        assert hasattr(solver, "get_status")
-        assert hasattr(solver, "get_objective_value")
-        assert hasattr(solver, "format_solution")
-        assert hasattr(solver, "change_variable_bounds")
-        assert hasattr(solver, "change_variable_objective")
-        assert hasattr(solver, "solve")
-        assert hasattr(solver, "set_parameter")
-
-    def test_creation(self, solver_test, model):
-        solver, old_solution, infeasible_model = solver_test
-        solver.create_problem(model)
-
-    def test_solve_feasible(self, solver_test, model):
-        solver, old_solution, infeasible_model = solver_test
-        solution = solver.solve(model)
-        assert solution.status == "optimal"
-        assert abs(old_solution - solution.f) < 10 ** -4
-
-    def test_solve_minimize(self, solver_test, model):
-        solver, old_solution, infeasible_model = solver_test
-        solution = solver.solve(model, objective_sense='minimize')
-        assert solution.status == "optimal"
-        assert abs(solution.f) < 10 ** -4
-
-    def test_low_level_control(self, solver_test):
-        solver, old_solution, infeasible_model = solver_test
-        lp = solver.create_problem(infeasible_model)
-        solver.solve_problem(lp)
-        assert solver.get_status(lp) == "infeasible"
-        # going to make feasible
-        solver.change_variable_bounds(lp, 0, -2., 2.)
-        solver.change_variable_bounds(lp, 1, -2., 2.)
-        solver.solve_problem(lp)
-        # should now be feasible, but obj = 0
-        assert solver.get_status(lp) == "optimal"
-        assert abs(solver.get_objective_value(lp)) < 10 ** -4
-        # should now have obj = 2 (maximize should be the default)
-        solver.change_variable_objective(lp, 0, 1.)
-        solver.solve_problem(lp)
-        assert abs(solver.get_objective_value(lp) - 2) < 10 ** -4
-        # should now solve with obj = -2
-        solver.solve_problem(lp, objective_sense="minimize")
-        assert abs(solver.get_objective_value(lp) + 2) < 10 ** -4
-        # should now have obj = 4
-        solver.change_variable_objective(lp, 0, 2.)
-        solver.solve_problem(lp, objective_sense="maximize")
-        assert abs(solver.get_objective_value(lp) - 4) < 10 ** -4
-        # make sure the solution looks good still
-        solution = solver.format_solution(lp, infeasible_model)
-        assert abs(solution.x[0] - 2) < 10 ** -4
-        assert abs(solution.x[1] + 2) < 10 ** -4
-        assert abs(solution.x_dict["rxn1"] - 2) < 10 ** -4
-        assert abs(solution.x_dict["rxn2"] + 2) < 10 ** -4
-
-    def test_set_objective_sense(self, solver_test, model):
-        solver, old_solution, infeasible_model = solver_test
-        maximize = solver.create_problem(model, objective_sense="maximize")
-        minimize = solver.create_problem(model, objective_sense="minimize")
-        solver.solve_problem(maximize)
-        solver.solve_problem(minimize)
-        max_solution = solver.format_solution(maximize, model)
-        min_solution = solver.format_solution(minimize, model)
-        assert min_solution.status == "optimal"
-        assert abs(min_solution.f) < 10 ** -4
-        assert abs(old_solution - max_solution.f) < 10 ** -4
-        assert max_solution.status == "optimal"
-        # if we set minimize at creation, can we override it at solve
-        solver.solve_problem(minimize, objective_sense="maximize")
-        override_minimize = solver.format_solution(minimize, model)
-        assert abs(max_solution.f - override_minimize.f) < 10 ** -4
-
-    def test_solve_mip(self, solver_test):
-        solver, old_solution, infeasible_model = solver_test
-        if not hasattr(solver, "_SUPPORTS_MILP") or not solver._SUPPORTS_MILP:
-            pytest.skip("no milp support")
-        cobra_model = Model('MILP_implementation_test')
-        constraint = Metabolite("constraint")
-        constraint._bound = 2.5
-        x = Reaction("x")
-        x.lower_bound = 0.
-        x.add_metabolites({constraint: 2.5})
-        y = Reaction("y")
-        y.lower_bound = 0.
-        y.add_metabolites({constraint: 1.})
-        cobra_model.add_reactions([x, y])
-        x.objective_coefficient = 1.
-        y.objective_coefficient = 1.
-        float_sol = solver.solve(cobra_model)
-        # add an integer constraint
-        y.variable_kind = "integer"
-        int_sol = solver.solve(cobra_model)
-        assert abs(float_sol.f - 2.5) < 10 ** -5
-        assert abs(float_sol.x_dict["y"] - 2.5) < 10 ** -5
-        assert int_sol.status == "optimal"
-        assert abs(int_sol.f - 2.2) < 10 ** -3
-        assert abs(int_sol.x_dict["y"] - 2.0) < 10 ** -3
-
-    def test_solve_infeasible(self, solver_test):
-        solver, old_solution, infeasible_model = solver_test
-        solution = solver.solve(infeasible_model)
-        assert solution.status == "infeasible"
-
-    def test_independent_creation(self, solver_test, model):
-        solver, old_solution, infeasible_model = solver_test
-        feasible_lp = solver.create_problem(model)
-        infeasible_lp = solver.create_problem(infeasible_model)
-        solver.solve_problem(feasible_lp)
-        solver.solve_problem(infeasible_lp)
-        feasible_solution = solver.format_solution(feasible_lp, model)
-        infeasible_solution = solver.format_solution(infeasible_lp,
-                                                     infeasible_model)
-        assert feasible_solution.status == "optimal"
-        assert abs(old_solution - feasible_solution.f) < 10 ** -4
-        assert infeasible_solution.status == "infeasible"
-
-    def test_change_coefficient(self, solver_test):
-        solver, old_solution, infeasible_model = solver_test
-        c = Metabolite("c")
-        c._bound = 6
-        x = Reaction("x")
-        x.lower_bound = 1.
-        y = Reaction("y")
-        y.lower_bound = 0.
-        x.add_metabolites({c: 1})
-        z = Reaction("z")
-        z.add_metabolites({c: 1})
-        m = Model("test_model")
-        m.add_reactions([x, y, z])
-        z.objective_coefficient = 1
-        # change an existing coefficient
-        lp = solver.create_problem(m)
-        solver.solve_problem(lp)
-        sol1 = solver.format_solution(lp, m)
-        assert sol1.status == "optimal"
-        solver.change_coefficient(lp, 0, 0, 2)
-        solver.solve_problem(lp)
-        sol2 = solver.format_solution(lp, m)
-        assert sol2.status == "optimal"
-        assert abs(sol1.f - 5.0) < 10 ** -3
-        assert abs(sol2.f - 4.0) < 10 ** -3
-        # change a new coefficient
-        z.objective_coefficient = 0.
-        y.objective_coefficient = 1.
-        lp = solver.create_problem(m)
-        solver.change_coefficient(lp, 0, 1, 2)
-        solver.solve_problem(lp)
-        solution = solver.format_solution(lp, m)
-        assert solution.status == "optimal"
-        assert abs(solution.x_dict["y"] - 2.5) < 10 ** -3
-
-    def test_inequality(self, solver_test):
-        solver, old_solution, infeasible_model = solver_test
-        # The space enclosed by the constraints is a 2D triangle with
-        # vertexes as (3, 0), (1, 2), and (0, 1)
-        # c1 encodes y - x > 1 ==> y > x - 1
-        # c2 encodes y + x < 3 ==> y < 3 - x
-        c1 = Metabolite("c1")
-        c2 = Metabolite("c2")
-        x = Reaction("x")
-        x.lower_bound = 0
-        y = Reaction("y")
-        y.lower_bound = 0
-        x.add_metabolites({c1: -1, c2: 1})
-        y.add_metabolites({c1: 1, c2: 1})
-        c1._bound = 1
-        c1._constraint_sense = "G"
-        c2._bound = 3
-        c2._constraint_sense = "L"
-        m = Model()
-        m.add_reactions([x, y])
-        # test that optimal values are at the vertices
-        m.objective = "x"
-        assert abs(solver.solve(m).f - 1.0) < 10 ** -3
-        assert abs(solver.solve(m).x_dict["y"] - 2.0) < 10 ** -3
-        m.objective = "y"
-        assert abs(solver.solve(m).f - 3.0) < 10 ** -3
-        assert abs(
-            solver.solve(m, objective_sense="minimize").f - 1.0) < 10 ** -3
-
-    @pytest.mark.skipif(scipy is None,
-                        reason="scipy required for quadratic objectives")
-    def test_quadratic(self, solver_test):
-        solver, old_solution, infeasible_model = solver_test
-        if not hasattr(solver, "set_quadratic_objective"):
-            pytest.skip("no qp support")
-        c = Metabolite("c")
-        c._bound = 2
-        x = Reaction("x")
-        x.lower_bound = 0.
-        y = Reaction("y")
-        y.lower_bound = 0.
-        x.add_metabolites({c: 1})
-        y.add_metabolites({c: 1})
-        m = Model()
-        m.add_reactions([x, y])
-        x.objective_coefficient = -0.5
-        y.objective_coefficient = -0.5
-        lp = solver.create_problem(m)
-        quadratic_obj = scipy.sparse.eye(2) * 2
-        solver.set_quadratic_objective(lp, quadratic_obj)
-        solver.solve_problem(lp, objective_sense="minimize")
-        solution = solver.format_solution(lp, m)
-        assert solution.status == "optimal"
-        # Respecting linear objectives also makes the objective value 1.
-        assert abs(solution.f - 1.) < 10 ** -3
-        assert abs(solution.x_dict["y"] - 1.) < 10 ** -3
-        assert abs(solution.x_dict["y"] - 1.) < 10 ** -3
-        # When the linear objectives are removed the objective value is 2.
-        solver.change_variable_objective(lp, 0, 0.)
-        solver.change_variable_objective(lp, 1, 0.)
-        solver.solve_problem(lp, objective_sense="minimize")
-        solution = solver.format_solution(lp, m)
-        assert solution.status == "optimal"
-        assert abs(solution.f - 2.) < 10 ** -3
-        # test quadratic from solve function
-        solution = solver.solve(m, quadratic_component=quadratic_obj,
-                                objective_sense="minimize")
-        assert solution.status == "optimal"
-        assert abs(solution.f - 1.) < 10 ** -3
-        c._bound = 6
-        z = Reaction("z")
-        x.objective_coefficient = 0.
-        y.objective_coefficient = 0.
-        z.lower_bound = 0.
-        z.add_metabolites({c: 1})
-        m.add_reaction(z)
-        solution = solver.solve(m, quadratic_component=scipy.sparse.eye(3),
-                                objective_sense="minimize")
-        # should be 12 not 24 because 1/2 (V^T Q V)
-        assert solution.status == "optimal"
-        assert abs(solution.f - 6) < 10 ** -3
-        assert abs(solution.x_dict["x"] - 2) < 10 ** -6
-        assert abs(solution.x_dict["y"] - 2) < 10 ** -6
-        assert abs(solution.x_dict["z"] - 2) < 10 ** -6
diff --git a/cobra/test/test_util.py b/cobra/test/test_util.py
index d04fc65..30a4b3a 100644
--- a/cobra/test/test_util.py
+++ b/cobra/test/test_util.py
@@ -1,8 +1,6 @@
 # -*- coding: utf-8 -*-
 from __future__ import absolute_import
 
-from builtins import zip
-
 import re
 from copy import copy, deepcopy
 from pickle import HIGHEST_PROTOCOL, dumps, loads
diff --git a/cobra/topology/__init__.py b/cobra/topology/__init__.py
deleted file mode 100644
index 96ab383..0000000
--- a/cobra/topology/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
-
-from cobra.topology.reporter_metabolites import identify_reporter_metabolites
diff --git a/cobra/topology/reporter_metabolites.py b/cobra/topology/reporter_metabolites.py
deleted file mode 100644
index ba09a6d..0000000
--- a/cobra/topology/reporter_metabolites.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
-
-from cobra.exceptions import DefunctError
-
-
-def identify_reporter_metabolites(*args, **kwargs):
-    raise DefunctError('identify_reporter_metabolites')
diff --git a/cobra/util/solver.py b/cobra/util/solver.py
index 2fecd92..1370e80 100644
--- a/cobra/util/solver.py
+++ b/cobra/util/solver.py
@@ -19,20 +19,15 @@ from types import ModuleType
 from warnings import warn
 
 import optlang
-import sympy
+from optlang.symbolics import Basic, Zero
 
-from cobra.exceptions import OptimizationError, OPTLANG_TO_EXCEPTIONS_DICT
+from cobra.exceptions import OptimizationError, SolverNotFound,\
+    OPTLANG_TO_EXCEPTIONS_DICT
 from cobra.util.context import get_context
 
 
-class SolverNotFound(Exception):
-    """A simple Exception when a solver can not be found."""
-
-    pass
-
-
 # Define all the solvers that are found in optlang.
-solvers = {match.split("_")[0]: getattr(optlang, match)
+solvers = {match.split("_interface")[0]: getattr(optlang, match)
            for match in dir(optlang) if "_interface" in match}
 
 # Defines all the QP solvers implemented in optlang.
@@ -110,7 +105,7 @@ def set_objective(model, value, additive=False):
         If the objective is not linear and `additive` is true, only values
         of class Objective.
 
-    additive : bool
+    additive : boolmodel.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1)
         If true, add the terms to the current objective, otherwise start with
         an empty objective.
     """
@@ -129,14 +124,14 @@ def set_objective(model, value, additive=False):
 
         if not additive:
             model.solver.objective = interface.Objective(
-                sympy.S.Zero, direction=model.solver.objective.direction)
+                Zero, direction=model.solver.objective.direction)
         for reaction, coef in value.items():
             model.solver.objective.set_linear_coefficients(
                 {reaction.forward_variable: coef,
                  reaction.reverse_variable: -coef})
 
-    elif isinstance(value, (sympy.Basic, optlang.interface.Objective)):
-        if isinstance(value, sympy.Basic):
+    elif isinstance(value, (Basic, optlang.interface.Objective)):
+        if isinstance(value, Basic):
             value = interface.Objective(
                 value, direction=model.solver.objective.direction,
                 sloppy=False)
@@ -237,41 +232,30 @@ def choose_solver(model, solver=None, qp=False):
     model : a cobra model
         The model for which to choose the solver.
     solver : str, optional
-        The name of the solver to be used. Optlang solvers should be prefixed
-        by "optlang-", for instance "optlang-glpk".
+        The name of the solver to be used.
     qp : boolean, optional
         Whether the solver needs Quadratic Programming capabilities.
 
     Returns
     -------
-    legacy : boolean
-        Whether the returned solver is a legacy (old cobra solvers) version or
-        an optlang solver (legacy = False).
-    solver : a cobra or optlang solver interface
-        Returns a valid solver for the problem. May be a cobra solver or an
-        optlang interface.
+    solver : an optlang solver interface
+        Returns a valid solver for the problem.
 
     Raises
     ------
     SolverNotFound
         If no suitable solver could be found.
     """
-    legacy = False
     if solver is None:
         solver = model.problem
-    elif "optlang-" in solver:
-        solver = interface_to_str(solver)
-        solver = solvers[solver]
     else:
-        legacy = True
-        solver = legacy_solvers.solver_dict[solver]
+        model.solver = solver
 
     # Check for QP, raise error if no QP solver found
-    # optlang only since old interface interprets None differently
     if qp and interface_to_str(solver) not in qp_solvers:
         solver = solvers[get_solver_name(qp=True)]
 
-    return legacy, solver
+    return solver
 
 
 def add_cons_vars_to_problem(model, what, **kwargs):
@@ -439,6 +423,3 @@ def assert_optimal(model, message='optimization failed'):
     """
     if model.solver.status != optlang.interface.OPTIMAL:
         raise OPTLANG_TO_EXCEPTIONS_DICT[model.solver.status](message)
-
-
-import cobra.solvers as legacy_solvers  # noqa
diff --git a/config.sh b/config.sh
deleted file mode 100644
index 9a5d12a..0000000
--- a/config.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-# Define custom utilities
-# Test for OSX with [ -n "$IS_OSX" ]
-
-function pre_build {
-    # Any stuff that you need to do before you start building the wheels
-    # Runs in the root directory of this repository.
-    if [ -n "$IS_OSX" ]; then
-        export CC=clang
-        export CXX=clang++
-        export CFLAGS="-fPIC -O3 -arch i386 -arch x86_64 -g -DNDEBUG -mmacosx-version-min=10.6"
-    else
-        yum install -y libxslt libxml2 libxml2-devel libxslt-devel
-    fi
-    curl -O http://ftp.gnu.org/gnu/glpk/glpk-4.61.tar.gz
-    tar xzf glpk-4.61.tar.gz
-    (cd glpk-4.61 \
-            && ./configure --disable-reentrant \
-            && make \
-            && make install)
-    pip install cython
-    cython -a cobra/solvers/cglpk.pyx
-    export PATH="$PATH:/usr/local/bin"
-}
-
-function build_wheel {
-    # Set default building method to pip
-    build_bdist_wheel $@
-    # setup.py sdist fails with
-    # error: [Errno 2] No such file or directory: 'venv/lib/python3.5/_dummy_thread.py'
-    # for python less than 3.5
-    if [[ `python -c 'import sys; print(sys.version.split()[0] >= "3.6.0")'` == "True" ]]; then
-        python setup.py sdist --dist-dir $(abspath ${WHEEL_SDIR:-wheelhouse})
-    else
-        echo "skip sdist"
-    fi
-    # remove glpk installation to ensure using the packaged binaries
-	(cd glpk-4.61 && make uninstall)
-}
-
-function run_tests_in_repo {
-    # Run tests from within source repo
-	# trick is to run the /installed/ package 
-    # coverage run --source=cobra setup.py test
-	if [ -n "$IS_OSX" ]; then
-		echo -e " testing for mac... "
-	else
-		wget --no-check-certificate \
-			 https://opencobra.github.io/pypi_cobrapy_travis/esolver.gz
-		gzip -f -d esolver.gz
-		chmod +x esolver
-		export PATH=$PWD:$PATH
-		# which pkg-config
-		# pip install matplotlib
-	fi
-	mkdir -p $HOME/.config/matplotlib
-	echo 'backend: Agg' >> $HOME/.config/matplotlib/matplotlibrc
-	COVERAGEXML=`python -c "import os,sys; print(os.path.realpath('coverage.xml'))"`
-	COVERAGERC=`python -c "import os,sys; print(os.path.realpath('../.coveragerc'))"`
-	(pytest --pyargs -v -rsx --cov=cobra --cov-report=xml:${COVERAGEXML} \
-			--cov-config=${COVERAGERC} --benchmark-skip cobra &&
-			mv ${COVERAGEXML} ..)
-}
-
-function run_tests {
-    # Runs tests on installed distribution from an empty directory
-    pip install python-libsbml==5.12.1 -f https://s3.eu-central-1.amazonaws.com/moonlight-science/wheelhouse/index.html --no-cache-dir
-    run_tests_in_repo
-}
diff --git a/develop-requirements.txt b/develop-requirements.txt
index 6e5a340..316502b 100644
--- a/develop-requirements.txt
+++ b/develop-requirements.txt
@@ -1,20 +1,8 @@
 # Requirements for developing cobrapy
-six
-Cython>=0.21
-numpy>=1.6
-scipy>=0.11.0
-python-libsbml
-lxml
-matplotlib
-palettable
-zmq
-pandas>=0.17.0
-tabulate
-tox
-pep8
 pytest
-optlang
-swiglpk
 pytest-benchmark
+pytest-cov
+codecov
+jsonschema
+tox
 bumpversion
-pydocstyle
diff --git a/documentation_builder/.gitignore b/documentation_builder/.gitignore
new file mode 100644
index 0000000..994e3c9
--- /dev/null
+++ b/documentation_builder/.gitignore
@@ -0,0 +1 @@
+_autogen/
diff --git a/documentation_builder/autodoc.sh b/documentation_builder/autodoc.sh
deleted file mode 100755
index f5a71b3..0000000
--- a/documentation_builder/autodoc.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-rm cobra.rst cobra.*.rst
-sphinx-apidoc -o . ../cobra \
-    ../cobra/test ../cobra/solvers/ \
-    ../cobra/solvers/legacy.py
-rm modules.rst
diff --git a/documentation_builder/cobra.core.rst b/documentation_builder/cobra.core.rst
deleted file mode 100644
index 504ce1f..0000000
--- a/documentation_builder/cobra.core.rst
+++ /dev/null
@@ -1,94 +0,0 @@
-cobra.core package
-==================
-
-Submodules
-----------
-
-cobra.core.arraybasedmodel module
----------------------------------
-
-.. automodule:: cobra.core.arraybasedmodel
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.core.dictlist module
---------------------------
-
-.. automodule:: cobra.core.dictlist
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.core.formula module
--------------------------
-
-.. automodule:: cobra.core.formula
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.core.gene module
-----------------------
-
-.. automodule:: cobra.core.gene
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.core.metabolite module
-----------------------------
-
-.. automodule:: cobra.core.metabolite
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.core.model module
------------------------
-
-.. automodule:: cobra.core.model
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.core.object module
-------------------------
-
-.. automodule:: cobra.core.object
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.core.reaction module
---------------------------
-
-.. automodule:: cobra.core.reaction
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.core.solution module
---------------------------
-
-.. automodule:: cobra.core.solution
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.core.species module
--------------------------
-
-.. automodule:: cobra.core.species
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: cobra.core
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/documentation_builder/cobra.design.rst b/documentation_builder/cobra.design.rst
deleted file mode 100644
index 2b12889..0000000
--- a/documentation_builder/cobra.design.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-cobra.design package
-====================
-
-Submodules
-----------
-
-cobra.design.design_algorithms module
--------------------------------------
-
-.. automodule:: cobra.design.design_algorithms
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: cobra.design
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/documentation_builder/cobra.flux_analysis.rst b/documentation_builder/cobra.flux_analysis.rst
deleted file mode 100644
index a8bb287..0000000
--- a/documentation_builder/cobra.flux_analysis.rst
+++ /dev/null
@@ -1,110 +0,0 @@
-cobra.flux_analysis package
-===========================
-
-Submodules
-----------
-
-cobra.flux_analysis.deletion_worker module
-------------------------------------------
-
-.. automodule:: cobra.flux_analysis.deletion_worker
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.double_deletion module
-------------------------------------------
-
-.. automodule:: cobra.flux_analysis.double_deletion
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.gapfilling module
--------------------------------------
-
-.. automodule:: cobra.flux_analysis.gapfilling
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.loopless module
------------------------------------
-
-.. automodule:: cobra.flux_analysis.loopless
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.moma module
--------------------------------
-
-.. automodule:: cobra.flux_analysis.moma
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.parsimonious module
----------------------------------------
-
-.. automodule:: cobra.flux_analysis.parsimonious
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.phenotype_phase_plane module
-------------------------------------------------
-
-.. automodule:: cobra.flux_analysis.phenotype_phase_plane
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.reaction module
------------------------------------
-
-.. automodule:: cobra.flux_analysis.reaction
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.sampling module
------------------------------------
-
-.. automodule:: cobra.flux_analysis.sampling
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.single_deletion module
-------------------------------------------
-
-.. automodule:: cobra.flux_analysis.single_deletion
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.summary module
-----------------------------------
-
-.. automodule:: cobra.flux_analysis.summary
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.flux_analysis.variability module
---------------------------------------
-
-.. automodule:: cobra.flux_analysis.variability
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: cobra.flux_analysis
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/documentation_builder/cobra.io.rst b/documentation_builder/cobra.io.rst
deleted file mode 100644
index ad3615f..0000000
--- a/documentation_builder/cobra.io.rst
+++ /dev/null
@@ -1,46 +0,0 @@
-cobra.io package
-================
-
-Submodules
-----------
-
-cobra.io.json module
---------------------
-
-.. automodule:: cobra.io.json
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.io.mat module
--------------------
-
-.. automodule:: cobra.io.mat
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.io.sbml module
---------------------
-
-.. automodule:: cobra.io.sbml
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.io.sbml3 module
----------------------
-
-.. automodule:: cobra.io.sbml3
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: cobra.io
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/documentation_builder/cobra.manipulation.rst b/documentation_builder/cobra.manipulation.rst
deleted file mode 100644
index c97bfbf..0000000
--- a/documentation_builder/cobra.manipulation.rst
+++ /dev/null
@@ -1,46 +0,0 @@
-cobra.manipulation package
-==========================
-
-Submodules
-----------
-
-cobra.manipulation.annotate module
-----------------------------------
-
-.. automodule:: cobra.manipulation.annotate
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.manipulation.delete module
---------------------------------
-
-.. automodule:: cobra.manipulation.delete
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.manipulation.modify module
---------------------------------
-
-.. automodule:: cobra.manipulation.modify
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.manipulation.validate module
-----------------------------------
-
-.. automodule:: cobra.manipulation.validate
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: cobra.manipulation
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/documentation_builder/cobra.rst b/documentation_builder/cobra.rst
deleted file mode 100644
index 81f032f..0000000
--- a/documentation_builder/cobra.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-cobra package
-=============
-
-Subpackages
------------
-
-.. toctree::
-
-    cobra.core
-    cobra.design
-    cobra.flux_analysis
-    cobra.io
-    cobra.manipulation
-    cobra.topology
-    cobra.util
-
-Submodules
-----------
-
-cobra.config module
--------------------
-
-.. automodule:: cobra.config
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.exceptions module
------------------------
-
-.. automodule:: cobra.exceptions
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: cobra
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/documentation_builder/cobra.topology.rst b/documentation_builder/cobra.topology.rst
deleted file mode 100644
index 9b745ef..0000000
--- a/documentation_builder/cobra.topology.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-cobra.topology package
-======================
-
-Submodules
-----------
-
-cobra.topology.reporter_metabolites module
-------------------------------------------
-
-.. automodule:: cobra.topology.reporter_metabolites
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: cobra.topology
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/documentation_builder/cobra.util.rst b/documentation_builder/cobra.util.rst
deleted file mode 100644
index 1b2b49e..0000000
--- a/documentation_builder/cobra.util.rst
+++ /dev/null
@@ -1,46 +0,0 @@
-cobra.util package
-==================
-
-Submodules
-----------
-
-cobra.util.array module
------------------------
-
-.. automodule:: cobra.util.array
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.util.context module
--------------------------
-
-.. automodule:: cobra.util.context
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.util.solver module
-------------------------
-
-.. automodule:: cobra.util.solver
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-cobra.util.util module
-----------------------
-
-.. automodule:: cobra.util.util
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: cobra.util
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/documentation_builder/conf.py b/documentation_builder/conf.py
index f695145..1a79089 100644
--- a/documentation_builder/conf.py
+++ b/documentation_builder/conf.py
@@ -13,12 +13,15 @@
 # serve to show the default.
 
 import sys
-import os
+from os.path import dirname, join
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0, os.path.abspath('..'))
+DOCS_ROOT = dirname(__file__)
+PROJECT_ROOT = dirname(DOCS_ROOT)
+
+sys.path.insert(0, PROJECT_ROOT)
 
 
 # In order to build documentation that requires libraries to import
@@ -37,11 +40,19 @@ class Mock(object):
             return Mock()
 
 
-MOCK_MODULES = ['numpy', 'scipy', 'scipy.optimize', 'scipy.sparse', 'scipy.io',
-                'scipy.stats', 'pp', 'libsbml', 'pandas', 'tabulate',
-                'optlang', 'optlang.interface', 'sympy', 'sympy.core',
-                'sympy.core.singleton', 'future', 'future.utils', 'ruamel',
-                'ruamel.yaml']
+# These modules should correspond to the importable Python packages.
+MOCK_MODULES = [
+    'numpy',
+    'scipy', 'scipy.optimize', 'scipy.sparse', 'scipy.io', 'scipy.stats',
+    'pp',
+    'libsbml',
+    'pandas',
+    'tabulate',
+    'optlang', 'optlang.interface', 'optlang.symbolics',
+    'optlang.symbolics.core',
+    'future', 'future.utils',
+    'ruamel', 'ruamel.yaml'
+]
 for mod_name in MOCK_MODULES:
     sys.modules[mod_name] = Mock()
 
@@ -49,9 +60,18 @@ for mod_name in MOCK_MODULES:
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
-              'sphinx.ext.mathjax', 'sphinx.ext.viewcode',
-              'sphinx.ext.napoleon', 'sphinx.ext.mathjax', 'nbsphinx']
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.mathjax',
+    'sphinx.ext.viewcode',
+    'sphinx.ext.napoleon',
+    'sphinx.ext.autosummary',
+    'nbsphinx'
+]
+
+# Napoleon settings
+napoleon_numpy_docstring = True
 
 # The master toctree document.
 master_doc = 'index'
@@ -123,3 +143,17 @@ intersphinx_mapping = {"http://docs.python.org/": None,
                        "http://docs.scipy.org/doc/numpy/": None,
                        "http://docs.scipy.org/doc/scipy/reference": None}
 intersphinx_cache_limit = 10  # days to keep the cached inventories
+
+# -- sphinx-apidoc calling ---------------------------------------------
+
+
+def run_apidoc(_):
+    from sphinx.apidoc import main
+
+    mod_path = join(PROJECT_ROOT, 'cobra')
+    auto_path = join(DOCS_ROOT, '_autogen')
+    main([None, '-f', '-d', '2', '-e', '-o', auto_path, mod_path])
+
+
+def setup(app):
+    app.connect('builder-inited', run_apidoc)
diff --git a/documentation_builder/index.rst b/documentation_builder/index.rst
index 27c8df2..5b80599 100644
--- a/documentation_builder/index.rst
+++ b/documentation_builder/index.rst
@@ -1,5 +1,6 @@
 Documentation for COBRApy
 =========================
+
 For installation instructions, please see `INSTALL.rst
 <https://github.com/opencobra/cobrapy/blob/master/INSTALL.rst>`_.
 
@@ -24,7 +25,7 @@ be viewed at `nbviewer
     constraints_objectives
     pymatbridge
     faq
-    cobra
+    _autogen/modules
 
 
 Indices and tables
diff --git a/documentation_builder/phenotype_phase_plane.ipynb b/documentation_builder/phenotype_phase_plane.ipynb
index 6b7497f..406bb87 100644
--- a/documentation_builder/phenotype_phase_plane.ipynb
+++ b/documentation_builder/phenotype_phase_plane.ipynb
@@ -9,10 +9,7 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "source": [
     "Production envelopes (aka phenotype phase planes) will show distinct phases of optimal growth with different use of two different substrates. For more information, see [Edwards et al.](http://dx.doi.org/10.1002/bit.10047)\n",
     "\n",
@@ -22,11 +19,7 @@
   {
    "cell_type": "code",
    "execution_count": 1,
-   "metadata": {
-    "collapsed": false,
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "import cobra.test\n",
@@ -37,10 +30,7 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "source": [
     "We want to make a phenotype phase plane to evaluate uptakes of Glucose and Oxygen."
    ]
@@ -48,11 +38,7 @@
   {
    "cell_type": "code",
    "execution_count": 2,
-   "metadata": {
-    "collapsed": false,
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "prod_env = production_envelope(model, [\"EX_glc__D_e\", \"EX_o2_e\"])"
@@ -61,73 +47,126 @@
   {
    "cell_type": "code",
    "execution_count": 3,
-   "metadata": {
-    "collapsed": false,
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/html": [
        "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
        "<table border=\"1\" class=\"dataframe\">\n",
        "  <thead>\n",
        "    <tr style=\"text-align: right;\">\n",
        "      <th></th>\n",
+       "      <th>carbon_source</th>\n",
+       "      <th>carbon_yield_maximum</th>\n",
+       "      <th>carbon_yield_minimum</th>\n",
+       "      <th>flux_maximum</th>\n",
+       "      <th>flux_minimum</th>\n",
+       "      <th>mass_yield_maximum</th>\n",
+       "      <th>mass_yield_minimum</th>\n",
        "      <th>EX_glc__D_e</th>\n",
        "      <th>EX_o2_e</th>\n",
-       "      <th>direction</th>\n",
-       "      <th>flux</th>\n",
        "    </tr>\n",
        "  </thead>\n",
        "  <tbody>\n",
        "    <tr>\n",
        "      <th>0</th>\n",
+       "      <td>EX_glc__D_e</td>\n",
+       "      <td>1.442300e-13</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.000000</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
        "      <td>-10.0</td>\n",
        "      <td>-60.000000</td>\n",
-       "      <td>minimum</td>\n",
-       "      <td>0.0</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>1</th>\n",
+       "      <td>EX_glc__D_e</td>\n",
+       "      <td>1.310050e+00</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.072244</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
        "      <td>-10.0</td>\n",
        "      <td>-56.842105</td>\n",
-       "      <td>minimum</td>\n",
-       "      <td>0.0</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2</th>\n",
+       "      <td>EX_glc__D_e</td>\n",
+       "      <td>2.620100e+00</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.144488</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
        "      <td>-10.0</td>\n",
        "      <td>-53.684211</td>\n",
-       "      <td>minimum</td>\n",
-       "      <td>0.0</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>3</th>\n",
+       "      <td>EX_glc__D_e</td>\n",
+       "      <td>3.930150e+00</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.216732</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
        "      <td>-10.0</td>\n",
        "      <td>-50.526316</td>\n",
-       "      <td>minimum</td>\n",
-       "      <td>0.0</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>4</th>\n",
+       "      <td>EX_glc__D_e</td>\n",
+       "      <td>5.240200e+00</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.288975</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
        "      <td>-10.0</td>\n",
        "      <td>-47.368421</td>\n",
-       "      <td>minimum</td>\n",
-       "      <td>0.0</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
        "</table>\n",
        "</div>"
       ],
       "text/plain": [
-       "   EX_glc__D_e    EX_o2_e direction  flux\n",
-       "0        -10.0 -60.000000   minimum   0.0\n",
-       "1        -10.0 -56.842105   minimum   0.0\n",
-       "2        -10.0 -53.684211   minimum   0.0\n",
-       "3        -10.0 -50.526316   minimum   0.0\n",
-       "4        -10.0 -47.368421   minimum   0.0"
+       "  carbon_source  carbon_yield_maximum  carbon_yield_minimum  flux_maximum  \\\n",
+       "0   EX_glc__D_e          1.442300e-13                   0.0      0.000000   \n",
+       "1   EX_glc__D_e          1.310050e+00                   0.0      0.072244   \n",
+       "2   EX_glc__D_e          2.620100e+00                   0.0      0.144488   \n",
+       "3   EX_glc__D_e          3.930150e+00                   0.0      0.216732   \n",
+       "4   EX_glc__D_e          5.240200e+00                   0.0      0.288975   \n",
+       "\n",
+       "   flux_minimum  mass_yield_maximum  mass_yield_minimum  EX_glc__D_e  \\\n",
+       "0           0.0                 NaN                 NaN        -10.0   \n",
+       "1           0.0                 NaN                 NaN        -10.0   \n",
+       "2           0.0                 NaN                 NaN        -10.0   \n",
+       "3           0.0                 NaN                 NaN        -10.0   \n",
+       "4           0.0                 NaN                 NaN        -10.0   \n",
+       "\n",
+       "     EX_o2_e  \n",
+       "0 -60.000000  \n",
+       "1 -56.842105  \n",
+       "2 -53.684211  \n",
+       "3 -50.526316  \n",
+       "4 -47.368421  "
       ]
      },
      "execution_count": 3,
@@ -141,10 +180,7 @@
   },
   {
    "cell_type": "markdown",
-   "metadata": {
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "source": [
     "If we specify the carbon source, we can also get the carbon and mass yield. For example, temporarily setting the objective to produce acetate instead we could get production envelope as follows and pandas to quickly plot the results."
    ]
@@ -152,99 +188,123 @@
   {
    "cell_type": "code",
    "execution_count": 4,
-   "metadata": {
-    "collapsed": false,
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "prod_env = production_envelope(\n",
-    "    model, [\"EX_o2_e\"], objective=\"EX_ac_e\", c_source=\"EX_glc__D_e\")"
+    "    model, [\"EX_o2_e\"], objective=\"EX_ac_e\", carbon_sources=\"EX_glc__D_e\")"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": 5,
-   "metadata": {
-    "collapsed": false,
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "outputs": [
     {
      "data": {
       "text/html": [
        "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
        "<table border=\"1\" class=\"dataframe\">\n",
        "  <thead>\n",
        "    <tr style=\"text-align: right;\">\n",
        "      <th></th>\n",
-       "      <th>EX_o2_e</th>\n",
        "      <th>carbon_source</th>\n",
-       "      <th>carbon_yield</th>\n",
-       "      <th>direction</th>\n",
-       "      <th>flux</th>\n",
-       "      <th>mass_yield</th>\n",
+       "      <th>carbon_yield_maximum</th>\n",
+       "      <th>carbon_yield_minimum</th>\n",
+       "      <th>flux_maximum</th>\n",
+       "      <th>flux_minimum</th>\n",
+       "      <th>mass_yield_maximum</th>\n",
+       "      <th>mass_yield_minimum</th>\n",
+       "      <th>EX_o2_e</th>\n",
        "    </tr>\n",
        "  </thead>\n",
        "  <tbody>\n",
        "    <tr>\n",
        "      <th>0</th>\n",
-       "      <td>-60.000000</td>\n",
        "      <td>EX_glc__D_e</td>\n",
+       "      <td>2.385536e-15</td>\n",
        "      <td>0.0</td>\n",
-       "      <td>minimum</td>\n",
+       "      <td>0.000000</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>2.345496e-15</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>-60.000000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>1</th>\n",
-       "      <td>-56.842105</td>\n",
        "      <td>EX_glc__D_e</td>\n",
+       "      <td>5.263158e-02</td>\n",
        "      <td>0.0</td>\n",
-       "      <td>minimum</td>\n",
+       "      <td>1.578947</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>5.174819e-02</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>-56.842105</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2</th>\n",
-       "      <td>-53.684211</td>\n",
        "      <td>EX_glc__D_e</td>\n",
+       "      <td>1.052632e-01</td>\n",
        "      <td>0.0</td>\n",
-       "      <td>minimum</td>\n",
+       "      <td>3.157895</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>1.034964e-01</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>-53.684211</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>3</th>\n",
-       "      <td>-50.526316</td>\n",
        "      <td>EX_glc__D_e</td>\n",
+       "      <td>1.578947e-01</td>\n",
        "      <td>0.0</td>\n",
-       "      <td>minimum</td>\n",
+       "      <td>4.736842</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>1.552446e-01</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>-50.526316</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>4</th>\n",
-       "      <td>-47.368421</td>\n",
        "      <td>EX_glc__D_e</td>\n",
+       "      <td>2.105263e-01</td>\n",
        "      <td>0.0</td>\n",
-       "      <td>minimum</td>\n",
+       "      <td>6.315789</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>2.069927e-01</td>\n",
        "      <td>0.0</td>\n",
+       "      <td>-47.368421</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
        "</table>\n",
        "</div>"
       ],
       "text/plain": [
-       "     EX_o2_e carbon_source  carbon_yield direction  flux  mass_yield\n",
-       "0 -60.000000   EX_glc__D_e           0.0   minimum   0.0         0.0\n",
-       "1 -56.842105   EX_glc__D_e           0.0   minimum   0.0         0.0\n",
-       "2 -53.684211   EX_glc__D_e           0.0   minimum   0.0         0.0\n",
-       "3 -50.526316   EX_glc__D_e           0.0   minimum   0.0         0.0\n",
-       "4 -47.368421   EX_glc__D_e           0.0   minimum   0.0         0.0"
+       "  carbon_source  carbon_yield_maximum  carbon_yield_minimum  flux_maximum  \\\n",
+       "0   EX_glc__D_e          2.385536e-15                   0.0      0.000000   \n",
+       "1   EX_glc__D_e          5.263158e-02                   0.0      1.578947   \n",
+       "2   EX_glc__D_e          1.052632e-01                   0.0      3.157895   \n",
+       "3   EX_glc__D_e          1.578947e-01                   0.0      4.736842   \n",
+       "4   EX_glc__D_e          2.105263e-01                   0.0      6.315789   \n",
+       "\n",
+       "   flux_minimum  mass_yield_maximum  mass_yield_minimum    EX_o2_e  \n",
+       "0           0.0        2.345496e-15                 0.0 -60.000000  \n",
+       "1           0.0        5.174819e-02                 0.0 -56.842105  \n",
+       "2           0.0        1.034964e-01                 0.0 -53.684211  \n",
+       "3           0.0        1.552446e-01                 0.0 -50.526316  \n",
+       "4           0.0        2.069927e-01                 0.0 -47.368421  "
       ]
      },
      "execution_count": 5,
@@ -259,9 +319,7 @@
   {
    "cell_type": "code",
    "execution_count": 6,
-   "metadata": {
-    "collapsed": true
-   },
+   "metadata": {},
    "outputs": [],
    "source": [
     "%matplotlib inline"
@@ -270,27 +328,13 @@
   {
    "cell_type": "code",
    "execution_count": 7,
-   "metadata": {
-    "collapsed": false,
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "outputs": [
     {
      "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAELCAYAAAAiIMZEAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4wLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvpW3flQAAIABJREFUeJzt3XlcVXX+x/HXBwQRURTBFQlUcN8R\nsNVcUmvSnNwwTS2XGm1smWacaX5pZc3kODY1NU24Zu5WmpWVmdmismnuK+64gai4Itv39wfooIFc\n5cLh3vt5Ph4+4p775Z73Vy/vDt977zlijEEppZRzcbM6gFJKKfvTcldKKSek5a6UUk5Iy10ppZyQ\nlrtSSjkhLXellHJCNpW7iPQQkd0ikiQi4wu5/y0R2ZT/Z4+InLV/VKWUUraS4t7nLiLuwB6gG5AM\nJADR [...]
       "text/plain": [
-       "<matplotlib.axes._subplots.AxesSubplot at 0x10fc37630>"
-      ]
-     },
-     "execution_count": 7,
-     "metadata": {},
-     "output_type": "execute_result"
-    },
-    {
-     "data": {
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAELCAYAAAAiIMZEAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xd4VGX+/vH3J53QAiG0hEiAAAGlhtBEigUQhRWkKQpI\nty8/XXF1BYW1reLqiigiBFSqiKIgqNhpKUgPJUQgoUMglJD+/P5I9BsxkAmZyZmZfF7XlcvMmWfO\n3I8mt5Mzz5wjxhiUUkq5Fw+rAyillLI/LXellHJDWu5KKeWGtNyVUsoNabkrpZQb0nJXSik3ZFO5\ni0gvEdkjIokiMqmI+98QkS0FX3tF5Kz9oyqllLKVFLfOXUQ8gb3ArUAKEAsMNcbsusL4R4DWxpgH\n7JxVKaWUjWx55R4FJBpjkowxWcAioN9Vxg8FFtojnFJKqWvjZcOYYCC50O0UoH1RA0XkOiAM+O4K\n948F [...]
-      "text/plain": [
-       "<matplotlib.figure.Figure at 0x10fc24a90>"
+       "<matplotlib.figure.Figure at 0x7f1db7b6c048>"
       ]
      },
      "metadata": {},
@@ -298,16 +342,13 @@
     }
    ],
    "source": [
-    "prod_env[prod_env.direction == 'maximum'].plot(\n",
-    "    kind='line', x='EX_o2_e', y='carbon_yield')"
+    "prod_env.plot(\n",
+    "    kind='line', x='EX_o2_e', y='carbon_yield_maximum');"
    ]
   },
   {
    "cell_type": "markdown",
-   "metadata": {
-    "deletable": true,
-    "editable": true
-   },
+   "metadata": {},
    "source": [
     "Previous versions of cobrapy included more tailored plots for phase planes which have now been dropped in order to improve maintainability and enhance the focus of cobrapy. Plotting for cobra models is intended for another package."
    ]
@@ -329,9 +370,9 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.5.2"
+   "version": "3.6.3"
   }
  },
  "nbformat": 4,
- "nbformat_minor": 0
+ "nbformat_minor": 1
 }
diff --git a/documentation_builder/requirements.txt b/documentation_builder/requirements.txt
index c23edec..db6888a 100644
--- a/documentation_builder/requirements.txt
+++ b/documentation_builder/requirements.txt
@@ -1,2 +1,4 @@
+Sphinx
+sphinxcontrib-napoleon
 nbsphinx>=0.2.4
 ipykernel
diff --git a/make-mac-wheels.sh b/make-mac-wheels.sh
deleted file mode 100755
index cf1da29..0000000
--- a/make-mac-wheels.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-
-set -e
-
-for PYBIN in /Library/Frameworks/Python.framework/Versions/*/bin; do
-	${PYBIN}/pip install wheel delocate
-    ${PYBIN}/python setup.py bdist_wheel
-done
-
-# Bundle external shared libraries into the wheels
-for whl in dist/cobra*.whl; do
-    delocate-wheel $whl -w dist/wheelhouse/
-done
-
diff --git a/manylinux_builder/Dockerfile b/manylinux_builder/Dockerfile
deleted file mode 100644
index 0866756..0000000
--- a/manylinux_builder/Dockerfile
+++ /dev/null
@@ -1,6 +0,0 @@
-FROM quay.io/pypa/manylinux1_x86_64
-
-ENV GLPK_VER="4.61"
-RUN wget http://ftp.gnu.org/gnu/glpk/glpk-${GLPK_VER}.tar.gz -O - | tar xz
-WORKDIR glpk-${GLPK_VER}
-RUN ./configure && make install
diff --git a/manylinux_builder/README.md b/manylinux_builder/README.md
deleted file mode 100644
index 2f3a6ec..0000000
--- a/manylinux_builder/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-This script uses docker to build manylinux wheels for cobrapy. It only
-requires a working docker installation.
-
-To build manylinux wheels, run ```./run_cobrapy_builder.sh```. The
-built wheels will then be placed in the ```wheelhouse``` folder.
diff --git a/manylinux_builder/build_cobrapy.sh b/manylinux_builder/build_cobrapy.sh
deleted file mode 100755
index 6703649..0000000
--- a/manylinux_builder/build_cobrapy.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-for PYBIN in /opt/python/*/bin; do
-    ${PYBIN}/pip wheel cobra --pre
-done
-
-# Bundle external shared libraries into the wheels
-for whl in cobra*.whl; do
-    auditwheel repair $whl -w /io/wheelhouse/
-done
-
diff --git a/manylinux_builder/run_cobrapy_builder.sh b/manylinux_builder/run_cobrapy_builder.sh
deleted file mode 100755
index 3a59861..0000000
--- a/manylinux_builder/run_cobrapy_builder.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-docker build -t cobrapy_builder .
-docker run --rm -v `pwd`:/io cobrapy_builder /io/build_cobrapy.sh
diff --git a/release-notes/0.10.0.md b/release-notes/0.10.0.md
new file mode 100644
index 0000000..21147be
--- /dev/null
+++ b/release-notes/0.10.0.md
@@ -0,0 +1,19 @@
+# Release notes for cobrapy 0.10.0
+
+## Fixes
+
+* The solvers packaged with cobrapy have been removed.
+* cobra is now a pure Python package and no longer has compiled extensions.
+* Massively simplified continuous testing and deployment.
+* The `solver` argument and configuration keyword arguments have been removed.
+  Please set the solver on the model and configure it before calling
+  optimization functions.
+* The deprecated `ArrayBasedModel` has been removed. Please use the function
+  `create_stoichiometric_matrix` instead.
+* `scipy` is no longer required for MOMA based methods.
+* A few deprecated functions have been removed.
+* Test cases have been adjusted.
+* Small adjustments to loopless FVA.
+* The deletion study interface and functions were improved and now use the
+  optlang interface.
+
diff --git a/release-notes/0.10.1.md b/release-notes/0.10.1.md
new file mode 100644
index 0000000..2531a37
--- /dev/null
+++ b/release-notes/0.10.1.md
@@ -0,0 +1,9 @@
+# Release notes for cobrapy 0.10.1
+
+## Fixes
+
+* `pandas` has dropped support for Python 3.4 and we are doing the same for our
+  automated tests. If you are running Python 3.4 and have a working installation
+  of numpy and pandas, adding cobrapy on top of that is no problem.
+* Updated some URLs in the links.
+
diff --git a/release-notes/0.11.0.md b/release-notes/0.11.0.md
new file mode 100644
index 0000000..1525a0f
--- /dev/null
+++ b/release-notes/0.11.0.md
@@ -0,0 +1,14 @@
+# Release notes for cobrapy 0.11.0
+
+## New features
+
+* Add an argument `processes` to the functions `find_essential_genes` and
+  `find_essential_reactions`.
+* Automatic documentation generation should hopefully mean that it's never
+  out-of-date.
+
+## Backwards incompatible changes
+
+* Homogenize all function and class arguments using multiprocessing to accept an
+  integer for `processes`. This affects functions from deletion and sampling
+  code.
diff --git a/release-notes/0.9.1.md b/release-notes/0.9.1.md
new file mode 100644
index 0000000..b7845d5
--- /dev/null
+++ b/release-notes/0.9.1.md
@@ -0,0 +1,19 @@
+# Release notes for cobrapy 0.9.1
+
+## Fixes
+
+* All around improvements to cobrapy's continuous integration.
+* Pin the scipy version to 0.19.1 until we can upgrade our sparse matrix code to
+  scipy 1.0.
+
+## New features
+
+* Compatibility with symengine. Symengine can now power optlang and thus make
+  many simulation tasks faster. Please try it out!
+  It's as simple as `pip install symengine`.
+* When writing to JSON or YAML metabolites, reactions, and genes by default
+  maintain the order of the model. Their annotations are alphabetically sorted.
+* Duplicate reactions are now properly ignored. They were causing `ValueError`s
+  before.
+* Automatic GitHub releases upon tagged releases.
+* Automatic deployment of website upon tagged releases.
diff --git a/release-notes/next-release.md b/release-notes/next-release.md
index 7d5acdd..e20774c 100644
--- a/release-notes/next-release.md
+++ b/release-notes/next-release.md
@@ -7,3 +7,4 @@
 ## Deprecated features
 
 ## Backwards incompatible changes
+
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
deleted file mode 100755
index 7607376..0000000
--- a/scripts/deploy.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-if [[ -n "${MB_PYTHON_VERSION}" ]]; then
-	echo -e " starting deploy for branch ${TRAVIS_BRANCH} .."
-	pip install twine
-	twine upload --skip-existing --username "${PYPI_USERNAME}" --password "${PYPI_PASSWORD}" ${TRAVIS_BUILD_DIR}/wheelhouse/*
-fi;
diff --git a/scripts/deploy_website.sh b/scripts/deploy_website.sh
new file mode 100755
index 0000000..5807453
--- /dev/null
+++ b/scripts/deploy_website.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+set -e
+
+target=/tmp/cobrapy-website
+
+git clone "https://github.com/opencobra/cobrapy-website.git" "${target}"
+cd "${target}"
+
+git config user.name "Deployment Bot"
+git config user.email "deploy at travis-ci.org"
+git remote rm origin
+git remote add origin "https://user:${GITHUB_TOKEN}@github.com/opencobra/cobrapy-website.git" &> /dev/null
+
+git checkout master
+python "${TRAVIS_BUILD_DIR}"/scripts/publish_release.py "${TRAVIS_BUILD_DIR}"/release-notes "${target}"/content/releases "${TRAVIS_TAG}"
+git add .
+git commit -m "feat: publish ${TRAVIS_TAG} on $(date +'%F %T')"
+git push origin master
+cd "${TRAVIS_BUILD_DIR}"
+
diff --git a/scripts/prepare_notes.sh b/scripts/prepare_notes.sh
new file mode 100755
index 0000000..e287b4d
--- /dev/null
+++ b/scripts/prepare_notes.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+set -e
+
+if [[ -n "${TRAVIS_TAG}" && "${TRAVIS_OS_NAME}" == "linux" && "${MB_PYTHON_VERSION}" == "3.6" ]]; then
+    echo "Parsing ${TRAVIS_BUILD_DIR}/release-notes/${TRAVIS_TAG}.md"
+    export TAG_NOTES=$(cat "${TRAVIS_BUILD_DIR}/release-notes/${TRAVIS_TAG}.md")
+fi
+
+set +e
diff --git a/scripts/publish_release.py b/scripts/publish_release.py
new file mode 100644
index 0000000..d99cfa4
--- /dev/null
+++ b/scripts/publish_release.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""Insert a TOML header into the latest release note."""
+
+from __future__ import absolute_import, print_function
+
+import sys
+from datetime import date
+from glob import glob
+from builtins import open
+from os.path import join, basename
+from shutil import copy
+
+
+def insert_break(lines, break_pos=9):
+    """
+    Insert a <!--more--> tag for larger release notes.
+
+    Parameters
+    ----------
+    lines : list of str
+        The content of the release note.
+    break_pos : int
+        Line number before which a break should approximately be inserted.
+
+    Returns
+    -------
+    list of str
+        The text with the inserted tag or no modification if it was
+        sufficiently short.
+    """
+    def line_filter(line):
+        if len(line) == 0:
+            return True
+        return any(line.startswith(c) for c in "-*+")
+
+    if len(lines) <= break_pos:
+        return lines
+    newlines = [
+        i for i, line in enumerate(lines[break_pos:], start=break_pos)
+        if line_filter(line.strip())]
+    if len(newlines) > 0:
+        break_pos = newlines[0]
+    lines.insert(break_pos, "<!--more-->\n")
+    return lines
+
+
+def build_hugo_md(filename, tag, bump):
+    """
+    Build the markdown release notes for Hugo.
+
+    Inserts the required TOML header with specific values and adds a break
+    for long release notes.
+
+    Parameters
+    ----------
+    filename : str, path
+        The release notes file.
+    tag : str
+        The tag, following semantic versioning, of the current release.
+    bump : {"major", "minor", "patch", "alpha", "beta"}
+        The type of release.
+
+    """
+    header = [
+        '+++\n',
+        'date = "{}"\n'.format(date.today().isoformat()),
+        'title = "{}"\n'.format(tag),
+        'author = "The COBRApy Team"\n',
+        'release = "{}"\n'.format(bump),
+        '+++\n',
+        '\n'
+    ]
+    with open(filename, "r") as file_h:
+        content = insert_break(file_h.readlines())
+    header.extend(content)
+    with open(filename, "w") as file_h:
+        file_h.writelines(header)
+
+
+def intify(filename):
+    """
+    Turn a release note filename into something sortable.
+
+    Parameters
+    ----------
+    filename : str
+        A release note of expected filename format '<major>.<minor>.<patch>.md'.
+
+    Returns
+    -------
+    tuple
+        A pair of the major and minor versions as integers.
+
+    """
+    tmp = filename[:-3].split(".")
+    return int(tmp[0]), int(tmp[1])
+
+
+def find_bump(target, tag):
+    """Identify the kind of release by comparing to existing ones."""
+    tmp = tag.split(".")
+    existing = [intify(basename(f)) for f in glob(join(target, "[0-9]*.md"))]
+    latest = max(existing)
+    if int(tmp[0]) > latest[0]:
+        return "major"
+    elif int(tmp[1]) > latest[1]:
+        return "minor"
+    else:
+        return "patch"
+
+
+def main(argv):
+    """
+    Identify the release type and create a new target file with TOML header.
+
+    Requires three arguments.
+
+    """
+    source, target, tag = argv
+    if "a" in tag:
+        bump = "alpha"
+    if "b" in tag:
+        bump = "beta"
+    else:
+        bump = find_bump(target, tag)
+    filename = "{}.md".format(tag)
+    destination = copy(join(source, filename), target)
+    build_hugo_md(destination, tag, bump)
+
+
+if __name__ == "__main__":
+    if len(sys.argv) != 4:
+        print("Usage:\n{} <source dir> <target dir> <tag>"
+              "".format(sys.argv[0]))
+        sys.exit(2)
+    sys.exit(main(sys.argv[1:]))
diff --git a/setup.cfg b/setup.cfg
index 73d72f1..be910b0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 0.9.0
+current_version = 0.11.0
 commit = True
 tag = True
 parse = (?P<major>\d+)
diff --git a/setup.py b/setup.py
index bc1dcb6..a15156d 100644
--- a/setup.py
+++ b/setup.py
@@ -1,137 +1,29 @@
 # -*- coding: utf-8 -*-
 
-from os.path import abspath, dirname, isfile, join
-from sys import argv
+from __future__ import absolute_import
+
 from warnings import warn
+from sys import argv, version_info
 
 from setuptools import setup, find_packages
 
-# cython is optional for building. The c file can be used directly. However,
-# for certain functions, the c file must be generated, which requires cython.
-try:
-    from Cython.Build import cythonize
-    from distutils.version import StrictVersion
-    import Cython
-    try:
-        cython_version = StrictVersion(Cython.__version__)
-    except ValueError:
-        raise ImportError("Cython version not parseable")
-    else:
-        if cython_version < StrictVersion("0.21"):
-            raise ImportError("Cython version too old to use")
-except ImportError:
-    cythonize = None
-    for k in ["sdist", "develop"]:
-        if k in argv:
-            raise Exception("Cython >= 0.21 required for " + k)
-
-# Begin constructing arguments for building
-setup_kwargs = {}
-
-# for building the cglpk solver
-try:
-    from distutils.extension import Extension
-    from distutils.command.build_ext import build_ext
-    from os import name
-    from platform import system
-
-    class FailBuild(build_ext):
-        """allow building of the C extension to fail"""
-        def run(self):
-            try:
-                build_ext.run(self)
-            except Exception as e:
-                warn(e)
-
-        def build_extension(self, ext):
-            try:
-                build_ext.build_extension(self, ext)
-            except:
-                None
-
-    build_args = {}
-    setup_kwargs["cmdclass"] = {"build_ext": FailBuild}
-    # MAC OS X needs some additional configuration tweaks
-    # Build should be run with the python.org python
-    # Cython will output C which could generate warnings in clang
-    # due to the addition of additional unneeded functions. Because
-    # this is a known phenomenon, these warnings are silenced to
-    # make other potential warnings which do signal errors stand
-    # out.
-    if system() == "Darwin":
-        build_args["extra_compile_args"] = ["-Wno-unused-function"]
-
-    build_args["libraries"] = ["glpk"]
-    # It is possible to statically link libglpk to the built extension. This
-    # allows for simplified installation without the need to install libglpk to
-    # the system, and is also usueful when installing a particular version of
-    # glpk which conflicts with thesystem version. A static libglpk.a can be
-    # built by running configure with the export CLFAGS="-fPIC" and copying the
-    # file from src/.libs to either the default lib directory or to the build
-    # directory. For an example script, see
-    # https://gist.github.com/aebrahim/94a2b231d86821f7f225
-    include_dirs = []
-    library_dirs = []
-    if isfile("libglpk.a"):
-        library_dirs.append(abspath("."))
-    if isfile("glpk.h"):
-        include_dirs.append(abspath("."))
-    # if the glpk files are not in the current directory attempt to
-    # auto-detect their location by finding the location of the glpsol
-    # command
-    if name == "posix" and len(include_dirs) == 0 and len(library_dirs) == 0:
-        from subprocess import check_output
-        try:
-            glpksol_path = check_output(["which", "glpsol"],
-                                        universal_newlines=True).strip()
-            glpk_path = abspath(join(dirname(glpksol_path), ".."))
-            include_dirs.append(join(glpk_path, "include"))
-            library_dirs.append(join(glpk_path, "lib"))
-        except Exception as e:
-            print('Could not autodetect include and library dirs: ' + str(e))
-    if len(include_dirs) > 0:
-        build_args["include_dirs"] = include_dirs
-    if len(library_dirs) > 0:
-        build_args["library_dirs"] = library_dirs
-    # use cython if present, otherwise use c file
-    if cythonize:
-        ext_modules = cythonize([Extension("cobra.solvers.cglpk",
-                                           ["cobra/solvers/cglpk.pyx"],
-                                           **build_args)],
-                                force=True)
-    else:
-        ext_modules = [Extension("cobra.solvers.cglpk",
-                                 ["cobra/solvers/cglpk.c"], **build_args)]
-except Exception as e:
-    print('Could not build CGLPK: {}'.format(e))
-    ext_modules = None
+if version_info[:2] == (3, 4):
+    warn("Support for Python 3.4 was dropped by pandas. Since cobrapy is a "
+         "pure Python package you can still install it but will have to "
+         "carefully manage your own pandas and numpy versions. We no longer "
+         "include it in our automatic testing.")
 
+setup_kwargs = dict()
 setup_requirements = []
 # prevent pytest-runner from being installed on every invocation
 if {'pytest', 'test', 'ptr'}.intersection(argv):
     setup_requirements.append("pytest-runner")
 
 extras = {
-    'matlab': ["pymatbridge"],
-    'sbml': ["python-libsbml", "lxml"],
-    'array': ["scipy>=0.11.0"],
-    'display': ["matplotlib", "palettable"]
+    'array': ["scipy"],
+    'sbml': ["python-libsbml", "lxml"]
 }
-
-all_extras = {'Cython>=0.21'}
-for extra in extras.values():
-    all_extras.update(extra)
-extras["all"] = sorted(list(all_extras))
-
-# If using bdist_wininst, the installer will not get dependencies like
-# a setuptools installation does. Therefore, for the one external dependency,
-# which is six.py, we can just download it here and include it in the
-# installer.
-
-# The file six.py will need to be manually downloaded and placed in the
-# same directory as setup.py.
-if "bdist_wininst" in argv:
-    setup_kwargs["py_modules"] = ["six"]
+extras["all"] = sorted(list(extras))
 
 try:
     with open('README.rst') as handle:
@@ -139,32 +31,43 @@ try:
     with open('INSTALL.rst') as handle:
         install = handle.read()
     setup_kwargs["long_description"] = readme + "\n\n" + install
-except:
+except IOError:
     setup_kwargs["long_description"] = ''
 
 setup(
     name="cobra",
-    version="0.9.0",
+    version="0.11.0",
     packages=find_packages(),
     setup_requires=setup_requirements,
-    install_requires=["future", "swiglpk", "optlang>=1.2.1",
-                      "ruamel.yaml<0.15",
-                      "pandas>=0.17.0", "numpy>=1.6", "tabulate"],
-    tests_require=["jsonschema > 2.5", "pytest", "pytest-benchmark"],
+    install_requires=[
+        "six",
+        "future",
+        "swiglpk",
+        "ruamel.yaml<0.15",
+        "numpy>=1.6",
+        "pandas>=0.17.0",
+        "optlang>=1.2.5",
+        "tabulate"
+    ],
+    tests_require=[
+        "jsonschema > 2.5",
+        "pytest",
+        "pytest-benchmark"
+    ],
     extras_require=extras,
-    ext_modules=ext_modules,
-
     package_data={
-         '': ['test/data/*',
-              'mlab/matlab_scripts/*m']},
-
+         '': [
+             'test/data/*',
+             'mlab/matlab_scripts/*m'
+         ]
+    },
     author="The cobrapy core team",
     author_email="cobra-pie at googlegroups.com",
     description="COBRApy is a package for constraints-based modeling of "
     "biological networks",
     license="LGPL/GPL v2+",
-    keywords="metabolism biology linear programming optimization flux"
-    " balance analysis fba",
+    keywords=("metabolism biology linear programming optimization flux"
+              " balance analysis fba"),
     url="https://opencobra.github.io/cobrapy",
     test_suite="cobra.test.suite",
     download_url='https://pypi.python.org/pypi/cobra',
@@ -181,10 +84,10 @@ setup(
         'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
-        'Programming Language :: Cython',
         'Programming Language :: Python :: Implementation :: CPython',
         'Topic :: Scientific/Engineering',
         'Topic :: Scientific/Engineering :: Bio-Informatics'
     ],
     platforms="GNU/Linux, Mac OS X >= 10.7, Microsoft Windows >= 7",
-    **setup_kwargs)
+    **setup_kwargs
+)
diff --git a/tox.ini b/tox.ini
index 0b4e78e..1660b22 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = pep8, py27, py35, py36
+envlist = pep8, py27, py34, py35, py36, sbml, array
 
 [testenv]
 passenv =
@@ -9,12 +9,23 @@ passenv =
 deps=
     pytest
     pytest-benchmark
+    pytest-cov
+    codecov
+    jsonschema
 commands =
-    pytest --benchmark-skip cobra
+    pytest --benchmark-skip --cov=cobra cobra/test
+    - codecov
 
 [testenv:pep8]
 skip_install = True
 deps =
     pep8
 commands =
-    pep8 --exclude=solvers --show-source cobra
+    pep8 --show-source cobra
+
+[testenv:sbml]
+extras = sbml
+
+[testenv:array]
+extras = array
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/python-cobra.git



More information about the debian-med-commit mailing list