[med-svn] [python-cobra] 01/05: Imported Upstream version 0.4.1

Afif Elghraoui afif at moszumanska.debian.org
Fri Mar 25 03:45:50 UTC 2016


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

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

commit e296849d1418169563e6670d0ca1c808b8e889a9
Author: Afif Elghraoui <afif at ghraoui.name>
Date:   Thu Mar 24 20:00:35 2016 -0700

    Imported Upstream version 0.4.1
---
 .gitignore                                         |  89 ++-
 .travis.yml                                        |   4 +-
 appveyor.yml                                       |   5 +-
 appveyor/build_glpk.py                             |   4 +-
 cobra/VERSION                                      |   2 +-
 cobra/__init__.py                                  |  26 +-
 cobra/core/Metabolite.py                           |  35 +-
 cobra/core/Model.py                                |  20 +
 cobra/core/Reaction.py                             | 118 ++--
 cobra/design/design_algorithms.py                  |   4 +-
 cobra/flux_analysis/deletion_worker.py             |   7 +-
 cobra/flux_analysis/double_deletion.py             |   6 +-
 cobra/flux_analysis/essentiality.py                |  49 +-
 cobra/flux_analysis/gapfilling.py                  |   3 +-
 cobra/flux_analysis/loopless.py                    |   2 +-
 cobra/flux_analysis/moma.py                        |   3 +-
 cobra/flux_analysis/parsimonious.py                | 114 ++--
 cobra/flux_analysis/phenotype_phase_plane.py       |   5 +-
 cobra/flux_analysis/reaction.py                    | 167 +++---
 cobra/flux_analysis/single_deletion.py             |   6 +-
 cobra/flux_analysis/summary.py                     | 191 ++++++
 cobra/flux_analysis/variability.py                 |   7 +-
 cobra/io/mat.py                                    |   2 +-
 cobra/solvers/cglpk.pyx                            |  24 +-
 cobra/solvers/coin.py                              |   2 +-
 cobra/test/flux_analysis.py                        | 162 +++++-
 cobra/test/io_tests.py                             |   4 +
 cobra/test/unit_tests.py                           |  86 +++
 cobra/topology/__init__.py                         |   2 +-
 cobra/topology/reporter_metabolites.py             | 114 ++--
 documentation_builder/autodoc.sh                   |   2 -
 documentation_builder/building_model.ipynb         |  22 +-
 documentation_builder/building_model.rst           | 179 ------
 documentation_builder/cobra.design.rst             |  22 +
 documentation_builder/cobra.flux_analysis.rst      |   8 +
 documentation_builder/cobra.manipulation.rst       |  16 +
 documentation_builder/cobra.rst                    |   1 +
 documentation_builder/conf.py                      | 171 +-----
 documentation_builder/deletions.ipynb              | 230 ++++----
 documentation_builder/deletions.rst                | 640 ---------------------
 documentation_builder/faq.ipynb                    |  32 +-
 documentation_builder/faq.rst                      | 178 ------
 documentation_builder/gapfilling.ipynb             |  92 ++-
 documentation_builder/gapfilling.rst               | 104 ----
 documentation_builder/getting_started.ipynb        |  64 +--
 documentation_builder/getting_started.rst          | 489 ----------------
 documentation_builder/io.ipynb                     |  20 +-
 documentation_builder/io.rst                       | 170 ------
 documentation_builder/loopless.ipynb               |  24 +-
 documentation_builder/loopless.rst                 | 194 -------
 .../loopless_files/loopless_3_0.png                | Bin 10699 -> 0 bytes
 documentation_builder/milp.ipynb                   |  10 +-
 documentation_builder/milp.rst                     | 283 ---------
 documentation_builder/phenotype_phase_plane.ipynb  |  38 +-
 documentation_builder/phenotype_phase_plane.rst    |  85 ---
 .../phenotype_phase_plane_3_0.png                  | Bin 89909 -> 0 bytes
 .../phenotype_phase_plane_5_0.png                  | Bin 100924 -> 0 bytes
 .../phenotype_phase_plane_5_1.png                  | Bin 92540 -> 0 bytes
 .../phenotype_phase_plane_7_0.png                  | Bin 63193 -> 0 bytes
 documentation_builder/pymatbridge.ipynb            |  23 +-
 documentation_builder/pymatbridge.rst              | 124 ----
 documentation_builder/qp.ipynb                     |  26 +-
 documentation_builder/qp.rst                       | 190 ------
 documentation_builder/qp_files/qp_12_0.png         | Bin 14601 -> 0 bytes
 documentation_builder/qp_files/qp_1_0.png          | Bin 18553 -> 0 bytes
 documentation_builder/requirements.txt             |   2 +
 documentation_builder/simulating.ipynb             | 489 +++++++++++-----
 documentation_builder/simulating.rst               | 436 --------------
 documentation_builder/solvers.ipynb                |  34 +-
 documentation_builder/solvers.rst                  | 451 ---------------
 70 files changed, 1662 insertions(+), 4450 deletions(-)

diff --git a/.gitignore b/.gitignore
index 7357a55..7c0be42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,19 +1,90 @@
-*.pyc
-gurobi.log
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
 *$py.class
-documentation
-cobra.egg-info
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
 build/
+develop-eggs/
 dist/
-.DS_Store
+downloads/
+eggs/
 .eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask instance folder
+instance/
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# IPython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# dotenv
+.env
+
+# custom
+gurobi.log
+documentation
+documentation_builder/test*\.*
+examples/faq.py
+cobra.egg-info
 setuptools-*egg
 setuptools-*.tar.gz
 cobra/solvers/cglpk.c
