diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3fa953d..bf99290 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -31,6 +31,12 @@ jobs:
name: upload coverage to codecov
command: bash <(curl -s https://codecov.io/bash)
+ - run:
+ name: run linting via flake8
+ command: |
+ . venv/bin/activate
+ flake8 gilp
+
- save_cache:
key: deps1-{{ .Branch }}-{{ checksum "test_requirements.txt" }}
paths:
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..cd67370
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,13 @@
+[flake8]
+ignore =
+ E121, E123, E133, E226, E24, ; default
+ E231, ; sometimes no whitespace between commas is clearer
+ W503, ; W503 conflicts with W504 standard
+max-line-length = 79
+per-file-ignores =
+ ./__init__.py:F401
+ gilp/__init__.py:F401
+ ; standard to ignore F401 in __init__.py
+ ./_constants.py:E741
+ gilp/_constants.py:E741
+ ; Plotly's bad choice in variable names
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index dc72b17..62e8d71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,8 @@
### Python ###
test.py
-test.ipynb
+time_test.py
+test*.ipynb
# DS.Store
**/.DS_Store
diff --git a/gilp/_constants.py b/gilp/_constants.py
new file mode 100644
index 0000000..ebed364
--- /dev/null
+++ b/gilp/_constants.py
@@ -0,0 +1,185 @@
+"""Constants.
+
+This module contains all constants for gilp (except for LP examples).
+"""
+
+__author__ = 'Henry Robbins'
+
+# Color Theme -- Using Google's Material Design Color System
+# https://material.io/design/color/the-color-system.html
+
+PRIMARY_COLOR = '#1565c0'
+PRIMARY_LIGHT_COLOR = '#5e92f3'
+PRIMARY_DARK_COLOR = '#003c8f'
+SECONDARY_COLOR = '#d50000'
+SECONDARY_LIGHT_COLOR = '#ff5131'
+SECONDARY_DARK_COLOR = '#9b0000'
+PRIMARY_FONT_COLOR = '#ffffff'
+SECONDARY_FONT_COLOR = '#ffffff'
+# Grayscale
+TERTIARY_COLOR = '#DFDFDF'
+TERTIARY_LIGHT_COLOR = 'white' # Jupyter Notebook: white, Sphinx: #FCFCFC
+TERTIARY_DARK_COLOR = '#404040'
+
+# Figure Dimensions
+FIG_HEIGHT = 500
+FIG_WIDTH = 950 # Jupyter Notebook: 950, Sphinx: 700
+LEGEND_WIDTH = 200
+COMP_WIDTH = (FIG_WIDTH - LEGEND_WIDTH) / 2
+
+ISOPROFIT_STEPS = 25
+"""Number of isoprofit planes/lines plotted to the figure."""
+
+# Plotly Default Attributes
+
+LAYOUT = dict(width=FIG_WIDTH,
+ height=FIG_HEIGHT,
+ title=dict(text="Geometric Interpretation of LPs",
+ font=dict(size=18,
+ color=TERTIARY_DARK_COLOR),
+ x=0, y=0.99, xanchor='left', yanchor='top'),
+ legend=dict(title=dict(text='Constraint(s)',
+ font=dict(size=14)),
+ font=dict(size=13),
+ x=(1 - LEGEND_WIDTH / FIG_WIDTH) / 2, y=1,
+ xanchor='left', yanchor='top'),
+ margin=dict(l=0, r=0, b=0, t=int(FIG_HEIGHT/15)),
+ font=dict(family='Arial', color=TERTIARY_DARK_COLOR),
+ paper_bgcolor=TERTIARY_LIGHT_COLOR,
+ plot_bgcolor=TERTIARY_LIGHT_COLOR,
+ hovermode='closest',
+ clickmode='none',
+ dragmode='turntable')
+"""Layout attributes."""
+
+AXIS_2D = dict(gridcolor=TERTIARY_COLOR, gridwidth=1, linewidth=2,
+ linecolor=TERTIARY_DARK_COLOR, tickcolor=TERTIARY_COLOR,
+ ticks='outside', rangemode='tozero', showspikes=False,
+ title_standoff=15, automargin=True, zerolinewidth=2)
+"""2d axis attributes."""
+
+AXIS_3D = dict(backgroundcolor=TERTIARY_LIGHT_COLOR, showbackground=True,
+ gridcolor=TERTIARY_COLOR, gridwidth=2, showspikes=False,
+ linecolor=TERTIARY_DARK_COLOR, zerolinecolor='white',
+ rangemode='tozero', ticks='')
+"""3d axis attributes."""
+
+SLIDER = dict(x=0.5 + ((LEGEND_WIDTH / FIG_WIDTH) / 2), xanchor="left",
+ yanchor="bottom", lenmode='fraction', len=COMP_WIDTH / FIG_WIDTH,
+ active=0, tickcolor='white', ticklen=0)
+"""slider attributes."""
+
+TABLE = dict(header_font_color=[SECONDARY_COLOR, 'black'],
+ header_fill_color=TERTIARY_LIGHT_COLOR,
+ cells_font_color=[['black', SECONDARY_COLOR, 'black'],
+ ['black', 'black', 'black']],
+ cells_fill_color=TERTIARY_LIGHT_COLOR,
+ visible=False)
+"""table attributes."""
+
+SCATTER = dict(mode='markers',
+ hoverinfo='none',
+ visible=True,
+ showlegend=False,
+ fillcolor=PRIMARY_COLOR,
+ line=dict(width=4,
+ color=PRIMARY_DARK_COLOR),
+ marker_line=dict(width=2,
+ color=SECONDARY_COLOR),
+ marker=dict(size=9,
+ color=TERTIARY_LIGHT_COLOR,
+ opacity=0.99))
+"""2d scatter attributes."""
+
+SCATTER_3D = dict(mode='markers',
+ hoverinfo='none',
+ visible=True,
+ showlegend=False,
+ surfacecolor=PRIMARY_LIGHT_COLOR,
+ line=dict(width=6,
+ color=PRIMARY_COLOR),
+ marker_line=dict(width=1,
+ color=SECONDARY_COLOR),
+ marker=dict(size=5,
+ symbol='circle-open',
+ color=SECONDARY_LIGHT_COLOR,
+ opacity=0.99))
+"""3d scatter attributes."""
+
+# Plotly Template Attributes
+
+CANONICAL_TABLE = dict(header=dict(height=30,
+ font_size=13,
+ line=dict(color='black', width=1)),
+ cells=dict(height=25,
+ font_size=13,
+ line=dict(color='black',width=1)),
+ columnwidth=[1,0.8])
+"""Template attributes for an LP table in canonical tableau form."""
+
+DICTIONARY_TABLE = dict(header=dict(height=25,
+ font_size=14,
+ align=['left', 'right', 'left'],
+ line_color=TERTIARY_LIGHT_COLOR,
+ line_width=1),
+ cells=dict(height=25,
+ font_size=14,
+ align=['left', 'right', 'left'],
+ line_color=TERTIARY_LIGHT_COLOR,
+ line_width=1),
+ columnwidth=[50/COMP_WIDTH,
+ 25/COMP_WIDTH,
+ 1 - (75/COMP_WIDTH)])
+"""Template attributes for an LP table in dictionary tableau form."""
+
+BFS_SCATTER = dict(marker=dict(size=20, color='gray', opacity=1e-7),
+ hoverinfo='text',
+ hoverlabel=dict(bgcolor=TERTIARY_LIGHT_COLOR,
+ bordercolor=TERTIARY_DARK_COLOR,
+ font_family='Arial',
+ font_color=TERTIARY_DARK_COLOR,
+ align='left'))
+"""Template attributes for an LP basic feasible solutions (BFS)."""
+
+VECTOR = dict(mode='lines', line_color=SECONDARY_COLOR, visible=False)
+"""Template attributes for a 2d or 3d vector."""
+
+CONSTRAINT_LINE = dict(mode='lines', showlegend=True,
+ line=dict(width=2, dash='15,3,5,3'))
+"""Template attributes for (2d) LP constraints."""
+
+ISOPROFIT_LINE = dict(mode='lines', visible=False,
+ line=dict(color=SECONDARY_COLOR, width=4, dash=None))
+"""Template attributes for (2d) LP isoprofit lines."""
+
+REGION_2D_POLYGON = dict(mode="lines", opacity=0.2, fill="toself",
+ line=dict(width=3, color=PRIMARY_DARK_COLOR))
+"""Template attributes for (2d) LP feasible region."""
+
+REGION_3D_POLYGON = dict(mode="lines", opacity=0.2,
+ line=dict(width=5, color=PRIMARY_DARK_COLOR))
+"""Template attributes for (3d) LP feasible region."""
+
+CONSTRAINT_POLYGON = dict(surfacecolor='gray', mode="none",
+ opacity=0.5, visible='legendonly',
+ showlegend=True)
+"""Template attributes for (3d) LP constraints."""
+
+ISOPROFIT_IN_POLYGON = dict(mode="lines+markers",
+ surfacecolor=SECONDARY_COLOR,
+ marker=dict(size=5,
+ symbol='circle',
+ color=SECONDARY_COLOR),
+ line=dict(width=5,
+ color=SECONDARY_COLOR),
+ visible=False)
+"""Template attributes for (3d) LP isoprofit plane (interior)."""
+
+ISOPROFIT_OUT_POLYGON = dict(surfacecolor='gray', mode="none",
+ opacity=0.3, visible=False)
+"""Template attributes for (3d) LP isoprofit plane (exterior)."""
+
+BNB_NODE = dict(visible=False, align="center",
+ bordercolor=TERTIARY_DARK_COLOR, borderwidth=2, borderpad=3,
+ font=dict(size=12, color=TERTIARY_DARK_COLOR), ax=0, ay=0)
+"""Template attributes for a branch and bound node."""
diff --git a/gilp/_graphic.py b/gilp/_graphic.py
index 4724a54..f178d8e 100644
--- a/gilp/_graphic.py
+++ b/gilp/_graphic.py
@@ -227,7 +227,8 @@ def linear_string(A: np.ndarray,
str: String representation of the linear combination.
"""
# This function returns the correct sign (+ or -) prefix for a number
- def sign(num: float): return {-1: ' - ', 0: ' + ', 1: ' + '}[np.sign(num)]
+ def sign(num: float):
+ return {-1: ' - ', 0: ' + ', 1: ' + '}[np.sign(num)]
s = ''
if constant is not None:
diff --git a/gilp/simplex.py b/gilp/simplex.py
index 2a109c7..a830fac 100644
--- a/gilp/simplex.py
+++ b/gilp/simplex.py
@@ -515,9 +515,9 @@ def _initial_solution(lp: LP,
else:
x = _validate(x, [lp.n, n], 'Initial solution')
- if (np.allclose(np.dot(A,x), b, atol=feas_tol) and
- all(x >= np.zeros((n,1)) - feas_tol) and
- len(np.nonzero(x)[0]) <= m):
+ if (np.allclose(np.dot(A,x), b, atol=feas_tol)
+ and all(x >= np.zeros((n,1)) - feas_tol)
+ and len(np.nonzero(x)[0]) <= m):
B = list(np.nonzero(x)[0])
N = list(set(range(lp.n+lp.m)) - set(B))
while len(B) < m: # if initial solution is degenerate
diff --git a/gilp/tests/test_simplex.py b/gilp/tests/test_simplex.py
index a82aad6..59215d9 100644
--- a/gilp/tests/test_simplex.py
+++ b/gilp/tests/test_simplex.py
@@ -399,10 +399,10 @@ def test_branch_and_bound_manual():
assert not iteration.fathomed
assert iteration.incumbent is None
assert iteration.best_bound is None
- assert all(gilp.simplex(iteration.right_LP).x[:2] ==
- np.array([[1.8],[4]]))
- assert all(gilp.simplex(iteration.left_LP).x[:2] ==
- np.array([[3],[3]]))
+ assert all(gilp.simplex(iteration.right_LP).x[:2]
+ == np.array([[1.8],[4]]))
+ assert all(gilp.simplex(iteration.left_LP).x[:2]
+ == np.array([[3],[3]]))
@pytest.mark.parametrize("lp,x,val",[
diff --git a/gilp/visualize.py b/gilp/visualize.py
index cdd8a5c..77a835a 100644
--- a/gilp/visualize.py
+++ b/gilp/visualize.py
@@ -16,199 +16,23 @@
import numpy as np
import plotly.graph_objects as plt
from typing import Union, List, Tuple
+from ._constants import (AXIS_2D, AXIS_3D, BFS_SCATTER, BNB_NODE,
+ CANONICAL_TABLE, CONSTRAINT_LINE, CONSTRAINT_POLYGON,
+ DICTIONARY_TABLE, FIG_HEIGHT, FIG_WIDTH,
+ ISOPROFIT_IN_POLYGON, ISOPROFIT_LINE,
+ ISOPROFIT_OUT_POLYGON, ISOPROFIT_STEPS, LAYOUT,
+ LEGEND_WIDTH, PRIMARY_COLOR, PRIMARY_DARK_COLOR,
+ REGION_2D_POLYGON, REGION_3D_POLYGON, SCATTER,
+ SCATTER_3D, SECONDARY_COLOR, SLIDER, TABLE,
+ TERTIARY_DARK_COLOR, TERTIARY_LIGHT_COLOR, VECTOR)
from ._geometry import (intersection, interior_point, NoInteriorPoint,
- polytope_vertices, polytope_facets)
+ polytope_vertices, polytope_facets)
from ._graphic import (num_format, equation_string, linear_string, plot_tree,
Figure, label, table, vector, scatter, equation,
polygon, polytope)
from .simplex import (LP, simplex, branch_and_bound_iteration,
UnboundedLinearProgram, Infeasible)
-# COLOR THEME -- Using Google's Material Design Color System
-# https://material.io/design/color/the-color-system.html
-
-PRIMARY_COLOR = '#1565c0'
-PRIMARY_LIGHT_COLOR = '#5e92f3'
-PRIMARY_DARK_COLOR = '#003c8f'
-SECONDARY_COLOR = '#d50000'
-SECONDARY_LIGHT_COLOR = '#ff5131'
-SECONDARY_DARK_COLOR = '#9b0000'
-PRIMARY_FONT_COLOR = '#ffffff'
-SECONDARY_FONT_COLOR = '#ffffff'
-# Grayscale
-TERTIARY_COLOR = '#DFDFDF'
-TERTIARY_LIGHT_COLOR = 'white' # Jupyter Notebook: white, Sphinx: #FCFCFC
-TERTIARY_DARK_COLOR = '#404040'
-
-# FIGURE DIMENSIONS
-
-FIG_HEIGHT = 500
-"""Default figure height."""
-FIG_WIDTH = 950 # Jupyter Notebook: 950, Sphinx: 700
-"""Default figure width."""
-LEGEND_WIDTH = 200
-"""Default legend width."""
-COMP_WIDTH = (FIG_WIDTH - LEGEND_WIDTH) / 2
-"""Default width of the left and right component of a figure."""
-ISOPROFIT_STEPS = 25
-"""Number of isoprofit lines or plane to render."""
-
-# PLOTLY LAYOUT, AXIS, AND SLIDER ATTRIBUTES
-
-LAYOUT = dict(width=FIG_WIDTH,
- height=FIG_HEIGHT,
- title=dict(text="Geometric Interpretation of LPs",
- font=dict(size=18,
- color=TERTIARY_DARK_COLOR),
- x=0, y=0.99, xanchor='left', yanchor='top'),
- legend=dict(title=dict(text='Constraint(s)',
- font=dict(size=14)),
- font=dict(size=13),
- x=(1 - LEGEND_WIDTH / FIG_WIDTH) / 2, y=1,
- xanchor='left', yanchor='top'),
- margin=dict(l=0, r=0, b=0, t=int(FIG_HEIGHT/15)),
- font=dict(family='Arial', color=TERTIARY_DARK_COLOR),
- paper_bgcolor=TERTIARY_LIGHT_COLOR,
- plot_bgcolor=TERTIARY_LIGHT_COLOR,
- hovermode='closest',
- clickmode='none',
- dragmode='turntable')
-"""Default layout attributes."""
-
-AXIS_2D = dict(gridcolor=TERTIARY_COLOR, gridwidth=1, linewidth=2,
- linecolor=TERTIARY_DARK_COLOR, tickcolor=TERTIARY_COLOR,
- ticks='outside', rangemode='tozero', showspikes=False,
- title_standoff=15, automargin=True, zerolinewidth=2)
-"""Default 2d axis attributes."""
-
-AXIS_3D = dict(backgroundcolor=TERTIARY_LIGHT_COLOR, showbackground=True,
- gridcolor=TERTIARY_COLOR, gridwidth=2, showspikes=False,
- linecolor=TERTIARY_DARK_COLOR, zerolinecolor='white',
- rangemode='tozero', ticks='')
-"""Default 3d axis attributes."""
-
-SLIDER = dict(x=0.5 + ((LEGEND_WIDTH / FIG_WIDTH) / 2), xanchor="left",
- yanchor="bottom", lenmode='fraction', len=COMP_WIDTH / FIG_WIDTH,
- active=0, tickcolor='white', ticklen=0)
-"""Default slider attributes."""
-
-# PLOTLY DEFAULT TRACES
-
-TABLE = dict(header_font_color=[SECONDARY_COLOR, 'black'],
- header_fill_color=TERTIARY_LIGHT_COLOR,
- cells_font_color=[['black', SECONDARY_COLOR, 'black'],
- ['black', 'black', 'black']],
- cells_fill_color=TERTIARY_LIGHT_COLOR,
- visible=False)
-"""Default table attributes."""
-
-SCATTER = dict(mode='markers',
- hoverinfo='none',
- visible=True,
- showlegend=False,
- fillcolor=PRIMARY_COLOR,
- line=dict(width=4,
- color=PRIMARY_DARK_COLOR),
- marker_line=dict(width=2,
- color=SECONDARY_COLOR),
- marker=dict(size=9,
- color=TERTIARY_LIGHT_COLOR,
- opacity=0.99))
-"""Default 2d scatter attributes."""
-
-SCATTER_3D = dict(mode='markers',
- hoverinfo='none',
- visible=True,
- showlegend=False,
- surfacecolor=PRIMARY_LIGHT_COLOR,
- line=dict(width=6,
- color=PRIMARY_COLOR),
- marker_line=dict(width=1,
- color=SECONDARY_COLOR),
- marker=dict(size=5,
- symbol='circle-open',
- color=SECONDARY_LIGHT_COLOR,
- opacity=0.99))
-"""Default 3d scatter attributes."""
-
-# PLOTLY TRACE TEMPLATES
-
-CANONICAL_TABLE = dict(header=dict(height=30,
- font_size=13,
- line=dict(color='black', width=1)),
- cells=dict(height=25,
- font_size=13,
- line=dict(color='black',width=1)),
- columnwidth=[1,0.8])
-"""Template attributes for an LP table in canonical tableau form."""
-
-DICTIONARY_TABLE = dict(header=dict(height=25,
- font_size=14,
- align=['left', 'right', 'left'],
- line_color=TERTIARY_LIGHT_COLOR,
- line_width=1),
- cells=dict(height=25,
- font_size=14,
- align=['left', 'right', 'left'],
- line_color=TERTIARY_LIGHT_COLOR,
- line_width=1),
- columnwidth=[50/COMP_WIDTH,
- 25/COMP_WIDTH,
- 1 - (75/COMP_WIDTH)])
-"""Template attributes for an LP table in dictionary tableau form."""
-
-BFS_SCATTER = dict(marker=dict(size=20, color='gray', opacity=1e-7),
- hoverinfo='text',
- hoverlabel=dict(bgcolor=TERTIARY_LIGHT_COLOR,
- bordercolor=TERTIARY_DARK_COLOR,
- font_family='Arial',
- font_color=TERTIARY_DARK_COLOR,
- align='left'))
-"""Template attributes for an LP basic feasible solutions (BFS)."""
-
-VECTOR = dict(mode='lines', line_color=SECONDARY_COLOR, visible=False)
-"""Template attributes for a 2d or 3d vector."""
-
-CONSTRAINT_LINE = dict(mode='lines', showlegend=True,
- line=dict(width=2, dash='15,3,5,3'))
-"""Template attributes for (2d) LP constraints."""
-
-ISOPROFIT_LINE = dict(mode='lines', visible=False,
- line=dict(color=SECONDARY_COLOR, width=4, dash=None))
-"""Template attributes for (2d) LP isoprofit lines."""
-
-REGION_2D_POLYGON = dict(mode="lines", opacity=0.2, fill="toself",
- line=dict(width=3, color=PRIMARY_DARK_COLOR))
-"""Template attributes for (2d) LP feasible region."""
-
-REGION_3D_POLYGON = dict(mode="lines", opacity=0.2,
- line=dict(width=5, color=PRIMARY_DARK_COLOR))
-"""Template attributes for (3d) LP feasible region."""
-
-CONSTRAINT_POLYGON = dict(surfacecolor='gray', mode="none",
- opacity=0.5, visible='legendonly',
- showlegend=True)
-"""Template attributes for (3d) LP constraints."""
-
-ISOPROFIT_IN_POLYGON = dict(mode="lines+markers",
- surfacecolor=SECONDARY_COLOR,
- marker=dict(size=5,
- symbol='circle',
- color=SECONDARY_COLOR),
- line=dict(width=5,
- color=SECONDARY_COLOR),
- visible=False)
-"""Template attributes for (3d) LP isoprofit plane (interior)."""
-
-ISOPROFIT_OUT_POLYGON = dict(surfacecolor='gray', mode="none",
- opacity=0.3, visible=False)
-"""Template attributes for (3d) LP isoprofit plane (exterior)."""
-
-BNB_NODE = dict(visible=False, align="center",
- bordercolor=TERTIARY_DARK_COLOR, borderwidth=2, borderpad=3,
- font=dict(size=12, color=TERTIARY_DARK_COLOR), ax=0, ay=0)
-"""Template attributes for a branch and bound node."""
-
class InfiniteFeasibleRegion(Exception):
"""Raised when an LP is found to have an infinite feasible region and can
@@ -654,7 +478,10 @@ def tableau_strings(lp: LP,
header = ['(' + str(iteration) + ')', ' ', ' ']
content = []
content.append(['max','s.t.']+[' ' for i in range(m - 1)])
- def x_sub(i: int): return 'x' + str(i) + ''
+
+ def x_sub(i: int):
+ return 'x' + str(i) + ''
+
content.append(['z'] + [x_sub(B[i] + 1) for i in range(m)])
obj_func = ['= ' + linear_string(-T[0,1:n+m+1][N],
list(np.array(N)+1),
diff --git a/test_requirements.txt b/test_requirements.txt
index 280de59..3f42733 100644
--- a/test_requirements.txt
+++ b/test_requirements.txt
@@ -6,4 +6,5 @@ plotly==4.9.0
pytest==6.1.2
scipy==1.5.3
tox==3.20.1
-typing==3.7.4.3
\ No newline at end of file
+typing==3.7.4.3
+flake8==3.7.9
\ No newline at end of file