Skip to content

Commit

Permalink
Merge 6146b2c into 40a9466
Browse files Browse the repository at this point in the history
  • Loading branch information
bqpd committed Mar 21, 2021
2 parents 40a9466 + 6146b2c commit 44bf2d5
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 46 deletions.
59 changes: 59 additions & 0 deletions docs/source/advancedcommands.rst
@@ -1,6 +1,65 @@
Advanced Commands
*****************

.. _migp:

Choice Variables
================
If MOSEK 9 is installed, GPkit supports discretized free variables with the ``mosek_conif`` solver.
Choice variables are free in the sense of having multiple possible choices, but discretized in the
sense of having a limited set of possible solutions.

.. literalinclude:: examples/migp.py

If solved with the mosek_conif solver, the script above will print::

Optimal Cost
------------
[ 1.5 2.15 2.8 3.22 ... ]

~~~~~~~~
WARNINGS
~~~~~~~~
No Dual Solution
----------------
This model has the discretized choice variables [x] and hence no dual
solution. You can fix those variables to their optimal value and get
sensitivities to the resulting continuous problem by updating your model's
substitions with `sol["choicevariables"]`.
~~~~~~~~

Swept Variables
---------------
numerator : [ 0.5
1.15
1.8
2.45
3.1
3.75
4.4
5.05
5.7
6.35
7 ]

Free Variables
--------------
x : [ 1
1
1
2
2
2
2
2
2
3
3 ]

Note that the optimal values for ``x`` are discretized, clicking from 1
to 2 to 3 during the sweep, and that the solution has no dual variables.


Derived Variables
=================

Expand Down
10 changes: 10 additions & 0 deletions docs/source/examples/migp.py
@@ -0,0 +1,10 @@
import numpy as np
from gpkit import *

x = Variable("x", choices=range(1,4))
num = Variable("numerator", np.linspace(0.5, 7, 11))

m = Model(x + num/x)
sol = m.solve(verbosity=0)

print(sol.table())
59 changes: 59 additions & 0 deletions docs/source/examples/migp_output.txt
@@ -0,0 +1,59 @@

Optimal Cost
------------
[ 1.41 2.14 2.68 3.13 ... ]

~~~~~~~~
WARNINGS
~~~~~~~~
Freed Choice Variables
----------------------
This model has the discretized choice variables [x], but since the 'cvxopt' solver doesn't support discretization they were treated as continuous variables.
~~~~~~~~

Swept Variables
---------------
numerator : [ 0.5
1.15
1.8
2.45
3.1
3.75
4.4
5.05
5.7
6.35
7 ]

Free Variables
--------------
x : [ 0.707
1.07
1.34
1.57
1.76
1.94
2.1
2.25
2.39
2.52
2.65 ]

Variable Sensitivities
----------------------
numerator : [ +0.5
+0.5
+0.5
+0.5
+0.5
+0.5
+0.5
+0.5
+0.5
+0.5
+0.5 ]

Most Sensitive Constraints (in last sweep)
------------------------------------------
(none)

32 changes: 17 additions & 15 deletions docs/source/installation.rst
Expand Up @@ -4,42 +4,44 @@ Installation
************

1. If you are on Mac or Windows, we recommend installing `Anaconda <http://www.continuum.io/downloads>`_. Alternatively, `install pip and create a virtual environment <https://packaging.python.org/guides/installing-using-pip-and-virtualenv/>`_.
2. (optional) Install the MOSEK solver as directed below
3. Run ``pip install gpkit`` in the appropriate terminal or command prompt.
4. Open a Python prompt and run ``import gpkit`` to finish installation and run unit tests.
2. (optional) Install the MOSEK 9 solver with ``pip install Mosek``, then a license as described below
3. (optional) Install the MOSEK 8 solver as described below
4. Run ``pip install gpkit`` in the appropriate terminal or command prompt.
5. Open a Python prompt and run ``import gpkit`` to finish installation and run unit tests.

If you encounter any bugs please email ``gpkit@mit.edu``
or `raise a GitHub issue <http://github.com/convexengineering/gpkit/issues/new>`_.


Installing MOSEK
================
GPkit interfaces with two off the shelf solvers: cvxopt, and MOSEK.
Installing MOSEK 8
==================
GPkit interfaces with two off the shelf solvers: cvxopt, and MOSEK (versions 8 and 9).
Cvxopt is open source and installed by default; MOSEK requires a commercial licence or (free)
academic license.
academic license. In MOSEK version 8 GPkit uses the command-line interface ``mskexpopt`` solver, while
in MOSEK 9 it uses the more active exponential-cone interface (and hence supports :ref:`migp`).

