Concurrent optimization of orientation and topology¶

Google Collab Book

In [1]:
import matplotlib.pyplot as plt
import numpy as np
import torch
from scipy.optimize import bisect
from tqdm import tqdm

torch.set_default_dtype(torch.double)


from torchfem import Planar
from torchfem.rotations import planar_rotation

Parameters¶

In [2]:
# Orthotropic material parameters
E_1 = 100000.0
E_2 = 10000.0
nu_12 = 0.3
G_12 = 5000.0

# Mesh parameters
Nx = 60
Ny = 30

# Optimization parameters (SIMP penalization, volume fraction, move limit, step size)
p = 3
volfrac = 0.4
move = 0.1
ori_step = 1000.0

# Volume constraint
V_0 = volfrac * Nx * Ny

Material model¶

The material is a linear, elastic, homogeneous and orthotropic material. If we assume a plane stress state, we may write the relation between stress and strain (material model) in terms of the compliance tensor $$ \begin{pmatrix} \varepsilon_{xx}\\ \varepsilon_{yy}\\ 2\varepsilon_{xy} \end{pmatrix} = \begin{pmatrix} \frac{1}{E_{xx}} & -\frac{\nu_{xy}}{E_{xx}} & 0\\ -\frac{\nu_{yx}}{E_{yy}} & \frac{1}{E_{yy}} & 0\\ 0 & 0 & \frac{1}{G_{xy}} \end{pmatrix} \begin{pmatrix} \sigma_{xx}\\ \sigma_{yy}\\ \sigma_{xy} \end{pmatrix} $$ or as stiffness tensor $$ \begin{pmatrix} \sigma_{xx}\\ \sigma_{yy}\\ \sigma_{xy} \end{pmatrix} = \underbrace{\begin{pmatrix} \frac{E_{xx}}{1-\nu_{xy}\nu_{yx}} & \frac{\nu_{xy}E_{yy}}{1-\nu_{xy}\nu_{yx}} & 0\\ \frac{\nu_{yx}E_{xx}}{1-\nu_{xy}\nu_{yx}}& \frac{E_{yy}}{1-\nu_{xy}\nu_{yx}} & 0\\ 0 & 0 & G_{xy} \end{pmatrix}}_{\bm{C}} \begin{pmatrix} \varepsilon_{xx}\\ \varepsilon_{yy}\\ 2\varepsilon_{xy} \end{pmatrix} $$

The stiffness is characterized by four properties $E_{xx}, E_{yy}, G_{xy}, \nu_{xy}$. The tensor is symmetric with $$ \frac{\nu_{xy}}{E_{xx}}=\frac{\nu_{yx}}{E_{yy}} $$

Fortunately, we have a 'torchfem' function that computes the stiffness tensor from the four properties.

In [3]:
from torchfem.materials import OrthotropicElasticityPlaneStress

mat = OrthotropicElasticityPlaneStress(E_1, E_2, nu_12, G_12)

Mesh generation¶

We create a simple domain of $N_x$ by $N_y$ elements. The domain is a rectangle and we apply a singular force at the tip, while the left side is fully clamped.

In [4]:
# Create nodes
n1 = torch.linspace(0.0, Nx, Nx + 1)
n2 = torch.linspace(0.0, Ny, Ny + 1)
n1, n2 = torch.stack(torch.meshgrid(n1, n2, indexing="ij"))
nodes = torch.stack([n1.ravel(), n2.ravel()], dim=1)

# Create elements connecting nodes
elements = []
for i in range(Nx):
    for j in range(Ny):
        n0 = j + i * (Ny + 1)
        elements.append([n0, n0 + Ny + 1, n0 + Ny + 2, n0 + 1])
elements = torch.tensor(elements)

# Create the beam model
beam = Planar(nodes, elements, mat)

# Load at top
beam.forces[(Nx + 1) * (Ny + 1) - Ny - 1, 1] = -1

# Constrained displacement at left end
beam.constraints[nodes[:, 0] == 0, :] = True

# Create and plot the domain
beam.plot()
No description has been provided for this image

Objective function¶

The objective function is the compliance of the structure. The compliance is defined as $$ C = \frac{1}{2}\bm{u}^\top \bm{f} $$ where $\bm{u}$ is the global displacement vector and $\bm{f}$ is the global force vector.

