diff --git a/docs/development/installation.rst b/docs/development/installation.rst index 68b2177..e21d539 100644 --- a/docs/development/installation.rst +++ b/docs/development/installation.rst @@ -31,6 +31,10 @@ adjustments to the source code and see changes without re-installing. The pip install -e .[dev] +.. raw:: html + + MAKE SURE THIS IS ACCURATE + To run tests and see coverage, run the following in the virtual enviroment. .. code-block:: bash diff --git a/gilp/visualize.py b/gilp/visualize.py index 9c5753d..c1e11fa 100644 --- a/gilp/visualize.py +++ b/gilp/visualize.py @@ -204,20 +204,6 @@ def x_sub(i: int): return 'x' + str(i) + '' return header, content -def add_path(fig: plt.Figure, path: List[np.ndarray]) -> List[int]: - """Add vectors for visualizing the simplex path. Return vector indices.""" - fig.add_trace(scatter([path[0]], 'initial_sol')) - indices = [] - for i in range(len(path)-1): - a = np.round(path[i],7) - b = np.round(path[i+1],7) - d = (b-a)/ITERATION_STEPS - for j in range(ITERATION_STEPS): - fig.add_trace(vector(a,a+(j+1)*d)) - indices.append(len(fig.data)-1) - return indices - - def add_isoprofits(fig: plt.Figure, lp: LP) -> Tuple[List[int], List[float]]: """Add the set of isoprofit lines/planes which can be toggled over. @@ -263,6 +249,67 @@ def add_isoprofits(fig: plt.Figure, lp: LP) -> Tuple[List[int], List[float]]: return indices, objectives +def isoprofit_slider(isoprofit_IDs: List[int], + objectives: List[float], + fig: plt.Figure, + n: int) -> plt.layout.Slider: + '''Create a plotly slider to toggle between isoprofit lines / planes. + + Args: + isoprofit_IDs (List[int]): IDs of every isoprofit trace. + objectives (List[float]): Objective values for every isoprofit trace. + fig (plt.Figure): The figure containing the isoprofit traces. + n (int): The dimension of the LP the figure visualizes. + + Returns: + plt.layout.SLider: A plotly slider that can be added to a figure. + ''' + # Create each step of the isoprofit slider + iso_steps = [] + for i in range(len(isoprofit_IDs)): + visible = [fig.data[k].visible for k in range(len(fig.data))] + + # Set isoprofit line / plane visibilities + for j in isoprofit_IDs: + if n == 2: + visible[j] = False + if n == 3: + visible[j[0]] = False + visible[j[1]] = False + if n == 2: + visible[isoprofit_IDs[i]] = True + if n == 3: + visible[isoprofit_IDs[i][0]] = True + visible[isoprofit_IDs[i][1]] = True + + lb = objectives[i] + step = dict(method="update", label=lb, args=[{"visible": visible}]) + iso_steps.append(step) + + # Create the Plotly slider object + params = dict(x=TABLEAU_NORMALIZED_X_COORD, xanchor="left", + y=0.01, yanchor="bottom", + pad=dict(l=0, r=0, b=0, t=50), + lenmode='fraction', len=0.4, active=0, + currentvalue={"prefix": "Objective Value: "}, + tickcolor='white', ticklen=0, steps=iso_steps) + return plt.layout.Slider(params) + + +def add_path(fig: plt.Figure, path: List[np.ndarray]) -> List[int]: + """Add vectors for visualizing the simplex path. Return vector indices.""" + fig.add_trace(scatter([path[0]], 'initial_sol')) + indices = [] + for i in range(len(path)-1): + a = np.round(path[i],7) + b = np.round(path[i+1],7) + d = (b-a)/ITERATION_STEPS + for j in range(ITERATION_STEPS): + fig.add_trace(vector(a,a+(j+1)*d)) + indices.append(len(fig.data)-1) + return indices + + def add_tableaus(fig: plt.Figure, lp:LP, bases: List[int], @@ -297,37 +344,22 @@ def add_tableaus(fig: plt.Figure, return indices -def simplex_visual(lp: LP, - tableau_form: str = 'dictionary', - rule: str = 'bland', - initial_solution: np.ndarray = None, - iteration_limit: int = None) -> plt.Figure: - """Render a figure showing the geometry of simplex. +def iteration_slider(path_IDs: List[int], + table_IDs: List[int], + fig: plt.Figure, + n: int) -> plt.layout.Slider: + """Create a plotly slider to toggle between iterations of simplex Args: - lp (LP): LP on which to run simplex - tableau_form (str): Displayed tableau form. Default is 'dictionary' - rule (str): Pivot rule to be used. Default is 'bland' - initial_solution (np.ndarray): An initial solution. Default is None. - iteration_limit (int): A limit on simplex iterations. Default is None. + path_IDs (List[int]): IDs of every simplex path trace. + table_IDs (List[int]): IDs of every table trace. + fig (plt.Figure): The figure containing the traces. + n (int): The dimension of the LP the figure visualizes. Returns: - plt.Figure: A plotly figure which shows the geometry of simplex. + plt.layout.Slider: A plotly slider that can be added to a figure. """ - - fig = plot_lp(lp) # Plot feasible region - n,m,A,b,c = lp.get_inequality_form() - path, bases, value, opt = simplex(lp=lp, - pivot_rule=rule, - initial_solution=initial_solution, - iteration_limit=iteration_limit) - - # Keep track of indices for all the different traces - path_IDs = add_path(fig, [i[list(range(n)),:] for i in path]) - table_IDs = add_tableaus(fig, lp, bases, tableau_form) - isoprofit_IDs, objectives = add_isoprofits(fig,lp) - - # Add slider for toggling through simplex iterations + # Create each step of the iteration slider iter_steps = [] for i in range(len(path_IDs)+1): visible = [fig.data[j].visible for j in range(len(fig.data))] @@ -349,41 +381,60 @@ def simplex_visual(lp: LP, step = dict(method="update", label=lb, args=[{"visible": visible}]) iter_steps.append(step) - iter_slider = dict(x=TABLEAU_NORMALIZED_X_COORD, xanchor="left", - y=(85/FIG_HEIGHT), yanchor="bottom", - pad=dict(l=0, r=0, b=0, t=0), - lenmode='fraction', len=0.4, active=0, - currentvalue={"prefix": "Iteration: "}, - tickcolor='white', ticklen=0, steps=iter_steps) + # Create the Plotly slider object + params = dict(x=TABLEAU_NORMALIZED_X_COORD, xanchor="left", + y=(85/FIG_HEIGHT), yanchor="bottom", + pad=dict(l=0, r=0, b=0, t=0), + lenmode='fraction', len=0.4, active=0, + currentvalue={"prefix": "Iteration: "}, + tickcolor='white', ticklen=0, steps=iter_steps) + return plt.layout.Slider(params) - # Add slider for toggling through isoprofit lines / planes - iso_steps = [] - for i in range(len(isoprofit_IDs)): - visible = [fig.data[k].visible for k in range(len(fig.data))] - # Set isoprofit line / plane visibilities - for j in isoprofit_IDs: - if n == 2: - visible[j] = False - if n == 3: - visible[j[0]] = False - visible[j[1]] = False - if n == 2: - visible[isoprofit_IDs[i]] = True - if n == 3: - visible[isoprofit_IDs[i][0]] = True - visible[isoprofit_IDs[i][1]] = True +def lp_visual(lp: LP) -> plt.Figure: + """Render a plotly figure visualizing the geometry of an LP.""" - lb = objectives[i] - step = dict(method="update", label=lb, args=[{"visible": visible}]) - iso_steps.append(step) + fig = plot_lp(lp) # Plot feasible region + isoprofit_IDs, objectives = add_isoprofits(fig, lp) + iso_slider = isoprofit_slider(isoprofit_IDs, objectives, fig, lp.n) + fig.update_layout(sliders=[iso_slider]) + + return fig + + +def simplex_visual(lp: LP, + tableau_form: str = 'dictionary', + rule: str = 'bland', + initial_solution: np.ndarray = None, + iteration_limit: int = None) -> plt.Figure: + """Render a figure showing the geometry of simplex. + + Args: + lp (LP): LP on which to run simplex + tableau_form (str): Displayed tableau form. Default is 'dictionary' + rule (str): Pivot rule to be used. Default is 'bland' + initial_solution (np.ndarray): An initial solution. Default is None. + iteration_limit (int): A limit on simplex iterations. Default is None. + + Returns: + plt.Figure: A plotly figure which shows the geometry of simplex. + """ + + fig = plot_lp(lp) # Plot feasible region + n,m,A,b,c = lp.get_inequality_form() + path, bases, value, opt = simplex(lp=lp, + pivot_rule=rule, + initial_solution=initial_solution, + iteration_limit=iteration_limit) + + # Create all traces: isoprofit, path, and table + isoprofit_IDs, objectives = add_isoprofits(fig, lp) + path_IDs = add_path(fig, [i[list(range(n)),:] for i in path]) + table_IDs = add_tableaus(fig, lp, bases, tableau_form) - iso_slider = dict(x=TABLEAU_NORMALIZED_X_COORD, xanchor="left", - y=0.01, yanchor="bottom", - pad=dict(l=0, r=0, b=0, t=50), - lenmode='fraction', len=0.4, active=0, - currentvalue={"prefix": "Objective Value: "}, - tickcolor='white', ticklen=0, steps=iso_steps) + # Create sliders and add them to figure + iso_slider = isoprofit_slider(isoprofit_IDs, objectives, fig, n) + iter_slider = iteration_slider(path_IDs, table_IDs, fig, n) + fig.update_layout(sliders=[iso_slider, iter_slider]) - fig.update_layout(sliders=[iter_slider, iso_slider]) return fig