def test_tickedstroke(): fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12, 4)) path = Path.unit_circle() patch = patches.PathPatch(path, facecolor='none', lw=2, path_effects=[ path_effects.withTickedStroke(angle=-90, spacing=10, length=1) ]) ax1.add_patch(patch) ax1.axis('equal') ax1.set_xlim(-2, 2) ax1.set_ylim(-2, 2) ax2.plot( [0, 1], [0, 1], label=' ', path_effects=[path_effects.withTickedStroke(spacing=7, angle=135)]) nx = 101 x = np.linspace(0.0, 1.0, nx) y = 0.3 * np.sin(x * 8) + 0.4 ax2.plot(x, y, label=' ', path_effects=[path_effects.withTickedStroke()]) ax2.legend() nx = 101 ny = 105 # Set up survey vectors xvec = np.linspace(0.001, 4.0, nx) yvec = np.linspace(0.001, 4.0, ny) # Set up survey matrices. Design disk loading and gear ratio. x1, x2 = np.meshgrid(xvec, yvec) # Evaluate some stuff to plot g1 = -(3 * x1 + x2 - 5.5) g2 = -(x1 + 2 * x2 - 4) g3 = .8 + x1**-3 - x2 cg1 = ax3.contour(x1, x2, g1, [0], colors=('k', )) plt.setp(cg1.collections, path_effects=[path_effects.withTickedStroke(angle=135)]) cg2 = ax3.contour(x1, x2, g2, [0], colors=('r', )) plt.setp(cg2.collections, path_effects=[path_effects.withTickedStroke(angle=60, length=2)]) cg3 = ax3.contour(x1, x2, g3, [0], colors=('b', )) plt.setp(cg3.collections, path_effects=[path_effects.withTickedStroke(spacing=7)]) ax3.set_xlim(0, 4) ax3.set_ylim(0, 4)
def plotOptProb( obj, xRange, yRange, ineqCon=None, eqCon=None, nPoints=51, optPoint=None, conStyle="shaded", ax=None, colors=None, cmap=None, levels=None, labelAxes=True, ): """Generate a contour plot of a 2D constrained optimisation problem Parameters ---------- obj : function Objective function, should accept inputs in the form f = obj(x, y) where x and y are 2D arrays xRange : list or array Upper and lower limits of the plot in x yRange : list or array Upper and lower limits of the plot in y ineqCon : function or list of functions, optional Inequality constraint functions, should accept inputs in the form g = g(x, y) where x and y are 2D arrays. Constraints are assumed to be of the form g <= 0 eqCon : functions or list of functions, optional Equality constraint functions, should accept inputs in the form h = h(x, y) where x and y are 2D arrays. Constraints are assumed to be of the form h == 0 nPoints : int, optional Number of points in each direction to evaluate the objective and constraint functions at optPoint : list or array, optional Optimal Point, if you want to plot a point there, by default None conStyle : str, optional Controls how inequality constraints are represented, "shaded" will shade the infeasible regions while "hashed" will place hashed lines on the infeasible side of the feasible boundary, by default "shaded", note the "hashed" option only works for matplotlib >= 3.4 ax : matplotlib axes object, optional axes to plot, by default None, in which case a new figure will be created and returned by the function colors : list, optional List of colors to use for the constraint lines, by default uses the current matplotlib color cycle cmap : colormap, optional Colormap to use for the objective contours, by default will use nicePlots' parula map levels : list, array, int, optional Number or values of contour lines to plot for the objective function labelAxes : bool, optional Whether to label the x and y axes, by default True, in which case the axes will be labelled, "$X_1$" and "$X_2$" Returns ------- fig : matplotlib figure object Figure containing the plot. Returned only if no input ax object is specified ax : matplotlib axes object, but only if no ax object is specified Axis with the colored line. Returned only if no input ax object is specified """ # --- Create a new figure if the user did not supply an ax object --- returnFig = False if ax is None: fig, ax = plt.subplots() returnFig = True # --- If user provided only single inequality or equality constraint, convert it to an iterable --- cons = {} for inp, key in zip([eqCon, ineqCon], ["eqCon", "ineqCon"]): if inp is not None: if not hasattr(inp, "__iter__"): cons[key] = [inp] else: cons[key] = inp else: cons[key] = [] # --- Check that conStyle contains a supported value to avoid random conStyle arguments --- if conStyle.lower() not in ["shaded", "hashed"]: raise ValueError(f"conStyle: {conStyle} is not supported") # --- Check if user has a recent enough version of matplotlib to use hashed boundaries --- if conStyle.lower() == "hashed": try: patheffects.withTickedStroke except AttributeError: warnings.warn( "matplotlib >= 3.4 is required for hashed inequality constrain boundaries, switching to shaded inequality constraint style" ) conStyle = "shaded" # --- Define some default values if the user didn't provide them --- if cmap is None: cmap = parula_map if colors is None: colors = plt.rcParams["axes.prop_cycle"].by_key()["color"] nColor = len(colors) # --- Create grid of points for evaluating functions --- X, Y = np.meshgrid(np.linspace(xRange[0], xRange[1], nPoints), np.linspace(yRange[0], yRange[1], nPoints)) # --- Evaluate objective and constraint functions --- Fobj = obj(X, Y) g = [] for ineq in cons["ineqCon"]: g.append(ineq(X, Y)) h = [] for eq in cons["eqCon"]: h.append(eq(X, Y)) # --- Plot objective contours --- adjust_spines(ax, outward=True) ax.contour( X, Y, Fobj, levels=levels, cmap=cmap, ) # --- Plot constraint boundaries --- colorIndex = 0 for conValue in g: contour = ax.contour(X, Y, conValue, levels=[0.0], colors=colors[colorIndex % nColor]) if conStyle.lower() == "hashed": plt.setp(contour.collections, path_effects=[ patheffects.withTickedStroke(angle=60, length=2) ]) elif conStyle.lower() == "shaded": ax.contourf(X, Y, conValue, levels=[0.0, np.inf], colors=colors[colorIndex % nColor], alpha=0.4) colorIndex += 1 for conValue in h: ax.contour(X, Y, conValue, levels=[0.0], colors=colors[colorIndex % nColor]) # --- Plot optimal point if provided --- if optPoint is not None: ax.plot( optPoint[0], optPoint[1], "o", color="black", markeredgecolor=ax.get_facecolor(), markersize=10, clip_on=False, ) # --- Label axes if required --- if labelAxes: ax.set_xlabel("$x_1$") ax.set_ylabel("$x_2$", rotation="horizontal", ha="right") if returnFig: return fig, ax else: return
# Applying TickedStroke to paths # ============================== import matplotlib.patches as patches from matplotlib.path import Path import numpy as np import matplotlib.pyplot as plt import matplotlib.patheffects as patheffects fig, ax = plt.subplots(figsize=(6, 6)) path = Path.unit_circle() patch = patches.PathPatch(path, facecolor='none', lw=2, path_effects=[ patheffects.withTickedStroke(angle=-90, spacing=10, length=1) ]) ax.add_patch(patch) ax.axis('equal') ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) plt.show() ############################################################################### # Applying TickedStroke to lines # ============================== fig, ax = plt.subplots(figsize=(6, 6)) ax.plot([0, 1], [0, 1],
x1, x2 = np.meshgrid(xvec, yvec) # Evaluate some stuff to plot obj = x1**2 + x2**2 - 2 * x1 - 2 * x2 + 2 g1 = -(3 * x1 + x2 - 5.5) g2 = -(x1 + 2 * x2 - 4.5) g3 = 0.8 + x1**-3 - x2 cntr = ax.contour(x1, x2, obj, [0.01, 0.1, 0.5, 1, 2, 4, 8, 16], colors='black') ax.clabel(cntr, fmt="%2.1f", use_clabeltext=True) cg1 = ax.contour(x1, x2, g1, [0], colors='sandybrown') plt.setp(cg1.collections, path_effects=[patheffects.withTickedStroke(angle=135)]) cg2 = ax.contour(x1, x2, g2, [0], colors='orangered') plt.setp(cg2.collections, path_effects=[patheffects.withTickedStroke(angle=60, length=2)]) cg3 = ax.contour(x1, x2, g3, [0], colors='mediumblue') plt.setp(cg3.collections, path_effects=[patheffects.withTickedStroke(spacing=7)]) ax.set_xlim(0, 4) ax.set_ylim(0, 4) plt.show()
""" ============================== Lines with a ticked patheffect ============================== Ticks can be added along a line to mark one side as a barrier using `~matplotlib.patheffects.TickedStroke`. You can control the angle, spacing, and length of the ticks. The ticks will also appear appropriately in the legend. """ import numpy as np import matplotlib.pyplot as plt from matplotlib import patheffects fig, ax = plt.subplots(figsize=(6, 6)) ax.plot([0, 1], [0, 1], label="Line", path_effects=[patheffects.withTickedStroke(spacing=7, angle=135)]) nx = 101 x = np.linspace(0.0, 1.0, nx) y = 0.3*np.sin(x*8) + 0.4 ax.plot(x, y, label="Curve", path_effects=[patheffects.withTickedStroke()]) ax.legend() plt.show()