The compliance is a measure of the energy stored in the structure due to the applied force and we want to minimize w.r.t. to the relative element densities $\bm{\rho}$ and the element-wise fiber orientations $\bm{\phi}$ in order to get a stiff structure.

In [5]:
def compliance(rho, phi):
    # Rotate
    R = planar_rotation(phi)
    beam.material = mat.vectorize(beam.n_elem).rotate(R)
    # Apply density
    beam.material.C = rho[:, None, None, None, None] ** p * beam.material.C
    u, f, _, _, _ = beam.solve()
    return 0.5 * torch.inner(u.ravel(), f.ravel())

Filter¶

We employ a sensitivity filter for the relative densities to regularize the problem (mesh dependency) and to avoid checkerboard patterns. The filter is defined as $$ \widetilde{\frac{\partial C}{\partial \rho_j}} = \frac{1}{\rho_j} \frac{\sum_i H_{ji} \rho_i \frac{\partial C}{\partial \rho_i} }{\sum_i H_{ji}} $$ where $H_{ji}$ is the filter kernel. We use a simple linear filter kernel defined as $$ H_{ji} = \max(0, r - \lVert \bm{x}_i - \bm{x}_j \rVert) $$ with element centers $\bm{x}_i$ and $\bm{x}_j$ and filter radius $r$.

In [6]:
filter_radius = 1.5
ecenters = torch.stack([torch.mean(nodes[e], dim=0) for e in elements])
dist = torch.cdist(ecenters, ecenters)
H = filter_radius - dist
H[dist > filter_radius] = 0.0

The optimizer¶

In [7]:
def optimize(rho, phi, n_iter=100):
    # Bounds
    rho_min = 0.001 * torch.ones_like(rho)
    rho_max = torch.ones_like(rho)

    # Storage for compliance
    energies = []

    for _ in tqdm(range(n_iter)):
        C = compliance(rho, phi)
        dC_dphi = torch.autograd.grad(C, phi, retain_graph=True)[0]
        dC_drho = torch.autograd.grad(C, rho)[0]
        dC_drho = H @ (rho * dC_drho) / H.sum(dim=0) / rho

        # For a certain value of mu, apply the iteration scheme
        def make_step(mu):
            G_k = -dC_drho / mu
            upper = torch.min(rho_max, (1 + move) * rho)
            lower = torch.max(rho_min, (1 - move) * rho)
            rho_trial = G_k**0.5 * rho
            return torch.max(torch.min(rho_trial, upper), lower)

        # Constraint function
        def g(mu):
            rho_k = make_step(mu)
            return rho_k.sum() - V_0

        # Find the root of g(mu)
        with torch.no_grad():
            mu = bisect(g, 1e-10, 100.0)

        # Advance rho and phi
        rho.data = make_step(mu)
        phi.data -= ori_step * dC_dphi.data

        # Track compliance
        with torch.no_grad():
            energies.append(C.item())
    return energies
In [8]:
@torch.no_grad()
def plot_solution(rho, phi, ptype="element", color="lightskyblue"):
    # Compute properties
    centers = beam.nodes[beam.elements, :].mean(dim=1)
    dir = torch.stack([torch.cos(phi), -torch.sin(phi), torch.zeros_like(phi)]).T

    # Plot orientations
    beam.plot(element_property=rho, linewidth=0.2, cmap="gray_r")
    if ptype == "element":
        plt.quiver(
            centers[:, 0],
            centers[:, 1],
            dir[:, 0],
            dir[:, 1],
            pivot="middle",
            headlength=0,
            headaxislength=0,
            headwidth=0,
            width=0.003,
            color=color,
            alpha=rho,
        )
    if ptype == "stream":
        dir[rho < 0.75] = 0.0
        plt.streamplot(
            centers[:, 0].reshape(Nx, Ny).numpy()[:, 0],
            centers[:, 1].reshape(Nx, Ny).numpy()[0, :],
            dir[:, 0].reshape(Nx, Ny).T.numpy(),
            dir[:, 1].reshape(Nx, Ny).T.numpy(),
            color=color,
            broken_streamlines=False,
            arrowstyle="-",
            cmap="coolwarm",
        )
    plt.show()

Optimize with fibers initially aligned in x¶

