16. schematic
- manual drawing¶
This example demonstrates the basic functionality of the schematic
module.
The schematic module is a simple wrapper around matplotlib
that allows
programatically drawing diagrams, e.g. for tensor networks, in 2D and also
pseudo-3D. It is used as the backend for automatic drawing in
TensorNetwork.draw, but it also useful for
making manual diagrams not associated with a tensor network. The main object
is the Drawing
class.
16.1. Illustrative full examples¶
The following examples are intended to be illustrative of a full drawing.
If you supply a dict of presets
to Drawing
, then you can provide default
styling for various elements simply by name.
16.1.1. 2D example¶
%config InlineBackend.figure_formats = ['svg']
from quimb import schematic
presets = {
'bond': {'linewidth': 3},
'phys': {'linewidth': 1.5},
'center': {
# `get_wong_color` uses more colorblind friendly colors
'color': schematic.get_wong_color('orange'),
'hatch': '/////',
},
'left': {
'color': schematic.get_wong_color('bluedark'),
},
'right': {
'color': schematic.get_wong_color('blue'),
},
}
d = schematic.Drawing(presets=presets)
L = 10
center = 5
for i in range(10):
# draw tensor
d.circle((i, 0), preset=(
"center" if i == center else
"left" if i < center else
"right"
))
# draw physical index
d.line((i, 0), (i, -2/3), preset='phys')
# draw virtual bond
if i + 1 < L:
d.line((i, 0), (i + 1, 0), preset='bond')
# draw isometric conditions
if i != center:
d.arrowhead((i, -2/3), (i, 0), preset='phys')
if i < center - 1:
d.arrowhead((i, 0), (i + 1, 0), preset='bond')
if i > center + 1:
d.arrowhead((i, 0), (i - 1, 0), preset='bond')
# label the left
if center > 0:
d.text((center - 1, 0.8), 'LEFT')
d.patch_around([(i, 0) for i in range(center)], radius=0.5)
# label pair
if center + 1 < L:
d.patch_around_circles(
(center, 0), 0.3,
(center + 1, 0), 0.3,
facecolor=(.2, .8, .5, .4),
)
# label the right
if center + 2 < L:
d.text((center + 2, 0.8), 'RIGHT')
d.patch_around([(i, 0) for i in range(center + 2, L)], radius=0.5)
16.1.2. Pseudo-3D example¶
If you supply 3D coordinates to Drawing
methods then the objects will be
mapped by the axonometric projection to 2D and given appropriate z-ordering.
The projection and orientation can be controlled in the Drawing
constructor.
d = schematic.Drawing(presets=presets, figsize=(10, 10))
L = 10
radius = 0.15
for center in range(L):
# map the stage into a 3D x-coordinate
x = 2 * center
for i in range(10):
# draw tensor, can now use cube rather than circle
d.cube((x, i, 0), radius=radius, preset=(
"center" if i == center else
"left" if i < center else
"right"
))
# draw physical index
d.line((x, i, 0), (x, i, -2/3), preset='phys')
# draw virtual bond
if i + 1 < L:
d.line((x, i + radius, 0), (x, i + 1 - radius, 0), preset='bond')
# draw isometric conditions
if i != center:
d.arrowhead((x, i, -2/3), (x, i, 0), preset='phys')
if i < center - 1:
d.arrowhead((x, i, 0), (x, i + 1, 0), preset='bond')
if i > center + 1:
d.arrowhead((x, i, 0), (x, i - 1, 0), preset='bond')
# label the left
if center > 0:
d.patch_around([(x, i, 0) for i in range(center)], radius=3 * radius, smoothing=0.0)
# label pair
if center + 1 < L:
d.patch_around_circles(
(x, center, 0), 2.5 * radius,
(x, center + 1, 0), 2.5 * radius,
facecolor=(.2, .7, .3, .3),
)
# label the right
if center + 2 < L:
d.patch_around([(x, i, 0) for i in range(center + 2, L)], radius=3 * radius, smoothing=0.0)
d.text((0, 0, 1), '$T^i_{e_i}$', color=schematic.get_wong_color('orange'))
16.2. Individual elements:¶
Here we demonstrate the different types of individual element that can be placed.
The hash_to_color
function is useful way to deterministically generate colors
from hashable objects.
import numpy as np
16.2.1. Circles¶
Drawing.circle draws a circle with a given radius and center coordinates.
d = schematic.Drawing()
coos = [
(i, j, 0)
for i in range(4)
for j in range(4)
]
for coo in coos:
d.circle(
coo,
radius=np.random.uniform(0.2, 0.3),
color=schematic.hash_to_color(str(coo))
)
# dot is a simple alias circle
d.dot((1.5, 1.5, 0))
16.2.2. Cubes¶
Drawing.cube draws a cube with a given ‘radius’ and center coordinates, only for 3D coordinates.
d = schematic.Drawing()
coos = [
(i, j, 0)
for i in range(4)
for j in range(4)
]
for coo in coos:
d.cube(
coo,
radius=np.random.uniform(0.2, 0.3),
color=schematic.hash_to_color(str(coo))
)
16.2.3. Text¶
Drawing.text places text in data coordinates (including 3D). Drawing.label_ax and Drawing.label_fig are the same but default to axis and figure coordinates respectively.
d = schematic.Drawing()
coos = [
(i, j, 0)
for i in range(4)
for j in range(4)
]
for coo in coos:
d.text(
coo, str(coo),
color=schematic.hash_to_color(str(coo))
)
# labels are the same but use the axes or figure coordinates
d.label_ax(0.1, 0.9, '$\\mathbf{B}$', fontsize=20)
d.label_fig(0.5, 0.0, '$\\sum_e \\prod_i ~ T^i_{e_i}$', fontsize=16)
16.2.4. General shapes¶
Drawing.shape draws a general filled shape given a sequence of 2D or 3D coordinates.
d = schematic.Drawing()
rng = np.random.default_rng(1)
pts = rng.normal(size=(8, 3, 3))
for coos in pts:
d.shape(
coos,
alpha=0.8,
hatch="XXX",
edgecolor=schematic.hash_to_color(str(coos)),
)
16.2.5. Markers¶
Drawing.marker is a convenience method for specifying the shape of a patch using a single string or integer, to yield a regular polygon.
d = schematic.Drawing()
for p in range(3, 10):
d.marker((p, 0), marker=p)
d.text((p, 0.5), f"{p}-gon")
16.3. Lines and curves¶
16.3.1. Lines¶
The basic method for drawing lines between a pair of 2D or 3d points is Drawing.line.
d = schematic.Drawing()
d.line((0, 0, 0), (0, 0, 1))
d.line((0, 1, 0), (0, 1, 1), arrowhead=True)
d.line((1, 0, 0), (1, 0, 1), linewidth=4)
d.line((1, 1, 0), (1, 1, 1), linestyle=':')
16.3.2. Arrows and labels¶
You can easily add text and arrows along lines:
d = schematic.Drawing()
pa, pb, pc = (0, 0), (1, 1), (2, 0.5)
d.line(pa, pb, text="hello\n")
d.line(pb, pc, text=dict(text="world\n", color='red'), arrowhead=dict(center=1))
# calling `line` with `text=` is a shortcut for `text_between`
d.text_between(pa, pc, "Could this be a shortcut?", color="green")
16.3.3. Curves¶
If you want a line to pass through multiple points, you can use Drawing.curve to draw a smooth curve.
d = schematic.Drawing()
d.curve(
[(0, 0), (1, 1), (2.5, 0.5), (3.5, 1.5)],
linestyle='-.', linewidth=5,
)
# you can draw just the arrowhead separatel
d.arrowhead((1, 1), (2.5, 0.5), linewidth=5, width=0.15)
Curves pass exactly through all points given, with the smoothing
kwarg controlling… how smoothly they do this.
import matplotlib as mpl
d = schematic.Drawing()
rng = np.random.default_rng(1)
pts = rng.normal(size=(20, 3))
cm = mpl.colormaps.get_cmap('RdPu')
for pt in pts:
d.dot(pt, color='black', radius=0.05)
for smoothing in np.linspace(0.0, 2.0, 11):
d.curve(pts, smoothing=smoothing, color=cm(smoothing / 2))
d.label_ax(1.0, 0.60, "smoothing=0.0", color=cm(0.0))
d.label_ax(1.0, 0.65, "smoothing=1.0", color=cm(0.5))
d.label_ax(1.0, 0.70, "smoothing=2.0", color=cm(1.0))
16.3.4. Multi-edges¶
If you want to programmatically draw multiple lines from one place to the other (‘multi-edges’) you can use line_offset
:
d = schematic.Drawing()
pa, pb = (0, 0, 0), (0, 1, 1)
green = schematic.get_wong_color("green")
red = schematic.get_wong_color("red")
blue = schematic.get_wong_color("blue")
d.circle(pa, color=green)
d.circle(pb, color=red)
# you can still use arrowheads and text labels
d.line_offset(pa, pb, 0.2, arrowhead=dict(center=0.9), text='forwards\n', color=blue)
d.line_offset(pa, pb, 0.0, arrowhead=dict(center=0.9, reverse=True), text='backwards\n', color=blue)
d.line_offset(pa, pb, -0.2, arrowhead=dict(center=0.9, reverse="both"), text='both ways!\n', color=blue, midlength=0.4)
16.4. Highlighting areas and groups of objects¶
16.4.1. Patches around general areas¶
In technical drawings it is often useful to highlight areas. The Drawing.patch method does this by filling in a curve, given by a sequence of 2D or 3D coordinates.
d = schematic.Drawing(figsize=(4, 4))
d.marker((0, 0), marker='s', color=schematic.get_wong_color('yellow'))
d.marker((0, 1), marker='s', color=schematic.get_wong_color('bluedark'))
d.patch([
(-.3, -.3),
(+.3, -.3),
(+.3, +1.3),
(-.3, +1.3),
], smoothing=0.3)
16.4.2. Patches around two circles¶
If you want to specifically highlight two circles, you can use Drawing.patch_around_circles, and simply specify the two circles by their center coordinates and radii.
d = schematic.Drawing(figsize=(4, 4))
d.circle((0, 0), radius=3, color=schematic.get_wong_color('pink'))
d.circle((10, 1), radius=2, color=schematic.get_wong_color('blue'))
d.patch_around_circles(
(0, 0), 3,
(10, 1), 2,
padding=0.5,
)
16.4.3. Patches around arbitrary collections of objects¶
If you want to highlight an arbitrary collection of objects, you can call Drawing.patch_around, this computes the convex hull of the objects and draws a patch around it.
d = schematic.Drawing()
for pt in pts[:7]:
d.dot(pt, color='orange', radius=0.05)
for pt in pts[7:]:
d.dot(pt, color='black', radius=0.05)
d.patch_around(pts[:7], edgecolor='orange')
You can control how much padding is added around the perimeter of the objects
using the radius
kwarg.
d = schematic.Drawing()
for k, pt in enumerate(pts):
d.dot(pt, color=schematic.hash_to_color(str(k)), radius=0.05)
for k in range(1, len(pts)):
d.patch_around(
pts[:k],
radius=0.05 * k,
facecolor=schematic.hash_to_color(str(k - 1)),
linestyle="-",
zorder=-k,
alpha=0.5,
)