Mac OS X
- If ``which gcc`` does not return anything, install the `Apple Command Line Tools <https://developer.apple.com/downloads/index.action?=command%20line%20tools>`_.
- Download `MOSEK 8 <https://www.mosek.com/downloads/>`_, then:
- Move the ``mosek`` folder to your home directory
- Follow `these steps for Mac <http://docs.mosek.com/7.0/toolsinstall/Mac_OS_X_installation.html>`_.
- Request an `academic license file <http://license.mosek.com/academic>`_ and put it in ``~/mosek/``
- Request an `academic license file <http://license.mosek.com/academic>`_ and put it in ``~/mosek/``

Linux
- Download `MOSEK 8 <https://www.mosek.com/downloads/>`_, then:
- Move the ``mosek`` folder to your home directory
- Follow `these steps for Linux <http://docs.mosek.com/7.0/toolsinstall/Linux_UNIX_installation_instructions.html>`_.
- Request an `academic license file <http://license.mosek.com/academic>`_ and put it in ``~/mosek/``
- Request an `academic license file <http://license.mosek.com/academic>`_ and put it in ``~/mosek/``

Windows
- Make sure ``gcc`` is on your system path.
- To do this, type ``gcc`` into a command prompt.
- If you get ``executable not found``, then install the 64-bit version (x86_64 installer architecture dropdown option) with GCC version 6.4.0 or older of `mingw <http://sourceforge.net/projects/mingw-w64/>`_.
- In an Anaconda command prompt (or equivalent), run ``cd C:\Program Files\mingw-w64\x86_64-6.4.0-posix-seh-rt_v5-rev0\`` (or whatever corresponds to the correct installation directory; note that if mingw is in ``Program Files (x86)`` instead of ``Program Files`` you've installed the 32-bit version by mistake)
- Run ``mingw-w64`` to add it to your executable path. For step 3 of the install process you'll need to run ``pip install gpkit`` from this prompt.
- Download `MOSEK 8 <https://www.mosek.com/downloads/>`_, then:
- Follow `these steps for Windows <http://docs.mosek.com/7.0/toolsinstall/Windows_installation.html>`_.
- Request an `academic license file <http://license.mosek.com/academic>`_ and put it in ``C:\Users\(your_username)\mosek\``
- Make sure ``gcc`` is on your system path.
- To do this, type ``gcc`` into a command prompt.
- If you get ``executable not found``, then install the 64-bit version (x86_64 installer architecture dropdown option) with GCC version 6.4.0 or older of `mingw <http://sourceforge.net/projects/mingw-w64/>`_.
- In an Anaconda command prompt (or equivalent), run ``cd C:\Program Files\mingw-w64\x86_64-6.4.0-posix-seh-rt_v5-rev0\`` (or whatever corresponds to the correct installation directory; note that if mingw is in ``Program Files (x86)`` instead of ``Program Files`` you've installed the 32-bit version by mistake)
- Run ``mingw-w64`` to add it to your executable path. For step 3 of the install process you'll need to run ``pip install gpkit`` from this prompt.
- Request an `academic license file <http://license.mosek.com/academic>`_ and put it in ``C:\Users\(your_username)\mosek\``

Debugging your installation
===========================
Expand Down
30 changes: 29 additions & 1 deletion gpkit/constraints/gp.py
Expand Up @@ -69,6 +69,7 @@ class GeometricProgram:
>>> gp.solve()
"""
_result = solve_log = solver_out = model = v_ss = nu_by_posy = None
choicevaridxs = integersolve = None

def __init__(self, cost, constraints, substitutions, *, checkbounds=True):
self.cost, self.substitutions = cost, substitutions
Expand Down Expand Up @@ -155,6 +156,8 @@ def gen(self):
m_idx += 1
self.p_idxs = np.array(self.p_idxs, "int32") # to use array equalities
self.varidxs = {vk: i for i, vk in enumerate(self.varlocs)}
self.choicevaridxs = {vk: i for i, vk in enumerate(self.varlocs)
if vk.choices}
for j, (var, locs) in enumerate(self.varlocs.items()):
row.extend(locs)
col.extend([j]*len(locs))
Expand Down Expand Up @@ -191,6 +194,9 @@ def solve(self, solver=None, *, verbosity=1, gen_result=True, **kwargs):

solverargs = DEFAULT_SOLVER_KWARGS.get(solvername, {})
solverargs.update(kwargs)
if self.choicevaridxs and solvername == "mosek_conif":
solverargs["choicevaridxs"] = self.choicevaridxs
self.integersolve = True
starttime = time()
solver_out, infeasibility, original_stdout = {}, None, sys.stdout
try:
Expand Down Expand Up @@ -304,6 +310,27 @@ def _compile_result(self, solver_out):
result["constants"] = KeyDict(self.substitutions)
result["variables"] = KeyDict(result["freevariables"])
result["variables"].update(result["constants"])
result["soltime"] = solver_out["soltime"]
if self.integersolve:
result["choicevariables"] = KeyDict( \
{k: v for k, v in result["freevariables"].items()
if k in self.choicevaridxs})
result["warnings"] = {"No Dual Solution": [(\
"This model has the discretized choice variables"
" %s and hence no dual solution. You can fix those variables"
" to their optimal value and get sensitivities to the resulting"
" continuous problem by updating your model's substitions with"
" `sol[\"choicevariables\"]`."
% sorted(self.choicevaridxs.keys()), self.choicevaridxs)]}
return SolutionArray(result)
elif self.choicevaridxs:
result["warnings"] = {"Freed Choice Variables": [(\
"This model has the discretized choice variables"
" %s, but since the '%s' solver doesn't support discretization"
" they were treated as continuous variables."
% (sorted(self.choicevaridxs.keys()), solver_out["solver"]),
self.choicevaridxs)]}

result["sensitivities"] = {"constraints": {}}
la, self.nu_by_posy = self._generate_nula(solver_out)
cost_senss = sum(nu_i*exp for (nu_i, exp) in zip(self.nu_by_posy[0],
Expand Down Expand Up @@ -342,7 +369,6 @@ def _compile_result(self, solver_out):
result["sensitivities"]["variables"] = KeyDict(gpv_ss)
result["sensitivities"]["constants"] = \
result["sensitivities"]["variables"] # NOTE: backwards compat.
result["soltime"] = solver_out["soltime"]
return SolutionArray(result)

def check_solution(self, cost, primal, nu, la, tol, abstol=1e-20):
Expand Down Expand Up @@ -379,6 +405,8 @@ def almost_equal(num1, num2):
raise Infeasible("Primal solution violates constraint: %s is "
"greater than 1" % primal_exp_vals[mi].sum())
# check dual sol #
if self.integersolve:
return
# note: follows dual formulation in section 3.1 of
# http://web.mit.edu/~whoburg/www/papers/hoburg_phd_thesis.pdf
if not almost_equal(self.nu_by_posy[0].sum(), 1):
Expand Down
5 changes: 3 additions & 2 deletions gpkit/small_classes.py
Expand Up @@ -74,9 +74,10 @@ def __init__(self, output=None, *, verbosity=0):

def write(self, writ):
"Append and potentially write the new line."
if writ[:2] == "b'":
writ = writ[2:-1]
if writ != "\n":
writ = writ.rstrip("\n")
self.append(str(writ))
self.append(writ.rstrip("\n"))
if self.verbosity > 0: # pragma: no cover
self.output.write(writ)

Expand Down
14 changes: 10 additions & 4 deletions gpkit/solution_array.py
Expand Up @@ -243,7 +243,9 @@ def warnings_table(self, _, **kwargs):
if len(data_vec) == 0:
continue
if not hasattr(data_vec, "shape"):
data_vec = [data_vec]
data_vec = [data_vec] # not a sweep
if all((data == data_vec[0]).all() for data in data_vec[1:]):
data_vec = [data_vec[0]] # warnings identical across all sweeps
for i, data in enumerate(data_vec):
if len(data) == 0:
continue
Expand Down Expand Up @@ -340,7 +342,8 @@ class SolutionArray(DictOfLists):
"""
modelstr = ""
_name_collision_varkeys = None
table_titles = {"sweepvariables": "Swept Variables",
table_titles = {"choicevariables": "Choice Variables",
"sweepvariables": "Swept Variables",
"freevariables": "Free Variables",
"constants": "Fixed Variables", # TODO: change everywhere
"variables": "Variables"}
Expand Down Expand Up @@ -683,7 +686,7 @@ def table(self, showvars=(),
-------
str
"""
if sortmodelsbysenss:
if sortmodelsbysenss and "sensitivities" in self:
kwargs["sortmodelsbysenss"] = self["sensitivities"]["models"]
else:
kwargs["sortmodelsbysenss"] = False
Expand All @@ -700,7 +703,10 @@ def table(self, showvars=(),
showvars = self._parse_showvars(showvars)
strs = []
for table in tables:
if table == "cost":
if "sensitivities" not in self and ("sensitivities" in table or
"constraints" in table):
continue
elif table == "cost":
cost = self["cost"] # pylint: disable=unsubscriptable-object
if kwargs.get("latex", None): # cost is not printed for latex
continue
Expand Down

0 comments on commit 44bf2d5

Please sign in to comment.