-cobra/solvers/*so
 glpk.h
 libglpk.a
 .idea/
-.ipynb_checkpoints
-examples/faq.py
-documentation_builder/test*\.*
+.DS_Store
+.eggs/
diff --git a/.travis.yml b/.travis.yml
index 8a45213..7c2df5f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,7 +24,7 @@ env:
 before_install:
   - pip install pip --upgrade
   # These get cached
-  - pip install numpy scipy python-libsbml cython coveralls jsonschema six matplotlib
+  - pip install numpy scipy python-libsbml cython coveralls jsonschema six matplotlib pandas
   - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then pip install lxml glpk pep8 palettable; fi
   # Download esolver and add it to the path
   - wget https://opencobra.github.io/pypi_cobrapy_travis/esolver.gz
@@ -37,7 +37,7 @@ install:
 script:
   - coverage run --source=cobra setup.py test
   - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then
-    pep8 cobra/core cobra/design cobra/manipulation cobra/test cobra/version.py;
+    pep8 cobra --exclude=oven,solvers,sbml.py --show-source;
     fi
 after_success:
   - coveralls
diff --git a/appveyor.yml b/appveyor.yml
index b2cfa53..09aa50b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -5,6 +5,7 @@ environment:
     # /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:\\Python27"
@@ -38,13 +39,15 @@ init:
 
 cache:
     - glpk_build -> appveyor/build_glpk.py
+    - pip_cache -> appveyor.yml
 
 
 install:
   - "powershell appveyor\\install.ps1"
   - ps: Start-FileDownload 'https://bitbucket.org/gutworth/six/raw/default/six.py'
   - "%WITH_COMPILER% %PYTHON%/python appveyor/build_glpk.py"
-  - "%PYTHON%/python -m pip install Cython"
+  - "%PYTHON%/python -m pip install pip setuptools wheel --upgrade"
+  - "%PYTHON%/python -m pip install Cython jsonschema"
 
 build: off
 
diff --git a/appveyor/build_glpk.py b/appveyor/build_glpk.py
index 38216ba..1aff5cd 100644
--- a/appveyor/build_glpk.py
+++ b/appveyor/build_glpk.py
@@ -11,8 +11,8 @@ except ImportError:  # python 3
     import urllib.request as urllib2
 
 # these need to be set to the latest glpk version
-glpk_version = "4.57"
-glpk_md5 = "237531a54f73155842f8defe51aedb0f"
+glpk_version = "4.59"
+glpk_md5 = "c84ce7b8286ab91f2e871cd82050e2fe"
 
 glpk_build_dir = "glpk_build/glpk-%s" % glpk_version
 url = "http://ftp.gnu.org/gnu/glpk/glpk-%s.tar.gz" % glpk_version
diff --git a/cobra/VERSION b/cobra/VERSION
index 1d0ba9e..267577d 100644
--- a/cobra/VERSION
+++ b/cobra/VERSION
@@ -1 +1 @@
-0.4.0
+0.4.1
diff --git a/cobra/__init__.py b/cobra/__init__.py
index e57f01a..239c694 100644
--- a/cobra/__init__.py
+++ b/cobra/__init__.py
@@ -3,6 +3,19 @@ import warnings as _warnings
 from os.path import abspath as _abspath, dirname as _dirname
 from os import name as _name
 
+from .version import get_version
+from .core import Object, Metabolite, Gene, Reaction, Model, \
+    DictList, Species
+from . import io, flux_analysis, design
+
+try:
+    from .core import ArrayBasedModel
+except ImportError:
+    None
+
+__version__ = get_version()
+del get_version
+
 # set the warning format to be prettier and fit on one line
 _cobra_path = _dirname(_abspath(__file__))
 if _name == "posix":
@@ -15,16 +28,3 @@ def _warn_format(message, category, filename, lineno, file=None, line=None):
     shortname = filename.replace(_cobra_path, "cobra", 1)
     return _warning_base % (shortname, lineno, category.__name__, message)
 _warnings.formatwarning = _warn_format
-
-from .version import get_version
-__version__ = get_version()
-from .core import Object, Metabolite, Gene, Reaction, Model, \
-    DictList, Species
-from . import io, flux_analysis, design
-
-try:
-    from .core import ArrayBasedModel
-except ImportError:
-    None
-
-del get_version
diff --git a/cobra/core/Metabolite.py b/cobra/core/Metabolite.py
index 4a5875d..0449783 100644
--- a/cobra/core/Metabolite.py
+++ b/cobra/core/Metabolite.py
@@ -1,6 +1,8 @@
 from warnings import warn
 import re
 
+from six import iteritems
+
 from .Species import Species
 
 # Numbers are not required because of the |(?=[A-Z])? block. See the
@@ -15,8 +17,8 @@ class Metabolite(Species):
 
     """
 
-    def __init__(self, id=None, formula=None,
-                 name="", compartment=None):
+    def __init__(self, id=None, formula=None, name="",
+                 charge=None, compartment=None):
         """
         id: str
 
@@ -35,7 +37,7 @@ class Metabolite(Species):
         self.formula = formula
         # because in a Model a metabolite may participate in multiple Reactions
         self.compartment = compartment
-        self.charge = None
+        self.charge = charge
 
         self._constraint_sense = 'E'
         self._bound = 0.
@@ -79,6 +81,14 @@ class Metabolite(Species):
                 composition[element] = count
         return composition
 
+    @elements.setter
+    def elements(self, elements_dict):
+        def stringify(element, number):
+            return element if number == 1 else element + str(number)
+
+        self.formula = ''.join(stringify(e, n) for e, n in
+                               sorted(iteritems(elements_dict)))
+
     @property
     def formula_weight(self):
         """Calculate the formula weight"""
@@ -136,6 +146,25 @@ class Metabolite(Species):
         else:
             raise Exception(method + " is not 'subtractive' or 'destructive'")
 
+    def summary(self, **kwargs):
+        """Print a summary of the reactions which produce and consume this
+        metabolite. This method requires the model for which this metabolite is
+        a part to be solved.
+
+        threshold: float
+            a value below which to ignore reaction fluxes
+
+        fva: float (0->1), or None
+            Whether or not to include flux variability analysis in the output.
+            If given, fva should be a float between 0 and 1, representing the
+            fraction of the optimum objective to be searched.
+
+        """
+        try:
+            from ..flux_analysis.summary import metabolite_summary
+            return metabolite_summary(self, **kwargs)
+        except ImportError:
+            warn('Summary methods require pandas')
 
 elements_and_molecular_weights = {
     'H':   1.007940,
diff --git a/cobra/core/Model.py b/cobra/core/Model.py
index 2a62c4f..9c90a60 100644
--- a/cobra/core/Model.py
+++ b/cobra/core/Model.py
@@ -356,3 +356,23 @@ class Model(Object):
                 # from a list.
                 reaction.objective_coefficient = objectives[reaction_id] \
                     if hasattr(objectives, "items") else 1.
+
+    def summary(self, **kwargs):
+        """Print a summary of the input and output fluxes of the model. This
+        method requires the model to have been previously solved.
+
+        threshold: float
+            tolerance for determining if a flux is zero (not printed)
+        fva: int or None
+            Whether or not to calculate and report flux variability in the
+            output summary
+        round: int
+            number of digits after the decimal place to print
+
+        """
+
+        try:
+            from ..flux_analysis.summary import model_summary
+            return model_summary(self, **kwargs)
+        except ImportError:
+            warn('Summary methods require pandas')
diff --git a/cobra/core/Reaction.py b/cobra/core/Reaction.py
index 1110599..cdc94cd 100644
--- a/cobra/core/Reaction.py
+++ b/cobra/core/Reaction.py
@@ -288,7 +288,6 @@ class Reaction(Object):
         self._genes = set()
 
     def __setstate__(self, state):
-
         """Probably not necessary to set _model as the cobra.Model that
         contains self sets the _model attribute for all metabolites and genes
         in the reaction.
@@ -313,17 +312,26 @@ class Reaction(Object):
             x._reaction.add(self)
 
     def copy(self):
-        """When copying a reaction, it is necessary to deepcopy the
-        components so the list references aren't carried over.
+        """Copy a reaction
 
-        Additionally, a copy of a reaction is no longer in a cobra.Model.
+        The referenced metabolites and genes are also copied.
 
-        This should be fixed with self.__deecopy__ if possible
         """
-        # the_model = self._model
-        # self._model = None
+        # no references to model when copying
+        model = self._model
+        self._model = None
+        for i in self._metabolites:
+            i._model = None
+        for i in self._genes:
+            i._model = None
+        # now we can copy
         new_reaction = deepcopy(self)
-        # self._model = the_model
+        # restore the references
+        self._model = model
+        for i in self._metabolites:
+            i._model = model
+        for i in self._genes:
+            i._model = model
         return new_reaction
 
     def pop(self, metabolite_id):
@@ -349,65 +357,51 @@ class Reaction(Object):
         the_metabolite._reaction.remove(self)
         return the_coefficient
 
-    def __add__(self, other_reaction):
-        """Adds two reactions to each other.  Default behavior is
-        to combine the metabolites but only use the remaining parameters
-        from the first object.
-
-        TODO: Either clean up metabolite associations or remove function
-
-        TODO: Deal with gene association logic from adding reactions.
+    def __add__(self, other):
+        """Add two reactions
 
-        TODO: Simplify and add in an __iadd__
+        The stoichiometry will be the combined stoichiometry of the two
+        reactions, and the gene reaction rule will be both rules combined by an
+        and. All other attributes (i.e. reaction bounds) will match those of
+        the first reaction
 
         """
-        new_reaction = deepcopy(self)
-        new_reaction.id = self.id + '_' + other_reaction.id
-        new_reaction.add_metabolites(deepcopy(other_reaction._metabolites))
-        new_reaction._genes.update(deepcopy(other_reaction._genes))
-        # Make all the genes aware of this reaction
-        [x._reaction.add(new_reaction) for x in new_reaction._genes]
-        gpr_1 = new_reaction.gene_reaction_rule
-        gpr_2 = other_reaction.gene_reaction_rule
-        if gpr_1 != '' and gpr_2 != '':
-            new_reaction.gene_reaction_rule = '%s and %s' % (gpr_1, gpr_2)
-        elif gpr_2 != '':
-            new_reaction.gene_reaction_rule = gpr_2
+        new_reaction = self.copy()
+        new_reaction += other
         return new_reaction
 
-    def __sub__(self, other_reaction):
-        """Subtracts two reactions.  Default behavior is
-        to combine the metabolites but only use the remaining parameters
-        from the first object.
+    def __iadd__(self, other):
+        self.add_metabolites(other._metabolites, combine=True)
+        gpr1 = self.gene_reaction_rule.strip()
+        gpr2 = other.gene_reaction_rule.strip()
+        if gpr1 != '' and gpr2 != '':
+            self.gene_reaction_rule = "(%s) and (%s)" % \
+                (self.gene_reaction_rule, other.gene_reaction_rule)
+        elif gpr1 != '' and gpr2 == '':
+            self.gene_reaction_rule = gpr1
+        elif gpr1 == '' and gpr2 != '':
+            self.gene_reaction_rule = gpr2
+        return self
 
-        Note: This is equivalent to adding reactions after changing the sign
-        of the metabolites in other_reaction
+    def __sub__(self, other):
+        new = self.copy()
+        new -= other
+        return new
 
-        """
-        new_reaction = deepcopy(self)
-        if self is other_reaction:
-            other_reaction = deepcopy(other_reaction)
-        new_reaction.id = self.id + '_' + other_reaction.id
-        new_reaction.subtract_metabolites(
-            deepcopy(other_reaction._metabolites))
-        return new_reaction
-
-    def __imul__(self, the_coefficient):
-        """Allows the reaction coefficients to be rapidly scaled.
+    def __isub__(self, other):
+        self.subtract_metabolites(other._metabolites, combine=True)
+        return self
 
-        """
-        self._metabolites = {k: the_coefficient * v for k, v in
+    def __imul__(self, coefficient):
+        """Scale coefficients in a reaction"""
+        self._metabolites = {k: coefficient * v for k, v in
                              iteritems(self._metabolites)}
         return self
 
-    def __mul__(self, the_coefficient):
-        """Allows a reaction to be multiplied by a coefficient.
-
-        TODO: this should return a new reaction.
-
-        """
-        self *= the_coefficient
-        return self
+    def __mul__(self, coefficient):
+        new = self.copy()
+        new *= coefficient
+        return new
 
     @property
     def reactants(self):
@@ -505,7 +499,7 @@ class Reaction(Object):
         if add_to_container_model and hasattr(self._model, 'add_metabolites'):
             self._model.add_metabolites(new_metabolites)
 
-    def subtract_metabolites(self, metabolites):
+    def subtract_metabolites(self, metabolites, combine=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.
@@ -516,7 +510,8 @@ class Reaction(Object):
         .. note:: A final coefficient < 0 implies a reactant.
 
         """
-        self.add_metabolites({k: -v for k, v in iteritems(metabolites)})
+        self.add_metabolites({k: -v for k, v in iteritems(metabolites)},
+                             combine=combine)
 
     def clear_metabolites(self):
         """Remove all metabolites from the reaction"""
@@ -541,7 +536,8 @@ class Reaction(Object):
             id_type = 'name'
         reactant_bits = []
         product_bits = []
-        for the_metabolite, coefficient in iteritems(self._metabolites):
+        for the_metabolite, coefficient in sorted(
+                iteritems(self._metabolites), key=lambda x: x[0].id):
             name = str(getattr(the_metabolite, id_type))
             if _is_positive(coefficient):
                 product_bits.append(format(coefficient) + name)
@@ -596,8 +592,8 @@ class Reaction(Object):
         cobra_gene : :class:`~cobra.core.Gene.Gene`
 
         """
-        self._genes.remove(cobra_gene)
-        cobra_gene._reaction.remove(self)
+        self._genes.discard(cobra_gene)
+        cobra_gene._reaction.discard(self)
 
     def knock_out(self):
         """Change the upper and lower bounds of the reaction to 0."""
diff --git a/cobra/design/design_algorithms.py b/cobra/design/design_algorithms.py
index cd38b43..b355248 100644
--- a/cobra/design/design_algorithms.py
+++ b/cobra/design/design_algorithms.py
@@ -1,9 +1,7 @@
 from ..core import Model, Reaction, Metabolite
-from ..manipulation.modify import convert_to_irreversible, canonical_form
+from ..manipulation.modify import canonical_form
 
 from six import iteritems
-from collections import defaultdict
-from itertools import chain
 from copy import deepcopy
 
 
diff --git a/cobra/flux_analysis/deletion_worker.py b/cobra/flux_analysis/deletion_worker.py
index 887501a..359e7f2 100644
--- a/cobra/flux_analysis/deletion_worker.py
+++ b/cobra/flux_analysis/deletion_worker.py
@@ -58,6 +58,7 @@ class CobraDeletionPool(object):
     # 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)
@@ -88,7 +89,10 @@ class CobraDeletionPool(object):
         return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
-        self.terminate()
+        try:
+            self.terminate()
+        except:
+            pass
 
     def submit(self, indexes, label=None):
         self.job_queue.put((indexes, label))
@@ -122,6 +126,7 @@ class CobraDeletionPool(object):
 
 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
diff --git a/cobra/flux_analysis/double_deletion.py b/cobra/flux_analysis/double_deletion.py
index 012f9b5..945ad7e 100644
--- a/cobra/flux_analysis/double_deletion.py
+++ b/cobra/flux_analysis/double_deletion.py
@@ -1,12 +1,12 @@
 from warnings import warn
 from itertools import chain, product
 
-from six import iteritems, string_types
+from six import iteritems
 import numpy
 
 from ..solvers import get_solver_name, solver_dict
-from ..manipulation.delete import find_gene_knockout_reactions, \
-    get_compiled_gene_reaction_rules
+from ..manipulation.delete import (find_gene_knockout_reactions,
+                                   get_compiled_gene_reaction_rules)
 from .deletion_worker import CobraDeletionPool, CobraDeletionMockPool
 
 try:
diff --git a/cobra/flux_analysis/essentiality.py b/cobra/flux_analysis/essentiality.py
index ee4435e..be46440 100644
--- a/cobra/flux_analysis/essentiality.py
+++ b/cobra/flux_analysis/essentiality.py
@@ -4,38 +4,37 @@ try:
 except:
     warn("moma does not appear to be functional on your system")
 from cobra.manipulation import initialize_growth_medium
+
+
 def assess_medium_component_essentiality(cobra_model, the_components=None,
-                                         the_medium=None, medium_compartment='e', solver='glpk',
-                                         the_problem='return',
+                                         the_medium=None,
+                                         medium_compartment='e', solver='glpk',
                                          the_condition=None, method='fba'):
-    """Determines which components in an in silico medium are essential for growth in the
-    context of the remaining components.
+    """Determines which components in an in silico medium are essential for
+    growth in the context of the remaining components.
 
     cobra_model: A Model object.
 
-    the_components: None or a list of external boundary reactions that will be sequentially
-    disabled.
+    the_components: None or a list of external boundary reactions that will be
+    sequentially disabled.
 
     the_medium: Is None, a string, or a dictionary.  If a string then the
-    initialize_growth_medium function expects that the_model has an
-    attribute dictionary called media_compositions, which is a dictionary of
-    dictionaries for various medium compositions.  Where a medium
-    composition is a dictionary of external boundary reaction ids for the medium
-    components and the external boundary fluxes for each medium component.
+    initialize_growth_medium function expects that the_model has an attribute
+    dictionary called media_compositions, which is a dictionary of dictionaries
+    for various medium compositions.  Where a medium composition is a
+    dictionary of external boundary reaction ids for the medium components and
+    the external boundary fluxes for each medium component.
 
-    medium_compartment: the compartment in which the boundary reactions supplying the medium
-    components exist
+    medium_compartment: the compartment in which the boundary reactions
+    supplying the medium components exist
 
-    NOTE: that these fluxes must be negative because the convention is backwards means something
-    is feed into the system.
+    NOTE: that these fluxes must be negative because the convention is
+    backwards means something is feed into the system.
 
     solver: 'glpk', 'gurobi', or 'cplex'
 
-    the_problem: Is None, 'return', or an LP model object for the solver.
-
-    returns:
-     essentiality_dict:  A dictionary providing the maximum growth rate accessible when
-     the respective component is removed from the medium.
+    returns: essentiality_dict:  A dictionary providing the maximum growth rate
+    accessible when the respective component is removed from the medium.
 
     """
     if method.lower() == 'moma':
@@ -46,23 +45,25 @@ def assess_medium_component_essentiality(cobra_model, the_components=None,
         try:
             the_medium = cobra_model.media_compositions[the_medium]
         except:
-            raise Exception(the_medium + " is not in cobra_model.media_compositions")
+            raise Exception(
+                the_medium + " is not in cobra_model.media_compositions")
     if the_medium is not None:
         initialize_growth_medium(cobra_model, the_medium, medium_compartment)
         if the_components is None:
             the_components = the_medium.keys()
     if not the_components:
-            raise Exception("You need to specify the_components or the_medium")
+        raise Exception("You need to specify the_components or the_medium")
     essentiality_dict = {}
     for the_component in the_components:
         the_reaction = cobra_model.reactions.get_by_id(the_component)
         original_lower_bound = float(the_reaction.lower_bound)
         the_reaction.lower_bound = 0.
         if method.lower() == 'fba':
-            cobra_model.optimize(solver=solver, the_problem=the_problem)
+            cobra_model.optimize(solver=solver)
             objective_value = cobra_model.solution.f
         elif method.lower() == 'moma':
-           objective_value = moma(wt_model, cobra_model, solver=solver)['objective_value'] 
+            objective_value = moma(wt_model, cobra_model, solver=solver)[
+                'objective_value']
         essentiality_dict[the_component] = objective_value
         the_reaction.lower_bound = original_lower_bound
 
diff --git a/cobra/flux_analysis/gapfilling.py b/cobra/flux_analysis/gapfilling.py
index d3d1602..7f52b4f 100644
--- a/cobra/flux_analysis/gapfilling.py
+++ b/cobra/flux_analysis/gapfilling.py
@@ -12,6 +12,7 @@ class SUXModelMILP(Model):
     total number of added reactions. See the figure for more
     information on the structure of the matrix.
     """
+
     def __init__(self, model, Universal=None, threshold=.05,
                  penalties=None, dm_rxns=True, ex_rxns=False):
         Model.__init__(self, "")
@@ -137,7 +138,7 @@ def growMatch(model, Universal, dm_rxns=False, ex_rxns=False,
 
 
 def SMILEY(model, metabolite_id, Universal,
-           dm_rxns=False, ex_rxns=False, panalties=None, **solver_parameters):
+           dm_rxns=False, ex_rxns=False, penalties=None, **solver_parameters):
     """
     runs the SMILEY algorithm to determine which gaps should be
     filled in order for the model to create the metabolite with the
diff --git a/cobra/flux_analysis/loopless.py b/cobra/flux_analysis/loopless.py
index f1ed6b6..97923b7 100644
--- a/cobra/flux_analysis/loopless.py
+++ b/cobra/flux_analysis/loopless.py
@@ -29,7 +29,7 @@ def construct_loopless_model(cobra_model):
         # populate the S^T dict
         bound_id = "thermo_bound_" + reaction.id
         for met, stoic in iteritems(reaction._metabolites):
-                thermo_stoic["thermo_var_" + met.id][bound_id] = stoic
+            thermo_stoic["thermo_var_" + met.id][bound_id] = stoic
         # I * 1000 > v --> I * 1000 - v > 0
         reaction_ind = Reaction(reaction.id + "_indicator")
         reaction_ind.variable_kind = "integer"
diff --git a/cobra/flux_analysis/moma.py b/cobra/flux_analysis/moma.py
index 11b7967..a02b905 100644
--- a/cobra/flux_analysis/moma.py
+++ b/cobra/flux_analysis/moma.py
@@ -75,7 +75,8 @@ def create_euclidian_distance_lp(moma_model, solver):
 
 
 def solve_moma_model(moma_model, objective_id, solver=None, **solver_args):
-    solver = solver_dict[solver if solver else get_solver_name(qp=True)]
+    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)
diff --git a/cobra/flux_analysis/parsimonious.py b/cobra/flux_analysis/parsimonious.py
index 921c81d..4630214 100644
--- a/cobra/flux_analysis/parsimonious.py
+++ b/cobra/flux_analysis/parsimonious.py
@@ -1,8 +1,12 @@
-from ..manipulation import modify
+from six import iteritems
 
+from ..manipulation.modify import convert_to_irreversible, revert_to_reversible
+from ..solvers import solver_dict, get_solver_name
 
-def optimize_minimal_flux(model, already_irreversible=False,
-                          **optimize_kwargs):
+
+def optimize_minimal_flux(cobra_model, already_irreversible=False,
+                          fraction_of_optimum=1.0, solver=None,
+                          desired_objective_value=None, **optimize_kwargs):
     """Perform basic pFBA (parsimonius FBA) and minimize total flux.
 
     The function attempts to act as a drop-in replacement for optimize. It
@@ -11,53 +15,71 @@ def optimize_minimal_flux(model, already_irreversible=False,
     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
 
-    model : :class:`~cobra.core.Model` object
+    cobra_model : :class:`~cobra.core.Model` object
 
     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.
+        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.
+
+    solver : string of solver name
+        If None is given, the default solver will be used.
 
+    Updates everything in-place, returns model to original state at end.
     """
-    if not already_irreversible:
-        modify.convert_to_irreversible(model)
-    model.optimize(**optimize_kwargs)
-    # if the problem is infeasible
-    if model.solution.f is None:
-        raise Exception("model could not be solved")
-    old_f = model.solution.f
-    old_objective_coefficients = {}
-    old_lower_bounds = {}
-    old_upper_bounds = {}
-    for reaction in model.reactions:
-        # if the reaction has a nonzero objective coefficient, then
-        # the same flux should be maintained through that reaction
+
+    if len(cobra_model.objective) > 1:
+        raise ValueError('optimize_minimal_flux 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 optimize_minimal_flux')
+        optimize_kwargs.pop('objective_sense', None)
+
+    # Convert to irreversible, so all reactions will have a positive flux
+    convert_to_irreversible(cobra_model)
+
+    solver = solver_dict[get_solver_name() if solver is None else solver]
+    lp = solver.create_problem(cobra_model, **optimize_kwargs)
+    if not desired_objective_value:
+        solver.solve_problem(lp, objective_sense='maximize')
+        status = solver.get_status(lp)
+        if status != "optimal":
+            raise ValueError(
+                "pFBA requires optimal solution status, not {}".format(status))
+        desired_objective_value = solver.get_objective_value(lp)
+
+    for i, reaction in enumerate(cobra_model.reactions):
+
         if reaction.objective_coefficient != 0:
-            old_objective_coefficients[reaction] = \
-                reaction.objective_coefficient
-            old_lower_bounds[reaction] = reaction.lower_bound
-            old_upper_bounds[reaction] = reaction.upper_bound
-            x = model.solution.x_dict[reaction.id]
-            reaction.lower_bound = x
-            reaction.upper_bound = x
-            reaction.objective_coefficient = 0
-        else:
-            reaction.objective_coefficient = 1
-    # set to minimize flux
-    optimize_kwargs["objective_sense"] = "minimize"
-    model.optimize(**optimize_kwargs)
-    # make the model back the way it was
-    for reaction in model.reactions:
-        if reaction in old_objective_coefficients:
-            reaction.objective_coefficient = \
-                old_objective_coefficients[reaction]
-            reaction.lower_bound = old_lower_bounds[reaction]
-            reaction.upper_bound = old_upper_bounds[reaction]
-        else:
-            reaction.objective_coefficient = 0
-    # if the minimization problem was successful
-    if model.solution.f is not None:
-        model.solution.f = old_f
-    if not already_irreversible:
-        modify.revert_to_reversible(model)
-    return model.solution
+            # 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, cobra_model)
+
+    # Return the model to its original state
+    cobra_model.solution = solution
+    revert_to_reversible(cobra_model)
+
+    if solution.status == "optimal":
+        cobra_model.solution.f = sum([coeff * reaction.x for reaction, coeff in
+                                      iteritems(cobra_model.objective)])
+
+    return solution
diff --git a/cobra/flux_analysis/phenotype_phase_plane.py b/cobra/flux_analysis/phenotype_phase_plane.py
index b82d6f7..742ee2b 100644
--- a/cobra/flux_analysis/phenotype_phase_plane.py
+++ b/cobra/flux_analysis/phenotype_phase_plane.py
@@ -1,5 +1,5 @@
-from numpy import linspace, zeros, array, meshgrid, abs, empty, arange, \
-    int32, unravel_index, dtype
+from numpy import (linspace, zeros, meshgrid, abs, empty, arange, int32,
+                   unravel_index, dtype)
 from multiprocessing import Pool
 
 from ..solvers import solver_dict, get_solver_name
@@ -23,6 +23,7 @@ except ImportError:
 
 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,
diff --git a/cobra/flux_analysis/reaction.py b/cobra/flux_analysis/reaction.py
index c7e92d6..f2321fe 100644
--- a/cobra/flux_analysis/reaction.py
+++ b/cobra/flux_analysis/reaction.py
@@ -1,39 +1,42 @@
-#cobra.flux_analysis.reaction.py
-#functions for analyzing / creating objective functions
+# cobra.flux_analysis.reaction.py
+# functions for analyzing / creating objective functions
 from ..core.Reaction import Reaction
 from six import iteritems
 
+
 def assess(model, reaction, flux_coefficient_cutoff=0.001):
-    """Assesses the capacity of the model to produce the precursors for the reaction
-    and absorb the production of the reaction while the reaction is operating at, or
-    above, the specified cutoff.
+    """Assesses the capacity of the model to produce the precursors for the
+    reaction and absorb the production of the reaction while the reaction is
+    operating at, or above, the specified cutoff.
 
     model: A :class:`~cobra.core.Model` object
 
     reaction: A :class:`~cobra.core.Reaction` object
 
+    flux_coefficient_cutoff:  Float.  The minimum flux that reaction must carry
+    to be considered active.
 
-    flux_coefficient_cutoff:  Float.  The minimum flux that reaction must carry to
-    be considered active.
-
-    returns: True if the model can produce the precursors and absorb the products
-    for the reaction operating at, or above, flux_coefficient_cutoff.  Otherwise,
-    a dictionary of {'precursor': Status, 'product': Status}.  Where Status is the
-    results from assess_precursors and assess_products,
+    returns: True if the model can produce the precursors and absorb the
+    products for the reaction operating at, or above, flux_coefficient_cutoff.
+    Otherwise, a dictionary of {'precursor': Status, 'product': Status}.  Where
+    Status is the results from assess_precursors and assess_products,
     respectively.
 
     """
     reaction = model.reactions.get_by_id(reaction.id)
     model.optimize(new_objective={reaction: 1})
     if model.solution.f >= flux_coefficient_cutoff:
-        return(True)
+        return True
     else:
         results = {}
-        results['precursors'] = assess_precursors(model, reaction, flux_coefficient_cutoff)
-        results['products'] = assess_products(model, reaction, flux_coefficient_cutoff)
-        return(results)
+        results['precursors'] = assess_precursors(
+            model, reaction, flux_coefficient_cutoff)
+        results['products'] = assess_products(
+            model, reaction, flux_coefficient_cutoff)
+        return results
+
 
-def assess_precursors(model, reaction,  flux_coefficient_cutoff=0.001):
+def assess_precursors(model, reaction, flux_coefficient_cutoff=0.001):
     """Assesses the ability of the model to provide sufficient precursors for
     a reaction operating at, or beyond, the specified cutoff.
 
@@ -41,36 +44,37 @@ def assess_precursors(model, reaction,  flux_coefficient_cutoff=0.001):
 
     reaction: A :class:`~cobra.core.Reaction` object
 
+    flux_coefficient_cutoff: Float. The minimum flux that reaction must carry
+    to be considered active.
 
-    flux_coefficient_cutoff:  Float.  The minimum flux that reaction must carry to
-    be considered active.
+    returns: True if the precursors can be simultaneously produced at the
+    specified cutoff. False, if the model has the capacity to produce each
+    individual precursor at the specified threshold  but not all precursors at
+    the required level simultaneously. Otherwise a dictionary of the required
+    and the produced fluxes for each reactant that is not produced in
+    sufficient quantities.
 
-    returns: True if  the precursors can be simultaneously produced at the specified cutoff.  False, if the model has
-    the capacity to produce each individual precursor at the specified threshold  but not all precursors at the
-    required level simultaneously.   Otherwise a dictionary of the required and the produced
-    fluxes for each reactant that is not produced in sufficient quantities.
-    
     """
     model = model.copy()
     reaction = model.reactions.get_by_id(reaction.id)
     model.optimize(new_objective={reaction: 1})
     if model.solution.f >= flux_coefficient_cutoff:
-        return(True)
+        return True
     #
     simulation_results = {}
-    #build the sink reactions and add all at once
+    # build the sink reactions and add all at once
     sink_reactions = {}
     for the_component in reaction.get_reactants():
-        #add in a sink reaction for each component
-        sink_reaction = Reaction('test_sink_%s'%the_component.id)
-        #then simulate production ability
-        #then check it can exceed objective cutoff * component stoichiometric
-        #coefficient.
-        coefficient = reaction.get_coefficient(the_component) 
+        # add in a sink reaction for each component
+        sink_reaction = Reaction('test_sink_%s' % the_component.id)
+        # then simulate production ability
+        # then check it can exceed objective cutoff * component stoichiometric
+        # coefficient.
+        coefficient = reaction.get_coefficient(the_component)
         sink_reaction.add_metabolites({the_component: coefficient})
         sink_reaction.upper_bound = 1000
         sink_reactions[sink_reaction] = (the_component, coefficient)
-    #First assess whether all precursors can pbe produced simultaneously
+    # First assess whether all precursors can pbe produced simultaneously
     super_sink = Reaction("super_sink")
     for reaction in sink_reactions:
         super_sink += reaction
@@ -78,81 +82,94 @@ def assess_precursors(model, reaction,  flux_coefficient_cutoff=0.001):
     model.add_reactions(sink_reactions.keys() + [super_sink])
     model.optimize(new_objective=super_sink)
     if flux_coefficient_cutoff <= model.solution.f:
-        return(True)
+        return True
 
-    #Otherwise assess the ability of the model to produce each precursor individually.
-    #Now assess the ability of the model to produce each reactant for a reaction
+    # Otherwise assess the ability of the model to produce each precursor
+    # individually.  Now assess the ability of the model to produce each
+    # reactant for a reaction
     for sink_reaction, (component, coefficient) in iteritems(sink_reactions):
-        model.optimize(new_objective=sink_reaction) #Calculate the maximum amount of the
-        #metabolite that can be produced.
+        # Calculate the maximum amount of the
+        model.optimize(new_objective=sink_reaction)
+        # metabolite that can be produced.
         if flux_coefficient_cutoff > model.solution.f:
-            #Scale the results to a single unit
-            simulation_results.update({component:{'required':flux_coefficient_cutoff/abs(coefficient),
-                                                  'produced':model.solution.f/abs(coefficient)}})
+            # Scale the results to a single unit
+            simulation_results.update({
+                component:
+                    {
+                        'required': flux_coefficient_cutoff / abs(coefficient),
+                        'produced': model.solution.f / abs(coefficient)
+                    }
+            })
     if len(simulation_results) == 0:
         simulation_results = False
-    return(simulation_results)
+    return simulation_results
+
 
 def assess_products(model, reaction, flux_coefficient_cutoff=0.001):
-    """Assesses whether the model has the capacity to absorb the products of a reaction
-    at a given flux rate.  Useful for identifying which components might be blocking
-    a reaction from achieving a specific flux rate.
+    """Assesses whether the model has the capacity to absorb the products of
+    a reaction at a given flux rate.  Useful for identifying which components
+    might be blocking a reaction from achieving a specific flux rate.
 
     model: A :class:`~cobra.core.Model` object
 
     reaction: A :class:`~cobra.core.Reaction` object
 
-    flux_coefficient_cutoff:  Float.  The minimum flux that reaction must carry to
-    be considered active.
+    flux_coefficient_cutoff:  Float.  The minimum flux that reaction must carry
+    to be considered active.
 
-    returns: True if the model has the capacity to absorb all the reaction products being
-    simultaneously given the specified cutoff.   False, if the model has
-    the capacity to absorb each individual product but not all products at the
-    required level simultaneously.   Otherwise a dictionary of the required and the
-    capacity fluxes for each product that is not absorbed in sufficient quantities.
+    returns: True if the model has the capacity to absorb all the reaction
+    products being simultaneously given the specified cutoff.   False, if the
+    model has the capacity to absorb each individual product but not all
+    products at the required level simultaneously.   Otherwise a dictionary of
+    the required and the capacity fluxes for each product that is not absorbed
+    in sufficient quantities.
 
-    
     """
     model = model.copy()
     reaction = model.reactions.get_by_id(reaction.id)
     model.optimize(new_objective={reaction: 1})
     if model.solution.f >= flux_coefficient_cutoff:
-        return(True)
+        return True
     #
     simulation_results = {}
-    #build the sink reactions and add all at once
+    # build the sink reactions and add all at once
     source_reactions = {}
     for the_component in reaction.get_products():
-        #add in a sink reaction for each component
-        source_reaction = Reaction('test_source_%s'%the_component.id)
-        #then simulate production ability
-        #then check it can exceed objective cutoff * component stoichiometric
-        #coefficient.
-        coefficient = reaction.get_coefficient(the_component) 
+        # add in a sink reaction for each component
+        source_reaction = Reaction('test_source_%s' % the_component.id)
+        # then simulate production ability
+        # then check it can exceed objective cutoff * component stoichiometric
+        # coefficient.
+        coefficient = reaction.get_coefficient(the_component)
         source_reaction.add_metabolites({the_component: coefficient})
         source_reaction.upper_bound = 1000
         source_reactions[source_reaction] = (the_component, coefficient)
     #
     super_source = Reaction('super_source')
     for reaction in source_reactions:
-            super_source += reaction
+        super_source += reaction
     super_source.id = 'super_source'
     model.add_reactions(source_reactions.keys() + [super_source])
     model.optimize(new_objective=super_source)
     if flux_coefficient_cutoff <= model.solution.f:
-        return(True)
-
-    #Now assess the ability of the model to produce each reactant for a reaction
-    for source_reaction, (component, coefficient) in iteritems(source_reactions):
-        model.optimize(new_objective=source_reaction) #Calculate the maximum amount of the
-        #metabolite that can be produced.
+        return True
+
+    # Now assess the ability of the model to produce each reactant for a
+    # reaction
+    for source_reaction, (component, coefficient) in \
+            iteritems(source_reactions):
+        # Calculate the maximum amount of the
+        model.optimize(new_objective=source_reaction)
+        # metabolite that can be produced.
         if flux_coefficient_cutoff > model.solution.f:
-            #Scale the results to a single unit
-            simulation_results.update({component:{'required':flux_coefficient_cutoff/abs(coefficient),
-                                                  'capacity':model.solution.f/abs(coefficient)}})
+            # Scale the results to a single unit
+            simulation_results.update({
+                component:
+                    {
+                        'required': flux_coefficient_cutoff / abs(coefficient),
+                        'capacity': model.solution.f / abs(coefficient)}
+                    }
+            )
     if len(simulation_results) == 0:
         simulation_results = False
-    return(simulation_results)
-
-    
-    
+    return simulation_results
diff --git a/cobra/flux_analysis/single_deletion.py b/cobra/flux_analysis/single_deletion.py
index 4ce985f..f1b3027 100644
--- a/cobra/flux_analysis/single_deletion.py
+++ b/cobra/flux_analysis/single_deletion.py
@@ -97,7 +97,7 @@ def single_reaction_deletion_moma(cobra_model, reaction_list, solver=None,
     if moma is None:
         raise RuntimeError("scipy required for moma")
     solver = solver_dict[solver if solver else get_solver_name(qp=True)]
-    moma_model, moma_obj = moma.create_euclidian_moma_model(cobra_model)
+    moma_model, moma_objective = moma.create_euclidian_moma_model(cobra_model)
 
     growth_rate_dict = {}
     status_dict = {}
@@ -167,13 +167,13 @@ def single_gene_deletion_moma(cobra_model, gene_list, solver=None,
     if moma is None:
         raise RuntimeError("scipy required for moma")
     solver = solver if solver else get_solver_name(qp=True)
-    moma_model, moma_obj = moma.create_euclidian_moma_model(cobra_model)
+    moma_model, moma_objective = moma.create_euclidian_moma_model(cobra_model)
 
     growth_rate_dict = {}
     status_dict = {}
     for gene in gene_list:
         delete_model_genes(moma_model, [gene.id])
-        solution = moma.solve_moma_model(moma_model, moma_obj,
+        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
diff --git a/cobra/flux_analysis/summary.py b/cobra/flux_analysis/summary.py
new file mode 100644
index 0000000..dfce00a
--- /dev/null
+++ b/cobra/flux_analysis/summary.py
@@ -0,0 +1,191 @@
+from six.moves import zip_longest
+from six import iterkeys, print_, text_type
+
+import pandas as pd
+
+from .variability import flux_variability_analysis
+
+
+def format_long_string(string, max_length):
+    if len(string) > max_length:
+        string = string[:max_length - 3]
+        string += '...'
+    return string
+
+
+def metabolite_summary(met, threshold=0.01, fva=False, **solver_args):
+    """Print a summary of the reactions which produce and consume this
+    metabolite
+
+    threshold: float
+    a value below which to ignore reaction fluxes
+
+    fva: float (0->1), or None
+    Whether or not to include flux variability analysis in the output.
+    If given, fva should be a float between 0 and 1, representing the
+    fraction of the optimum objective to be searched.
+
+    """
+
+    def rxn_summary(r):
+        return {
+            'id': r.id,
+            'flux': r.x * r.metabolites[met],
+            'reaction': r.reaction,
+        }
+
+    flux_summary = pd.DataFrame((rxn_summary(r) for r in met.reactions))
+    assert flux_summary.flux.sum() < 1E-6, "Error in flux balance"
+    producing = flux_summary[flux_summary.flux > 0].copy()
+    consuming = flux_summary[flux_summary.flux < 0].copy()
+
+    for df in [producing, consuming]:
+        df['percent'] = df.flux / df.flux.sum()
+        df.drop(df[df['percent'] < threshold].index, axis=0,
+                inplace=True)
+
+    producing.sort_values('percent', ascending=False, inplace=True)
+    consuming.sort_values('percent', ascending=False, inplace=True)
+
+    if not fva:
+
+        producing.flux = producing.flux.apply(
+            lambda x: '{:6.2g}'.format(x))
+        consuming.flux = consuming.flux.apply(
+            lambda x: '{:6.2g}'.format(x))
+
+        flux_len = 6
+
+    else:
+        fva_results = pd.DataFrame(
+            flux_variability_analysis(met.model, met.reactions,
+                                      fraction_of_optimum=fva,
+                                      **solver_args)).T
+        half_span = (fva_results.maximum - fva_results.minimum) / 2
+        median = fva_results.minimum + half_span
+
+        producing.flux = producing.id.apply(
+            lambda x, median=median, err=half_span:
+            u'{0:0.2f} \u00B1 {1:0.2f}'.format(median[x], err[x]))
+        consuming.flux = consuming.id.apply(
+            lambda x, median=median, err=half_span:
+            u'{0:0.2f} \u00B1 {1:0.2f}'.format(median[x], err[x]))
+
+        flux_len = max(producing.flux.apply(len).max(),
+                       consuming.flux.apply(len).max()) + 1
+
+    for df in [producing, consuming]:
+
+        df['reaction'] = df['reaction'].map(
+            lambda x: format_long_string(x, 52))
+        df['id'] = df['id'].map(
+            lambda x: format_long_string(x, 8))
+
+    head = "PRODUCING REACTIONS -- " + format_long_string(met.name, 55)
+    print_(head)
+    print_("-" * len(head))
+    print_(("{0:^6} {1:>" + str(flux_len) + "} {2:>8} {3:^54}").format(
+        '%', 'FLUX', 'RXN ID', 'REACTION'))
+
+    for row in producing.iterrows():
+        print_((u"{0.percent:6.1%} {0.flux:>" + str(flux_len) +
+                "} {0.id:>8} {0.reaction:>54}").format(row[1]))
+
+    print_()
+    print_("CONSUMING REACTIONS -- " + format_long_string(met.name, 55))
+    print_("-" * len(head))
+    print_(("{0:^6} {1:>" + str(flux_len) + "} {2:>8} {3:^54}").format(
+        '%', 'FLUX', 'RXN ID', 'REACTION'))
+
+    for row in consuming.iterrows():
+        print_((u"{0.percent:6.1%} {0.flux:>" + str(flux_len) +
+                "} {0.id:>8} {0.reaction:>54}").format(row[1]))
+
+
+def model_summary(model, threshold=1E-8, fva=None, digits=2, **solver_args):
+    """Print a summary of the input and output fluxes of the model.
+
+    threshold: float
+        tolerance for determining if a flux is zero (not printed)
+
+    fva: int or None
+        Whether or not to calculate and report flux variability in the
+        output summary
+
+    digits: int
+        number of digits after the decimal place to print
+
+    """
+    obj_fluxes = pd.Series({'{:<15}'.format(r.id): '{:.3f}'.format(r.x)
+                            for r in iterkeys(model.objective)})
+
+    if not fva:
+
+        out_rxns = model.reactions.query(
+            lambda rxn: rxn.x > threshold, None
+        ).query(lambda x: x, 'boundary')
+
+        in_rxns = model.reactions.query(
+            lambda rxn: rxn.x < -threshold, None
+        ).query(lambda x: x, 'boundary')
+
+        out_fluxes = pd.Series({r.reactants[0]: r.x for r in out_rxns})
+        in_fluxes = pd.Series({r.reactants[0]: r.x for r in in_rxns})
+
+        # sort and round
+        out_fluxes.sort_values(ascending=False, inplace=True)
+        out_fluxes = out_fluxes.round(digits)
+        in_fluxes.sort_values(inplace=True)
+        in_fluxes = in_fluxes.round(digits)
+
+        table = pd.np.array(
+            [((a if a else ''), (b if b else ''), (c if c else ''))
+             for a, b, c in zip_longest(
+                ['IN FLUXES'] + in_fluxes.to_string().split('\n'),
+                ['OUT FLUXES'] + out_fluxes.to_string().split('\n'),
+                ['OBJECTIVES'] + obj_fluxes.to_string().split('\n'))])
+
+    else:
+        boundary_reactions = model.reactions.query(lambda x: x, 'boundary')
+
+        fva_results = pd.DataFrame(
+            flux_variability_analysis(model, reaction_list=boundary_reactions,
+                                      fraction_of_optimum=fva,
+                                      **solver_args)).T
+
+        half_span = (fva_results.maximum - fva_results.minimum) / 2
+        median = fva_results.minimum + half_span
+        rxn_data = pd.concat([median, half_span], 1)
+        rxn_data.columns = ['x', 'err']
+
+        for r in rxn_data.index:
+            rxn_data.loc[r, 'met'] = model.reactions.get_by_id(r).reactants[0]
+
+        rxn_data.set_index('met', drop=True, inplace=True)
+
+        out_fluxes = rxn_data[rxn_data.x > threshold]
+        in_fluxes = rxn_data[rxn_data.x < -threshold]
+
+        out_fluxes = out_fluxes.sort_values(by='x', ascending=False)
+        out_fluxes = out_fluxes.round(digits)
+        in_fluxes = in_fluxes.sort_values(by='x')
+        in_fluxes = in_fluxes.round(digits)
+
+        in_fluxes_s = in_fluxes.apply(
+            lambda x: u'{0:0.2f} \u00B1 {1:0.2f}'.format(x.x, x.err),
+            axis=1)
+        out_fluxes_s = out_fluxes.apply(
+            lambda x: u'{0:0.2f} \u00B1 {1:0.2f}'.format(x.x, x.err),
+            axis=1)
+        out_fluxes_s = out_fluxes.apply(lambda x: text_type(x.x) +
+                                        u" \u00B1 " + text_type(x.err), axis=1)
+
+        table = pd.np.array(
+            [((a if a else ''), (b if b else ''), (c if c else ''))
+             for a, b, c in zip_longest(
+                ['IN FLUXES'] + in_fluxes_s.to_string().split('\n'),
+                ['OUT FLUXES'] + out_fluxes_s.to_string().split('\n'),
+                ['OBJECTIVES'] + obj_fluxes.to_string().split('\n'))])
+
+    print_(u'\n'.join([u"{a:<30}{b:<30}{c:<20}".format(a=a, b=b, c=c) for
+                       a, b, c in table]))
diff --git a/cobra/flux_analysis/variability.py b/cobra/flux_analysis/variability.py
index e712199..ead05bf 100644
--- a/cobra/flux_analysis/variability.py
+++ b/cobra/flux_analysis/variability.py
@@ -1,7 +1,6 @@
 from warnings import warn
 
-from six import iteritems, string_types
-from ..core.Metabolite import Metabolite
+from six import iteritems
 from ..solvers import solver_dict, get_solver_name
 
 
@@ -34,8 +33,8 @@ def flux_variability_analysis(cobra_model, reaction_list=None,
     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)
+        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:
diff --git a/cobra/io/mat.py b/cobra/io/mat.py
index a7443de..5ee1c76 100644
--- a/cobra/io/mat.py
+++ b/cobra/io/mat.py
@@ -142,7 +142,7 @@ def from_mat_struct(mat_struct, model_id=None):
         c_vec = None
         warn("objective vector 'c' not found")
     model = Model()
-    if "description" in m:
+    if "description" in m.dtype.names:
         model.id = m["description"][0, 0][0]
     elif model_id is not None:
         model.id = model_id
diff --git a/cobra/solvers/cglpk.pyx b/cobra/solvers/cglpk.pyx
index eb1a989..0d4fd3b 100644
--- a/cobra/solvers/cglpk.pyx
+++ b/cobra/solvers/cglpk.pyx
@@ -8,8 +8,11 @@ from cpython.version cimport PY_MAJOR_VERSION
 
 from tempfile import NamedTemporaryFile as _NamedTemporaryFile  # for pickling
 from os import unlink as _unlink
+import sys
+from contextlib import contextmanager as _contextmanager
 from warnings import warn as _warn
 
+from six import StringIO
 try:
     from sympy import Basic, Number
 except:
@@ -31,6 +34,22 @@ 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",
@@ -265,6 +284,8 @@ cdef class GLP:
             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
@@ -296,7 +317,8 @@ cdef class GLP:
         self.parameters.tm_lim = time_limit
         
         if fast_status != 0:
-            glp_adv_basis(glp, 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:
diff --git a/cobra/solvers/coin.py b/cobra/solvers/coin.py
index feda120..d4ca17c 100644
--- a/cobra/solvers/coin.py
+++ b/cobra/solvers/coin.py
@@ -27,7 +27,7 @@ class Coin(CyClpSimplex):
 
 
 def create_problem(cobra_model, objective_sense="maximize", **kwargs):
-    m = cobra_model.to_array_based_model()
+    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):
diff --git a/cobra/test/flux_analysis.py b/cobra/test/flux_analysis.py
index 0ba8cf1..60d6cce 100644
--- a/cobra/test/flux_analysis.py
+++ b/cobra/test/flux_analysis.py
@@ -4,8 +4,10 @@ from warnings import warn
 import sys
 from os.path import join
 from json import load
+from contextlib import contextmanager
+import re
 
-from six import iteritems
+from six import iteritems, StringIO
 
 try:
     import numpy
@@ -15,6 +17,10 @@ try:
     import matplotlib
 except:
     matplotlib = None
+try:
+    import pandas
+except:
+    pandas = None
 
 if __name__ == "__main__":
     sys.path.insert(0, "../..")
@@ -32,6 +38,18 @@ else:
     from ..flux_analysis import *
 
 
+ at contextmanager
+def captured_output():
+    """ A context manager to test the IO summary methods """
+    new_out, new_err = StringIO(), StringIO()
+    old_out, old_err = sys.stdout, sys.stderr
+    try:
+        sys.stdout, sys.stderr = new_out, new_err
+        yield sys.stdout, sys.stderr
+    finally:
+        sys.stdout, sys.stderr = old_out, old_err
+
+
 class TestCobraFluxAnalysis(TestCase):
     """Test the simulation functions in cobra.flux_analysis"""
 
@@ -47,6 +65,48 @@ class TestCobraFluxAnalysis(TestCase):
             self.assertAlmostEqual(model.solution.f, 0.8739, places=3)
             self.assertAlmostEqual(sum(abs_x), 518.4221, places=3)
 
+            # Test desired_objective_value
+            desired_objective = 0.8
+            optimize_minimal_flux(model, solver=solver,
+                                  desired_objective_value=desired_objective)
+            abs_x = [abs(i) for i in model.solution.x]
+            self.assertEqual(model.solution.status, "optimal")
+            self.assertAlmostEqual(model.solution.f, desired_objective,
+                                   places=3)
+            self.assertAlmostEqual(sum(abs_x), 476.1594, places=3)
+
+            # Test fraction_of_optimum
+            optimize_minimal_flux(model, solver=solver,
+                                  fraction_of_optimum=0.95)
+            abs_x = [abs(i) for i in model.solution.x]
+            self.assertEqual(model.solution.status, "optimal")
+            self.assertAlmostEqual(model.solution.f, 0.95*0.8739, places=3)
+            self.assertAlmostEqual(sum(abs_x), 493.4400, places=3)
+
+            # Make sure the model works for non-unity objective values
+            model.reactions.Biomass_Ecoli_core.objective_coefficient = 2
+            optimize_minimal_flux(model, solver=solver)
+            self.assertAlmostEqual(model.solution.f, 2*0.8739, places=3)
+            model.reactions.Biomass_Ecoli_core.objective_coefficient = 1
+
+            # Test some erroneous inputs -- multiple objectives
+            model.reactions.ATPM.objective_coefficient = 1
+            with self.assertRaises(ValueError):
+                optimize_minimal_flux(model, solver=solver)
+            model.reactions.ATPM.objective_coefficient = 0
+
+            # Minimization of objective
+            with self.assertRaises(ValueError):
+                optimize_minimal_flux(model, solver=solver,
+                                      objective_sense='minimize')
+
+            # Infeasible solution
+            atpm = float(model.reactions.ATPM.lower_bound)
+            model.reactions.ATPM.lower_bound = 500
+            with self.assertRaises(ValueError):
+                optimize_minimal_flux(model, solver=solver)
+            model.reactions.ATPM.lower_bound = atpm
+
     def test_single_gene_deletion(self):
         cobra_model = create_test_model("textbook")
         # expected knockouts for textbook model
@@ -225,11 +285,16 @@ class TestCobraFluxAnalysis(TestCase):
         U.add_reaction(r)
         r.build_reaction_from_string("a --> d", verbose=False)
 
+        # GrowMatch
         result = gapfilling.growMatch(m, U)[0]
         self.assertEqual(len(result), 1)
         self.assertEqual(result[0].id, "a2b")
+        # SMILEY
+        result = gapfilling.SMILEY(m, "b", U)[0]
+        self.assertEqual(len(result), 1)
+        self.assertEqual(result[0].id, "a2b")
 
-        # 2 rounds with exchange reactions
+        # 2 rounds of GrowMatch with exchange reactions
         result = gapfilling.growMatch(m, None, ex_rxns=True, iterations=2)
         self.assertEqual(len(result), 2)
         self.assertEqual(len(result[0]), 1)
@@ -250,6 +315,99 @@ class TestCobraFluxAnalysis(TestCase):
             self.skipTest("can't test plots without matplotlib")
         data.plot()
 
+    def check_entries(self, out, desired_entries):
+        """ensure each entry in desired_entries appears in output"""
+        output = out.getvalue().strip()
+        output_set = set((re.sub('\s', '', l) for l in output.splitlines()))
+
+        for item in desired_entries:
+            self.assertIn(re.sub('\s', '', item), output_set)
+
+    @skipIf(pandas is None, "summary methods require pandas")
+    def test_summary_methods(self):
+
+        # Test model summary methods
+        model = create_test_model("textbook")
+        model.optimize()
+        desired_entries = [
+            u'glc__D_e     -9.76 \u00B1 0.24'
+            u'co2_e       21.81 \u00B1 2.86',
+            u'nh4_e        -4.84 \u00B1 0.32'
+            u'h_e         19.51 \u00B1 2.86',
+            u'pi_e         -3.13 \u00B1 0.08'
+            u'for_e        2.86 \u00B1 2.86',
+            u'ac_e         0.95 \u00B1 0.95',
+            u'acald_e      0.64 \u00B1 0.64',
+            u'pyr_e        0.64 \u00B1 0.64',
+            u'etoh_e       0.55 \u00B1 0.55',
+            u'lac__D_e     0.54 \u00B1 0.54',
+            u'succ_e       0.42 \u00B1 0.42',
+            u'akg_e        0.36 \u00B1 0.36',
+            u'glu__L_e     0.32 \u00B1 0.32'
+        ]
+        for solver in solver_dict:
+            with captured_output() as (out, err):
+                model.summary(fva=0.95, solver=solver)
+            self.check_entries(out, desired_entries)
+
+        # test non-fva version (these should be fixed for textbook model
+        desired_entries = [
+            "o2_e       -21.80",
+            "glc__D_e   -10.00",
+            "nh4_e       -4.77",
+            "pi_e        -3.21",
+            "h2o_e    29.18",
+            "co2_e    22.81",
+            "h_e      17.53",
+            "Biomass_Ecoli_core    0.874"
+        ]
+        # Need to use a different method here because
+        # there are multiple entries per line.
+        with captured_output() as (out, err):
+            model.summary()
+        s = out.getvalue()
+        for i in desired_entries:
+            self.assertIn(i, s)
+
+        # Test metabolite summary methods
+        desired_entries = [
+            'PRODUCING REACTIONS -- Ubiquinone-8',
+            '-----------------------------------',
+            '%      FLUX   RXN ID'
+            'REACTION',
+            '100.0%     44    CYTBD'
+            '2.0 h_c + 0.5 o2_c + q8h2_c --> h2o_c + 2.0 h_e +...',
+            'CONSUMING REACTIONS -- Ubiquinone-8',
+            '-----------------------------------',
+            '88.4%    -39   NADH16'
+            '4.0 h_c + nadh_c + q8_c --> 3.0 h_e + nad_c + q8h2_c',
+            '11.6%   -5.1    SUCDi'
+            'q8_c + succ_c --> fum_c + q8h2_c',
+        ]
+        with captured_output() as (out, err):
+            model.metabolites.q8_c.summary()
+        self.check_entries(out, desired_entries)
+
+        desired_entries = [
+            u'PRODUCING REACTIONS -- D-Fructose 1,6-bisphosphate',
+            u'--------------------------------------------------',
+            u'  %            FLUX   RXN ID'
+            u'REACTION',
+            u'100.0%  7.71 \u00B1 1.54      PFK'
+            u'atp_c + f6p_c --> adp_c + fdp_c + h_c',
+            u'CONSUMING REACTIONS -- D-Fructose 1,6-bisphosphate',
+            u'--------------------------------------------------',
+            u'  %            FLUX   RXN ID'
+            u'REACTION',
+            u'100.0%  7.54 \u00B1 1.37      FBA'
+            u'fdp_c <=> dhap_c + g3p_c',
+        ]
+        for solver in solver_dict:
+            with captured_output() as (out, err):
+                model.metabolites.fdp_c.summary(fva=0.99, solver=solver)
+            self.check_entries(out, desired_entries)
+
+
 # make a test suite to run all of the tests
 loader = TestLoader()
 suite = loader.loadTestsFromModule(sys.modules[__name__])
diff --git a/cobra/test/io_tests.py b/cobra/test/io_tests.py
index 05b237e..7125d47 100644
--- a/cobra/test/io_tests.py
+++ b/cobra/test/io_tests.py
@@ -59,6 +59,10 @@ class TestCobraIO(object):
         model2.optimize()
         self.assertAlmostEqual(model1.solution.f, model2.solution.f,
                                places=3)
+        # ensure the references are correct
+        self.assertIs(model2.metabolites[0]._model, model2)
+        self.assertIs(model2.reactions[0]._model, model2)
+        self.assertIs(model2.genes[0]._model, model2)
         self.extra_comparisons(model1, model2)
 
     def extra_comparisons(self, model1, model2):
diff --git a/cobra/test/unit_tests.py b/cobra/test/unit_tests.py
index eb984e0..7610754 100644
--- a/cobra/test/unit_tests.py
+++ b/cobra/test/unit_tests.py
@@ -368,6 +368,75 @@ class TestReactions(CobraTestCase):
         self.assertIn(model.metabolites.foo, pgi._metabolites)
         self.assertEqual(len(model.metabolites), m + 1)
 
+    def test_copy(self):
+        model = self.model
+        PGI = model.reactions.PGI
+        copied = PGI.copy()
+        self.assertIsNot(PGI, copied)
+        self.assertIs(PGI._model, model)
+        self.assertIsNot(copied._model, model)
+        # the copy should refer to different metabolites and genes
+        for met in copied.metabolites:
+            self.assertIsNot(met, model.metabolites.get_by_id(met.id))
+            self.assertIsNot(met.model, model)
+        for gene in copied.genes:
+            self.assertIsNot(gene, model.genes.get_by_id(gene.id))
+            self.assertIsNot(gene.model, model)
+
+    def test_iadd(self):
+        model = self.model
+        PGI = model.reactions.PGI
+        EX_h2o = model.reactions.EX_h2o_e
+        original_PGI_gpr = PGI.gene_reaction_rule
+        PGI += EX_h2o
+        self.assertEqual(PGI.gene_reaction_rule, original_PGI_gpr)
+        self.assertEqual(PGI.metabolites[model.metabolites.h2o_e], -1.0)
+        # original should not have changed
+        self.assertEqual(EX_h2o.gene_reaction_rule, '')
+        self.assertEqual(EX_h2o.metabolites[model.metabolites.h2o_e], -1.0)
+        # what about adding a reaction not in the model
+        new_reaction = Reaction("test")
+        new_reaction.add_metabolites({Metabolite("A"): -1, Metabolite("B"): 1})
+        PGI += new_reaction
+        self.assertEqual(PGI.gene_reaction_rule, original_PGI_gpr)
+        self.assertEqual(len(PGI.gene_reaction_rule), 5)
+        # and vice versa
+        new_reaction += PGI
+        self.assertEqual(len(new_reaction.metabolites), 5)  # not 7
+        self.assertEqual(len(new_reaction.genes), 1)
+        self.assertEqual(new_reaction.gene_reaction_rule, original_PGI_gpr)
+        # what about combining 2 gpr's
+        model.reactions.ACKr += model.reactions.ACONTa
+        self.assertEqual(model.reactions.ACKr.gene_reaction_rule,
+                         "(b2296 or b3115 or b1849) and (b0118 or b1276)")
+        self.assertEqual(len(model.reactions.ACKr.genes), 5)
+
+    def test_add(self):
+        # not in place addition should work on a copy
+        model = self.model
+        new = model.reactions.PGI + model.reactions.EX_h2o_e
+        self.assertIsNot(new._model, model)
+        self.assertEqual(len(new.metabolites), 3)
+        # the copy should refer to different metabolites and genes
+        # This currently fails because add_metabolites does not copy.
+        # Should that be changed?
+        # for met in new.metabolites:
+        #    self.assertIsNot(met, model.metabolites.get_by_id(met.id))
+        #    self.assertIsNot(met.model, model)
+        for gene in new.genes:
+            self.assertIsNot(gene, model.genes.get_by_id(gene.id))
+            self.assertIsNot(gene.model, model)
+
+    def test_mul(self):
+        new = self.model.reactions.PGI * 2
+        self.assertEqual(set(new.metabolites.values()), {-2, 2})
+
+    def test_sub(self):
+        model = self.model
+        new = model.reactions.PGI - model.reactions.EX_h2o_e
+        self.assertIsNot(new._model, model)
+        self.assertEqual(len(new.metabolites), 3)
+
 
 class TestCobraMetabolites(CobraTestCase):
     def test_metabolite_formula(self):
@@ -376,6 +445,17 @@ class TestCobraMetabolites(CobraTestCase):
         self.assertEqual(met.elements, {"H": 2, "O": 1})
         self.assertEqual(met.formula_weight, 18.01528)
 
+    def test_foruma_element_setting(self):
+        model = self.model
+        met = model.metabolites[1]
+        orig_formula = str(met.formula)
+        orig_elements = dict(met.elements)
+
+        met.formula = ''
+        self.assertEqual(met.elements, {})
+        met.elements = orig_elements
+        self.assertEqual(met.formula, orig_formula)
+
 
 class TestCobraModel(CobraTestCase):
     """test core cobra functions"""
@@ -428,6 +508,12 @@ class TestCobraModel(CobraTestCase):
             i.id += "_other"
         other.repair()
         model.add_reactions(other.reactions)
+        # what if the other reaction has an error in its GPR
+        m1 = create_test_model("textbook")
+        m2 = create_test_model("textbook")
+        m1.reactions.PGI.remove_from_model()
+        m2.genes.b4025._reaction.clear()
+        m1.add_reaction(m2.reactions.PGI)
 
     def test_model_remove_reaction(self):
         old_reaction_count = len(self.model.reactions)
diff --git a/cobra/topology/__init__.py b/cobra/topology/__init__.py
index 6446300..1bb2a5e 100644
--- a/cobra/topology/__init__.py
+++ b/cobra/topology/__init__.py
@@ -2,7 +2,7 @@ from os import name as __name
 from sys import modules as __modules
 from warnings import warn
 if __name == 'java':
-    warn("%s is not yet supported on jython"%__modules[__name__])
+    warn("%s is not yet supported on jython" % __modules[__name__])
 
 else:
     from .reporter_metabolites import *
diff --git a/cobra/topology/reporter_metabolites.py b/cobra/topology/reporter_metabolites.py
index 84bd6f2..3b9ec74 100644
--- a/cobra/topology/reporter_metabolites.py
+++ b/cobra/topology/reporter_metabolites.py
@@ -1,17 +1,16 @@
-#cobra.topology.reporter_metabolites.py: Module for topological analysis of cobra_models
-#Based on Patil et al 2005 PNAS 102:2685-9
-#TODO: Validate cobra.core compliance
+# Based on Patil et al 2005 PNAS 102:2685-9
+# TODO: Validate cobra.core compliance
 from __future__ import print_function
-from copy import deepcopy
-from numpy import array, corrcoef, mean, std, tril, where, unique, zeros
+from numpy import array, mean, std, where
 from scipy.stats import norm, randint
-from collections import defaultdict
 from six import iteritems
 
+
 def identify_reporter_metabolites(cobra_model, reaction_scores_dict,
-                                  number_of_randomizations=1000, number_of_layers=1,
+                                  number_of_randomizations=1000,
                                   scoring_metric='default', score_type='p',
-                                  entire_network=False, background_correction=True,
+                                  entire_network=False,
+                                  background_correction=True,
                                   ignore_external_boundary_reactions=False):
     """Calculate the aggregate Z-score for the metabolites in the model.
     Ignore reactions that are solely spontaneous or orphan. Allow the scores to
@@ -22,39 +21,36 @@ def identify_reporter_metabolites(cobra_model, reaction_scores_dict,
 
     TODO: CHANGE TO USING DICTIONARIES for the_reactions: the_scores
 
-    reaction_scores_dict:  A dictionary where the keys are reactions in cobra_model.reactions
-    and the values are the scores.  Currently, only supports a single numeric value as
-    the value; however, this will be updated to allow for lists
+    reaction_scores_dict:  A dictionary where the keys are reactions in
+    cobra_model.reactions and the values are the scores.  Currently, only
+    supports a single numeric value as the value; however, this will be updated
+    to allow for lists
 
     number_of_randomizations: Integer.  Number of random shuffles of the
     scores to assess which are significant.
 
-    number_of_layers: 1 is the only option supported
-    
-    scoring_metric:  default means divide by k**0.5
+    scoring_metric: default means divide by k**0.5
 
     score_type: 'p' Is the only option at the moment and indicates p-value.
 
-    entire_network: Boolean.  Currently, only compares scores calculated from the_reactions
-
-    background_correction: Boolean.  If True apply background correction to the aggreagate
-    Z-score
+    entire_network: Boolean. Currently, only compares scores calculated from
+    the_reactions
 
-    ignore_external_boundary_reactions: Not yet implemented. Boolean.  If True do not count exchange reactions when
-    calculating the score.
+    background_correction: Boolean.  If True apply background correction to the
+    aggreagate Z-score
 
-    
-
-    
+    ignore_external_boundary_reactions: Not yet implemented. Boolean.  If True
+    do not count exchange reactions when calculating the score.
     """
-    #Add in a function to calculate based on correlation coefficients and to
-    #deal with other multidimensional data. 
+
+    # Add in a function to calculate based on correlation coefficients and to
+    # deal with other multidimensional data.
     the_reactions = reaction_scores_dict.keys()
     the_scores = reaction_scores_dict.values()
     if score_type == 'p' and not hasattr(the_scores[0], '__iter__'):
-        #minimum and maximum p-values are used to prevent numerical problems.
-        #haven't decided whether an arbitrary min / max 1e-15 is preferred to
-        #blunting the ends based on the values closest to 0 or 1.
+        # minimum and maximum p-values are used to prevent numerical problems.
+        # haven't decided whether an arbitrary min / max 1e-15 is preferred to
+        # blunting the ends based on the values closest to 0 or 1.
         the_reactions = reaction_scores_dict.keys()
         the_scores = array(reaction_scores_dict.values())
         minimum_p = min(the_scores[the_scores.nonzero()[0]])
@@ -62,23 +58,23 @@ def identify_reporter_metabolites(cobra_model, reaction_scores_dict,
         the_scores[where(the_scores < minimum_p)] = minimum_p
         the_scores[where(the_scores > maximum_p)] = maximum_p
         the_scores = -norm.ppf(the_scores)
-        #update the dictionary with the new scores
+        # update the dictionary with the new scores
         reaction_scores_dict = dict(zip(the_reactions, the_scores))
     elif hasattr(the_scores[0], '__iter__'):
-        #In the case that the_scores is a list of lists, assume that each list is
-        #the score for each reaction in the_reactions across all reactions.  Then
-        #for each metabolite, calculate the invnorm(|Pearson Correlation
-        #Coefficient| for each reaction pair that it links.
+        # In the case that the_scores is a list of lists, assume that each list
+        # is the score for each reaction in the_reactions across all reactions.
+        # Then for each metabolite, calculate the invnorm(|Pearson Correlation
+        # Coefficient| for each reaction pair that it links.
         raise Exception("This isn't implemented yet")
-    
-    #Get the connectivity for each metabolite
+
+    # Get the connectivity for each metabolite
     the_metabolites = set()
-    [the_metabolites.update(x._metabolites)
-     for x in reaction_scores_dict];
+    for x in reaction_scores_dict:
+        the_metabolites.update(x._metabolites)
 
     metabolite_scores = {}
     metabolite_connections = {}
-    #Calculate the score for each metabolite
+    # Calculate the score for each metabolite
     for the_metabolite in the_metabolites:
         nonspontaneous_connections = [x for x in the_metabolite._reaction
                                       if x.gene_reaction_rule.lower() not in
@@ -95,44 +91,46 @@ def identify_reporter_metabolites(cobra_model, reaction_scores_dict,
         metabolite_scores[the_metabolite] = tmp_score
         metabolite_connections[the_metabolite] = number_of_connections
 
-    #NOTE: Doing the corrections based only on the significantly perturbed scores
-    #is probably going to underestimate the significance.
+    # NOTE: Doing the corrections based only on the significantly perturbed
+    # scores is probably going to underestimate the significance.
     if background_correction:
         correction_dict = {}
         for i in set(metabolite_connections.values()):
-            #if entire_network # add in a section to deal with the situation where
-            #the entire network structure is considered by only have p-values for
-            #a limited subset.
+            # if entire_network # add in a section to deal with the situation
+            # where the entire network structure is considered by only have
+            # p-values for a limited subset.
             #
-            #Basically, what we're doing here is that for each i we select i
-            #scores number_of_randomizations times
-            the_random_indices = randint.rvs(0,len(the_scores), size=(number_of_randomizations, i))
-            random_score_distribution = array([sum(the_scores[x]) for x in list(the_random_indices)]) /i**0.5
+            # Basically, what we're doing here is that for each i we select i
+            # scores number_of_randomizations times
+            the_random_indices = randint.rvs(
+                0, len(the_scores), size=(number_of_randomizations, i))
+            random_score_distribution = array(
+                [sum(the_scores[x])
+                 for x in list(the_random_indices)]) / i**0.5
             correction_dict[i] = [mean(random_score_distribution),
-                                      std(random_score_distribution,ddof=1)] 
+                                  std(random_score_distribution, ddof=1)]
 
     for the_metabolite, the_score in iteritems(metabolite_scores):
         number_of_connections = metabolite_connections[the_metabolite]
         if number_of_connections > 0:
-            #Correct based on background distribution
+            # Correct based on background distribution
             if background_correction:
-                #if the list of scores is only for significant perturbations then the
-                #background correction shouldn't be applied because the current sampling
-                #method only takes into account the_scores not the entire network.
-                #It'd be more accurate to assign unscored reactions a default score.
+                # if the list of scores is only for significant perturbations
+                # then the background correction shouldn't be applied because
+                # the current sampling method only takes into account
+                # the_scores not the entire network.  It'd be more accurate to
+                # assign unscored reactions a default score.
                 the_score = ((the_score / number_of_connections**.5) -
                              correction_dict[number_of_connections][0]) / \
-                             correction_dict[number_of_connections][1]
+                    correction_dict[number_of_connections][1]
             else:
                 the_score = the_score / number_of_connections**.5
-            #Update the score
+            # Update the score
             metabolite_scores[the_metabolite] = the_score
 
-
-
     return_dictionary = {'scores': metabolite_scores,
                          'connections': metabolite_connections}
     if background_correction:
         return_dictionary['corrections'] = correction_dict
 
-    return(return_dictionary)
+    return return_dictionary
diff --git a/documentation_builder/autodoc.sh b/documentation_builder/autodoc.sh
index e2474e0..36a46ff 100755
--- a/documentation_builder/autodoc.sh
+++ b/documentation_builder/autodoc.sh
@@ -3,5 +3,3 @@ sphinx-apidoc -o . ../cobra ../cobra/oven ../cobra/external \
     ../cobra/test ../cobra/solvers/ ../cobra/test_all.py \
     ../cobra/version.py ../cobra/solvers/legacy.py
 rm modules.rst
-
-ipython nbconvert --to=rst *.ipynb
diff --git a/documentation_builder/building_model.ipynb b/documentation_builder/building_model.ipynb
index 9ff3d19..7068791 100644
--- a/documentation_builder/building_model.ipynb
+++ b/documentation_builder/building_model.ipynb
@@ -99,7 +99,7 @@
     {
      "data": {
       "text/plain": [
-       "'malACP_c + h_c + ddcaACP_c --> co2_c + 3omrsACP_c + ACP_c'"
+       "'ddcaACP_c + h_c + malACP_c --> 3omrsACP_c + ACP_c + co2_c'"
       ]
      },
      "execution_count": 3,
@@ -136,7 +136,7 @@
     {
      "data": {
       "text/plain": [
-       "frozenset({<Gene STM2378 at 0x7ff664f45490>, <Gene STM1197 at 0x7ff664f45950>})"
+       "frozenset({<Gene STM2378 at 0x7fada4592908>, <Gene STM1197 at 0x7fada45927f0>})"
       ]
      },
      "execution_count": 4,
@@ -232,16 +232,16 @@
      "text": [
       "Reactions\n",
       "---------\n",
-      "3OAS140 : malACP_c + h_c + ddcaACP_c --> co2_c + 3omrsACP_c + ACP_c\n",
+      "3OAS140 : ddcaACP_c + h_c + malACP_c --> 3omrsACP_c + ACP_c + co2_c\n",
       "\n",
       "Metabolites\n",
       "-----------\n",
+      "3omrsACP_c : C25H45N2O9PRS\n",
+      "ddcaACP_c : C23H43N2O8PRS\n",
+      "    ACP_c : C11H21N2O7PRS\n",
       "    co2_c : CO2\n",
       " malACP_c : C14H22N2O10PRS\n",
       "      h_c : H\n",
-      "ddcaACP_c : C23H43N2O8PRS\n",
-      "3omrsACP_c : C25H45N2O9PRS\n",
-      "    ACP_c : C11H21N2O7PRS\n",
       "\n",
       "Genes\n",
       "-----\n",
@@ -275,21 +275,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/building_model.rst b/documentation_builder/building_model.rst
deleted file mode 100644
index 8a654d0..0000000
--- a/documentation_builder/building_model.rst
+++ /dev/null
@@ -1,179 +0,0 @@
-
-Building a Model
-================
-
-This simple example demonstrates how to create a model, create a
-reaction, and then add the reaction to the model.
-
-We'll use the '3OAS140' reaction from the STM\_1.0 model:
-
-1.0 malACP[c] + 1.0 h[c] + 1.0 ddcaACP[c] :math:`\rightarrow` 1.0 co2[c]
-+ 1.0 ACP[c] + 1.0 3omrsACP[c]
-
-First, create the model and reaction.
-
-.. code:: python
-
-    from cobra import Model, Reaction, Metabolite
-    # Best practise: SBML compliant IDs
-    cobra_model = Model('example_cobra_model')
-    
-    reaction = Reaction('3OAS140')
-    reaction.name = '3 oxoacyl acyl carrier protein synthase n C140 '
-    reaction.subsystem = 'Cell Envelope Biosynthesis'
-    reaction.lower_bound = 0.  # This is the default
-    reaction.upper_bound = 1000.  # This is the default
-    reaction.objective_coefficient = 0. # this is the default
-
-We need to create metabolites as well. If we were using an existing
-model, we could use get\_by\_id to get the apporpriate Metabolite
-objects instead.
-
-.. code:: python
-
-    ACP_c = Metabolite('ACP_c',
-                       formula='C11H21N2O7PRS',
-                       name='acyl-carrier-protein',
-                       compartment='c')
-    omrsACP_c = Metabolite('3omrsACP_c',
-                           formula='C25H45N2O9PRS',
-                           name='3-Oxotetradecanoyl-acyl-carrier-protein',
-                           compartment='c')
-    co2_c = Metabolite('co2_c',
-                       formula='CO2',
-                       name='CO2',
-                       compartment='c')
-    malACP_c = Metabolite('malACP_c',
-                          formula='C14H22N2O10PRS',
-                          name='Malonyl-acyl-carrier-protein',
-                          compartment='c')
-    h_c = Metabolite('h_c',
-                     formula='H',
-                     name='H',
-                     compartment='c')
-    ddcaACP_c = Metabolite('ddcaACP_c',
-                           formula='C23H43N2O8PRS',
-                           name='Dodecanoyl-ACP-n-C120ACP',
-                           compartment='c')
-
-Adding metabolites to a reaction requires using a dictionary of the
-metabolites and their stoichiometric coefficients. A group of
-metabolites can be added all at once, or they can be added one at a
-time.
-
-.. code:: python
-
-    reaction.add_metabolites({malACP_c: -1.0,
-                              h_c: -1.0,
-                              ddcaACP_c: -1.0,
-                              co2_c: 1.0,
-                              ACP_c: 1.0,
-                              omrsACP_c: 1.0})
-    
-    
-    reaction.reaction  # This gives a string representation of the reaction
-
-
-
-
-.. parsed-literal::
-
-    'malACP_c + h_c + ddcaACP_c --> 3omrsACP_c + ACP_c + co2_c'
-
-
-
-The gene\_reaction\_rule is a boolean representation of the gene
-requirements for this reaction to be active as described in
-`Schellenberger et al 2011 Nature Protocols
-6(9):1290-307 <http://dx.doi.org/doi:10.1038/nprot.2011.308>`__. We will
-assign the gene reaction rule string, which will automatically create
-the corresponding gene objects.
-
-.. code:: python
-
-    reaction.gene_reaction_rule = '( STM2378 or STM1197 )'
-    reaction.genes
-
-
-
-
-.. parsed-literal::
-
-    frozenset({<Gene STM1197 at 0x7feea0ae9850>, <Gene STM2378 at 0x7feea0ae9b10>})
-
-
-
-At this point in time, the model is still empty
-
-.. code:: python
-
-    print('%i reactions in initial model' % len(cobra_model.reactions))
-    print('%i metabolites in initial model' % len(cobra_model.metabolites))
-    print('%i genes in initial model' % len(cobra_model.genes))
-
-
-.. parsed-literal::
-
-    0 reactions in initial model
-    0 metabolites in initial model
-    0 genes in initial model
-
-
-We will add the reaction to the model, which will also add all
-associated metabolites and genes
-
-.. code:: python
-
-    cobra_model.add_reaction(reaction)
-    
-    # Now there are things in the model
-    print('%i reaction in model' % len(cobra_model.reactions))
-    print('%i metabolites in model' % len(cobra_model.metabolites))
-    print('%i genes in model' % len(cobra_model.genes))
-
-
-.. parsed-literal::
-
-    1 reaction in model
-    6 metabolites in model
-    2 genes in model
-
-
-We can iterate through the model objects to observe the contents
-
-.. code:: python
-
-    # Iterate through the the objects in the model
-    print("Reactions")
-    print("---------")
-    for x in cobra_model.reactions:
-        print("%s : %s" % (x.id, x.reaction))
-    print("Metabolites")
-    print("-----------")
-    for x in cobra_model.metabolites:
-        print('%s : %s' % (x.id, x.formula))
-    print("Genes")
-    print("-----")
-    for x in cobra_model.genes:
-        reactions_list_str = "{" + ", ".join((i.id for i in x.reactions)) + "}"
-        print("%s is associated with reactions: %s" % (x.id, reactions_list_str))
-
-
-.. parsed-literal::
-
-    Reactions
-    ---------
-    3OAS140 : malACP_c + h_c + ddcaACP_c --> 3omrsACP_c + ACP_c + co2_c
-    Metabolites
-    -----------
-    3omrsACP_c : C25H45N2O9PRS
-    ACP_c : C11H21N2O7PRS
-    co2_c : CO2
-    malACP_c : C14H22N2O10PRS
-    h_c : H
-    ddcaACP_c : C23H43N2O8PRS
-    Genes
-    -----
-    STM2378 is associated with reactions: {3OAS140}
-    STM1197 is associated with reactions: {3OAS140}
-
diff --git a/documentation_builder/cobra.design.rst b/documentation_builder/cobra.design.rst
new file mode 100644
index 0000000..2b12889
--- /dev/null
+++ b/documentation_builder/cobra.design.rst
@@ -0,0 +1,22 @@
+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
index 430bc07..4763c36 100644
--- a/documentation_builder/cobra.flux_analysis.rst
+++ b/documentation_builder/cobra.flux_analysis.rst
@@ -84,6 +84,14 @@ cobra.flux_analysis.single_deletion module
     :undoc-members:
     :show-inheritance:
 
+cobra.flux_analysis.summary module
+----------------------------------
+
+.. automodule:: cobra.flux_analysis.summary
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
 cobra.flux_analysis.variability module
 --------------------------------------
 
diff --git a/documentation_builder/cobra.manipulation.rst b/documentation_builder/cobra.manipulation.rst
index 2d2c9cb..c97bfbf 100644
--- a/documentation_builder/cobra.manipulation.rst
+++ b/documentation_builder/cobra.manipulation.rst
@@ -4,6 +4,14 @@ cobra.manipulation package
 Submodules
 ----------
 
+cobra.manipulation.annotate module
+----------------------------------
+
+.. automodule:: cobra.manipulation.annotate
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
 cobra.manipulation.delete module
 --------------------------------
 
@@ -20,6 +28,14 @@ cobra.manipulation.modify module
     :undoc-members:
     :show-inheritance:
 
+cobra.manipulation.validate module
+----------------------------------
+
+.. automodule:: cobra.manipulation.validate
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
 
 Module contents
 ---------------
diff --git a/documentation_builder/cobra.rst b/documentation_builder/cobra.rst
index e5e9693..494b311 100644
--- a/documentation_builder/cobra.rst
+++ b/documentation_builder/cobra.rst
@@ -7,6 +7,7 @@ Subpackages
 .. toctree::
 
     cobra.core
+    cobra.design
     cobra.flux_analysis
     cobra.io
     cobra.manipulation
diff --git a/documentation_builder/conf.py b/documentation_builder/conf.py
index cb7304f..ae009a1 100644
--- a/documentation_builder/conf.py
+++ b/documentation_builder/conf.py
@@ -17,9 +17,9 @@ import os
 # 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('.'))
 sys.path.insert(0, os.path.abspath('..'))
 
+
 # In order to build documentation that requires libraries to import
 class Mock(object):
     def __init__(self, *args, **kwargs):
@@ -37,36 +37,24 @@ class Mock(object):
 
 MOCK_MODULES = ['numpy', 'scipy', 'scipy.sparse', 'scipy.io', 'scipy.stats',
                 'glpk', 'gurobipy', 'gurobipy.GRB', 'cplex', 'pp', 'libsbml',
-                'cplex.exceptions']
+                'cplex.exceptions', 'pandas']
 for mod_name in MOCK_MODULES:
     sys.modules[mod_name] = Mock()
 
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# -- General configuration ----------------------------------------------------
 
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+# 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']
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
+              'sphinx.ext.napoleon', 'sphinx.ext.mathjax', 'nbsphinx']
 
 # The master toctree document.
 master_doc = 'index'
 
 # General information about the project.
 project = u'cobra'
-copyright = u'2015, Daniel Robert Hyduke and Ali Ebrahim'
+copyright = u'2016, Daniel Robert Hyduke and Ali Ebrahim'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -78,123 +66,17 @@ version = read_release_version()
 # The full version, including alpha/beta/rc tags.
 release = get_version()
 
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
-exclude_patterns = ['_build', 'version.py']
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+exclude_patterns = ['_build', 'version.py', '.ipynb_checkpoints']
 
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
 pygments_style = 'sphinx'
 
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
 
-# -- Options for HTML output ---------------------------------------------------
+# -- Options for HTML output --------------------------------------------------
 
 mathjax_path = 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
 
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-#html_theme = 'alabaster'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-#html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it.  The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'cobradoc'
-
-
 # -- Options for LaTeX output --------------------------------------------------
 
 latex_elements = {
@@ -215,27 +97,6 @@ latex_documents = [
    u'Daniel Robert Hyduke and Ali Ebrahim', 'manual'),
 ]
 
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
 # -- Options for manual page output --------------------------------------------
 
 # One entry per manual page. List of tuples
@@ -245,10 +106,6 @@ man_pages = [
      [u'Daniel Robert Hyduke and Ali Ebrahim'], 1)
 ]
 
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-
 # -- Options for Texinfo output ------------------------------------------------
 
 # Grouping the document tree into Texinfo files. List of tuples
@@ -261,16 +118,6 @@ texinfo_documents = [
    'Miscellaneous'),
 ]
 
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
-
-
 # Example configuration for intersphinx: refer to the Python standard library.
 intersphinx_mapping = {"http://docs.python.org/": None,
                        "http://docs.scipy.org/doc/numpy/": None,
diff --git a/documentation_builder/deletions.ipynb b/documentation_builder/deletions.ipynb
index bcf1e4e..f426cf0 100644
--- a/documentation_builder/deletions.ipynb
+++ b/documentation_builder/deletions.ipynb
@@ -248,102 +248,102 @@
        "  <tbody>\n",
        "    <tr>\n",
        "      <th>ACALD</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACALDt</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACKr</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACONTa</th>\n",
-       "      <td>-3.963237e-27</td>\n",
+       "      <td>0.0000</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACONTb</th>\n",
-       "      <td>6.162976e-33</td>\n",
+       "      <td>0.0000</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACt2r</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ADK1</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>AKGDH</th>\n",
-       "      <td>8.583074e-01</td>\n",
+       "      <td>0.8583</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>AKGt2r</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ALCD2x</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ATPM</th>\n",
-       "      <td>9.166475e-01</td>\n",
+       "      <td>0.9166</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ATPS4r</th>\n",
-       "      <td>3.742299e-01</td>\n",
+       "      <td>0.3742</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>Biomass_Ecoli_core</th>\n",
-       "      <td>0.000000e+00</td>\n",
+       "      <td>0.0000</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>CO2t</th>\n",
-       "      <td>4.616696e-01</td>\n",
+       "      <td>0.4617</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>CS</th>\n",
-       "      <td>-5.916457e-30</td>\n",
+       "      <td>-0.0000</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>CYTBD</th>\n",
-       "      <td>2.116629e-01</td>\n",
+       "      <td>0.2117</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>D_LACt2</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ENO</th>\n",
-       "      <td>-3.266892e-18</td>\n",
+       "      <td>-0.0000</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ETOHt2r</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>EX_ac_e</th>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.8739</td>\n",
        "      <td>optimal</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
@@ -352,26 +352,26 @@
       ],
       "text/plain": [
        "                    growth_rates   status\n",
-       "ACALD               8.739215e-01  optimal\n",
-       "ACALDt              8.739215e-01  optimal\n",
-       "ACKr                8.739215e-01  optimal\n",
-       "ACONTa             -3.963237e-27  optimal\n",
-       "ACONTb              6.162976e-33  optimal\n",
-       "ACt2r               8.739215e-01  optimal\n",
-       "ADK1                8.739215e-01  optimal\n",
-       "AKGDH               8.583074e-01  optimal\n",
-       "AKGt2r              8.739215e-01  optimal\n",
-       "ALCD2x              8.739215e-01  optimal\n",
-       "ATPM                9.166475e-01  optimal\n",
-       "ATPS4r              3.742299e-01  optimal\n",
-       "Biomass_Ecoli_core  0.000000e+00  optimal\n",
-       "CO2t                4.616696e-01  optimal\n",
-       "CS                 -5.916457e-30  optimal\n",
-       "CYTBD               2.116629e-01  optimal\n",
-       "D_LACt2             8.739215e-01  optimal\n",
-       "ENO                -3.266892e-18  optimal\n",
-       "ETOHt2r             8.739215e-01  optimal\n",
-       "EX_ac_e             8.739215e-01  optimal"
+       "ACALD                     0.8739  optimal\n",
+       "ACALDt                    0.8739  optimal\n",
+       "ACKr                      0.8739  optimal\n",
+       "ACONTa                    0.0000  optimal\n",
+       "ACONTb                    0.0000  optimal\n",
+       "ACt2r                     0.8739  optimal\n",
+       "ADK1                      0.8739  optimal\n",
+       "AKGDH                     0.8583  optimal\n",
+       "AKGt2r                    0.8739  optimal\n",
+       "ALCD2x                    0.8739  optimal\n",
+       "ATPM                      0.9166  optimal\n",
+       "ATPS4r                    0.3742  optimal\n",
+       "Biomass_Ecoli_core        0.0000  optimal\n",
+       "CO2t                      0.4617  optimal\n",
+       "CS                       -0.0000  optimal\n",
+       "CYTBD                     0.2117  optimal\n",
+       "D_LACt2                   0.8739  optimal\n",
+       "ENO                      -0.0000  optimal\n",
+       "ETOHt2r                   0.8739  optimal\n",
+       "EX_ac_e                   0.8739  optimal"
       ]
      },
      "execution_count": 4,
@@ -383,7 +383,7 @@
     "gr, st = single_reaction_deletion(cobra_model,\n",
     "                                  cobra_model.reactions[:20])\n",
     "pandas.DataFrame.from_dict({\"growth_rates\": gr,\n",
-    "                            \"status\": st})"
+    "                            \"status\": st}).round(4)"
    ]
   },
   {
@@ -420,55 +420,55 @@
        "  <tbody>\n",
        "    <tr>\n",
        "      <th>b2464</th>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.864759</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.704037</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8648</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.704</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>b0008</th>\n",
-       "      <td>0.864759</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.704037</td>\n",
+       "      <td>0.8648</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.704</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>b2935</th>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>0.704037</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.0000</td>\n",
+       "      <td>0.704</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>b2465</th>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.704037</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.0000</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.704</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>b3919</th>\n",
-       "      <td>0.704037</td>\n",
-       "      <td>0.704037</td>\n",
-       "      <td>0.704037</td>\n",
-       "      <td>0.704037</td>\n",
-       "      <td>0.704037</td>\n",
+       "      <td>0.7040</td>\n",
+       "      <td>0.7040</td>\n",
+       "      <td>0.7040</td>\n",
+       "      <td>0.7040</td>\n",
+       "      <td>0.704</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
        "</table>\n",
        "</div>"
       ],
       "text/plain": [
-       "          b2464     b0008     b2935     b2465     b3919\n",
-       "b2464  0.873922  0.864759  0.873922  0.873922  0.704037\n",
-       "b0008  0.864759  0.873922  0.873922  0.873922  0.704037\n",
-       "b2935  0.873922  0.873922  0.873922  0.000000  0.704037\n",
-       "b2465  0.873922  0.873922  0.000000  0.873922  0.704037\n",
-       "b3919  0.704037  0.704037  0.704037  0.704037  0.704037"
+       "        b2464   b0008   b2935   b2465  b3919\n",
+       "b2464  0.8739  0.8648  0.8739  0.8739  0.704\n",
+       "b0008  0.8648  0.8739  0.8739  0.8739  0.704\n",
+       "b2935  0.8739  0.8739  0.8739  0.0000  0.704\n",
+       "b2465  0.8739  0.8739  0.0000  0.8739  0.704\n",
+       "b3919  0.7040  0.7040  0.7040  0.7040  0.704"
       ]
      },
      "execution_count": 5,
@@ -478,7 +478,7 @@
    ],
    "source": [
     "double_gene_deletion(cobra_model, cobra_model.genes[-5:],\n",
-    "                     return_frame=True)"
+    "                     return_frame=True).round(4)"
    ]
   },
   {
@@ -499,22 +499,22 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Double gene deletions for 200 genes completed in 11.13 sec with 2 cores\n",
-      "Double gene deletions for 200 genes completed in 14.14 sec with 1 core\n",
-      "Speedup of 1.27x\n"
+      "Double gene deletions for 200 genes completed in 27.03 sec with 2 cores\n",
+      "Double gene deletions for 200 genes completed in 40.73 sec with 1 core\n",
+      "Speedup of 1.51x\n"
      ]
     }
    ],
    "source": [
     "start = time()  # start timer()\n",
-    "double_gene_deletion(ecoli_model, ecoli_model.genes[:200],\n",
+    "double_gene_deletion(ecoli_model, ecoli_model.genes[:300],\n",
     "                     number_of_processes=2)\n",
     "t1 = time() - start\n",
     "print(\"Double gene deletions for 200 genes completed in \"\n",
     "      \"%.2f sec with 2 cores\" % t1)\n",
     "\n",
     "start = time()  # start timer()\n",
-    "double_gene_deletion(ecoli_model, ecoli_model.genes[:200],\n",
+    "double_gene_deletion(ecoli_model, ecoli_model.genes[:300],\n",
     "                     number_of_processes=1)\n",
     "t2 = time() - start\n",
     "print(\"Double gene deletions for 200 genes completed in \"\n",
@@ -555,55 +555,55 @@
        "  <tbody>\n",
        "    <tr>\n",
        "      <th>ACKr</th>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.873922</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8739</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACONTa</th>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>0.000000</td>\n",
+       "      <td>0.0000</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.0000</td>\n",
+       "      <td>0.0000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACONTb</th>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0.000000</td>\n",
-       "      <td>0.000000</td>\n",
+       "      <td>0.0000</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.0000</td>\n",
+       "      <td>0.0000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACt2r</th>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.873922</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8739</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ADK1</th>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0</td>\n",
-       "      <td>0.873922</td>\n",
-       "      <td>0.873922</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.0</td>\n",
+       "      <td>0.8739</td>\n",
+       "      <td>0.8739</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
        "</table>\n",
        "</div>"
       ],
       "text/plain": [
-       "            ACKr  ACONTa  ACONTb     ACt2r      ADK1\n",
-       "ACKr    0.873922       0       0  0.873922  0.873922\n",
-       "ACONTa  0.000000       0       0  0.000000  0.000000\n",
-       "ACONTb  0.000000       0       0  0.000000  0.000000\n",
-       "ACt2r   0.873922       0       0  0.873922  0.873922\n",
-       "ADK1    0.873922       0       0  0.873922  0.873922"
+       "          ACKr  ACONTa  ACONTb   ACt2r    ADK1\n",
+       "ACKr    0.8739     0.0     0.0  0.8739  0.8739\n",
+       "ACONTa  0.0000     0.0     0.0  0.0000  0.0000\n",
+       "ACONTb  0.0000     0.0     0.0  0.0000  0.0000\n",
+       "ACt2r   0.8739     0.0     0.0  0.8739  0.8739\n",
+       "ADK1    0.8739     0.0     0.0  0.8739  0.8739"
       ]
      },
      "execution_count": 7,
@@ -614,27 +614,27 @@
    "source": [
     "double_reaction_deletion(cobra_model,\n",
     "                         cobra_model.reactions[2:7],\n",
-    "                         return_frame=True)"
+    "                         return_frame=True).round(4)"
    ]
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/deletions.rst b/documentation_builder/deletions.rst
deleted file mode 100644
index 5c39e20..0000000
--- a/documentation_builder/deletions.rst
+++ /dev/null
@@ -1,640 +0,0 @@
-
-Simulating Deletions
-====================
-
-.. code:: python
-
-    import pandas
-    from time import time
-    
-    import cobra.test
-    
-    cobra_model = cobra.test.create_test_model("textbook")
-    ecoli_model = cobra.test.create_test_model("ecoli")
-
-Single Deletions
-----------------
-
-Perform all single gene deletions on a model
-
-.. code:: python
-
-    growth_rates, statuses = cobra.flux_analysis.single_gene_deletion(cobra_model)
-
-These can also be done for only a subset of genes
-
-.. code:: python
-
-    growth_rates, statuses = cobra.flux_analysis.single_gene_deletion(cobra_model, cobra_model.genes[:20])
-    pandas.DataFrame.from_dict({"growth_rates": growth_rates, "status": statuses})
-
-
-
-
-.. raw:: html
-
-    <div>
-    <table border="1" class="dataframe">
-      <thead>
-        <tr style="text-align: right;">
-          <th></th>
-          <th>growth_rates</th>
-          <th>status</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <th>b0116</th>
-          <td>0.782351</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b0118</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b0351</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b0356</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b0474</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b0726</th>
-          <td>0.858307</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b0727</th>
-          <td>0.858307</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b1241</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b1276</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b1478</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b1849</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b2296</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b2587</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b3115</th>
-          <td>0.873922</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b3734</th>
-          <td>0.374230</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b3735</th>
-          <td>0.374230</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b3736</th>
-          <td>0.374230</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b3737</th>
-          <td>0.374230</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>b3738</th>
-          <td>0.374230</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>s0001</th>
-          <td>0.211141</td>
-          <td>optimal</td>
-        </tr>
-      </tbody>
-    </table>
-    </div>
-
-
-
-This can also be done for reactions
-
-.. code:: python
-
-    growth_rates, statuses = cobra.flux_analysis.single_reaction_deletion(cobra_model, cobra_model.reactions[:20])
-    pandas.DataFrame.from_dict({"growth_rates": growth_rates, "status": statuses})
-
-
-
-
-.. raw:: html
-
-    <div>
-    <table border="1" class="dataframe">
-      <thead>
-        <tr style="text-align: right;">
-          <th></th>
-          <th>growth_rates</th>
-          <th>status</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <th>ACALD</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ACALDt</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ACKr</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ACONTa</th>
-          <td>-3.963237e-27</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ACONTb</th>
-          <td>6.162976e-33</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ACt2r</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ADK1</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>AKGDH</th>
-          <td>8.583074e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>AKGt2r</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ALCD2x</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ATPM</th>
-          <td>9.166475e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ATPS4r</th>
-          <td>3.742299e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>Biomass_Ecoli_core</th>
-          <td>0.000000e+00</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>CO2t</th>
-          <td>4.616696e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>CS</th>
-          <td>-5.916457e-30</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>CYTBD</th>
-          <td>2.116629e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>D_LACt2</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ENO</th>
-          <td>-3.266892e-18</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>ETOHt2r</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-        <tr>
-          <th>EX_ac_e</th>
-          <td>8.739215e-01</td>
-          <td>optimal</td>
-        </tr>
-      </tbody>
-    </table>
-    </div>
-
-
-
-Double Deletions
-----------------
-
-Double deletions run in a similar way. Passing in return\_frame=True
-will cause them to format the results as a pandas Dataframe
-
-.. code:: python
-
-    cobra.flux_analysis.double_gene_deletion(cobra_model, cobra_model.genes[-10:], return_frame=True)
-
-
-
-
-.. raw:: html
-
-    <div>
-    <table border="1" class="dataframe">
-      <thead>
-        <tr style="text-align: right;">
-          <th></th>
-          <th>b0724</th>
-          <th>b0723</th>
-          <th>b0721</th>
-          <th>b0729</th>
-          <th>b0728</th>
-          <th>b2464</th>
-          <th>b0008</th>
-          <th>b2935</th>
-          <th>b2465</th>
-          <th>b3919</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <th>b0724</th>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.704037</td>
-        </tr>
-        <tr>
-          <th>b0723</th>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.704037</td>
-        </tr>
-        <tr>
-          <th>b0721</th>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.704037</td>
-        </tr>
-        <tr>
-          <th>b0729</th>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.704037</td>
-        </tr>
-        <tr>
-          <th>b0728</th>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.704037</td>
-        </tr>
-        <tr>
-          <th>b2464</th>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.864759</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.704037</td>
-        </tr>
-        <tr>
-          <th>b0008</th>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.864759</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.704037</td>
-        </tr>
-        <tr>
-          <th>b2935</th>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.000000</td>
-          <td>0.704037</td>
-        </tr>
-        <tr>
-          <th>b2465</th>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.814298</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.000000</td>
-          <td>0.873922</td>
-          <td>0.704037</td>
-        </tr>
-        <tr>
-          <th>b3919</th>
-          <td>0.704037</td>
-          <td>0.704037</td>
-          <td>0.704037</td>
-          <td>0.704037</td>
-          <td>0.704037</td>
-          <td>0.704037</td>
-          <td>0.704037</td>
-          <td>0.704037</td>
-          <td>0.704037</td>
-          <td>0.704037</td>
-        </tr>
-      </tbody>
-    </table>
-    </div>
-
-
-
-By default, the double deletion function will automatically use
-multiprocessing, splitting the task over up to 4 cores if they are
-available. The number of cores can be manually sepcified as well.
-Setting use of a single core will disable use of the multiprocessing
-library, which often aids debuggging.
-
-.. code:: python
-
-    start = time()  # start timer()
-    cobra.flux_analysis.double_gene_deletion(ecoli_model, ecoli_model.genes[:100], number_of_processes=2)
-    t1 = time() - start
-    print("Double gene deletions for 100 genes completed in %.2f sec with 2 cores" % t1)
-    
-    start = time()  # start timer()
-    cobra.flux_analysis.double_gene_deletion(ecoli_model, ecoli_model.genes[:100], number_of_processes=1)
-    t2 = time() - start
-    print("Double gene deletions for 100 genes completed in %.2f sec with 1 core" % t2)
-    
-    print("Speedup of %.2fx" % (t2/t1))
-
-
-.. parsed-literal::
-
-    Double gene deletions for 100 genes completed in 1.69 sec with 2 cores
-    Double gene deletions for 100 genes completed in 2.02 sec with 1 core
-    Speedup of 1.20x
-
-
-Double deletions can also be run for reactions
-
-.. code:: python
-
-    cobra.flux_analysis.double_reaction_deletion(cobra_model, cobra_model.reactions[:10], return_frame=True)
-
-
-
-
-.. raw:: html
-
-    <div>
-    <table border="1" class="dataframe">
-      <thead>
-        <tr style="text-align: right;">
-          <th></th>
-          <th>ACALD</th>
-          <th>ACALDt</th>
-          <th>ACKr</th>
-          <th>ACONTa</th>
-          <th>ACONTb</th>
-          <th>ACt2r</th>
-          <th>ADK1</th>
-          <th>AKGDH</th>
-          <th>AKGt2r</th>
-          <th>ALCD2x</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <th>ACALD</th>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-        </tr>
-        <tr>
-          <th>ACALDt</th>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-        </tr>
-        <tr>
-          <th>ACKr</th>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-        </tr>
-        <tr>
-          <th>ACONTa</th>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-        </tr>
-        <tr>
-          <th>ACONTb</th>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-          <td>0.000000</td>
-        </tr>
-        <tr>
-          <th>ACt2r</th>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-        </tr>
-        <tr>
-          <th>ADK1</th>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-        </tr>
-        <tr>
-          <th>AKGDH</th>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-          <td>0.858307</td>
-        </tr>
-        <tr>
-          <th>AKGt2r</th>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-        </tr>
-        <tr>
-          <th>ALCD2x</th>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0</td>
-          <td>0</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-          <td>0.858307</td>
-          <td>0.873922</td>
-          <td>0.873922</td>
-        </tr>
-      </tbody>
-    </table>
-    </div>
-
-
diff --git a/documentation_builder/faq.ipynb b/documentation_builder/faq.ipynb
index c7d001c..4f6188c 100644
--- a/documentation_builder/faq.ipynb
+++ b/documentation_builder/faq.ipynb
@@ -104,7 +104,7 @@
     {
      "data": {
       "text/plain": [
-       "<Metabolite test_dcaACP_c at 0x688b450>"
+       "<Metabolite test_dcaACP_c at 0x7f90c2b97978>"
       ]
      },
      "execution_count": 2,
@@ -205,7 +205,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Trying to set it directly will result in an error: "
+    "Trying to set it directly will result in an error or warning: "
    ]
   },
   {
@@ -216,10 +216,10 @@
    },
    "outputs": [
     {
-     "name": "stdout",
+     "name": "stderr",
      "output_type": "stream",
      "text": [
-      "AttributeError(\"can't set attribute\",)\n"
+      "cobra/core/Reaction.py:192 \u001b[1;31mUserWarning\u001b[0m: Setting reaction reversibility is ignored\n"
      ]
     }
    ],
@@ -282,7 +282,19 @@
    "metadata": {
     "collapsed": false
    },
-   "outputs": [],
+   "outputs": [
+    {
+     "ename": "AttributeError",
+     "evalue": "'module' object has no attribute 'gurobi_solver'",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[1;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
+      "\u001b[1;32m<ipython-input-7-76bbced9b9d6>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m      5\u001b[0m \u001b[0mglp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"test.mps\"\u001b[0m\u001b[1;33m)\u001b[0m  \u001b[1;31m# will not rewrite objective\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      6\u001b[0m \u001b[1;31m# gurobi\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 7 [...]
+      "\u001b[1;31mAttributeError\u001b[0m: 'module' object has no attribute 'gurobi_solver'"
+     ]
+    }
+   ],
    "source": [
     "model = cobra.test.create_test_model()\n",
     "# glpk through cglpk\n",
@@ -316,21 +328,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/faq.rst b/documentation_builder/faq.rst
deleted file mode 100644
index a333331..0000000
--- a/documentation_builder/faq.rst
+++ /dev/null
@@ -1,178 +0,0 @@
-
-FAQ
-===
-
-This document will address frequently asked questions not addressed in
-other pages of the documentation.
-
-How do I install cobrapy?
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Please see the
-`INSTALL.md <https://github.com/opencobra/cobrapy/blob/master/INSTALL.md>`__
-file.
-
-How do I cite cobrapy?
-~~~~~~~~~~~~~~~~~~~~~~
-
-Please cite the 2013 publication:
-`10.1186/1752-0509-7-74 <http://dx.doi.org/doi:10.1186/1752-0509-7-74>`__
-
-How do I rename reactions or metabolites?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-TL;DR Use Model.repair afterwards
-
-When renaming metabolites or reactions, there are issues because cobra
-indexes based off of ID's, which can cause errors. For example:
-
-.. code:: python
-
-    from __future__ import print_function
-    import cobra.test
-    model = cobra.test.create_test_model()
-    
-    for metabolite in model.metabolites:
-        metabolite.id = "test_" + metabolite.id
-    
-    try:
-        model.metabolites.get_by_id(model.metabolites[0].id)
-    except KeyError as e:
-        print(repr(e))
-
-
-.. parsed-literal::
-
-    KeyError('test_dcaACP_c',)
-
-
-The Model.repair function will rebuild the necessary indexes
-
-.. code:: python
-
-    model.repair()
-    model.metabolites.get_by_id(model.metabolites[0].id)
-
-
-
-
-.. parsed-literal::
-
-    <Metabolite test_dcaACP_c at 0x688b450>
-
-
-
-How do I delete a gene?
-~~~~~~~~~~~~~~~~~~~~~~~
-
-That depends on what precisely you mean by delete a gene.
-
-If you want to simulate the model with a gene knockout, use the
-cobra.maniupulation.delete\_model\_genes function. The effects of this
-function are reversed by cobra.manipulation.undelete\_model\_genes.
-
-.. code:: python
-
-    model = cobra.test.create_test_model()
-    PGI = model.reactions.get_by_id("PGI")
-    print("bounds before knockout:", (PGI.lower_bound, PGI.upper_bound))
-    cobra.manipulation.delete_model_genes(model, ["STM4221"])
-    print("bounds after knockouts", (PGI.lower_bound, PGI.upper_bound))
-
-
-.. parsed-literal::
-
-    bounds before knockout: (-1000.0, 1000.0)
-    bounds after knockouts (0.0, 0.0)
-
-
-If you want to actually remove all traces of a gene from a model, this
-is more difficult because this will require changing all the
-gene\_reaction\_rule strings for reactions involving the gene.
-
-How do I change the reversibility of a Reaction?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Reaction.reversibility is a property in cobra which is computed when it
-is requested from the lower and upper bounds.
-
-.. code:: python
-
-    model = cobra.test.create_test_model()
-    model.reactions.get_by_id("PGI").reversibility
-
-
-
-
-.. parsed-literal::
-
-    True
-
-
-
-Trying to set it directly will result in an error:
-
-.. code:: python
-
-    try:
-        model.reactions.get_by_id("PGI").reversibility = False
-    except Exception as e:
-        print(repr(e))
-
-
-.. parsed-literal::
-
-    AttributeError("can't set attribute",)
-
-
-The way to change the reversibility is to change the bounds to make the
-reaction irreversible.
-
-.. code:: python
-
-    model.reactions.get_by_id("PGI").lower_bound = 10
-    model.reactions.get_by_id("PGI").reversibility
-
-
-
-
-.. parsed-literal::
-
-    False
-
-
-
-How do I generate an LP file from a COBRA model?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-While the cobrapy does not include python code to support this feature
-directly, many of the bundled solvers have this capability. Create the
-problem with one of these solvers, and use its appropriate function.
-
-Please note that unlike the LP file format, the MPS file format does not
-specify objective direction and is always a minimzation. Some (but not
-all) solvers will rewrite the maximization as a minimzation.
-
-.. code:: python
-
-    model = cobra.test.create_test_model()
-    # glpk through cglpk
-    glp = cobra.solvers.cglpk.create_problem(model)
-    glp.write("test.lp")
-    glp.write("test.mps")  # will not rewrite objective
-    # gurobi
-    gurobi_problem = cobra.solvers.gurobi_solver.create_problem(model)
-    gurobi_problem.write("test.lp")
-    gurobi_problem.write("test.mps")  # rewrites objective
-    # cplex
-    cplex_problem = cobra.solvers.cplex_solver.create_problem(model)
-    cplex_problem.write("test.lp")
-    cplex_problem.write("test.mps")  # rewrites objective
-
-How do I visualize my flux solutions?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-cobrapy works well with the `escher <https://escher.github.io/>`__
-package, which is well suited to this purpose. Consult the `escher
-documentation <https://escher.readthedocs.org/en/latest/>`__ for
-examples.
diff --git a/documentation_builder/gapfilling.ipynb b/documentation_builder/gapfilling.ipynb
index 2962827..4c3ada6 100644
--- a/documentation_builder/gapfilling.ipynb
+++ b/documentation_builder/gapfilling.ipynb
@@ -62,7 +62,7 @@
     {
      "data": {
       "text/plain": [
-       "3.067723590211908e-08"
+       "2.821531499799383e-12"
       ]
      },
      "execution_count": 3,
@@ -78,6 +78,8 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "## GrowMatch\n",
+    "\n",
     "We will use GrowMatch to add back the minimal number of reactions from this set of \"universal\" reactions (in this case just the ones we removed) to allow it to grow."
    ]
   },
@@ -89,22 +91,21 @@
    },
    "outputs": [
     {
-     "data": {
-      "text/plain": [
-       "[[<Reaction GF6PTA at 0x7fcecddbc390>,\n",
-       "  <Reaction MAN6PI_reverse at 0x7fcecddbc450>,\n",
-       "  <Reaction F6PA_reverse at 0x7fcecddbc490>,\n",
-       "  <Reaction PGI_reverse at 0x7fcecddbc510>,\n",
-       "  <Reaction TKT2_reverse at 0x7fcecddbc590>]]"
-      ]
-     },
-     "execution_count": 4,
-     "metadata": {},
-     "output_type": "execute_result"
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "GF6PTA\n",
+      "FBP\n",
+      "MAN6PI_reverse\n",
+      "TKT2_reverse\n",
+      "PGI_reverse\n"
+     ]
     }
    ],
    "source": [
-    "cobra.flux_analysis.growMatch(model, Universal)"
+    "r = cobra.flux_analysis.growMatch(model, Universal)\n",
+    "for e in r[0]:\n",
+    "    print(e.id)"
    ]
   },
   {
@@ -126,29 +127,29 @@
      "output_type": "stream",
      "text": [
       "---- Run 1 ----\n",
-      "FBP\n",
       "GF6PTA\n",
+      "FBP\n",
       "MAN6PI_reverse\n",
-      "PGI_reverse\n",
       "TKT2_reverse\n",
+      "PGI_reverse\n",
       "---- Run 2 ----\n",
-      "TALA\n",
       "F6PP\n",
       "GF6PTA\n",
+      "TALA\n",
       "MAN6PI_reverse\n",
       "F6PA_reverse\n",
       "---- Run 3 ----\n",
-      "F6PP\n",
       "GF6PTA\n",
       "MAN6PI_reverse\n",
-      "F6PA_reverse\n",
       "TKT2_reverse\n",
+      "F6PA_reverse\n",
+      "PGI_reverse\n",
       "---- Run 4 ----\n",
-      "TALA\n",
-      "FBP\n",
+      "F6PP\n",
       "GF6PTA\n",
-      "MAN6PI_reverse\n",
-      "PGI_reverse\n"
+      "FBP\n",
+      "TALA\n",
+      "MAN6PI_reverse\n"
      ]
     }
    ],
@@ -160,25 +161,60 @@
     "    for e in entries:\n",
     "        print(e.id)"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## SMILEY\n",
+    "\n",
+    "SMILEY is very similar to growMatch, only instead of setting growth as the objective, it sets production of a specific metabolite"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "GF6PTA\n",
+      "MAN6PI_reverse\n",
+      "TKT2_reverse\n",
+      "F6PA_reverse\n",
+      "PGI_reverse\n"
+     ]
+    }
+   ],
+   "source": [
+    "r = cobra.flux_analysis.gapfilling.SMILEY(model, \"ac_e\",\n",
+    "                                          Universal)\n",
+    "for e in r[0]:\n",
+    "    print(e.id)"
+   ]
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/gapfilling.rst b/documentation_builder/gapfilling.rst
deleted file mode 100644
index 77c0d1c..0000000
--- a/documentation_builder/gapfilling.rst
+++ /dev/null
@@ -1,104 +0,0 @@
-
-Gapfillling
-===========
-
-GrowMatch and SMILEY are gap-filling algorithms, which try to to make
-the minimal number of changes to a model and allow it to simulate
-growth. For more information, see `Kumar et
-al. <http://dx.doi.org/10.1371/journal.pcbi.1000308>`__. Please note
-that these algorithms are Mixed-Integer Linear Programs, which need
-solvers such as gurobi or cplex to function correctly.
-
-.. code:: python
-
-    import cobra.test
-    
-    model = cobra.test.create_test_model("salmonella")
-
-In this model D-Fructose-6-phosphate is an essential metabolite. We will
-remove all the reactions using it, and at them to a separate model.
-
-.. code:: python
-
-    # remove some reactions and add them to the universal reactions
-    Universal = cobra.Model("Universal_Reactions")
-    for i in [i.id for i in model.metabolites.f6p_c.reactions]:
-        reaction = model.reactions.get_by_id(i)
-        Universal.add_reaction(reaction.copy())
-        reaction.remove_from_model()
-
-Now, because of these gaps, the model won't grow.
-
-.. code:: python
-
-    model.optimize().f
-
-
-
-
-.. parsed-literal::
-
-    3.067723590211908e-08
-
-
-
-We will use GrowMatch to add back the minimal number of reactions from
-this set of "universal" reactions (in this case just the ones we
-removed) to allow it to grow.
-
-.. code:: python
-
-    cobra.flux_analysis.growMatch(model, Universal)
-
-
-
-
-.. parsed-literal::
-
-    [[<Reaction GF6PTA at 0x7fcecddbc390>,
-      <Reaction MAN6PI_reverse at 0x7fcecddbc450>,
-      <Reaction F6PA_reverse at 0x7fcecddbc490>,
-      <Reaction PGI_reverse at 0x7fcecddbc510>,
-      <Reaction TKT2_reverse at 0x7fcecddbc590>]]
-
-
-
-We can obtain multiple possible reaction sets by having the algorithm go
-through multiple iterations.
-
-.. code:: python
-
-    result = cobra.flux_analysis.growMatch(model, Universal, iterations=4)
-    for i, entries in enumerate(result):
-        print("---- Run %d ----" % (i + 1))
-        for e in entries:
-            print(e.id)
-
-
-.. parsed-literal::
-
-    ---- Run 1 ----
-    FBP
-    GF6PTA
-    MAN6PI_reverse
-    PGI_reverse
-    TKT2_reverse
-    ---- Run 2 ----
-    TALA
-    F6PP
-    GF6PTA
-    MAN6PI_reverse
-    F6PA_reverse
-    ---- Run 3 ----
-    F6PP
-    GF6PTA
-    MAN6PI_reverse
-    F6PA_reverse
-    TKT2_reverse
-    ---- Run 4 ----
-    TALA
-    FBP
-    GF6PTA
-    MAN6PI_reverse
-    PGI_reverse
-
diff --git a/documentation_builder/getting_started.ipynb b/documentation_builder/getting_started.ipynb
index d4dd70a..c3b0874 100644
--- a/documentation_builder/getting_started.ipynb
+++ b/documentation_builder/getting_started.ipynb
@@ -58,7 +58,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Just like a regular list, objects in the DictList can be retrived by index. For example, to get the 30th reaction in the model (at index 29 because of [0-indexing](https://en.wikipedia.org/wiki/Z ero-based_numbering)):"
+    "Just like a regular list, objects in the DictList can be retrived by index. For example, to get the 30th reaction in the model (at index 29 because of [0-indexing](https://en.wikipedia.org/wiki/Zero-based_numbering)):"
    ]
   },
   {
@@ -71,7 +71,7 @@
     {
      "data": {
       "text/plain": [
-       "<Reaction EX_glu__L_e at 0x7f9b62fcf650>"
+       "<Reaction EX_glu__L_e at 0x7f56b0ea3198>"
       ]
      },
      "execution_count": 3,
@@ -100,7 +100,7 @@
     {
      "data": {
       "text/plain": [
-       "<Metabolite atp_c at 0x7f9b6305ded0>"
+       "<Metabolite atp_c at 0x7f56b0ed7cc0>"
       ]
      },
      "execution_count": 4,
@@ -165,7 +165,7 @@
     {
      "data": {
       "text/plain": [
-       "<Reaction PGI at 0x7f9b63050910>"
+       "<Reaction PGI at 0x7f56b0e396d8>"
       ]
      },
      "execution_count": 6,
@@ -388,7 +388,7 @@
     {
      "data": {
       "text/plain": [
-       "'g6p_c --> ham + h_c + green_eggs + f6p_c'"
+       "'g6p_c --> f6p_c + green_eggs + h_c + ham'"
       ]
      },
      "execution_count": 14,
@@ -447,7 +447,7 @@
     {
      "data": {
       "text/plain": [
-       "<Metabolite atp_c at 0x7f9b6305ded0>"
+       "<Metabolite atp_c at 0x7f56b0ed7cc0>"
       ]
      },
      "execution_count": 16,
@@ -589,10 +589,10 @@
     {
      "data": {
       "text/plain": [
-       "frozenset({<Reaction Biomass_Ecoli_core at 0x7f9b62fbcbd0>,\n",
-       "           <Reaction G6PDH2r at 0x7f9b62fcff50>,\n",
-       "           <Reaction PGI at 0x7f9b63050910>,\n",
-       "           <Reaction GLCpts at 0x7f9b631ccad0>})"
+       "frozenset({<Reaction GLCpts at 0x7f56b0eabcc0>,\n",
+       "           <Reaction Biomass_Ecoli_core at 0x7f56b0e9e358>,\n",
+       "           <Reaction G6PDH2r at 0x7f56b0eab9e8>,\n",
+       "           <Reaction PGI at 0x7f56b0e396d8>})"
       ]
      },
      "execution_count": 21,
@@ -660,7 +660,7 @@
     {
      "data": {
       "text/plain": [
-       "frozenset({<Gene b4025 at 0x7f9b63027bd0>})"
+       "frozenset({<Gene b4025 at 0x7f56b0e8fac8>})"
       ]
      },
      "execution_count": 23,
@@ -682,7 +682,7 @@
     {
      "data": {
       "text/plain": [
-       "<Gene b4025 at 0x7f9b63027bd0>"
+       "<Gene b4025 at 0x7f56b0e8fac8>"
       ]
      },
      "execution_count": 24,
@@ -712,7 +712,7 @@
     {
      "data": {
       "text/plain": [
-       "frozenset({<Reaction PGI at 0x7f9b63050910>})"
+       "frozenset({<Reaction PGI at 0x7f56b0e396d8>})"
       ]
      },
      "execution_count": 25,
@@ -741,7 +741,7 @@
     {
      "data": {
       "text/plain": [
-       "frozenset({<Gene eggs at 0x7f9b63057310>, <Gene spam at 0x7f9b63057590>})"
+       "frozenset({<Gene eggs at 0x7f56b0e35ba8>, <Gene spam at 0x7f56b0e390f0>})"
       ]
      },
      "execution_count": 26,
@@ -793,7 +793,7 @@
     {
      "data": {
       "text/plain": [
-       "<Gene spam at 0x7f9b63057590>"
+       "<Gene spam at 0x7f56b0e390f0>"
       ]
      },
      "execution_count": 28,
@@ -820,28 +820,6 @@
    },
    "outputs": [
     {
-     "data": {
-      "text/plain": [
-       "3"
-      ]
-     },
-     "execution_count": 29,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "len(\"%3d\"% 0)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 30,
-   "metadata": {
-    "collapsed": false
-   },
-   "outputs": [
-    {
      "name": "stdout",
      "output_type": "stream",
      "text": [
@@ -871,7 +849,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 31,
+   "execution_count": 30,
    "metadata": {
     "collapsed": false
    },
@@ -892,21 +870,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/getting_started.rst b/documentation_builder/getting_started.rst
deleted file mode 100644
index 7658ece..0000000
--- a/documentation_builder/getting_started.rst
+++ /dev/null
@@ -1,489 +0,0 @@
-
-Getting Started
-===============
-
-To begin with, cobrapy comes with bundled models for *Salmonella* and
-*E. coli*, as well as a "textbook" model of *E. coli* core metabolism.
-To load a test model, type
-
-.. code:: python
-
-    from __future__ import print_function
-    import cobra.test
-    
-    model = cobra.test.create_test_model("textbook")  # "ecoli" and "salmonella" are also valid arguments
-
-The reactions, metabolites, and genes attributes of the cobrapy model
-are a special type of list called a DictList, and each one is made up of
-Reaction, Metabolite and Gene objects respectively.
-
-.. code:: python
-
-    print(len(model.reactions))
-    print(len(model.metabolites))
-    print(len(model.genes))
-
-
-.. parsed-literal::
-
-    95
-    72
-    137
-
-
-Just like a regular list, objects in the DictList can be retrived by
-index. For example, to get the 30th reaction in the model (at index 29
-because of
-`0-indexing <https://en.wikipedia.org/wiki/Z%20ero-based_numbering>`__):
-
-.. code:: python
-
-    model.reactions[29]
-
-
-
-
-.. parsed-literal::
-
-    <Reaction EX_glu__L_e at 0x7fbbe05e5590>
-
-
-
-Addictionally, items can be retrived by their id using the get\_by\_id()
-function. For example, to get the cytosolic atp metabolite object (the
-id is "atp\_c"), we can do the following:
-
-.. code:: python
-
-    model.metabolites.get_by_id("atp_c")
-
-
-
-
-.. parsed-literal::
-
-    <Metabolite atp_c at 0x7fbbe0617350>
-
-
-
-As an added bonus, users with an interactive shell such as IPython will
-be able to tab-complete to list elements inside a list. While this is
-not recommended behavior for most code because of the possibility for
-characters like "-" inside ids, this is very useful while in an
-interactive prompt:
-
-.. code:: python
-
-    model.reactions.EX_glc__D_e.lower_bound
-
-
-
-
-.. parsed-literal::
-
-    -10.0
-
-
-
-Reactions
----------
-
-We will consider the reaction glucose 6-phosphate isomerase, which
-interconverts glucose 6-phosphate and fructose 6-phosphate. The reaction
-id for this reaction in our test model is PGI.
-
-.. code:: python
-
-    pgi = model.reactions.get_by_id("PGI")
-    pgi
-
-
-
-
-.. parsed-literal::
-
-    <Reaction PGI at 0x7fbbe0611790>
-
-
-
-We can view the full name and reaction catalyzed as strings
-
-.. code:: python
-
-    print(pgi.name)
-    print(pgi.reaction)
-
-
-.. parsed-literal::
-
-    glucose-6-phosphate isomerase
-    g6p_c <=> f6p_c
-
-
-We can also view reaction upper and lower bounds. Because the
-pgi.lower\_bound < 0, and pgi.upper\_bound > 0, pgi is reversible
-
-.. code:: python
-
-    print(pgi.lower_bound, "< pgi <", pgi.upper_bound)
-    print(pgi.reversibility)
-
-
-.. parsed-literal::
-
-    -1000.0 < pgi < 1000.0
-    True
-
-
-We can also ensure the reaction is mass balanced. This function will
-return elements which violate mass balance. If it comes back empty, then
-the reaction is mass balanced.
-
-.. code:: python
-
-    pgi.check_mass_balance()
-
-
-
-
-.. parsed-literal::
-
-    {}
-
-
-
-In order to add a metabolite, we pass in a dict with the metabolite
-object and its coefficient
-
-.. code:: python
-
-    pgi.add_metabolites({model.metabolites.get_by_id("h_c"): -1})
-    pgi.reaction
-
-
-
-
-.. parsed-literal::
-
-    'g6p_c + h_c <=> f6p_c'
-
-
-
-The reaction is no longer mass balanced
-
-.. code:: python
-
-    pgi.check_mass_balance()
-
-
-
-
-.. parsed-literal::
-
-    {'H': -1.0}
-
-
-
-We can remove the metabolite, and the reaction will be balanced once
-again.
-
-.. code:: python
-
-    pgi.pop(model.metabolites.get_by_id("h_c"))
-    print(pgi.reaction)
-    print(pgi.check_mass_balance())
-
-
-.. parsed-literal::
-
-    g6p_c <=> f6p_c
-    {}
-
-
-It is also possible to build the reaction from a string. However, care
-must be taken when doing this to ensure reaction id's match those in the
-model. The direction of the arrow is also used to update the upper and
-lower bounds.
-
-.. code:: python
-
-    pgi.reaction = "g6p_c --> f6p_c + h_c + green_eggs + ham"
-
-
-.. parsed-literal::
-
-    unknown metabolite 'green_eggs' created
-    unknown metabolite 'ham' created
-
-
-.. code:: python
-
-    pgi.reaction
-
-
-
-
-.. parsed-literal::
-
-    'g6p_c --> green_eggs + ham + h_c + f6p_c'
-
-
-
-.. code:: python
-
-    pgi.reaction = "g6p_c <=> f6p_c"
-    pgi.reaction
-
-
-
-
-.. parsed-literal::
-
-    'g6p_c <=> f6p_c'
-
-
-
-Metabolites
------------
-
-We will consider cytosolic atp as our metabolite, which has the id
-atp\_c in our test model.
-
-.. code:: python
-
-    atp = model.metabolites.get_by_id("atp_c")
-    atp
-
-
-
-
-.. parsed-literal::
-
-    <Metabolite atp_c at 0x7fbbe0617350>
-
-
-
-We can print out the metabolite name and compartment (cytosol in this
-case).
-
-.. code:: python
-
-    print(atp.name)
-    print(atp.compartment)
-
-
-.. parsed-literal::
-
-    ATP
-    c
-
-
-We can see that ATP is a charged molecule in our model.
-
-.. code:: python
-
-    atp.charge
-
-
-
-
-.. parsed-literal::
-
-    -4
-
-
-
-We can see the chemical formula for the metabolite as well.
-
-.. code:: python
-
-    print(atp.formula)
-
-
-.. parsed-literal::
-
-    C10H12N5O13P3
-
-
-The reactions attribute gives a frozenset of all reactions using the
-given metabolite. We can use this to count the number of reactions which
-use atp.
-
-.. code:: python
-
-    len(atp.reactions)
-
-
-
-
-.. parsed-literal::
-
-    13
-
-
-
-A metabolite like glucose 6-phosphate will participate in fewer
-reactions.
-
-.. code:: python
-
-    model.metabolites.get_by_id("g6p_c").reactions
-
-
-
-
-.. parsed-literal::
-
-    frozenset({<Reaction G6PDH2r at 0x7fbbe05fd050>,
-               <Reaction GLCpts at 0x7fbbe05fd150>,
-               <Reaction PGI at 0x7fbbe0611790>,
-               <Reaction Biomass_Ecoli_core at 0x7fbbe0650ed0>})
-
-
-
-Genes
------
-
-The gene\_reaction\_rule is a boolean representation of the gene
-requirements for this reaction to be active as described in
-`Schellenberger et al 2011 Nature Protocols
-6(9):1290-307 <http://dx.doi.org/doi:10.1038/nprot.2011.308>`__.
-
-The GPR is stored as the gene\_reaction\_rule for a Reaction object as a
-string.
-
-.. code:: python
-
-    gpr = pgi.gene_reaction_rule
-    gpr
-
-
-
-
-.. parsed-literal::
-
-    'b4025'
-
-
-
-Corresponding gene objects also exist. These objects are tracked by the
-reactions itself, as well as by the model
-
-.. code:: python
-
-    pgi.genes
-
-
-
-
-.. parsed-literal::
-
-    frozenset({<Gene b4025 at 0x7fbbe063dc90>})
-
-
-
-.. code:: python
-
-    pgi_gene = model.genes.get_by_id("b4025")
-    pgi_gene
-
-
-
-
-.. parsed-literal::
-
-    <Gene b4025 at 0x7fbbe063dc90>
-
-
-
-Each gene keeps track of the reactions it catalyzes
-
-.. code:: python
-
-    pgi_gene.reactions
-
-
-
-
-.. parsed-literal::
-
-    frozenset({<Reaction PGI at 0x7fbbe0611790>})
-
-
-
-Altering the gene\_reaction\_rule will create new gene objects if
-necessary and update all relationships.
-
-.. code:: python
-
-    pgi.gene_reaction_rule = "(spam or eggs)"
-    pgi.genes
-
-
-
-
-.. parsed-literal::
-
-    frozenset({<Gene eggs at 0x7fbbe0611b50>, <Gene spam at 0x7fbbe0611e90>})
-
-
-
-.. code:: python
-
-    pgi_gene.reactions
-
-
-
-
-.. parsed-literal::
-
-    frozenset()
-
-
-
-Newly created genes are also added to the model
-
-.. code:: python
-
-    model.genes.get_by_id("spam")
-
-
-
-
-.. parsed-literal::
-
-    <Gene spam at 0x7fbbe0611e90>
-
-
-
-The delete\_model\_genes function will evaluate the gpr and set the
-upper and lower bounds to 0 if the reaction is knocked out. This
-function can preserve existing deletions or reset them using the
-cumulative\_deletions flag.
-
-.. code:: python
-
-    cobra.manipulation.delete_model_genes(model, ["spam"], cumulative_deletions=True)
-    print(pgi.lower_bound, "< pgi <", pgi.upper_bound)
-    cobra.manipulation.delete_model_genes(model, ["eggs"], cumulative_deletions=True)
-    print(pgi.lower_bound, "< pgi <", pgi.upper_bound)
-
-
-.. parsed-literal::
-
-    -1000 < pgi < 1000
-    0.0 < pgi < 0.0
-
-
-The undelete\_model\_genes can be used to reset a gene deletion
-
-.. code:: python
-
-    cobra.manipulation.undelete_model_genes(model)
-    print(pgi.lower_bound, "< pgi <", pgi.upper_bound)
-
-
-.. parsed-literal::
-
-    -1000 < pgi < 1000
-
diff --git a/documentation_builder/io.ipynb b/documentation_builder/io.ipynb
index 0eaca37..697d1f5 100644
--- a/documentation_builder/io.ipynb
+++ b/documentation_builder/io.ipynb
@@ -71,7 +71,7 @@
     {
      "data": {
       "text/plain": [
-       "<Model mini_textbook at 0x7f1a5462e290>"
+       "<Model mini_textbook at 0x7fa5e44d1a58>"
       ]
      },
      "execution_count": 2,
@@ -113,7 +113,7 @@
     {
      "data": {
       "text/plain": [
-       "<Model mini_textbook at 0x7f1a2291a610>"
+       "<Model mini_textbook at 0x7fa5ba4d12e8>"
       ]
      },
      "execution_count": 4,
@@ -156,7 +156,7 @@
     {
      "data": {
       "text/plain": [
-       "<Model mini_textbook at 0x7f1a2292ae90>"
+       "<Model mini_textbook at 0x7fa5ba4a3128>"
       ]
      },
      "execution_count": 6,
@@ -205,7 +205,7 @@
     {
      "data": {
       "text/plain": [
-       "<Model mini_textbook at 0x7f1a5462e150>"
+       "<Model mini_textbook at 0x7fa5ba483198>"
       ]
      },
      "execution_count": 8,
@@ -235,7 +235,7 @@
     {
      "data": {
       "text/plain": [
-       "<Model mini_textbook at 0x7f1a54e93f90>"
+       "<Model mini_textbook at 0x7fa5ba4a3f28>"
       ]
      },
      "execution_count": 9,
@@ -284,21 +284,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/io.rst b/documentation_builder/io.rst
deleted file mode 100644
index e9dd914..0000000
--- a/documentation_builder/io.rst
+++ /dev/null
@@ -1,170 +0,0 @@
-
-Reading and Writing Models
-==========================
-
-Cobrapy supports reading and writing models in SBML (with and without
-FBC), JSON, MAT, and pickle formats. Generally, SBML with FBC version 2
-is the preferred format for general use. The JSON format may be more
-useful for cobrapy-specific functionality.
-
-The package also ships with test models in various formats for testing
-purposes.
-
-.. code:: python
-
-    import cobra.test
-    import os
-    
-    print("mini test files: ")
-    print(", ".join([i for i in os.listdir(cobra.test.data_directory) if i.startswith("mini")]))
-    
-    textbook_model = cobra.test.create_test_model("textbook")
-    ecoli_model = cobra.test.create_test_model("ecoli")
-    salmonella_model = cobra.test.create_test_model("salmonella")
-
-
-.. parsed-literal::
-
-    mini test files: 
-    mini.mat, mini_cobra.xml, mini.json, mini_fbc2.xml.gz, mini_fbc2.xml.bz2, mini_fbc2.xml, mini_fbc1.xml, mini.pickle
-
-
-SBML
-----
-
-The `Systems Biology Markup Language <http://sbml.org>`__ is an
-XML-based standard format for distributing models which has support for
-COBRA models through the `FBC
-extension <http://sbml.org/Documents/Specifications/SBML_Level_3/Packages/Flux_Balance_Constraints_%28flux%29>`__
-version 2.
-
-Cobrapy has native support for reading and writing SBML with FBCv2.
-Please note that all id's in the model must conform to the SBML SID
-requirements in order to generate a valid SBML file.
-
-.. code:: python
-
-    cobra.io.read_sbml_model(os.path.join(cobra.test.data_directory, "mini_fbc2.xml"))
-
-
-
-
-.. parsed-literal::
-
-    <Model mini_textbook at 0x7f246d4e2e50>
-
-
-
-.. code:: python
-
-    cobra.io.write_sbml_model(textbook_model, "test_fbc2.xml")
-
-There are other dialects of SBML prior to FBC 2 which have previously
-been use to encode COBRA models. The primary ones is the "COBRA" dialect
-which used the "notes" fields in SBML files.
-
-Cobrapy can use `libsbml <http://sbml.org/Software/libSBML>`__, which
-must be installed separately (see installation instructions) to read and
-write these files. When reading in a model, it will automatically detect
-whether fbc was used or not. When writing a model, the use\_fbc\_package
-flag can be used can be used.
-
-.. code:: python
-
-    cobra.io.read_sbml_model(os.path.join(cobra.test.data_directory, "mini_cobra.xml"))
-
-
-
-
-.. parsed-literal::
-
-    <Model mini_textbook at 0x7f2436c65a10>
-
-
-
-.. code:: python
-
-    cobra.io.write_sbml_model(textbook_model, "test_cobra.xml", use_fbc_package=False)
-
-JSON
-----
-
-cobrapy models have a `JSON <https://en.wikipedia.org/wiki/JSON>`__
-(JavaScript Object Notation) representation. This format was crated for
-interoperability with `escher <https://escher.github.io>`__.
-
-.. code:: python
-
-    cobra.io.load_json_model(os.path.join(cobra.test.data_directory, "mini.json"))
-
-
-
-
-.. parsed-literal::
-
-    <Model mini_textbook at 0x7f2436c7c850>
-
-
-
-.. code:: python
-
-    cobra.io.save_json_model(textbook_model, "test.json")
-
-MATLAB
-------
-
-Often, models may be imported and exported soley for the purposes of
-working with the same models in cobrapy and the `MATLAB cobra
-toolbox <http://opencobra.github.io/cobratoolbox/>`__. MATLAB has its
-own ".mat" format for storing variables. Reading and writing to these
-mat files from python requires scipy.
-
-A mat file can contain multiple MATLAB variables. Therefore, the
-variable name of the model in the MATLAB file can be passed into the
-reading function:
-
-.. code:: python
-
-    cobra.io.load_matlab_model(os.path.join(cobra.test.data_directory, "mini.mat"),
-                               variable_name="mini_textbook")
-
-
-
-
-.. parsed-literal::
-
-    <Model mini_textbook at 0x7f2436c7c810>
-
-
-
-If the mat file contains only a single model, cobra can figure out which
-variable to read from, and the variable\_name paramter is unnecessary.
-
-.. code:: python
-
-    cobra.io.load_matlab_model(os.path.join(cobra.test.data_directory, "mini.mat"))
-
-
-
-
-.. parsed-literal::
-
-    <Model mini_textbook at 0x7f2436c65510>
-
-
-
-Saving models to mat files is also relatively straightforward
-
-.. code:: python
-
-    cobra.io.save_matlab_model(textbook_model, "test.mat")
-
-Pickle
-------
-
-Cobra models can be serialized using the python serialization format,
-`pickle <https://docs.python.org/2/library/pickle.html>`__.
-
-Please note that use of the pickle format is generally not recommended
-for most use cases. JSON, SBML, and MAT are generally the preferred
-formats.
diff --git a/documentation_builder/loopless.ipynb b/documentation_builder/loopless.ipynb
index ea33bc6..c3bdc00 100644
--- a/documentation_builder/loopless.ipynb
+++ b/documentation_builder/loopless.ipynb
@@ -84,10 +84,10 @@
    "outputs": [
     {
      "data": {
-      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9QYXR0ZXJuIDUgMCBSCi9Qcm9jU2V0\nIFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9FeHRHU3RhdGUgNCAwIFIK\nL1NoYWRpbmcgNiAwIFIgL0ZvbnQgMyAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9Hcm91cCA8\nPCAvQ1MgL0RldmljZVJHQiAvUyAvVHJhbnNwYXJlbmN5IC9UeXBlIC9Hcm91cCA+PiAvUGFyZW50\nIDIgMCBSCi9NZWRpYUJveCBbIDAgMCA2MjguMTU5Mzc1IDI4NC4wMDc4MTI1IF0gL0Fubm90cyBb [...]
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnQAAAEcCAYAAABOJillAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xm4HFWd//H3NwkhCQEERZBF2cVRcJ0Iygzu27CKiCIg\nBkdFEBUXGFHEDXcFJOqo7KAjICq4oCKuSIgbiiigsoiAwA8wCiFAku/vj1NtLpfcvburq/v9ep5+\n6nbf6q5voKv7c8+pc05kJpIkSWquaXUXIEmSpKkx0EmSJDWcgU6SJKnhDHSSJEkNZ6CTJElqOAOd\nJElSwxnoJEmSGs5AJ0mS1HAGOkmSpIYz0EmSJDWcgU6SJKnhDHSSJEkNZ6CTJElqOAOdJElSwxno\nJEmSGs5AJ0mS1HAGOkmSpIYz0EmSJDWcgU6SJKnhDHSSJEkNZ6CTJElqOAOdJElSwxnoJEmSGs5A\nJ0mS [...]
+      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9QYXR0ZXJuIDUgMCBSIC9YT2JqZWN0IDcgMCBSCi9Qcm9jU2V0\nIFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9Gb250IDMgMCBSIC9TaGFk\naW5nIDYgMCBSCi9FeHRHU3RhdGUgNCAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9UeXBlIC9Q\nYWdlIC9Hcm91cCA8PCAvVHlwZSAvR3JvdXAgL1MgL1RyYW5zcGFyZW5jeSAvQ1MgL0RldmljZVJH\nQiA+PgovQ29udGVudHMgOSAwIFIgL1Jlc291cmNlcyA4IDAgUiAvTWVkaWFCb3ggWyAwIDAgNjI4 [...]
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnQAAAEcCAYAAABOJillAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xe4JFWd//H3dwJMIo0iCEiQsLpG1B3BBCtmAUHSGhhZ\nVBQHI6YVFzBhzmDARaIRRAXUURGVIEEFFFFBJYiAyA9wgAnAzHx/f5y6zuVy8+3u6up+v56nn7rd\nXdX1vTNd3Z97TtU5kZlIkiSpuabVXYAkSZKmxkAnSZLUcAY6SZKkhjPQSZIkNZyBTpIkqeEMdJIk\nSQ1noJMkSWo4A50kSVLDGegkSZIazkAnSZLUcAY6SZKkhjPQSZIkNZyBTpIkqeEMdJIkSQ1noJMk\nSWo4A50kSVLDGegkSZIazkAnSZLUcAY6SZKkhjPQSZIkNZyBTpIkqeEMdJIkSQ1noJMkSWo4A50k\nSVLD [...]
       "text/plain": [
-       "<matplotlib.figure.Figure at 0x7fe343a72ed0>"
+       "<matplotlib.figure.Figure at 0x7fc7e517e748>"
       ]
      },
      "metadata": {},
@@ -141,7 +141,7 @@
     {
      "data": {
       "text/plain": [
-       "<Solution 1000.00 at 0x7fe342fb1950>"
+       "<Solution 1000.00 at 0x7fc7bd7a3b38>"
       ]
      },
      "execution_count": 4,
@@ -170,7 +170,7 @@
     {
      "data": {
       "text/plain": [
-       "<Solution 'infeasible' at 0x7fe342fb1790>"
+       "<Solution 'infeasible' at 0x7fc7bd550208>"
       ]
      },
      "execution_count": 5,
@@ -200,7 +200,7 @@
     {
      "data": {
       "text/plain": [
-       "<Solution 0.38 at 0x7fe33b67ba90>"
+       "<Solution 0.38 at 0x7fc7b6d2d048>"
       ]
      },
      "execution_count": 6,
@@ -223,7 +223,7 @@
     {
      "data": {
       "text/plain": [
-       "<Solution 0.98 at 0x7fe342fb1a90>"
+       "<Solution 0.98 at 0x7fc7b74ad240>"
       ]
      },
      "execution_count": 7,
@@ -239,21 +239,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/loopless.rst b/documentation_builder/loopless.rst
deleted file mode 100644
index ef613cc..0000000
--- a/documentation_builder/loopless.rst
+++ /dev/null
@@ -1,194 +0,0 @@
-
-Loopless FBA
-============
-
-The goal of this procedure is identification of a thermodynamically
-consistent flux state without loops, as implied by the name.
-
-Usually, the model has the following constraints.
-
-.. math::  S \cdot v = 0 
-
-.. math::  lb \le v \le ub 
-
-However, this will allow for thermodynamically infeasible loops
-(referred to as type 3 loops) to occur, where flux flows around a cycle
-without any net change of metabolites. For most cases, this is not a
-major issue, as solutions with these loops can usually be converted to
-equivalent solutions without them. However, if a flux state is desired
-which does not exhibit any of these loops, loopless FBA can be used. The
-formulation used here is modified from `Schellenberger et
-al. <http://dx.doi.org/10.1016/j.bpj.2010.12.3707>`__
-
-We can make the model irreversible, so that all reactions will satisfy
-
-.. math::  0 \le lb \le v \le ub \le \max(ub) 
-
-We will add in boolean indicators as well, such that
-
-.. math::  \max(ub) \cdot i \ge v 
-
-.. math::  i \in \{0, 1\} 
-
-We also want to ensure that an entry in the row space of S also exists
-with negative values wherever v is nonzero. In this expression,
-:math:`1-i` acts as a not to indicate inactivity of a reaction.
-
-.. math::  S^\mathsf T x - (1 - i) (\max(ub) + 1) \le -1 
-
-We will construct an LP integrating both constraints.
-
-.. math::
-
-    \left(
-   \begin{matrix}
-   S & 0 & 0\\
-   -I & \max(ub)I & 0 \\
-   0 & (\max(ub) + 1)I & S^\mathsf T
-   \end{matrix}
-   \right)
-   \cdot
-   \left(
-   \begin{matrix}
-   v \\
-   i \\
-   x
-   \end{matrix}
-   \right)
-   \begin{matrix}
-   &=& 0 \\
-   &\ge& 0 \\
-   &\le& \max(ub)
-   \end{matrix}
-
-Note that these extra constraints are not applied to boundary reactions
-which bring metabolites in and out of the system.
-
-.. code:: python
-
-    from matplotlib.pylab import *
-    %matplotlib inline
-    
-    import cobra.test
-    from cobra import Reaction, Metabolite, Model
-    from cobra.flux_analysis.loopless import construct_loopless_model
-    from cobra.solvers import get_solver_name
-
-We will demonstrate with a toy model which has a simple loop cycling A
--> B -> C -> A, with A allowed to enter the system and C allowed to
-leave. A graphical view of the system is drawn below:
-
-.. code:: python
-
-    figure(figsize=(10.5, 4.5), frameon=False)
-    gca().axis("off")
-    xlim(0.5, 3.5)
-    ylim(0.7, 2.2)
-    arrow_params = {"head_length": 0.08, "head_width": 0.1, "ec": "k", "fc": "k"}
-    text_params = {"fontsize": 25, "horizontalalignment": "center", "verticalalignment": "center"}
-    arrow(0.5, 1, 0.85, 0, **arrow_params)  # EX_A
-    arrow(1.5, 1, 0.425, 0.736, **arrow_params)  # v1
-    arrow(2.04, 1.82, 0.42, -0.72, **arrow_params)  # v2
-    arrow(2.4, 1, -0.75, 0, **arrow_params)  # v3
-    arrow(2.6, 1, 0.75, 0, **arrow_params)
-    # reaction labels
-    text(0.9, 1.15, "EX_A", **text_params)
-    text(1.6, 1.5, r"v$_1$", **text_params)
-    text(2.4, 1.5, r"v$_2$", **text_params)
-    text(2, 0.85, r"v$_3$", **text_params)
-    text(2.9, 1.15, "DM_C", **text_params)
-    # metabolite labels
-    scatter(1.5, 1, s=250, color='#c994c7')
-    text(1.5, 0.9, "A", **text_params)
-    scatter(2, 1.84, s=250, color='#c994c7')
-    text(2, 1.95, "B", **text_params)
-    scatter(2.5, 1, s=250, color='#c994c7')
-    text(2.5, 0.9, "C", **text_params);
-
-
-
-.. image:: loopless_files/loopless_3_0.png
-
-
-.. code:: python
-
-    test_model = Model()
-    test_model.add_metabolites(Metabolite("A"))
-    test_model.add_metabolites(Metabolite("B"))
-    test_model.add_metabolites(Metabolite("C"))
-    EX_A = Reaction("EX_A")
-    EX_A.add_metabolites({test_model.metabolites.A: 1})
-    DM_C = Reaction("DM_C")
-    DM_C.add_metabolites({test_model.metabolites.C: -1})
-    v1 = Reaction("v1")
-    v1.add_metabolites({test_model.metabolites.A: -1, test_model.metabolites.B: 1})
-    v2 = Reaction("v2")
-    v2.add_metabolites({test_model.metabolites.B: -1, test_model.metabolites.C: 1})
-    v3 = Reaction("v3")
-    v3.add_metabolites({test_model.metabolites.C: -1, test_model.metabolites.A: 1})
-    DM_C.objective_coefficient = 1
-    test_model.add_reactions([EX_A, DM_C, v1, v2, v3])
-
-While this model contains a loop, a flux state exists which has no flux
-through reaction v3, and is identified by loopless FBA.
-
-.. code:: python
-
-    construct_loopless_model(test_model).optimize()
-
-
-
-
-.. parsed-literal::
-
-    <Solution 1000.00 at 0x7f003ad82850>
-
-
-
-However, if flux is forced through v3, then there is no longer a
-feasible loopless solution.
-
-.. code:: python
-
-    v3.lower_bound = 1
-    construct_loopless_model(test_model).optimize()
-
-
-
-
-.. parsed-literal::
-
-    <Solution 'infeasible' at 0x7f003ad82f10>
-
-
-
-Loopless FBA is also possible on genome scale models, but it requires a
-capable MILP solver.
-
-.. code:: python
-
-    salmonella = cobra.test.create_test_model("salmonella")
-    construct_loopless_model(salmonella).optimize(solver=get_solver_name(mip=True))
-
-
-
-
-.. parsed-literal::
-
-    <Solution 0.38 at 0x7f003a496190>
-
-
-
-.. code:: python
-
-    ecoli = cobra.test.create_test_model("ecoli")
-    construct_loopless_model(ecoli).optimize(solver=get_solver_name(mip=True))
-
-
-
-
-.. parsed-literal::
-
-    <Solution 0.98 at 0x7f003ae06b50>
-
-
diff --git a/documentation_builder/loopless_files/loopless_3_0.png b/documentation_builder/loopless_files/loopless_3_0.png
deleted file mode 100644
index 6b1bc6c..0000000
Binary files a/documentation_builder/loopless_files/loopless_3_0.png and /dev/null differ
diff --git a/documentation_builder/milp.ipynb b/documentation_builder/milp.ipynb
index 6d5bc93..7e92e1f 100644
--- a/documentation_builder/milp.ipynb
+++ b/documentation_builder/milp.ipynb
@@ -401,21 +401,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/milp.rst b/documentation_builder/milp.rst
deleted file mode 100644
index 7cc3595..0000000
--- a/documentation_builder/milp.rst
+++ /dev/null
@@ -1,283 +0,0 @@
-
-Mixed-Integer Linear Programming
-================================
-
-Ice Cream
----------
-
-This example was originally contributed by Joshua Lerman.
-
-An ice cream stand sells cones and popsicles. It wants to maximize its
-profit, but is subject to a budget.
-
-We can write this problem as a linear program:
-
-    **max** cone :math:`\cdot` cone\_margin + popsicle :math:`\cdot`
-    popsicle margin
-
-    *subject to*
-
-    cone :math:`\cdot` cone\_cost + popsicle :math:`\cdot`
-    popsicle\_cost :math:`\le` budget
-
-.. code:: python
-
-    cone_selling_price = 7.
-    cone_production_cost = 3.
-    popsicle_selling_price = 2.
-    popsicle_production_cost = 1.
-    starting_budget = 100.
-
-This problem can be written as a cobra.Model
-
-.. code:: python
-
-    from cobra import Model, Metabolite, Reaction
-    
-    cone = Reaction("cone")
-    popsicle = Reaction("popsicle")
-    
-    # constrainted to a budget
-    budget = Metabolite("budget")
-    budget._constraint_sense = "L"
-    budget._bound = starting_budget
-    cone.add_metabolites({budget: cone_production_cost})
-    popsicle.add_metabolites({budget: popsicle_production_cost})
-    
-    # objective coefficient is the profit to be made from each unit
-    cone.objective_coefficient = cone_selling_price - cone_production_cost
-    popsicle.objective_coefficient = popsicle_selling_price - \
-                                     popsicle_production_cost
-    
-    m = Model("lerman_ice_cream_co")
-    m.add_reactions((cone, popsicle))
-    
-    m.optimize().x_dict
-
-
-
-
-.. parsed-literal::
-
-    {'cone': 33.333333333333336, 'popsicle': 0.0}
-
-
-
-In reality, cones and popsicles can only be sold in integer amounts. We
-can use the variable kind attribute of a cobra.Reaction to enforce this.
-
-.. code:: python
-
-    cone.variable_kind = "integer"
-    popsicle.variable_kind = "integer"
-    m.optimize().x_dict
-
-
-
-
-.. parsed-literal::
-
-    {'cone': 33.0, 'popsicle': 1.0}
-
-
-
-Now the model makes both popsicles and cones.
-
-Restaurant Order
-----------------
-
-To tackle the less immediately obvious problem from the following `XKCD
-comic <http://xkcd.com/287/>`__:
-
-.. code:: python
-
-    from IPython.display import Image
-    Image(url=r"http://imgs.xkcd.com/comics/np_complete.png")
-
-
-
-
-.. raw:: html
-
-    <img src="http://imgs.xkcd.com/comics/np_complete.png"/>
-
-
-
-We want a solution satisfying the following constraints:
-
-:math:`\left(\begin{matrix}2.15&2.75&3.35&3.55&4.20&5.80\end{matrix}\right) \cdot \vec v = 15.05`
-
-:math:`\vec v_i \ge 0`
-
-:math:`\vec v_i \in \mathbb{Z}`
-
-This problem can be written as a COBRA model as well.
-
-.. code:: python
-
-    total_cost = Metabolite("constraint")
-    total_cost._bound = 15.05
-    
-    costs = {"mixed_fruit": 2.15, "french_fries": 2.75, "side_salad": 3.35,
-             "hot_wings": 3.55, "mozarella_sticks": 4.20, "sampler_plate": 5.80}
-    
-    m = Model("appetizers")
-    
-    for item, cost in costs.items():
-        r = Reaction(item)
-        r.add_metabolites({total_cost: cost})
-        r.variable_kind = "integer"
-        m.add_reaction(r)
-    
-    # To add to the problem, suppose we don't want to eat all mixed fruit.
-    m.reactions.mixed_fruit.objective_coefficient = 1
-        
-    m.optimize(objective_sense="minimize").x_dict
-
-
-
-
-.. parsed-literal::
-
-    {'french_fries': 0.0,
-     'hot_wings': 2.0,
-     'mixed_fruit': 1.0,
-     'mozarella_sticks': 0.0,
-     'sampler_plate': 1.0,
-     'side_salad': 0.0}
-
-
-
-There is another solution to this problem, which would have been
-obtained if we had maximized for mixed fruit instead of minimizing.
-
-.. code:: python
-
-    m.optimize(objective_sense="maximize").x_dict
-
-
-
-
-.. parsed-literal::
-
-    {'french_fries': 0.0,
-     'hot_wings': 0.0,
-     'mixed_fruit': 7.0,
-     'mozarella_sticks': 0.0,
-     'sampler_plate': 0.0,
-     'side_salad': 0.0}
-
-
-
-Boolean Indicators
-------------------
-
-To give a COBRA-related example, we can create boolean variables as
-integers, which can serve as indicators for a reaction being active in a
-model. For a reaction flux :math:`v` with lower bound -1000 and upper
-bound 1000, we can create a binary variable :math:`b` with the following
-constraints:
-
-:math:`b \in \{0, 1\}`
-
-:math:`-1000 \cdot b \le v \le 1000 \cdot b`
-
-To introduce the above constraints into a cobra model, we can rewrite
-them as follows
-
-:math:`v \le b \cdot 1000 \Rightarrow v- 1000\cdot b \le 0`
-
-:math:`-1000 \cdot b \le v \Rightarrow v + 1000\cdot b \ge 0`
-
-.. code:: python
-
-    import cobra.test
-    model = cobra.test.create_test_model("textbook")
-    
-    # an indicator for pgi
-    pgi = model.reactions.get_by_id("PGI")
-    # make a boolean variable
-    pgi_indicator = Reaction("indicator_PGI")
-    pgi_indicator.lower_bound = 0
-    pgi_indicator.upper_bound = 1
-    pgi_indicator.variable_kind = "integer"
-    # create constraint for v - 1000 b <= 0
-    pgi_plus = Metabolite("PGI_plus")
-    pgi_plus._constraint_sense = "L"
-    # create constraint for v + 1000 b >= 0
-    pgi_minus = Metabolite("PGI_minus")
-    pgi_minus._constraint_sense = "G"
-    
-    pgi_indicator.add_metabolites({pgi_plus: -1000, pgi_minus: 1000})
-    pgi.add_metabolites({pgi_plus: 1, pgi_minus: 1})
-    model.add_reaction(pgi_indicator)
-    
-    
-    # an indicator for zwf
-    zwf = model.reactions.get_by_id("G6PDH2r")
-    zwf_indicator = Reaction("indicator_ZWF")
-    zwf_indicator.lower_bound = 0
-    zwf_indicator.upper_bound = 1
-    zwf_indicator.variable_kind = "integer"
-    # create constraint for v - 1000 b <= 0
-    zwf_plus = Metabolite("ZWF_plus")
-    zwf_plus._constraint_sense = "L"
-    # create constraint for v + 1000 b >= 0
-    zwf_minus = Metabolite("ZWF_minus")
-    zwf_minus._constraint_sense = "G"
-    
-    zwf_indicator.add_metabolites({zwf_plus: -1000, zwf_minus: 1000})
-    zwf.add_metabolites({zwf_plus: 1, zwf_minus: 1})
-    
-    # add the indicator reactions to the model
-    model.add_reaction(zwf_indicator)
-
-
-In a model with both these reactions active, the indicators will also be
-active
-
-.. code:: python
-
-    solution = model.optimize()
-    print("PGI indicator = %d" % solution.x_dict["indicator_PGI"])
-    print("ZWF indicator = %d" % solution.x_dict["indicator_ZWF"])
-    print("PGI flux = %.2f" % solution.x_dict["PGI"])
-    print("ZWF flux = %.2f" % solution.x_dict["G6PDH2r"])
-
-
-.. parsed-literal::
-
-    PGI indicator = 1
-    ZWF indicator = 1
-    PGI flux = 4.86
-    ZWF flux = 4.96
-
-
-Because these boolean indicators are in the model, additional
-constraints can be applied on them. For example, we can prevent both
-reactions from being active at the same time by adding the following
-constraint:
-
-:math:`b_\text{pgi} + b_\text{zwf} = 1`
-
-.. code:: python
-
-    or_constraint = Metabolite("or")
-    or_constraint._bound = 1
-    zwf_indicator.add_metabolites({or_constraint: 1})
-    pgi_indicator.add_metabolites({or_constraint: 1})
-    
-    solution = model.optimize()
-    print("PGI indicator = %d" % solution.x_dict["indicator_PGI"])
-    print("ZWF indicator = %d" % solution.x_dict["indicator_ZWF"])
-    print("PGI flux = %.2f" % solution.x_dict["PGI"])
-    print("ZWF flux = %.2f" % solution.x_dict["G6PDH2r"])
-
-
-.. parsed-literal::
-
-    PGI indicator = 1
-    ZWF indicator = 0
-    PGI flux = 9.82
-    ZWF flux = 0.00
-
diff --git a/documentation_builder/phenotype_phase_plane.ipynb b/documentation_builder/phenotype_phase_plane.ipynb
index dcf05d2..0b16484 100644
--- a/documentation_builder/phenotype_phase_plane.ipynb
+++ b/documentation_builder/phenotype_phase_plane.ipynb
@@ -47,10 +47,10 @@
    "outputs": [
     {
      "data": {
-      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9QYXR0ZXJuIDUgMCBSCi9Qcm9jU2V0\nIFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9FeHRHU3RhdGUgNCAwIFIK\nL1NoYWRpbmcgNiAwIFIgL0ZvbnQgMyAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9Hcm91cCA8\nPCAvQ1MgL0RldmljZVJHQiAvUyAvVHJhbnNwYXJlbmN5IC9UeXBlIC9Hcm91cCA+PiAvUGFyZW50\nIDIgMCBSCi9NZWRpYUJveCBbIDAgMCA0MjQuOCAyODAuOCBdIC9Bbm5vdHMgWyBdIC9SZXNvdXJj [...]
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAagAAAEYCAYAAAAJeGK1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvWmQHFV69/vLrMza96WrWy3QAhK7BIgdiV2AQDBjhmGW\n63ccMxNj+0bYN+y44fAHf/Jyw+H7dRzvOMa+98Zrjz2rZ2HRAohFCAkQCCRAEqAFIamX6tqrsqpy\nvx+ys1SqaW2t7lZL5C+CkJBKffJUZZ0n/+c8z/MXbNvGw8PDw8NjviFe6Avw8PDw8PCYCi9AeXh4\neHjMS7wA5eHh4eExL/EClIeHh4fHvMQLUB4eHh4e8xLpDH/vpfh5eHh4eMw2wlR/6CkoDw8PD495\niRegPDw8PDzmJV6A8vDw8PCYl3gBysPDw8NjXuIFKA8PDw+PeYkXoDw8PDw85iVegPLw8PDwmJd4\nAcrD [...]
+      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1BhZ2VzIDIgMCBSIC9UeXBlIC9DYXRhbG9nID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9TaGFkaW5nIDYgMCBSIC9Gb250IDMgMCBSCi9Qcm9jU2V0IFsg\nL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9YT2JqZWN0IDcgMCBSCi9FeHRH\nU3RhdGUgNCAwIFIgL1BhdHRlcm4gNSAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9SZXNvdXJj\nZXMgOCAwIFIgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWyAwIDAgNDI0LjggMjgwLjggXQovQW5u\nb3RzIFsgXSAvR3JvdXAgPDwgL0NTIC9EZXZpY2VSR0IgL1MgL1RyYW5zcGFyZW5jeSAvVHlwZSAv [...]
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAagAAAEYCAYAAAAJeGK1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvWmQHGWa5/nzK+77yMhUSiAJJG5xFqe4xS0omqKoqt6e\nGqsqq55em+21bltr6w/zabp7ra1tP3bbdFv37K7tTM10F3VzSAIhDiEkQIBACCFA6EBSKjMy7giP\nCL/3g6cHoahMHanMVEr4zwxTogzF6+7h8T7+f9/nef6C4zj4+Pj4+PgsNsRzfQA+Pj4+Pj7T4Qco\nHx8fH59FiR+gfHx8fHwWJX6A8vHx8fFZlPgBysfHx8dnUSKf4vd+ip+Pj4+Pz3wjTPeXvoLy8fHx\n8VmU+AHKx8fHx2dR4gcoHx8fH59FiR+gfHx8fHwWJX6A8vHx8fFZlPgBysfHx8dnUeIHKB8fHx+f\nRYkf [...]
       "text/plain": [
-       "<matplotlib.figure.Figure at 0x7feab72c2750>"
+       "<matplotlib.figure.Figure at 0x7fd0f14fcc88>"
       ]
      },
      "metadata": {},
@@ -79,10 +79,10 @@
    "outputs": [
     {
      "data": {
-      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9QYXR0ZXJuIDUgMCBSCi9Qcm9jU2V0\nIFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9FeHRHU3RhdGUgNCAwIFIK\nL1NoYWRpbmcgNiAwIFIgL0ZvbnQgMyAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9Hcm91cCA8\nPCAvQ1MgL0RldmljZVJHQiAvUyAvVHJhbnNwYXJlbmN5IC9UeXBlIC9Hcm91cCA+PiAvUGFyZW50\nIDIgMCBSCi9NZWRpYUJveCBbIDAgMCA0MjQuOCAyODAuOCBdIC9Bbm5vdHMgWyBdIC9SZXNvdXJj [...]
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAagAAAEYCAYAAAAJeGK1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsnXV0VFn2tp/yiicQIQ4BAgnu7q6Nu7u2Ttt0zze/kZ5p\nmzbcadyDOwQJDgFCsAAhaIAQT1nKvj+KW1NkgoWEBPo+a/ViNQl1pe49+7znvHtvidVqRURERERE\npKQhLe4TEBERERERyQ8xQImIiIiIlEjEACUiIiIiUiIRA5SIiIiISIlEDFAiIiIiIiUS+Qt+Llr8\nRERERESKGkl+fykqKBERERGREokYoERERERESiRigBIRERERKZGIAUpEREREpEQiBigRERERkRKJ\nGKBEREREREokYoASERERESmRiAFKRERERKREIgYoEREREZESiRigRERERERKJGKAEhEREREpkYgB\nSkRE [...]
+      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1BhZ2VzIDIgMCBSIC9UeXBlIC9DYXRhbG9nID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9TaGFkaW5nIDYgMCBSIC9Gb250IDMgMCBSCi9Qcm9jU2V0IFsg\nL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9YT2JqZWN0IDcgMCBSCi9FeHRH\nU3RhdGUgNCAwIFIgL1BhdHRlcm4gNSAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9SZXNvdXJj\nZXMgOCAwIFIgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWyAwIDAgNDI0LjggMjgwLjggXQovQW5u\nb3RzIFsgXSAvR3JvdXAgPDwgL0NTIC9EZXZpY2VSR0IgL1MgL1RyYW5zcGFyZW5jeSAvVHlwZSAv [...]
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAagAAAEYCAYAAAAJeGK1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsnXdUVFn2tp/KVWQQBEEQUVFQzDlnbVOrbc45tx2m43T/\nvok903HsYM5tzpgzYs4RRcyioqhIpnL6/sBbUzKYEATt+6zVy9VSckPde/Z5z3n33hK73Y6IiIiI\niEhJQ1rcJyAiIiIiIpIfYoASERERESmRiAFKRERERKREIgYoEREREZESiRigRERERERKJPLn/Fy0\n+ImIiIiIFDWS/P5SVFAiIiIiIiUSMUCJiIiIiJRIxAAlIiIiIlIiEQOUiIiIiEiJRAxQIiIiIiIl\nEjFAiYiIiIiUSMQAJSIiIiJSIhEDlIiIiIhIiUQMUCIiIiIiJRIxQImIiIiIlEjEACUiIiIiUiIR\nA5SI [...]
       "text/plain": [
-       "<matplotlib.figure.Figure at 0x7feae80886d0>"
+       "<matplotlib.figure.Figure at 0x7fd0d8ff8e80>"
       ]
      },
      "metadata": {},
@@ -90,10 +90,10 @@
     },
     {
      "data": {
-      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9QYXR0ZXJuIDUgMCBSCi9Qcm9jU2V0\nIFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9FeHRHU3RhdGUgNCAwIFIK\nL1NoYWRpbmcgNiAwIFIgL0ZvbnQgMyAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9Hcm91cCA8\nPCAvQ1MgL0RldmljZVJHQiAvUyAvVHJhbnNwYXJlbmN5IC9UeXBlIC9Hcm91cCA+PiAvUGFyZW50\nIDIgMCBSCi9NZWRpYUJveCBbIDAgMCA0MjQuOCAyODAuOCBdIC9Bbm5vdHMgWyBdIC9SZXNvdXJj [...]
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAagAAAEYCAYAAAAJeGK1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsnXd4HNW5/z8zs31XW1Xcbbn3gm3cu8FgTA0BQgkJJJQQ\nSLklueEWSL25yU2jhsAludeQENovFIMxxt249957kbS97075/bGazVrITZZk2czneXiUWNKemdXs\nec/3nO/7voKmaRgYGBgYGLQ1xIt9AQYGBgYGBo1hBCgDAwMDgzaJEaAMDAwMDNokRoAyMDAwMGiT\nGAHKwMDAwKBNYjrL9w2Ln4GBgYFBSyM09o+GgjIwMDAwaJMYAcrAwMDAoE1iBCgDAwMDgzaJEaAM\nDAwMDNokRoAyMDAwMGiTGAHKwMDAwKBNYgQoAwMDA4M2iRGgDAwMDAzaJEaAMjAwMDBokxgBysDA\nwMCg [...]
+      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1BhZ2VzIDIgMCBSIC9UeXBlIC9DYXRhbG9nID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9TaGFkaW5nIDYgMCBSIC9Gb250IDMgMCBSCi9Qcm9jU2V0IFsg\nL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9YT2JqZWN0IDcgMCBSCi9FeHRH\nU3RhdGUgNCAwIFIgL1BhdHRlcm4gNSAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9SZXNvdXJj\nZXMgOCAwIFIgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWyAwIDAgNDI0LjggMjgwLjggXQovQW5u\nb3RzIFsgXSAvR3JvdXAgPDwgL0NTIC9EZXZpY2VSR0IgL1MgL1RyYW5zcGFyZW5jeSAvVHlwZSAv [...]
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAagAAAEYCAYAAAAJeGK1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsnXeYHNWVt9+q6pzDBGVplHNCQjlLCBAiGRtswBiwARvD\nOqzD2p93sddhWXsdFhDBxmGNMZhkEwQooJxzzlkaSTPTOYeq+v7oqXZrGKXRzGgk6n0enrFnWn2r\nqqvvqd+9v3OOoKoqOjo6Ojo6bQ3xch+Ajo6Ojo5OY+gBSkdHR0enTaIHKB0dHR2dNokeoHR0dHR0\n2iR6gNLR0dHRaZMYzvN33eKno6Ojo9PSCI39UldQOjo6OjptEj1A6ejo6Oi0SfQApaOjo6PTJtED\nlI6Ojo5Om0QPUDo6Ojo6bRI9QOno6OjotEn0AKWjo6Oj0ybRA5SOjo6OTptED1A6Ojo6Om0SPUDp\n6Ojo [...]
       "text/plain": [
-       "<matplotlib.figure.Figure at 0x7feab7137e50>"
+       "<matplotlib.figure.Figure at 0x7fd0c9b93748>"
       ]
      },
      "metadata": {},
@@ -121,10 +121,10 @@
    "outputs": [
     {
      "data": {
-      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9QYXR0ZXJuIDUgMCBSCi9Qcm9jU2V0\nIFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9FeHRHU3RhdGUgNCAwIFIK\nL1NoYWRpbmcgNiAwIFIgL0ZvbnQgMyAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9Hcm91cCA8\nPCAvQ1MgL0RldmljZVJHQiAvUyAvVHJhbnNwYXJlbmN5IC9UeXBlIC9Hcm91cCA+PiAvUGFyZW50\nIDIgMCBSCi9NZWRpYUJveCBbIDAgMCA0MjQuOCAyODAuOCBdIC9Bbm5vdHMgWyBdIC9SZXNvdXJj [...]
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAagAAAEYCAYAAAAJeGK1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvXmUHOV57/+p3pfp6e7p2aQRCEmWhMQiIUAIoQUkhNDK\nbmy8xRAbkjh2bs61f1nOTRyc3FznJDfOdRL7OI5xbExsA2bRjhDCArQgkEASSCAhabTN0t3T+95V\n9fujplrdPb3N3pLrc44Phpl3urpnup5+3vf7fL+CLMtoaGhoaGjUG7rxvgANDQ0NDY1SaAVKQ0ND\nQ6Mu0QqUhoaGhkZdohUoDQ0NDY26RCtQGhoaGhp1iaHK1zWJn4aGhobGaCOU+o9aB6WhoaGhUZdo\nBUpDQ0NDoy7RCpSGhoaGRl2iFSgNDQ0NjbpEK1AaGhoaGnWJVqA0NDQ0NOoSrUBpaGhoaNQlWoHS\n0NDQ [...]
+      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1BhZ2VzIDIgMCBSIC9UeXBlIC9DYXRhbG9nID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9TaGFkaW5nIDYgMCBSIC9Gb250IDMgMCBSCi9Qcm9jU2V0IFsg\nL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9YT2JqZWN0IDcgMCBSCi9FeHRH\nU3RhdGUgNCAwIFIgL1BhdHRlcm4gNSAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9SZXNvdXJj\nZXMgOCAwIFIgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWyAwIDAgNDI0LjggMjgwLjggXQovQW5u\nb3RzIFsgXSAvR3JvdXAgPDwgL0NTIC9EZXZpY2VSR0IgL1MgL1RyYW5zcGFyZW5jeSAvVHlwZSAv [...]
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAagAAAEYCAYAAAAJeGK1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvXmYW+WZ5v072pdSSSqpNpfxio2NARsDxhgv4AXjNQQC\nJHRWmIR0dzqZmWuSr5drpjOke/rruXqm05NMJ186HdIJoZMQwuLdBsxijI3BBhu8YGO81yaV9l3n\nnO+PU0eWVNpql53zuy4uMFWvdCRXnUfP+97PfQuyLKOhoaGhoVFv6Mb7AjQ0NDQ0NEqhFSgNDQ0N\njbpEK1AaGhoaGnWJVqA0NDQ0NOoSrUBpaGhoaNQlhipf1yR+GhoaGhqjjVDqf2odlIaGhoZGXaIV\nKA0NDQ2NukQrUBoaGhoadYlWoDQ0NDQ06hKtQGloaGho1CVagdLQ0NDQqEu0AqWhoaGhUZdoBUpD\nQ0ND [...]
       "text/plain": [
-       "<matplotlib.figure.Figure at 0x7feab6ff7490>"
+       "<matplotlib.figure.Figure at 0x7fd0c9a50cc0>"
       ]
      },
      "metadata": {},
@@ -156,8 +156,8 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "took 0.41 seconds with 1 process\n",
-      "took 0.22 seconds with 4 process\n"
+      "took 0.44 seconds with 1 process\n",
+      "took 0.25 seconds with 4 process\n"
      ]
     }
    ],
@@ -180,21 +180,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/phenotype_phase_plane.rst b/documentation_builder/phenotype_phase_plane.rst
deleted file mode 100644
index 783279d..0000000
--- a/documentation_builder/phenotype_phase_plane.rst
+++ /dev/null
@@ -1,85 +0,0 @@
-
-Phenotype Phase Plane
-=====================
-
-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>`__
-
-Cobrapy supports calculating and plotting (using
-`matplotlib <http://matplotlib.org>`__) these phenotype phase planes.
-Here, we will make one for the "textbook" *E. coli* core model.
-
-.. code:: python
-
-    %matplotlib inline
-    from time import time
-    
-    import cobra.test
-    from cobra.flux_analysis import calculate_phenotype_phase_plane
-    
-    model = cobra.test.create_test_model("textbook")
-
-We want to make a phenotype phase plane to evaluate uptakes of Glucose
-and Oxygen.
-
-.. code:: python
-
-    data = calculate_phenotype_phase_plane(model, "EX_glc__D_e", "EX_o2_e")
-    data.plot_matplotlib();
-
-
-
-.. image:: phenotype_phase_plane_files/phenotype_phase_plane_3_0.png
-
-
-If `palettable <https://github.com/jiffyclub/palettable>`__ is
-installed, other color schemes can be used as well
-
-.. code:: python
-
-    data.plot_matplotlib("Pastel1")
-    data.plot_matplotlib("Dark2");
-
-
-
-.. image:: phenotype_phase_plane_files/phenotype_phase_plane_5_0.png
-
-
-
-.. image:: phenotype_phase_plane_files/phenotype_phase_plane_5_1.png
-
-
-The number of points which are plotted in each dimension can also be
-changed
-
-.. code:: python
-
-    calculate_phenotype_phase_plane(model, "EX_glc__D_e", "EX_o2_e",
-                                    reaction1_npoints=20,
-                                    reaction2_npoints=20).plot_matplotlib();
-
-
-
-.. image:: phenotype_phase_plane_files/phenotype_phase_plane_7_0.png
-
-
-The code can also use multiple processes to speed up calculations
-
-.. code:: python
-
-    start_time = time()
-    calculate_phenotype_phase_plane(model, "EX_glc__D_e", "EX_o2_e", n_processes=1,
-                                    reaction1_npoints=100, reaction2_npoints=100)
-    print("took %.2f seconds with 1 process" % (time() - start_time))
-    start_time = time()
-    calculate_phenotype_phase_plane(model, "EX_glc__D_e", "EX_o2_e", n_processes=4,
-                                    reaction1_npoints=100, reaction2_npoints=100)
-    print("took %.2f seconds with 4 process" % (time() - start_time))
-
-
-.. parsed-literal::
-
-    took 0.41 seconds with 1 process
-    took 0.29 seconds with 4 process
-
diff --git a/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_3_0.png b/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_3_0.png
deleted file mode 100644
index bc6dac4..0000000
Binary files a/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_3_0.png and /dev/null differ
diff --git a/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_5_0.png b/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_5_0.png
deleted file mode 100644
index 8413a31..0000000
Binary files a/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_5_0.png and /dev/null differ
diff --git a/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_5_1.png b/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_5_1.png
deleted file mode 100644
index ff8c9d4..0000000
Binary files a/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_5_1.png and /dev/null differ
diff --git a/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_7_0.png b/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_7_0.png
deleted file mode 100644
index 47ce17c..0000000
Binary files a/documentation_builder/phenotype_phase_plane_files/phenotype_phase_plane_7_0.png and /dev/null differ
diff --git a/documentation_builder/pymatbridge.ipynb b/documentation_builder/pymatbridge.ipynb
index 64e2e68..51a7731 100644
--- a/documentation_builder/pymatbridge.ipynb
+++ b/documentation_builder/pymatbridge.ipynb
@@ -22,7 +22,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Starting MATLAB on ZMQ socket ipc:///tmp/pymatbridge-39fd5b7f-475a-40d3-b831-3adf4da6edd3\n",
+      "Starting MATLAB on ZMQ socket ipc:///tmp/pymatbridge-57ff5429-02d9-4e1a-8ed0-44e391fb0df7\n",
       "Send 'exit' command to kill the server\n",
       "....MATLAB started and connected!\n"
      ]
@@ -86,6 +86,7 @@
        "            rev: [95x1 double]\n",
        "       metNames: {72x1 cell}\n",
        "              b: [72x1 double]\n",
+       "      metCharge: [72x1 double]\n",
        "              c: [95x1 double]\n",
        "         csense: [72x1 char]\n",
        "          genes: {137x1 cell}\n",
@@ -93,7 +94,7 @@
        "           rxns: {95x1 cell}\n",
        "        grRules: {95x1 cell}\n",
        "       rxnNames: {95x1 cell}\n",
-       "    description: [8x1 char]\n",
+       "    description: [11x1 char]\n",
        "              S: [72x95 double]\n",
        "             ub: [95x1 double]\n",
        "             lb: [95x1 double]\n",
@@ -159,7 +160,7 @@
        "        stat: 1\n",
        "    origStat: 5\n",
        "      solver: 'glpk'\n",
-       "        time: 0.2327\n",
+       "        time: 3.2911\n",
        "\n"
       ]
      },
@@ -190,14 +191,14 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 5 µs, sys: 0 ns, total: 5 µs\n",
-      "Wall time: 10 µs\n"
+      "CPU times: user 0 ns, sys: 0 ns, total: 0 ns\n",
+      "Wall time: 5.48 µs\n"
      ]
     },
     {
      "data": {
       "text/plain": [
-       "0.8739215069684305"
+       "0.8739215069684909"
       ]
      },
      "execution_count": 7,
@@ -213,21 +214,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/pymatbridge.rst b/documentation_builder/pymatbridge.rst
deleted file mode 100644
index 8347018..0000000
--- a/documentation_builder/pymatbridge.rst
+++ /dev/null
@@ -1,124 +0,0 @@
-
-Using the COBRA toolbox with cobrapy
-====================================
-
-This example demonstrates using COBRA toolbox commands in MATLAB from
-python through
-`pymatbridge <http://arokem.github.io/python-matlab-bridge/>`__.
-
-.. code:: python
-
-    %load_ext pymatbridge
-
-
-.. parsed-literal::
-
-    Starting MATLAB on ZMQ socket ipc:///tmp/pymatbridge-39fd5b7f-475a-40d3-b831-3adf4da6edd3
-    Send 'exit' command to kill the server
-    ....MATLAB started and connected!
-
-
-.. code:: python
-
-    import cobra.test
-    m = cobra.test.create_test_model("textbook")
-
-The model\_to\_pymatbridge function will send the model to the workspace
-with the given variable name.
-
-.. code:: python
-
-    from cobra.io.mat import model_to_pymatbridge
-    model_to_pymatbridge(m, variable_name="model")
-
-Now in the MATLAB workspace, the variable name 'model' holds a COBRA
-toolbox struct encoding the model.
-
-.. code:: python
-
-    %%matlab
-    model
-
-
-
-.. parsed-literal::
-
-    
-    model = 
-    
-                rev: [95x1 double]
-           metNames: {72x1 cell}
-                  b: [72x1 double]
-                  c: [95x1 double]
-             csense: [72x1 char]
-              genes: {137x1 cell}
-        metFormulas: {72x1 cell}
-               rxns: {95x1 cell}
-            grRules: {95x1 cell}
-           rxnNames: {95x1 cell}
-        description: [8x1 char]
-                  S: [72x95 double]
-                 ub: [95x1 double]
-                 lb: [95x1 double]
-               mets: {72x1 cell}
-         subSystems: {95x1 cell}
-    
-
-
-
-First, we have to initialize the COBRA toolbox in MATLAB.
-
-.. code:: python
-
-    %%matlab --silent
-    warning('off'); % this works around a pymatbridge bug
-    addpath(genpath('~/cobratoolbox/'));
-    initCobraToolbox();
-
-Commands from the COBRA toolbox can now be run on the model
-
-.. code:: python
-
-    %%matlab
-    optimizeCbModel(model)
-
-
-
-.. parsed-literal::
-
-    
-    ans = 
-    
-               x: [95x1 double]
-               f: 0.8739
-               y: [71x1 double]
-               w: [95x1 double]
-            stat: 1
-        origStat: 5
-          solver: 'glpk'
-            time: 0.2327
-    
-
-
-
-FBA in the COBRA toolbox should give the same result as cobrapy
-
-.. code:: python
-
-    %time
-    m.optimize().f
-
-
-.. parsed-literal::
-
-    CPU times: user 5 µs, sys: 0 ns, total: 5 µs
-    Wall time: 10 µs
-
-
-
-
-.. parsed-literal::
-
-    0.8739215069684305
-
-
diff --git a/documentation_builder/qp.ipynb b/documentation_builder/qp.ipynb
index db3aad8..916b95f 100644
--- a/documentation_builder/qp.ipynb
+++ b/documentation_builder/qp.ipynb
@@ -30,10 +30,10 @@
    "outputs": [
     {
      "data": {
-      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9QYXR0ZXJuIDUgMCBSCi9Qcm9jU2V0\nIFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9FeHRHU3RhdGUgNCAwIFIK\nL1NoYWRpbmcgNiAwIFIgL0ZvbnQgMyAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9Hcm91cCA8\nPCAvQ1MgL0RldmljZVJHQiAvUyAvVHJhbnNwYXJlbmN5IC9UeXBlIC9Hcm91cCA+PiAvUGFyZW50\nIDIgMCBSCi9NZWRpYUJveCBbIDAgMCAzNTMuODg4MDY4MTgxOCAyOTQuMjE4MTgxODE4MiBdIC9B [...]
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWEAAAEmCAYAAACzoiEDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsnXlUVFe+779VzKPMoOCEOIAIzrNG42zUxCTGJMZEM5ik\nc5Pu28Nbb9217nt9+6773l2378qL3aaTKCbGmBgn1Dij4gg4gYJMAoIgUwEFVUXNVefs9weNLVah\nDPsMVezPWnfdzin87U1R9Tv7/IbvT0EIgZAoFAoFEGwBdnsBywHEAlC/Rwj5VtCFGQwGwwVQiOCE\n1wP4GRj09ytGAMp7hJjHCbowg8FguACCO2EAUCgUEwB4AsgDMBlAJSFEK/jCDAaDIXNEccKPFlMo\nCCFEIdqCDAaDIXOUUm+AwWAwBjLMCTMYDIaEePbh3/Q3fiFe/IPBYDDkg9NQLDsJMxgMhoQwJ8xg\nMBgS [...]
+      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9Qcm9jU2V0IFsgL1BERiAvVGV4dCAv\nSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdCi9Gb250IDMgMCBSIC9TaGFkaW5nIDYgMCBSIC9QYXR0\nZXJuIDUgMCBSIC9FeHRHU3RhdGUgNCAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9QYXJlbnQg\nMiAwIFIgL1Jlc291cmNlcyA4IDAgUiAvVHlwZSAvUGFnZSAvQ29udGVudHMgOSAwIFIKL01lZGlh\nQm94IFsgMCAwIDM1My44ODgwNjgxODE4IDI5NC4yMTgxODE4MTgyIF0KL0dyb3VwIDw8IC9UeXBl [...]
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWEAAAEmCAYAAACzoiEDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzsvWd0VFea7/2vUs45gEQSIkgIiZzBYGySARvbGNsYGxyw\n3R67e7qn58Ost+9Md6+5d9b03L6mG7dtEDbG2JgkwASBABElEQXKQhEJZamkqlLlqnP2+0EtGlEl\nUNgnVGn/1vLq5pT07K2S6jn7POH/KAghEBKFQqEAAs3AHg9gBYAYAKr3CCHfCLowg8FgOAEKEZzw\nBgA/AUF/v2IAoLxHiGmioAszGAyGEyC4EwYAhUIxGYA7gFwA0wBUEUI0gi/MYDAYMkcUJ/xwMYWC\nEEIUoi3IYDAYMkcp9QYYDAZjKMOcMIPBYEiI+wC+Z7DxC/HiHwwGgyEfHIZi2UmYwWAwJIQ5YQaD\nwZAQ [...]
       "text/plain": [
-       "<matplotlib.figure.Figure at 0x7faac037ff10>"
+       "<matplotlib.figure.Figure at 0x7f97b409d8d0>"
       ]
      },
      "metadata": {},
@@ -88,7 +88,7 @@
     {
      "data": {
       "text/plain": [
-       "<2x2 sparse matrix of type '<type 'numpy.float64'>'\n",
+       "<2x2 sparse matrix of type '<class 'numpy.float64'>'\n",
        "\twith 2 stored elements in Dictionary Of Keys format>"
       ]
      },
@@ -150,7 +150,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "gurobi\n"
+      "cplex\n"
      ]
     }
    ],
@@ -217,10 +217,10 @@
    "outputs": [
     {
      "data": {
-      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9QYXR0ZXJuIDUgMCBSCi9Qcm9jU2V0\nIFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9FeHRHU3RhdGUgNCAwIFIK\nL1NoYWRpbmcgNiAwIFIgL0ZvbnQgMyAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9Hcm91cCA8\nPCAvQ1MgL0RldmljZVJHQiAvUyAvVHJhbnNwYXJlbmN5IC9UeXBlIC9Hcm91cCA+PiAvUGFyZW50\nIDIgMCBSCi9NZWRpYUJveCBbIDAgMCAzNTMuODg4MDY4MTgxOCAyOTQuMjE4MTgxODE4MiBdIC9B [...]
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWEAAAEmCAYAAACzoiEDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XlUVFe+L/DvgWIoChAEBQVkEIkjCJpBozEajUZjTEcz\naNp0d+yYpO/Kff365va6767V6/Xt9V73uuutezt222mTNnY6mihmMIoT0TiPITiggMyTUsxFDVDT\nqTrvDxpbrAJhn6mq+H3W6tWhqs7eG4Rfndq/vX+bEwQBcuI4jgOi7cAnIcAKAEkAOjcJgrBD1o4J\nIcQPcAoE4ZcB7AHG/P2RXgBBFYJgmyprx4QQ4gdkD8IAwHHcLAAaAFcA5AGoFQTBKHvHhBDi4xQJ\nwnc74zhBEAROsQ4JIcTHBak9AEIIGc0oCBNCiIo0DNeInb9Qbv6DEEJ8h9epWLoTJoQQFVEQJoQQ\nFVEQ [...]
+      "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9Qcm9jU2V0IFsgL1BERiAvVGV4dCAv\nSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdCi9Gb250IDMgMCBSIC9TaGFkaW5nIDYgMCBSIC9QYXR0\nZXJuIDUgMCBSIC9FeHRHU3RhdGUgNCAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9QYXJlbnQg\nMiAwIFIgL1Jlc291cmNlcyA4IDAgUiAvVHlwZSAvUGFnZSAvQ29udGVudHMgOSAwIFIKL01lZGlh\nQm94IFsgMCAwIDM1My44ODgwNjgxODE4IDI5NC4yMTgxODE4MTgyIF0KL0dyb3VwIDw8IC9UeXBl [...]
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWEAAAEmCAYAAACzoiEDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XtU1Ne9N/73F2aAYQBBUFBQLiLxCoLmotEYjcZbjGk0\nF01N09rYpGfl/Pr09HQ9z1mr67R9nl+7znp+5zRpbGpSk6aJRjFNjOKNaLxfQ/ACAnK/CcN9mAvM\n7Tvz/f3BwYozKuzvbWb4vNbqapiZ794bhM98Z3/2/mxOEATIieM4DohxAJ9ogZUAkgF0bxEE4SNZ\nOyaEkADAKRCEXwKwBxjz34/0AwipFAT7NFk7JoSQACB7EAYAjuNmA9AAuAIgD0CdIAgm2TsmhBA/\np0gQvt0ZxwmCIHCKdUgIIX4uRO0BEELIaEZBmBBCVKRhuEbs/IVy8x+EEOI/fE7F0p0wIYSoiIIw\nIYSo [...]
       "text/plain": [
-       "<matplotlib.figure.Figure at 0x7faac4871d90>"
+       "<matplotlib.figure.Figure at 0x7f9796f1e400>"
       ]
      },
      "metadata": {},
@@ -265,21 +265,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/qp.rst b/documentation_builder/qp.rst
deleted file mode 100644
index e02f3e8..0000000
--- a/documentation_builder/qp.rst
+++ /dev/null
@@ -1,190 +0,0 @@
-
-Quadratic Programming
-=====================
-
-Suppose we want to minimize the Euclidean distance of the solution to
-the origin while subject to linear constraints. This will require a
-quadratic objective function. Consider this example problem:
-
-    **min** :math:`\frac{1}{2}\left(x^2 + y^2 \right)`
-
-    *subject to*
-
-    :math:`x + y = 2`
-
-    :math:`x \ge 0`
-
-    :math:`y \ge 0`
-
-This problem can be visualized graphically:
-
-.. code:: python
-
-    %matplotlib inline
-    from matplotlib.pyplot import figure, xlim, ylim
-    from mpl_toolkits.axes_grid.axislines import SubplotZero
-    from numpy import linspace, arange, sqrt, pi, sin, cos, sign
-    # axis style
-    def make_plot_ax():
-        fig = figure(figsize=(6, 5));
-        ax = SubplotZero(fig, 111); fig.add_subplot(ax)
-        for direction in ["xzero", "yzero"]:
-            ax.axis[direction].set_axisline_style("-|>")
-            ax.axis[direction].set_visible(True)
-        for direction in ["left", "right", "bottom", "top"]:
-            ax.axis[direction].set_visible(False)
-        xlim(-0.1, 2.1); ylim(xlim())
-        ticks = [0.5 * i for i in range(1, 5)]
-        labels = [str(i) if i == int(i) else "" for i in ticks]
-        ax.set_xticks(ticks); ax.set_yticks(ticks)
-        ax.set_xticklabels(labels); ax.set_yticklabels(labels)
-        ax.axis["yzero"].set_axis_direction("left")
-        return ax
-        
-    ax = make_plot_ax()
-    ax.plot((0, 2), (2, 0), 'b')
-    ax.plot([1], [1], 'bo')
-    
-    # circular grid
-    for r in sqrt(2.) + 0.125 * arange(-11, 6):
-        t = linspace(0., pi/2., 100)
-        ax.plot(r * cos(t), r * sin(t), '-.', color="gray")
-
-
-
-.. image:: qp_files/qp_1_0.png
-
-
-The objective can be rewritten as
-:math:`\frac{1}{2} v^T \cdot \mathbf Q \cdot v`, where
-:math:`v = \left(\begin{matrix} x \\ y\end{matrix} \right)` and
-:math:`\mathbf Q = \left(\begin{matrix} 1 & 0\\ 0 & 1 \end{matrix}\right)`
-
-The matrix :math:`\mathbf Q` can be passed into a cobra model as the
-quadratic objective.
-
-.. code:: python
-
-    import scipy
-    
-    from cobra import Reaction, Metabolite, Model, solvers
-
-The quadratic objective :math:`\mathbf Q` should be formatted as a scipy
-sparse matrix.
-
-.. code:: python
-
-    Q = scipy.sparse.eye(2).todok()
-    Q
-
-
-
-
-.. parsed-literal::
-
-    <2x2 sparse matrix of type '<type 'numpy.float64'>'
-    	with 2 stored elements in Dictionary Of Keys format>
-
-
-
-In this case, the quadratic objective is simply the identity matrix
-
-.. code:: python
-
-    Q.todense()
-
-
-
-
-.. parsed-literal::
-
-    matrix([[ 1.,  0.],
-            [ 0.,  1.]])
-
-
-
-We need to use a solver that supports quadratic programming, such as
-gurobi or cplex. If a solver which supports quadratic programming is
-installed, this function will return its name.
-
-.. code:: python
-
-    print(solvers.get_solver_name(qp=True))
-
-
-.. parsed-literal::
-
-    gurobi
-
-
-.. code:: python
-
-    c = Metabolite("c")
-    c._bound = 2
-    x = Reaction("x")
-    y = Reaction("y")
-    x.add_metabolites({c: 1})
-    y.add_metabolites({c: 1})
-    m = Model()
-    m.add_reactions([x, y])
-    sol = m.optimize(quadratic_component=Q, objective_sense="minimize")
-    sol.x_dict
-
-
-
-
-.. parsed-literal::
-
-    {'x': 1.0, 'y': 1.0}
-
-
-
-Suppose we change the problem to have a mixed linear and quadratic
-objective.
-
-    **min** :math:`\frac{1}{2}\left(x^2 + y^2 \right) - y`
-
-    *subject to*
-
-    :math:`x + y = 2`
-
-    :math:`x \ge 0`
-
-    :math:`y \ge 0`
-
-Graphically, this would be
-
-.. code:: python
-
-    ax = make_plot_ax()
-    ax.plot((0, 2), (2, 0), 'b')
-    ax.plot([0.5], [1.5], 'bo')
-    
-    yrange = linspace(1, 2, 11)
-    for r in (yrange ** 2 / 2. - yrange):
-        t = linspace(-sqrt(2 * r + 1) + 0.000001, sqrt(2 * r + 1) - 0.000001, 1000)
-        ax.plot(abs(t), 1 + sqrt(2 * r + 1 - t ** 2) * sign(t), '-.', color="gray")
-
-
-
-.. image:: qp_files/qp_12_0.png
-
-
-QP solvers in cobrapy will combine linear and quadratic coefficients.
-The linear portion will be obtained from the same objective\_coefficient
-attribute used with LP's.
-
-.. code:: python
-
-    y.objective_coefficient = -1
-    sol = m.optimize(quadratic_component=Q, objective_sense="minimize")
-    sol.x_dict
-
-
-
-
-.. parsed-literal::
-
-    {'x': 0.5, 'y': 1.5}
-
-
diff --git a/documentation_builder/qp_files/qp_12_0.png b/documentation_builder/qp_files/qp_12_0.png
deleted file mode 100644
index f04d583..0000000
Binary files a/documentation_builder/qp_files/qp_12_0.png and /dev/null differ
diff --git a/documentation_builder/qp_files/qp_1_0.png b/documentation_builder/qp_files/qp_1_0.png
deleted file mode 100644
index d4ef2a1..0000000
Binary files a/documentation_builder/qp_files/qp_1_0.png and /dev/null differ
diff --git a/documentation_builder/requirements.txt b/documentation_builder/requirements.txt
new file mode 100644
index 0000000..c23edec
--- /dev/null
+++ b/documentation_builder/requirements.txt
@@ -0,0 +1,2 @@
+nbsphinx>=0.2.4
+ipykernel
diff --git a/documentation_builder/simulating.ipynb b/documentation_builder/simulating.ipynb
index a7b6793..03a5659 100644
--- a/documentation_builder/simulating.ipynb
+++ b/documentation_builder/simulating.ipynb
@@ -41,7 +41,7 @@
     {
      "data": {
       "text/plain": [
-       "<Solution 0.87 at 0x7ff4fc14b210>"
+       "<Solution 0.87 at 0x10ddd0080>"
       ]
      },
      "execution_count": 2,
@@ -122,6 +122,114 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "### Analyzing FBA solutions\n",
+    "Models solved using FBA can be further analyzed by using summary methods, which output printed text to give a quick representation of model behavior. Calling the summary method on the entire model displays information on the input and output behavior of the model, along with the optimized objective."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "IN FLUXES                     OUT FLUXES                    OBJECTIVES          \n",
+      "o2_e       -21.80             h2o_e    29.18                Biomass_Ecoli_core    0.874\n",
+      "glc__D_e   -10.00             co2_e    22.81                                    \n",
+      "nh4_e       -4.77             h_e      17.53                                    \n",
+      "pi_e        -3.21                                                               \n"
+     ]
+    }
+   ],
+   "source": [
+    "model.summary()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "In addition, the input-output behavior of individual metabolites can also be inspected using summary methods. For instance, the following commands can be used to examine the overall redox balance of the model"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "PRODUCING REACTIONS -- Nicotinamide adenine dinucleotide - reduced\n",
+      "------------------------------------------------------------------\n",
+      "  %      FLUX   RXN ID                        REACTION                       \n",
+      " 41.6%     16     GAPD        g3p_c + nad_c + pi_c <=> 13dpg_c + h_c + nadh_c\n",
+      " 24.1%    9.3      PDH     coa_c + nad_c + pyr_c --> accoa_c + co2_c + nadh_c\n",
+      " 13.1%    5.1    AKGDH    akg_c + coa_c + nad_c --> co2_c + nadh_c + succoa_c\n",
+      " 13.1%    5.1      MDH              mal__L_c + nad_c <=> h_c + nadh_c + oaa_c\n",
+      "  8.0%    3.1 Bioma...   1.496 3pg_c + 3.7478 accoa_c + 59.81 atp_c + 0.36...\n",
+      "\n",
+      "CONSUMING REACTIONS -- Nicotinamide adenine dinucleotide - reduced\n",
+      "------------------------------------------------------------------\n",
+      "  %      FLUX   RXN ID                        REACTION                       \n",
+      "100.0%    -39   NADH16   4.0 h_c + nadh_c + q8_c --> 3.0 h_e + nad_c + q8h2_c\n"
+     ]
+    }
+   ],
+   "source": [
+    "model.metabolites.nadh_c.summary()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Or to get a sense of the main energy production and consumption reactions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "PRODUCING REACTIONS -- ATP\n",
+      "--------------------------\n",
+      "  %      FLUX   RXN ID                        REACTION                       \n",
+      " 66.6%     46   ATPS4r     adp_c + 4.0 h_e + pi_c <=> atp_c + h2o_c + 3.0 h_c\n",
+      " 23.4%     16      PGK                      3pg_c + atp_c <=> 13dpg_c + adp_c\n",
+      "  7.4%    5.1   SUCOAS     atp_c + coa_c + succ_c <=> adp_c + pi_c + succoa_c\n",
+      "  2.6%    1.8      PYK                  adp_c + h_c + pep_c --> atp_c + pyr_c\n",
+      "\n",
+      "CONSUMING REACTIONS -- ATP\n",
+      "--------------------------\n",
+      "  %      FLUX   RXN ID                        REACTION                       \n",
+      " 76.5%    -52 Bioma...   1.496 3pg_c + 3.7478 accoa_c + 59.81 atp_c + 0.36...\n",
+      " 12.3%   -8.4     ATPM                   atp_c + h2o_c --> adp_c + h_c + pi_c\n",
+      " 10.9%   -7.5      PFK                  atp_c + f6p_c --> adp_c + fdp_c + h_c\n"
+     ]
+    }
+   ],
+   "source": [
+    "model.metabolites.atp_c.summary()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
     "## Changing the Objectives\n",
     "\n",
     "The objective function is determined from the objective_coefficient attribute of the objective reaction(s). Generally, a \"biomass\" function which describes the composition of metabolites which make up a cell is used."
@@ -129,7 +237,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 8,
    "metadata": {
     "collapsed": false
    },
@@ -147,7 +255,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 9,
    "metadata": {
     "collapsed": false
    },
@@ -155,10 +263,10 @@
     {
      "data": {
       "text/plain": [
-       "{<Reaction Biomass_Ecoli_core at 0x7ff4cb9a8d90>: 1.0}"
+       "{<Reaction Biomass_Ecoli_core at 0x116510828>: 1.0}"
       ]
      },
-     "execution_count": 6,
+     "execution_count": 9,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -176,7 +284,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 10,
    "metadata": {
     "collapsed": false
    },
@@ -184,10 +292,10 @@
     {
      "data": {
       "text/plain": [
-       "{<Reaction ATPM at 0x7ff4cb9a8b10>: 1}"
+       "{<Reaction ATPM at 0x1165107b8>: 1}"
       ]
      },
-     "execution_count": 7,
+     "execution_count": 10,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -204,7 +312,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 11,
    "metadata": {
     "collapsed": false
    },
@@ -215,7 +323,7 @@
        "174.99999999999997"
       ]
      },
-     "execution_count": 8,
+     "execution_count": 11,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -233,7 +341,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 12,
    "metadata": {
     "collapsed": false
    },
@@ -241,10 +349,10 @@
     {
      "data": {
       "text/plain": [
-       "{<Reaction Biomass_Ecoli_core at 0x7ff4cb9a8d90>: 1.0}"
+       "{<Reaction Biomass_Ecoli_core at 0x116510828>: 1.0}"
       ]
      },
-     "execution_count": 9,
+     "execution_count": 12,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -267,7 +375,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 13,
    "metadata": {
     "collapsed": false
    },
@@ -287,133 +395,133 @@
        "  <tbody>\n",
        "    <tr>\n",
        "      <th>ACALD</th>\n",
-       "      <td>9.466331e-29</td>\n",
-       "      <td>3.720797e-15</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACALDt</th>\n",
-       "      <td>-6.310887e-29</td>\n",
-       "      <td>3.720797e-15</td>\n",
+       "      <td>-0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACKr</th>\n",
-       "      <td>-2.524355e-28</td>\n",
-       "      <td>3.933509e-15</td>\n",
+       "      <td>-0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACONTa</th>\n",
-       "      <td>6.007250e+00</td>\n",
-       "      <td>6.007250e+00</td>\n",
+       "      <td>6.00725</td>\n",
+       "      <td>6.00725</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACONTb</th>\n",
-       "      <td>6.007250e+00</td>\n",
-       "      <td>6.007250e+00</td>\n",
+       "      <td>6.00725</td>\n",
+       "      <td>6.00725</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACt2r</th>\n",
-       "      <td>6.121561e-28</td>\n",
-       "      <td>3.933509e-15</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ADK1</th>\n",
-       "      <td>-4.042971e-14</td>\n",
-       "      <td>0.000000e+00</td>\n",
+       "      <td>-0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>AKGDH</th>\n",
-       "      <td>5.064376e+00</td>\n",
-       "      <td>5.064376e+00</td>\n",
+       "      <td>5.06438</td>\n",
+       "      <td>5.06438</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>AKGt2r</th>\n",
-       "      <td>0.000000e+00</td>\n",
-       "      <td>7.079399e-15</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ALCD2x</th>\n",
-       "      <td>0.000000e+00</td>\n",
-       "      <td>5.729185e-15</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ATPM</th>\n",
-       "      <td>8.390000e+00</td>\n",
-       "      <td>8.390000e+00</td>\n",
+       "      <td>8.39000</td>\n",
+       "      <td>8.39000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ATPS4r</th>\n",
-       "      <td>4.551401e+01</td>\n",
-       "      <td>4.551401e+01</td>\n",
+       "      <td>45.51401</td>\n",
+       "      <td>45.51401</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>Biomass_Ecoli_core</th>\n",
-       "      <td>8.739215e-01</td>\n",
-       "      <td>8.739215e-01</td>\n",
+       "      <td>0.87392</td>\n",
+       "      <td>0.87392</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>CO2t</th>\n",
-       "      <td>-2.280983e+01</td>\n",
-       "      <td>-2.280983e+01</td>\n",
+       "      <td>-22.80983</td>\n",
+       "      <td>-22.80983</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>CS</th>\n",
-       "      <td>6.007250e+00</td>\n",
-       "      <td>6.007250e+00</td>\n",
+       "      <td>6.00725</td>\n",
+       "      <td>6.00725</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>CYTBD</th>\n",
-       "      <td>4.359899e+01</td>\n",
-       "      <td>4.359899e+01</td>\n",
+       "      <td>43.59899</td>\n",
+       "      <td>43.59899</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>D_LACt2</th>\n",
-       "      <td>3.660315e-28</td>\n",
-       "      <td>4.140787e-15</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ENO</th>\n",
-       "      <td>1.471614e+01</td>\n",
-       "      <td>1.471614e+01</td>\n",
+       "      <td>14.71614</td>\n",
+       "      <td>14.71614</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ETOHt2r</th>\n",
-       "      <td>0.000000e+00</td>\n",
-       "      <td>5.729185e-15</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>EX_ac_e</th>\n",
-       "      <td>-3.933509e-15</td>\n",
-       "      <td>0.000000e+00</td>\n",
+       "      <td>-0.00000</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
        "</table>\n",
        "</div>"
       ],
       "text/plain": [
-       "                         maximum       minimum\n",
-       "ACALD               9.466331e-29  3.720797e-15\n",
-       "ACALDt             -6.310887e-29  3.720797e-15\n",
-       "ACKr               -2.524355e-28  3.933509e-15\n",
-       "ACONTa              6.007250e+00  6.007250e+00\n",
-       "ACONTb              6.007250e+00  6.007250e+00\n",
-       "ACt2r               6.121561e-28  3.933509e-15\n",
-       "ADK1               -4.042971e-14  0.000000e+00\n",
-       "AKGDH               5.064376e+00  5.064376e+00\n",
-       "AKGt2r              0.000000e+00  7.079399e-15\n",
-       "ALCD2x              0.000000e+00  5.729185e-15\n",
-       "ATPM                8.390000e+00  8.390000e+00\n",
-       "ATPS4r              4.551401e+01  4.551401e+01\n",
-       "Biomass_Ecoli_core  8.739215e-01  8.739215e-01\n",
-       "CO2t               -2.280983e+01 -2.280983e+01\n",
-       "CS                  6.007250e+00  6.007250e+00\n",
-       "CYTBD               4.359899e+01  4.359899e+01\n",
-       "D_LACt2             3.660315e-28  4.140787e-15\n",
-       "ENO                 1.471614e+01  1.471614e+01\n",
-       "ETOHt2r             0.000000e+00  5.729185e-15\n",
-       "EX_ac_e            -3.933509e-15  0.000000e+00"
+       "                     maximum   minimum\n",
+       "ACALD                0.00000   0.00000\n",
+       "ACALDt              -0.00000   0.00000\n",
+       "ACKr                -0.00000   0.00000\n",
+       "ACONTa               6.00725   6.00725\n",
+       "ACONTb               6.00725   6.00725\n",
+       "ACt2r                0.00000   0.00000\n",
+       "ADK1                -0.00000   0.00000\n",
+       "AKGDH                5.06438   5.06438\n",
+       "AKGt2r               0.00000   0.00000\n",
+       "ALCD2x               0.00000   0.00000\n",
+       "ATPM                 8.39000   8.39000\n",
+       "ATPS4r              45.51401  45.51401\n",
+       "Biomass_Ecoli_core   0.87392   0.87392\n",
+       "CO2t               -22.80983 -22.80983\n",
+       "CS                   6.00725   6.00725\n",
+       "CYTBD               43.59899  43.59899\n",
+       "D_LACt2              0.00000   0.00000\n",
+       "ENO                 14.71614  14.71614\n",
+       "ETOHt2r              0.00000   0.00000\n",
+       "EX_ac_e             -0.00000   0.00000"
       ]
      },
-     "execution_count": 10,
+     "execution_count": 13,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -421,7 +529,7 @@
    "source": [
     "fva_result = cobra.flux_analysis.flux_variability_analysis(\n",
     "    model, model.reactions[:20])\n",
-    "pandas.DataFrame.from_dict(fva_result).T"
+    "pandas.DataFrame.from_dict(fva_result).T.round(5)"
    ]
   },
   {
@@ -433,7 +541,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 14,
    "metadata": {
     "collapsed": false
    },
@@ -453,133 +561,133 @@
        "  <tbody>\n",
        "    <tr>\n",
        "      <th>ACALD</th>\n",
-       "      <td>9.466331e-29</td>\n",
-       "      <td>-2.542370</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>-2.54237</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACALDt</th>\n",
-       "      <td>-6.310887e-29</td>\n",
-       "      <td>-2.542370</td>\n",
+       "      <td>-0.00000</td>\n",
+       "      <td>-2.54237</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACKr</th>\n",
-       "      <td>-3.029226e-28</td>\n",
-       "      <td>-3.813556</td>\n",
+       "      <td>-0.00000</td>\n",
+       "      <td>-3.81356</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACONTa</th>\n",
-       "      <td>8.894520e+00</td>\n",
-       "      <td>0.848587</td>\n",
+       "      <td>8.89452</td>\n",
+       "      <td>0.84859</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACONTb</th>\n",
-       "      <td>8.894520e+00</td>\n",
-       "      <td>0.848587</td>\n",
+       "      <td>8.89452</td>\n",
+       "      <td>0.84859</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ACt2r</th>\n",
-       "      <td>3.407879e-28</td>\n",
-       "      <td>-3.813556</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>-3.81356</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ADK1</th>\n",
-       "      <td>1.716100e+01</td>\n",
-       "      <td>0.000000</td>\n",
+       "      <td>17.16100</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>AKGDH</th>\n",
-       "      <td>8.045934e+00</td>\n",
-       "      <td>0.000000</td>\n",
+       "      <td>8.04593</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>AKGt2r</th>\n",
-       "      <td>0.000000e+00</td>\n",
-       "      <td>-1.430083</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>-1.43008</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ALCD2x</th>\n",
-       "      <td>0.000000e+00</td>\n",
-       "      <td>-2.214323</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>-2.21432</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ATPM</th>\n",
-       "      <td>2.555100e+01</td>\n",
-       "      <td>8.390000</td>\n",
+       "      <td>25.55100</td>\n",
+       "      <td>8.39000</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ATPS4r</th>\n",
-       "      <td>5.938106e+01</td>\n",
-       "      <td>34.825618</td>\n",
+       "      <td>59.38106</td>\n",
+       "      <td>34.82562</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>Biomass_Ecoli_core</th>\n",
-       "      <td>8.739215e-01</td>\n",
-       "      <td>0.786529</td>\n",
+       "      <td>0.87392</td>\n",
+       "      <td>0.78653</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>CO2t</th>\n",
-       "      <td>-1.520653e+01</td>\n",
-       "      <td>-26.528850</td>\n",
+       "      <td>-15.20653</td>\n",
+       "      <td>-26.52885</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>CS</th>\n",
-       "      <td>8.894520e+00</td>\n",
-       "      <td>0.848587</td>\n",
+       "      <td>8.89452</td>\n",
+       "      <td>0.84859</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>CYTBD</th>\n",
-       "      <td>5.123909e+01</td>\n",
-       "      <td>35.984865</td>\n",
+       "      <td>51.23909</td>\n",
+       "      <td>35.98486</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>D_LACt2</th>\n",
-       "      <td>0.000000e+00</td>\n",
-       "      <td>-2.145125</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>-2.14512</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ENO</th>\n",
-       "      <td>1.673252e+01</td>\n",
-       "      <td>8.686588</td>\n",
+       "      <td>16.73252</td>\n",
+       "      <td>8.68659</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>ETOHt2r</th>\n",
-       "      <td>0.000000e+00</td>\n",
-       "      <td>-2.214323</td>\n",
+       "      <td>0.00000</td>\n",
+       "      <td>-2.21432</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>EX_ac_e</th>\n",
-       "      <td>3.813556e+00</td>\n",
-       "      <td>0.000000</td>\n",
+       "      <td>3.81356</td>\n",
+       "      <td>0.00000</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
        "</table>\n",
        "</div>"
       ],
       "text/plain": [
-       "                         maximum    minimum\n",
-       "ACALD               9.466331e-29  -2.542370\n",
-       "ACALDt             -6.310887e-29  -2.542370\n",
-       "ACKr               -3.029226e-28  -3.813556\n",
-       "ACONTa              8.894520e+00   0.848587\n",
-       "ACONTb              8.894520e+00   0.848587\n",
-       "ACt2r               3.407879e-28  -3.813556\n",
-       "ADK1                1.716100e+01   0.000000\n",
-       "AKGDH               8.045934e+00   0.000000\n",
-       "AKGt2r              0.000000e+00  -1.430083\n",
-       "ALCD2x              0.000000e+00  -2.214323\n",
-       "ATPM                2.555100e+01   8.390000\n",
-       "ATPS4r              5.938106e+01  34.825618\n",
-       "Biomass_Ecoli_core  8.739215e-01   0.786529\n",
-       "CO2t               -1.520653e+01 -26.528850\n",
-       "CS                  8.894520e+00   0.848587\n",
-       "CYTBD               5.123909e+01  35.984865\n",
-       "D_LACt2             0.000000e+00  -2.145125\n",
-       "ENO                 1.673252e+01   8.686588\n",
-       "ETOHt2r             0.000000e+00  -2.214323\n",
-       "EX_ac_e             3.813556e+00   0.000000"
+       "                     maximum   minimum\n",
+       "ACALD                0.00000  -2.54237\n",
+       "ACALDt              -0.00000  -2.54237\n",
+       "ACKr                -0.00000  -3.81356\n",
+       "ACONTa               8.89452   0.84859\n",
+       "ACONTb               8.89452   0.84859\n",
+       "ACt2r                0.00000  -3.81356\n",
+       "ADK1                17.16100   0.00000\n",
+       "AKGDH                8.04593   0.00000\n",
+       "AKGt2r               0.00000  -1.43008\n",
+       "ALCD2x               0.00000  -2.21432\n",
+       "ATPM                25.55100   8.39000\n",
+       "ATPS4r              59.38106  34.82562\n",
+       "Biomass_Ecoli_core   0.87392   0.78653\n",
+       "CO2t               -15.20653 -26.52885\n",
+       "CS                   8.89452   0.84859\n",
+       "CYTBD               51.23909  35.98486\n",
+       "D_LACt2              0.00000  -2.14512\n",
+       "ENO                 16.73252   8.68659\n",
+       "ETOHt2r              0.00000  -2.21432\n",
+       "EX_ac_e              3.81356   0.00000"
       ]
      },
-     "execution_count": 11,
+     "execution_count": 14,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -587,7 +695,90 @@
    "source": [
     "fva_result = cobra.flux_analysis.flux_variability_analysis(\n",
     "    model, model.reactions[:20], fraction_of_optimum=0.9)\n",
-    "pandas.DataFrame.from_dict(fva_result).T"
+    "pandas.DataFrame.from_dict(fva_result).T.round(5)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Running FVA in summary methods\n",
+    "Flux variability analysis can also be embedded in calls to summary methods. For instance, the expected variability in substrate consumption and product formation can be quickly found by"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "IN FLUXES                     OUT FLUXES                    OBJECTIVES          \n",
+      "o2_e        -21.80 ± 1.91     h2o_e       27.86 ± 2.86      Biomass_Ecoli_core    0.874\n",
+      "glc__D_e     -9.76 ± 0.24     co2_e       21.81 ± 2.86                          \n",
+      "nh4_e        -4.84 ± 0.32     h_e         19.51 ± 2.86                          \n",
+      "pi_e         -3.13 ± 0.08     for_e        2.86 ± 2.86                          \n",
+      "                              ac_e         0.95 ± 0.95                          \n",
+      "                              acald_e      0.64 ± 0.64                          \n",
+      "                              pyr_e        0.64 ± 0.64                          \n",
+      "                              etoh_e       0.55 ± 0.55                          \n",
+      "                              lac__D_e     0.54 ± 0.54                          \n",
+      "                              succ_e       0.42 ± 0.42                          \n",
+      "                              akg_e        0.36 ± 0.36                          \n",
+      "                              glu__L_e     0.32 ± 0.32                          \n"
+     ]
+    }
+   ],
+   "source": [
+    "model.optimize()\n",
+    "model.summary(fva=0.95)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Similarly, variability in metabolite mass balances can also be checked with flux variability analysis"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "PRODUCING REACTIONS -- Pyruvate\n",
+      "-------------------------------\n",
+      "  %             FLUX   RXN ID                        REACTION                       \n",
+      " 85.0%   9.76 ± 0.24   GLCpts                     glc__D_e + pep_c --> g6p_c + pyr_c\n",
+      " 15.0%   6.13 ± 6.13      PYK                  adp_c + h_c + pep_c --> atp_c + pyr_c\n",
+      "\n",
+      "CONSUMING REACTIONS -- Pyruvate\n",
+      "-------------------------------\n",
+      "  %             FLUX   RXN ID                        REACTION                       \n",
+      " 78.9%  11.34 ± 7.43      PDH     coa_c + nad_c + pyr_c --> accoa_c + co2_c + nadh_c\n",
+      " 21.1%   0.85 ± 0.02 Bioma...   1.496 3pg_c + 3.7478 accoa_c + 59.81 atp_c + 0.36...\n"
+     ]
+    }
+   ],
+   "source": [
+    "model.metabolites.pyr_c.summary(fva=0.95)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "In these summary methods, the values are reported as a the center point +/- the range of the FVA solution, calculated from the maximum and minimum values."
    ]
   },
   {
@@ -601,7 +792,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 17,
    "metadata": {
     "collapsed": false
    },
@@ -620,7 +811,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 18,
    "metadata": {
     "collapsed": false
    },
@@ -631,7 +822,7 @@
        "1.1102230246251565e-16"
       ]
      },
-     "execution_count": 13,
+     "execution_count": 18,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -643,21 +834,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.4"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/simulating.rst b/documentation_builder/simulating.rst
deleted file mode 100644
index d007044..0000000
--- a/documentation_builder/simulating.rst
+++ /dev/null
@@ -1,436 +0,0 @@
-
-Simulating with FBA
-===================
-
-Simulations using flux balance analysis can be solved using
-Model.optimize(). This will maximize or minimize (maximizing is the
-default) flux through the objective reactions.
-
-.. code:: python
-
-    import pandas
-    pandas.options.display.max_rows = 100
-    
-    import cobra.test
-    model = cobra.test.create_test_model("textbook")
-
-Running FBA
------------
-
-.. code:: python
-
-    model.optimize()
-
-
-
-
-.. parsed-literal::
-
-    <Solution 0.87 at 0x7fe558058b50>
-
-
-
-The Model.optimize() function will return a Solution object, which will
-also be stored at model.solution. A solution object has several
-attributes:
-
--  f: the objective value
--  status: the status from the linear programming solver
--  x\_dict: a dictionary of {reaction\_id: flux\_value} (also called
-   "primal")
--  x: a list for x\_dict
--  y\_dict: a dictionary of {metabolite\_id: dual\_value}.
--  y: a list for y\_dict
-
-For example, after the last call to model.optimize(), the status should
-be 'optimal' if the solver returned no errors, and f should be the
-objective value
-
-.. code:: python
-
-    model.solution.status
-
-
-
-
-.. parsed-literal::
-
-    'optimal'
-
-
-
-.. code:: python
-
-    model.solution.f
-
-
-
-
-.. parsed-literal::
-
-    0.8739215069684305
-
-
-
-Changing the Objectives
------------------------
-
-The objective function is determined from the objective\_coefficient
-attribute of the objective reaction(s). Currently in the model, there is
-only one objective reaction, with an objective coefficient of 1.
-
-.. code:: python
-
-    model.objective
-
-
-
-
-.. parsed-literal::
-
-    {<Reaction Biomass_Ecoli_core at 0x7fe526516490>: 1.0}
-
-
-
-The objective function can be changed by assigning Model.objective,
-which can be a reaction object (or just it's name), or a dict of
-{Reaction: objective\_coefficient}.
-
-.. code:: python
-
-    # change the objective to ATPM
-    # the upper bound should be 1000 so we get the actual optimal value
-    model.reactions.get_by_id("ATPM").upper_bound = 1000.
-    model.objective = "ATPM"
-    model.objective
-
-
-
-
-.. parsed-literal::
-
-    {<Reaction ATPM at 0x7fe526516210>: 1}
-
-
-
-.. code:: python
-
-    model.optimize().f
-
-
-
-
-.. parsed-literal::
-
-    174.99999999999997
-
-
-
-The objective function can also be changed by setting
-Reaction.objective\_coefficient directly.
-
-.. code:: python
-
-    model.reactions.get_by_id("ATPM").objective_coefficient = 0.
-    model.reactions.get_by_id("Biomass_Ecoli_core").objective_coefficient = 1.
-    model.objective
-
-
-
-
-.. parsed-literal::
-
-    {<Reaction Biomass_Ecoli_core at 0x7fe526516490>: 1.0}
-
-
-
-Running FVA
------------
-
-FBA will not give always give unique solution, because multiple flux
-states can achieve the same optimum. FVA (or flux variability analysis)
-finds the ranges of each metabolic flux at the optimum.
-
-.. code:: python
-
-    fva_result = cobra.flux_analysis.flux_variability_analysis(model, model.reactions[:20])
-    pandas.DataFrame.from_dict(fva_result).T
-
-
-
-
-.. raw:: html
-
-    <div>
-    <table border="1" class="dataframe">
-      <thead>
-        <tr style="text-align: right;">
-          <th></th>
-          <th>maximum</th>
-          <th>minimum</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <th>ACALD</th>
-          <td>9.466331e-29</td>
-          <td>3.720797e-15</td>
-        </tr>
-        <tr>
-          <th>ACALDt</th>
-          <td>-6.310887e-29</td>
-          <td>3.720797e-15</td>
-        </tr>
-        <tr>
-          <th>ACKr</th>
-          <td>-2.524355e-28</td>
-          <td>3.933509e-15</td>
-        </tr>
-        <tr>
-          <th>ACONTa</th>
-          <td>6.007250e+00</td>
-          <td>6.007250e+00</td>
-        </tr>
-        <tr>
-          <th>ACONTb</th>
-          <td>6.007250e+00</td>
-          <td>6.007250e+00</td>
-        </tr>
-        <tr>
-          <th>ACt2r</th>
-          <td>6.121561e-28</td>
-          <td>3.933509e-15</td>
-        </tr>
-        <tr>
-          <th>ADK1</th>
-          <td>-4.042971e-14</td>
-          <td>0.000000e+00</td>
-        </tr>
-        <tr>
-          <th>AKGDH</th>
-          <td>5.064376e+00</td>
-          <td>5.064376e+00</td>
-        </tr>
-        <tr>
-          <th>AKGt2r</th>
-          <td>0.000000e+00</td>
-          <td>7.079399e-15</td>
-        </tr>
-        <tr>
-          <th>ALCD2x</th>
-          <td>0.000000e+00</td>
-          <td>5.729185e-15</td>
-        </tr>
-        <tr>
-          <th>ATPM</th>
-          <td>8.390000e+00</td>
-          <td>8.390000e+00</td>
-        </tr>
-        <tr>
-          <th>ATPS4r</th>
-          <td>4.551401e+01</td>
-          <td>4.551401e+01</td>
-        </tr>
-        <tr>
-          <th>Biomass_Ecoli_core</th>
-          <td>8.739215e-01</td>
-          <td>8.739215e-01</td>
-        </tr>
-        <tr>
-          <th>CO2t</th>
-          <td>-2.280983e+01</td>
-          <td>-2.280983e+01</td>
-        </tr>
-        <tr>
-          <th>CS</th>
-          <td>6.007250e+00</td>
-          <td>6.007250e+00</td>
-        </tr>
-        <tr>
-          <th>CYTBD</th>
-          <td>4.359899e+01</td>
-          <td>4.359899e+01</td>
-        </tr>
-        <tr>
-          <th>D_LACt2</th>
-          <td>3.660315e-28</td>
-          <td>4.140787e-15</td>
-        </tr>
-        <tr>
-          <th>ENO</th>
-          <td>1.471614e+01</td>
-          <td>1.471614e+01</td>
-        </tr>
-        <tr>
-          <th>ETOHt2r</th>
-          <td>0.000000e+00</td>
-          <td>5.729185e-15</td>
-        </tr>
-        <tr>
-          <th>EX_ac_e</th>
-          <td>-3.933509e-15</td>
-          <td>0.000000e+00</td>
-        </tr>
-      </tbody>
-    </table>
-    </div>
-
-
-
-Setting parameter fraction\_of\_optimium=0.90 would give the flux ranges
-for reactions at 90% optimality.
-
-.. code:: python
-
-    fva_result = cobra.flux_analysis.flux_variability_analysis(model, model.reactions[:20], fraction_of_optimum=0.9)
-    pandas.DataFrame.from_dict(fva_result).T
-
-
-
-
-.. raw:: html
-
-    <div>
-    <table border="1" class="dataframe">
-      <thead>
-        <tr style="text-align: right;">
-          <th></th>
-          <th>maximum</th>
-          <th>minimum</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <th>ACALD</th>
-          <td>9.466331e-29</td>
-          <td>-2.542370</td>
-        </tr>
-        <tr>
-          <th>ACALDt</th>
-          <td>-6.310887e-29</td>
-          <td>-2.542370</td>
-        </tr>
-        <tr>
-          <th>ACKr</th>
-          <td>-3.029226e-28</td>
-          <td>-3.813556</td>
-        </tr>
-        <tr>
-          <th>ACONTa</th>
-          <td>8.894520e+00</td>
-          <td>0.848587</td>
-        </tr>
-        <tr>
-          <th>ACONTb</th>
-          <td>8.894520e+00</td>
-          <td>0.848587</td>
-        </tr>
-        <tr>
-          <th>ACt2r</th>
-          <td>3.407879e-28</td>
-          <td>-3.813556</td>
-        </tr>
-        <tr>
-          <th>ADK1</th>
-          <td>1.716100e+01</td>
-          <td>0.000000</td>
-        </tr>
-        <tr>
-          <th>AKGDH</th>
-          <td>8.045934e+00</td>
-          <td>0.000000</td>
-        </tr>
-        <tr>
-          <th>AKGt2r</th>
-          <td>0.000000e+00</td>
-          <td>-1.430083</td>
-        </tr>
-        <tr>
-          <th>ALCD2x</th>
-          <td>0.000000e+00</td>
-          <td>-2.214323</td>
-        </tr>
-        <tr>
-          <th>ATPM</th>
-          <td>2.555100e+01</td>
-          <td>8.390000</td>
-        </tr>
-        <tr>
-          <th>ATPS4r</th>
-          <td>5.938106e+01</td>
-          <td>34.825618</td>
-        </tr>
-        <tr>
-          <th>Biomass_Ecoli_core</th>
-          <td>8.739215e-01</td>
-          <td>0.786529</td>
-        </tr>
-        <tr>
-          <th>CO2t</th>
-          <td>-1.520653e+01</td>
-          <td>-26.528850</td>
-        </tr>
-        <tr>
-          <th>CS</th>
-          <td>8.894520e+00</td>
-          <td>0.848587</td>
-        </tr>
-        <tr>
-          <th>CYTBD</th>
-          <td>5.123909e+01</td>
-          <td>35.984865</td>
-        </tr>
-        <tr>
-          <th>D_LACt2</th>
-          <td>0.000000e+00</td>
-          <td>-2.145125</td>
-        </tr>
-        <tr>
-          <th>ENO</th>
-          <td>1.673252e+01</td>
-          <td>8.686588</td>
-        </tr>
-        <tr>
-          <th>ETOHt2r</th>
-          <td>0.000000e+00</td>
-          <td>-2.214323</td>
-        </tr>
-        <tr>
-          <th>EX_ac_e</th>
-          <td>3.813556e+00</td>
-          <td>0.000000</td>
-        </tr>
-      </tbody>
-    </table>
-    </div>
-
-
-
-Running pFBA
-------------
-
-Parsimonious FBA (often written pFBA) finds a flux distribution which
-gives the optimal growth rate, but minimizes the total sum of flux. This
-involves solving two sequential linear programs, but is handled
-transparently by cobrapy. For more details on pFBA, please see `Lewis et
-al. (2010) <http://dx.doi.org/10.1038/msb.2010.47>`__.
-
-.. code:: python
-
-    FBA_solution = model.optimize()
-    pFBA_solution = cobra.flux_analysis.optimize_minimal_flux(model)
-
-These functions should give approximately the same objective value
-
-.. code:: python
-
-    abs(FBA_solution.f - pFBA_solution.f)
-
-
-
-
-.. parsed-literal::
-
-    1.1102230246251565e-16
-
-
diff --git a/documentation_builder/solvers.ipynb b/documentation_builder/solvers.ipynb
index 9b36268..06c76a8 100644
--- a/documentation_builder/solvers.ipynb
+++ b/documentation_builder/solvers.ipynb
@@ -74,7 +74,7 @@
     {
      "data": {
       "text/plain": [
-       "<Solution 0.87 at 0x7f15f00de750>"
+       "<Solution 0.87 at 0x7fd42ad90c18>"
       ]
      },
      "execution_count": 3,
@@ -136,7 +136,7 @@
     {
      "data": {
       "text/plain": [
-       "<Solution 0.87 at 0x7f15bc67a910>"
+       "<Solution 0.87 at 0x7fd42ad90908>"
       ]
      },
      "execution_count": 5,
@@ -167,7 +167,7 @@
     {
      "data": {
       "text/plain": [
-       "<cobra.solvers.cglpk.GLP at 0x402ea50>"
+       "<cobra.solvers.cglpk.GLP at 0x3e846e8>"
       ]
      },
      "execution_count": 6,
@@ -230,7 +230,7 @@
     {
      "data": {
       "text/plain": [
-       "<Solution 0.87 at 0x7f15d3cdb1d0>"
+       "<Solution 0.87 at 0x7fd42ad90668>"
       ]
      },
      "execution_count": 8,
@@ -261,7 +261,7 @@
     {
      "data": {
       "text/plain": [
-       "0.8739215069684305"
+       "0.8739215069684909"
       ]
      },
      "execution_count": 9,
@@ -345,7 +345,7 @@
     {
      "data": {
       "text/plain": [
-       "1.747843013936861"
+       "1.7478430139369818"
       ]
      },
      "execution_count": 12,
@@ -369,7 +369,7 @@
     {
      "data": {
       "text/plain": [
-       "0.8739215069684305"
+       "0.8739215069684909"
       ]
      },
      "execution_count": 13,
@@ -591,7 +591,7 @@
     {
      "data": {
       "text/plain": [
-       "0.8739215069684304"
+       "0.8739215069684912"
       ]
      },
      "execution_count": 22,
@@ -625,8 +625,8 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 153 ms, sys: 4.14 ms, total: 157 ms\n",
-      "Wall time: 155 ms\n"
+      "CPU times: user 171 ms, sys: 0 ns, total: 171 ms\n",
+      "Wall time: 171 ms\n"
      ]
     }
    ],
@@ -668,8 +668,8 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "CPU times: user 9.39 ms, sys: 192 µs, total: 9.58 ms\n",
-      "Wall time: 9.05 ms\n"
+      "CPU times: user 8.28 ms, sys: 25 µs, total: 8.31 ms\n",
+      "Wall time: 8.14 ms\n"
      ]
     }
    ],
@@ -705,21 +705,21 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 2",
+   "display_name": "Python 3",
    "language": "python",
-   "name": "python2"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
     "name": "ipython",
-    "version": 2
+    "version": 3
    },
    "file_extension": ".py",
    "mimetype": "text/x-python",
    "name": "python",
    "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.6"
+   "pygments_lexer": "ipython3",
+   "version": "3.4.3"
   }
  },
  "nbformat": 4,
diff --git a/documentation_builder/solvers.rst b/documentation_builder/solvers.rst
deleted file mode 100644
index fc714dc..0000000
--- a/documentation_builder/solvers.rst
+++ /dev/null
@@ -1,451 +0,0 @@
-
-Solver Interface
-================
-
-Each cobrapy solver must expose the following API. The solvers all will
-have their own distinct LP object types, but each can be manipulated by
-these functions. This API can be used directly when implementing
-algorithms efficiently on linear programs because it has 2 primary
-benefits:
-
-1. Avoid the overhead of creating and destroying LP's for each operation
-
-2. Many solver objects preserve the basis between subsequent LP's,
-   making each subsequent LP solve faster
-
-We will walk though the API with the cglpk solver, which links the
-cobrapy solver API with `GLPK <http://www.gnu.org/software/glpk/>`__'s C
-API.
-
-.. code:: python
-
-    import cobra.test
-    
-    model = cobra.test.create_test_model("textbook")
-    solver = cobra.solvers.cglpk
-
-Attributes and functions
-------------------------
-
-Each solver has some attributes:
-
-solver\_name
-~~~~~~~~~~~~
-
-The name of the solver. This is the name which will be used to select
-the solver in cobrapy functions.
-
-.. code:: python
-
-    solver.solver_name
-
-
-
-
-.. parsed-literal::
-
-    'cglpk'
-
-
-
-.. code:: python
-
-    model.optimize(solver="cglpk")
-
-
-
-
-.. parsed-literal::
-
-    <Solution 0.87 at 0x7f9148bb2250>
-
-
-
-\_SUPPORTS\_MILP
-~~~~~~~~~~~~~~~~
-
-The presence of this attribute tells cobrapy that the solver supports
-mixed-integer linear programming
-
-.. code:: python
-
-    solver._SUPPORTS_MILP
-
-
-
-
-.. parsed-literal::
-
-    True
-
-
-
-solve
-~~~~~
-
-Model.optimize is a wrapper for each solver's solve function. It takes
-in a cobra model and returns a solution
-
-.. code:: python
-
-    solver.solve(model)
-
-
-
-
-.. parsed-literal::
-
-    <Solution 0.87 at 0x7f917d50ed50>
-
-
-
-create\_problem
-~~~~~~~~~~~~~~~
-
-This creates the LP object for the solver.
-
-.. code:: python
-
-    lp = solver.create_problem(model, objective_sense="maximize")
-    lp
-
-
-
-
-.. parsed-literal::
-
-    <cobra.solvers.cglpk.GLP at 0x46e8aa0>
-
-
-
-solve\_problem
-~~~~~~~~~~~~~~
-
-Solve the LP object and return the solution status
-
-.. code:: python
-
-    solver.solve_problem(lp)
-
-
-
-
-.. parsed-literal::
-
-    'optimal'
-
-
-
-format\_solution
-~~~~~~~~~~~~~~~~
-
-Extract a cobra.Solution object from a solved LP object
-
-.. code:: python
-
-    solver.format_solution(lp, model)
-
-
-
-
-.. parsed-literal::
-
-    <Solution 0.87 at 0x7f917d50e9d0>
-
-
-
-get\_objective\_value
-~~~~~~~~~~~~~~~~~~~~~
-
-Extract the objective value from a solved LP object
-
-.. code:: python
-
-    solver.get_objective_value(lp)
-
-
-
-
-.. parsed-literal::
-
-    0.8739215069684305
-
-
-
-get\_status
-~~~~~~~~~~~
-
-Get the solution status of a solved LP object
-
-.. code:: python
-
-    solver.get_status(lp)
-
-
-
-
-.. parsed-literal::
-
-    'optimal'
-
-
-
-change\_variable\_objective
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-change the objective coefficient a reaction at a particular index. This
-does not change any of the other objectives which have already been set.
-This example will double and then revert the biomass coefficient.
-
-.. code:: python
-
-    model.reactions.index("Biomass_Ecoli_core")
-
-
-
-
-.. parsed-literal::
-
-    12
-
-
-
-.. code:: python
-
-    solver.change_variable_objective(lp, 12, 2)
-    solver.solve_problem(lp)
-    solver.get_objective_value(lp)
-
-
-
-
-.. parsed-literal::
-
-    1.747843013936861
-
-
-
-.. code:: python
-
-    solver.change_variable_objective(lp, 12, 1)
-    solver.solve_problem(lp)
-    solver.get_objective_value(lp)
-
-
-
-
-.. parsed-literal::
-
-    0.8739215069684305
-
-
-
-change variable\_bounds
-~~~~~~~~~~~~~~~~~~~~~~~
-
-change the lower and upper bounds of a reaction at a particular index.
-This example will set the lower bound of the biomass to an infeasible
-value, then revert it.
-
-.. code:: python
-
-    solver.change_variable_bounds(lp, 12, 1000, 1000)
-    solver.solve_problem(lp)
-
-
-
-
-.. parsed-literal::
-
-    'infeasible'
-
-
-
-.. code:: python
-
-    solver.change_variable_bounds(lp, 12, 0, 1000)
-    solver.solve_problem(lp)
-
-
-
-
-.. parsed-literal::
-
-    'optimal'
-
-
-
-change\_coefficient
-~~~~~~~~~~~~~~~~~~~
-
-Change a coefficient in the stoichiometric matrix. In this example, we
-will set the entry for ADP in the ATMP reaction to in infeasible value,
-then reset it.
-
-.. code:: python
-
-    model.metabolites.index("atp_c")
-
-
-
-
-.. parsed-literal::
-
-    16
-
-
-
-.. code:: python
-
-    model.reactions.index("ATPM")
-
-
-
-
-.. parsed-literal::
-
-    10
-
-
-
-.. code:: python
-
-    solver.change_coefficient(lp, 16, 10, -10)
-    solver.solve_problem(lp)
-
-
-
-
-.. parsed-literal::
-
-    'infeasible'
-
-
-
-.. code:: python
-
-    solver.change_coefficient(lp, 16, 10, -1)
-    solver.solve_problem(lp)
-
-
-
-
-.. parsed-literal::
-
-    'optimal'
-
-
-
-set\_parameter
-~~~~~~~~~~~~~~
-
-Set a solver parameter. Each solver will have its own particular set of
-unique paramters. However, some have unified names. For example, all
-solvers should accept "tolerance\_feasibility."
-
-.. code:: python
-
-    solver.set_parameter(lp, "tolerance_feasibility", 1e-9)
-
-.. code:: python
-
-    solver.set_parameter(lp, "objective_sense", "minimize")
-    solver.solve_problem(lp)
-    solver.get_objective_value(lp)
-
-
-
-
-.. parsed-literal::
-
-    0.0
-
-
-
-.. code:: python
-
-    solver.set_parameter(lp, "objective_sense", "maximize")
-    solver.solve_problem(lp)
-    solver.get_objective_value(lp)
-
-
-
-
-.. parsed-literal::
-
-    0.8739215069684304
-
-
-
-Example with FVA
-----------------
-
-Consider flux variability analysis (FVA), which requires maximizing and
-minimizing every reaction with the original biomass value fixed at its
-optimal value. If we used the cobra Model API in a naive implementation,
-we would do the following:
-
-.. code:: python
-
-    %%time
-    # work on a copy of the model so the original is not changed
-    fva_model = model.copy()
-    
-    # set the lower bound on the objective to be the optimal value
-    f = fva_model.optimize().f
-    for objective_reaction, coefficient in fva_model.objective.items():
-        objective_reaction.lower_bound = coefficient * f
-    
-    # now maximize and minimze every reaction to find its bounds
-    fva_result = {}
-    for r in fva_model.reactions:
-        fva_model.change_objective(r)
-        fva_result[r.id] = {}
-        fva_result[r.id]["maximum"] = fva_model.optimize(objective_sense="maximize").f
-        fva_result[r.id]["minimum"] = fva_model.optimize(objective_sense="minimize").f
-
-
-.. parsed-literal::
-
-    CPU times: user 144 ms, sys: 667 µs, total: 145 ms
-    Wall time: 141 ms
-
-
-Instead, we could use the solver API to do this more efficiently. This
-is roughly how cobrapy implementes FVA. It keeps uses the same LP object
-and repeatedly maximizes and minimizes it. This allows the solver to
-preserve the basis, and is much faster. The speed increase is even more
-noticeable the larger the model gets.
-
-.. code:: python
-
-    %%time
-    # create the LP object
-    lp = solver.create_problem(model)
-    
-    # set the lower bound on the objective to be the optimal value
-    solver.solve_problem(lp)
-    f = solver.get_objective_value(lp)
-    for objective_reaction, coefficient in model.objective.items():
-        objective_index = model.reactions.index(objective_reaction)
-        # old objective is no longer the objective
-        solver.change_variable_objective(lp, objective_index, 0.)
-        solver.change_variable_bounds(lp, objective_index, f * coefficient, objective_reaction.upper_bound)
-    
-    # now maximize and minimze every reaction to find its bounds
-    fva_result = {}
-    for index, r in enumerate(model.reactions):
-        solver.change_variable_objective(lp, index, 1.)
-        fva_result[r.id] = {}
-        solver.solve_problem(lp, objective_sense="maximize")
-        fva_result[r.id]["maximum"] = solver.get_objective_value(lp)
-        solver.solve_problem(lp, objective_sense="minimize")
-        fva_result[r.id]["minimum"] = solver.get_objective_value(lp)
-        solver.change_variable_objective(lp, index, 0.)
-
-
-.. parsed-literal::
-
-    CPU times: user 9.85 ms, sys: 251 µs, total: 10.1 ms
-    Wall time: 9.94 ms
-

-- 
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