In [9]:
# Design variables
phi = 0.0 * torch.ones((len(beam.elements)), requires_grad=True)
rho = volfrac * torch.ones((len(beam.elements)), requires_grad=True)
In [10]:
energies = optimize(rho, phi, 200)
plt.semilogy(energies, ".-k")
plt.grid()
plt.title("Optimization history")
plt.xlabel("Iteration")
plt.ylabel("Compliance")
plt.show()
  0%|          | 0/200 [00:00<?, ?it/s]
  0%|          | 1/200 [00:00<00:31,  6.40it/s]
  1%|          | 2/200 [00:00<00:30,  6.56it/s]
  2%|▏         | 3/200 [00:00<00:29,  6.60it/s]
  2%|▏         | 4/200 [00:00<00:29,  6.62it/s]
  2%|▎         | 5/200 [00:00<00:29,  6.63it/s]
  3%|▎         | 6/200 [00:00<00:29,  6.65it/s]
  4%|▎         | 7/200 [00:01<00:29,  6.64it/s]
  4%|▍         | 8/200 [00:01<00:28,  6.65it/s]
  4%|▍         | 9/200 [00:01<00:28,  6.65it/s]
  5%|▌         | 10/200 [00:01<00:28,  6.66it/s]
  6%|▌         | 11/200 [00:01<00:28,  6.67it/s]
  6%|▌         | 12/200 [00:01<00:28,  6.68it/s]
  6%|▋         | 13/200 [00:01<00:27,  6.68it/s]
  7%|▋         | 14/200 [00:02<00:27,  6.69it/s]
  8%|▊         | 15/200 [00:02<00:27,  6.69it/s]
  8%|▊         | 16/200 [00:02<00:27,  6.68it/s]
  8%|▊         | 17/200 [00:02<00:27,  6.68it/s]
  9%|▉         | 18/200 [00:02<00:27,  6.64it/s]
 10%|▉         | 19/200 [00:02<00:27,  6.64it/s]
 10%|█         | 20/200 [00:03<00:27,  6.63it/s]
 10%|█         | 21/200 [00:03<00:27,  6.63it/s]
 11%|█         | 22/200 [00:03<00:26,  6.63it/s]
 12%|█▏        | 23/200 [00:03<00:26,  6.62it/s]
 12%|█▏        | 24/200 [00:03<00:26,  6.64it/s]
 12%|█▎        | 25/200 [00:03<00:26,  6.63it/s]
 13%|█▎        | 26/200 [00:03<00:26,  6.63it/s]
 14%|█▎        | 27/200 [00:04<00:26,  6.57it/s]
 14%|█▍        | 28/200 [00:04<00:26,  6.58it/s]
 14%|█▍        | 29/200 [00:04<00:25,  6.61it/s]
 15%|█▌        | 30/200 [00:04<00:25,  6.58it/s]
 16%|█▌        | 31/200 [00:04<00:25,  6.56it/s]
 16%|█▌        | 32/200 [00:04<00:25,  6.58it/s]
 16%|█▋        | 33/200 [00:04<00:25,  6.59it/s]
 17%|█▋        | 34/200 [00:05<00:25,  6.59it/s]
 18%|█▊        | 35/200 [00:05<00:25,  6.60it/s]
 18%|█▊        | 36/200 [00:05<00:24,  6.62it/s]
 18%|█▊        | 37/200 [00:05<00:24,  6.57it/s]
 19%|█▉        | 38/200 [00:05<00:24,  6.49it/s]
 20%|█▉        | 39/200 [00:05<00:24,  6.54it/s]
 20%|██        | 40/200 [00:06<00:24,  6.53it/s]
 20%|██        | 41/200 [00:06<00:24,  6.53it/s]
 21%|██        | 42/200 [00:06<00:24,  6.54it/s]
 22%|██▏       | 43/200 [00:06<00:23,  6.57it/s]
 22%|██▏       | 44/200 [00:06<00:23,  6.57it/s]
 22%|██▎       | 45/200 [00:06<00:23,  6.56it/s]
 23%|██▎       | 46/200 [00:06<00:23,  6.55it/s]
 24%|██▎       | 47/200 [00:07<00:23,  6.53it/s]
 24%|██▍       | 48/200 [00:07<00:23,  6.54it/s]
 24%|██▍       | 49/200 [00:07<00:22,  6.57it/s]
 25%|██▌       | 50/200 [00:07<00:22,  6.55it/s]
 26%|██▌       | 51/200 [00:07<00:22,  6.52it/s]
 26%|██▌       | 52/200 [00:07<00:22,  6.53it/s]
 26%|██▋       | 53/200 [00:08<00:22,  6.48it/s]
 27%|██▋       | 54/200 [00:08<00:22,  6.50it/s]
 28%|██▊       | 55/200 [00:08<00:22,  6.52it/s]
 28%|██▊       | 56/200 [00:08<00:22,  6.54it/s]
 28%|██▊       | 57/200 [00:08<00:21,  6.51it/s]
 29%|██▉       | 58/200 [00:08<00:21,  6.53it/s]
 30%|██▉       | 59/200 [00:08<00:21,  6.53it/s]
 30%|███       | 60/200 [00:09<00:21,  6.45it/s]
 30%|███       | 61/200 [00:09<00:21,  6.49it/s]
 31%|███       | 62/200 [00:09<00:21,  6.49it/s]
 32%|███▏      | 63/200 [00:09<00:21,  6.51it/s]
 32%|███▏      | 64/200 [00:09<00:20,  6.51it/s]
 32%|███▎      | 65/200 [00:09<00:20,  6.52it/s]
 33%|███▎      | 66/200 [00:10<00:20,  6.55it/s]
 34%|███▎      | 67/200 [00:10<00:20,  6.56it/s]
 34%|███▍      | 68/200 [00:10<00:20,  6.56it/s]
 34%|███▍      | 69/200 [00:10<00:19,  6.56it/s]
 35%|███▌      | 70/200 [00:10<00:19,  6.56it/s]
 36%|███▌      | 71/200 [00:10<00:19,  6.56it/s]
 36%|███▌      | 72/200 [00:10<00:19,  6.54it/s]
 36%|███▋      | 73/200 [00:11<00:19,  6.55it/s]
 37%|███▋      | 74/200 [00:11<00:19,  6.55it/s]
 38%|███▊      | 75/200 [00:11<00:19,  6.55it/s]
 38%|███▊      | 76/200 [00:11<00:18,  6.54it/s]
 38%|███▊      | 77/200 [00:11<00:18,  6.49it/s]
 39%|███▉      | 78/200 [00:11<00:18,  6.51it/s]
 40%|███▉      | 79/200 [00:12<00:18,  6.50it/s]
 40%|████      | 80/200 [00:12<00:18,  6.52it/s]
 40%|████      | 81/200 [00:12<00:18,  6.50it/s]
 41%|████      | 82/200 [00:12<00:18,  6.51it/s]
 42%|████▏     | 83/200 [00:12<00:17,  6.51it/s]
 42%|████▏     | 84/200 [00:12<00:17,  6.48it/s]
 42%|████▎     | 85/200 [00:12<00:17,  6.46it/s]
 43%|████▎     | 86/200 [00:13<00:17,  6.49it/s]
 44%|████▎     | 87/200 [00:13<00:17,  6.50it/s]
 44%|████▍     | 88/200 [00:13<00:17,  6.52it/s]
 44%|████▍     | 89/200 [00:13<00:17,  6.52it/s]
 45%|████▌     | 90/200 [00:13<00:16,  6.55it/s]
 46%|████▌     | 91/200 [00:13<00:16,  6.54it/s]
 46%|████▌     | 92/200 [00:14<00:16,  6.52it/s]
 46%|████▋     | 93/200 [00:14<00:16,  6.53it/s]
 47%|████▋     | 94/200 [00:14<00:16,  6.52it/s]
 48%|████▊     | 95/200 [00:14<00:16,  6.51it/s]
 48%|████▊     | 96/200 [00:14<00:15,  6.52it/s]
 48%|████▊     | 97/200 [00:14<00:15,  6.53it/s]
 49%|████▉     | 98/200 [00:14<00:15,  6.53it/s]
 50%|████▉     | 99/200 [00:15<00:15,  6.53it/s]
 50%|█████     | 100/200 [00:15<00:15,  6.54it/s]
 50%|█████     | 101/200 [00:15<00:15,  6.54it/s]
 51%|█████     | 102/200 [00:15<00:14,  6.53it/s]
 52%|█████▏    | 103/200 [00:15<00:14,  6.54it/s]
 52%|█████▏    | 104/200 [00:15<00:14,  6.53it/s]
 52%|█████▎    | 105/200 [00:16<00:14,  6.53it/s]
 53%|█████▎    | 106/200 [00:16<00:14,  6.53it/s]
 54%|█████▎    | 107/200 [00:16<00:14,  6.54it/s]
 54%|█████▍    | 108/200 [00:16<00:14,  6.54it/s]
 55%|█████▍    | 109/200 [00:16<00:13,  6.53it/s]
 55%|█████▌    | 110/200 [00:16<00:13,  6.54it/s]
 56%|█████▌    | 111/200 [00:16<00:13,  6.54it/s]
 56%|█████▌    | 112/200 [00:17<00:13,  6.50it/s]
 56%|█████▋    | 113/200 [00:17<00:13,  6.51it/s]
 57%|█████▋    | 114/200 [00:17<00:13,  6.51it/s]
 57%|█████▊    | 115/200 [00:17<00:13,  6.53it/s]
 58%|█████▊    | 116/200 [00:17<00:12,  6.53it/s]
 58%|█████▊    | 117/200 [00:17<00:12,  6.52it/s]
 59%|█████▉    | 118/200 [00:18<00:12,  6.51it/s]
 60%|█████▉    | 119/200 [00:18<00:12,  6.50it/s]
 60%|██████    | 120/200 [00:18<00:12,  6.52it/s]
 60%|██████    | 121/200 [00:18<00:12,  6.53it/s]
 61%|██████    | 122/200 [00:18<00:11,  6.53it/s]
 62%|██████▏   | 123/200 [00:18<00:11,  6.52it/s]
 62%|██████▏   | 124/200 [00:18<00:11,  6.49it/s]
 62%|██████▎   | 125/200 [00:19<00:11,  6.46it/s]
 63%|██████▎   | 126/200 [00:19<00:11,  6.49it/s]
 64%|██████▎   | 127/200 [00:19<00:11,  6.46it/s]
 64%|██████▍   | 128/200 [00:19<00:11,  6.46it/s]
 64%|██████▍   | 129/200 [00:19<00:10,  6.48it/s]
 65%|██████▌   | 130/200 [00:19<00:10,  6.50it/s]
 66%|██████▌   | 131/200 [00:20<00:10,  6.52it/s]
 66%|██████▌   | 132/200 [00:20<00:10,  6.50it/s]
 66%|██████▋   | 133/200 [00:20<00:10,  6.50it/s]
 67%|██████▋   | 134/200 [00:20<00:10,  6.50it/s]
 68%|██████▊   | 135/200 [00:20<00:09,  6.51it/s]
 68%|██████▊   | 136/200 [00:20<00:09,  6.52it/s]
 68%|██████▊   | 137/200 [00:20<00:09,  6.53it/s]
 69%|██████▉   | 138/200 [00:21<00:09,  6.54it/s]
 70%|██████▉   | 139/200 [00:21<00:09,  6.55it/s]
 70%|███████   | 140/200 [00:21<00:09,  6.53it/s]
 70%|███████   | 141/200 [00:21<00:09,  6.52it/s]
 71%|███████   | 142/200 [00:21<00:08,  6.52it/s]
 72%|███████▏  | 143/200 [00:21<00:08,  6.52it/s]
 72%|███████▏  | 144/200 [00:21<00:08,  6.53it/s]
 72%|███████▎  | 145/200 [00:22<00:08,  6.54it/s]
 73%|███████▎  | 146/200 [00:22<00:08,  6.54it/s]
 74%|███████▎  | 147/200 [00:22<00:08,  6.56it/s]
 74%|███████▍  | 148/200 [00:22<00:07,  6.54it/s]
 74%|███████▍  | 149/200 [00:22<00:07,  6.55it/s]
 75%|███████▌  | 150/200 [00:22<00:07,  6.52it/s]
 76%|███████▌  | 151/200 [00:23<00:07,  6.52it/s]
 76%|███████▌  | 152/200 [00:23<00:07,  6.52it/s]
 76%|███████▋  | 153/200 [00:23<00:07,  6.53it/s]
 77%|███████▋  | 154/200 [00:23<00:07,  6.54it/s]
 78%|███████▊  | 155/200 [00:23<00:06,  6.53it/s]
 78%|███████▊  | 156/200 [00:23<00:06,  6.53it/s]
 78%|███████▊  | 157/200 [00:23<00:06,  6.54it/s]
 79%|███████▉  | 158/200 [00:24<00:06,  6.56it/s]
 80%|███████▉  | 159/200 [00:24<00:06,  6.56it/s]
 80%|████████  | 160/200 [00:24<00:06,  6.53it/s]
 80%|████████  | 161/200 [00:24<00:05,  6.52it/s]
 81%|████████  | 162/200 [00:24<00:06,  6.25it/s]
 82%|████████▏ | 163/200 [00:24<00:05,  6.23it/s]
 82%|████████▏ | 164/200 [00:25<00:05,  6.33it/s]
 82%|████████▎ | 165/200 [00:25<00:05,  6.40it/s]
 83%|████████▎ | 166/200 [00:25<00:05,  6.43it/s]
 84%|████████▎ | 167/200 [00:25<00:05,  6.46it/s]
 84%|████████▍ | 168/200 [00:25<00:04,  6.49it/s]
 84%|████████▍ | 169/200 [00:25<00:04,  6.49it/s]
 85%|████████▌ | 170/200 [00:26<00:04,  6.38it/s]
 86%|████████▌ | 171/200 [00:26<00:04,  6.44it/s]
 86%|████████▌ | 172/200 [00:26<00:04,  6.48it/s]
 86%|████████▋ | 173/200 [00:26<00:04,  6.52it/s]
 87%|████████▋ | 174/200 [00:26<00:03,  6.54it/s]
 88%|████████▊ | 175/200 [00:26<00:03,  6.53it/s]
 88%|████████▊ | 176/200 [00:26<00:03,  6.51it/s]
 88%|████████▊ | 177/200 [00:27<00:03,  6.51it/s]
 89%|████████▉ | 178/200 [00:27<00:03,  6.50it/s]
 90%|████████▉ | 179/200 [00:27<00:03,  6.52it/s]
 90%|█████████ | 180/200 [00:27<00:03,  6.51it/s]
 90%|█████████ | 181/200 [00:27<00:02,  6.52it/s]
 91%|█████████ | 182/200 [00:27<00:02,  6.53it/s]
 92%|█████████▏| 183/200 [00:28<00:02,  6.53it/s]
 92%|█████████▏| 184/200 [00:28<00:02,  6.53it/s]
 92%|█████████▎| 185/200 [00:28<00:02,  6.51it/s]
 93%|█████████▎| 186/200 [00:28<00:02,  6.51it/s]
 94%|█████████▎| 187/200 [00:28<00:01,  6.51it/s]
 94%|█████████▍| 188/200 [00:28<00:01,  6.52it/s]
 94%|█████████▍| 189/200 [00:28<00:01,  6.32it/s]
 95%|█████████▌| 190/200 [00:29<00:01,  6.37it/s]
 96%|█████████▌| 191/200 [00:29<00:01,  6.42it/s]
 96%|█████████▌| 192/200 [00:29<00:01,  6.35it/s]
 96%|█████████▋| 193/200 [00:29<00:01,  6.40it/s]
 97%|█████████▋| 194/200 [00:29<00:00,  6.46it/s]
 98%|█████████▊| 195/200 [00:29<00:00,  6.49it/s]
 98%|█████████▊| 196/200 [00:30<00:00,  6.52it/s]
 98%|█████████▊| 197/200 [00:30<00:00,  6.47it/s]
 99%|█████████▉| 198/200 [00:30<00:00,  6.48it/s]
100%|█████████▉| 199/200 [00:30<00:00,  6.49it/s]
100%|██████████| 200/200 [00:30<00:00,  6.51it/s]
100%|██████████| 200/200 [00:30<00:00,  6.53it/s]

No description has been provided for this image
In [11]:
plot_solution(rho, phi)
No description has been provided for this image

Generate fiber paths with streamlines¶

In [12]:
u, f, σ, _, _ = beam.solve()
e = torch.stack([torch.linalg.eigvalsh(s) for s in σ])
mag, pos = torch.max(torch.abs(e), dim=1)
pval = torch.tensor([ev[p] for ev, p in zip(e, pos)])
pval = pval.reshape(Nx, Ny).T.numpy()
plot_solution(rho, phi, ptype="stream", color=pval)
No description has been provided for this image