weac 2.6.4__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- weac/__init__.py +2 -14
- weac/constants.py +37 -0
- weac/logging_config.py +39 -0
- {weac-2.6.4.dist-info → weac-3.0.0.dist-info}/METADATA +194 -62
- weac-3.0.0.dist-info/RECORD +8 -0
- weac/eigensystem.py +0 -658
- weac/inverse.py +0 -51
- weac/layered.py +0 -64
- weac/mixins.py +0 -2083
- weac/plot.py +0 -675
- weac/tools.py +0 -334
- weac-2.6.4.dist-info/RECORD +0 -12
- {weac-2.6.4.dist-info → weac-3.0.0.dist-info}/WHEEL +0 -0
- {weac-2.6.4.dist-info → weac-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {weac-2.6.4.dist-info → weac-3.0.0.dist-info}/top_level.txt +0 -0
weac/plot.py
DELETED
|
@@ -1,675 +0,0 @@
|
|
|
1
|
-
"""Plotting resources for the WEak Layer AntiCrack nucleation model."""
|
|
2
|
-
# pylint: disable=invalid-name,too-many-locals,too-many-branches
|
|
3
|
-
# pylint: disable=too-many-arguments,too-many-statements
|
|
4
|
-
|
|
5
|
-
# Standard library imports
|
|
6
|
-
import colorsys
|
|
7
|
-
import os
|
|
8
|
-
|
|
9
|
-
# Third party imports
|
|
10
|
-
import matplotlib.colors as mc
|
|
11
|
-
import matplotlib.pyplot as plt
|
|
12
|
-
import numpy as np
|
|
13
|
-
|
|
14
|
-
# Local application imports
|
|
15
|
-
from weac.tools import isnotebook
|
|
16
|
-
|
|
17
|
-
# === SET PLOT STYLES =========================================================
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def set_plotstyles():
|
|
21
|
-
"""Define styles plot markers, labels and colors."""
|
|
22
|
-
labelstyle = { # Text style of plot labels
|
|
23
|
-
"backgroundcolor": "w",
|
|
24
|
-
"horizontalalignment": "center",
|
|
25
|
-
"verticalalignment": "center",
|
|
26
|
-
}
|
|
27
|
-
# markerstyle = { # Style of plot markers
|
|
28
|
-
# 'marker': 'o',
|
|
29
|
-
# 'markersize': 5,
|
|
30
|
-
# 'markerfacecolor': 'w',
|
|
31
|
-
# 'zorder': 3}
|
|
32
|
-
colors = np.array(
|
|
33
|
-
[ # TUD color palette
|
|
34
|
-
["#DCDCDC", "#B5B5B5", "#898989", "#535353"], # gray
|
|
35
|
-
["#5D85C3", "#005AA9", "#004E8A", "#243572"], # blue
|
|
36
|
-
["#009CDA", "#0083CC", "#00689D", "#004E73"], # ocean
|
|
37
|
-
["#50B695", "#009D81", "#008877", "#00715E"], # teal
|
|
38
|
-
["#AFCC50", "#99C000", "#7FAB16", "#6A8B22"], # green
|
|
39
|
-
["#DDDF48", "#C9D400", "#B1BD00", "#99A604"], # lime
|
|
40
|
-
["#FFE05C", "#FDCA00", "#D7AC00", "#AE8E00"], # yellow
|
|
41
|
-
["#F8BA3C", "#F5A300", "#D28700", "#BE6F00"], # sand
|
|
42
|
-
["#EE7A34", "#EC6500", "#CC4C03", "#A94913"], # orange
|
|
43
|
-
["#E9503E", "#E6001A", "#B90F22", "#961C26"], # red
|
|
44
|
-
["#C9308E", "#A60084", "#951169", "#732054"], # magenta
|
|
45
|
-
["#804597", "#721085", "#611C73", "#4C226A"], # purple
|
|
46
|
-
]
|
|
47
|
-
)
|
|
48
|
-
return labelstyle, colors
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# === CONVENIENCE FUNCTIONS ===================================================
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class MidpointNormalize(mc.Normalize):
|
|
55
|
-
"""Colormap normalization to a specified midpoint. Default is 0."""
|
|
56
|
-
|
|
57
|
-
def __init__(self, vmin, vmax, midpoint=0, clip=False):
|
|
58
|
-
"""Inizialize normalization."""
|
|
59
|
-
self.midpoint = midpoint
|
|
60
|
-
mc.Normalize.__init__(self, vmin, vmax, clip)
|
|
61
|
-
|
|
62
|
-
def __call__(self, value, clip=None):
|
|
63
|
-
"""Make instances callable as functions."""
|
|
64
|
-
normalized_min = max(
|
|
65
|
-
0,
|
|
66
|
-
0.5 * (1 - abs((self.midpoint - self.vmin) / (self.midpoint - self.vmax))),
|
|
67
|
-
)
|
|
68
|
-
normalized_max = min(
|
|
69
|
-
1,
|
|
70
|
-
0.5 * (1 + abs((self.vmax - self.midpoint) / (self.midpoint - self.vmin))),
|
|
71
|
-
)
|
|
72
|
-
normalized_mid = 0.5
|
|
73
|
-
x, y = (
|
|
74
|
-
[self.vmin, self.midpoint, self.vmax],
|
|
75
|
-
[normalized_min, normalized_mid, normalized_max],
|
|
76
|
-
)
|
|
77
|
-
return np.ma.masked_array(np.interp(value, x, y))
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def outline(grid):
|
|
81
|
-
"""Extract outline values of a 2D array (matrix, grid)."""
|
|
82
|
-
top = grid[0, :-1]
|
|
83
|
-
right = grid[:-1, -1]
|
|
84
|
-
bot = grid[-1, :0:-1]
|
|
85
|
-
left = grid[::-1, 0]
|
|
86
|
-
|
|
87
|
-
return np.hstack([top, right, bot, left])
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def significant_digits(decimal):
|
|
91
|
-
"""
|
|
92
|
-
Get the number of significant digits.
|
|
93
|
-
|
|
94
|
-
Arguments
|
|
95
|
-
---------
|
|
96
|
-
decimal : float
|
|
97
|
-
Decimal number.
|
|
98
|
-
|
|
99
|
-
Returns
|
|
100
|
-
-------
|
|
101
|
-
int
|
|
102
|
-
Number of significant digits.
|
|
103
|
-
"""
|
|
104
|
-
return -int(np.floor(np.log10(decimal)))
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def tight_central_distribution(limit, samples=100, tightness=1.5):
|
|
108
|
-
"""
|
|
109
|
-
Provide values within a given interval distributed tightly around 0.
|
|
110
|
-
|
|
111
|
-
Parameters
|
|
112
|
-
----------
|
|
113
|
-
limit : float
|
|
114
|
-
Maximum and minimum of value range.
|
|
115
|
-
samples : int, optional
|
|
116
|
-
Number of values. Default is 100.
|
|
117
|
-
tightness : int, optional
|
|
118
|
-
Degree of value densification at center. 1.0 corresponds
|
|
119
|
-
to equal spacing. Default is 1.5.
|
|
120
|
-
|
|
121
|
-
Returns
|
|
122
|
-
-------
|
|
123
|
-
ndarray
|
|
124
|
-
Array of values more tightly spaced around 0.
|
|
125
|
-
"""
|
|
126
|
-
stop = limit ** (1 / tightness)
|
|
127
|
-
levels = np.linspace(0, stop, num=int(samples / 2), endpoint=True) ** tightness
|
|
128
|
-
return np.unique(np.hstack([-levels[::-1], levels]))
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def adjust_lightness(color, amount=0.5):
|
|
132
|
-
"""
|
|
133
|
-
Adjust color lightness.
|
|
134
|
-
|
|
135
|
-
Arguments
|
|
136
|
-
----------
|
|
137
|
-
color : str or tuple
|
|
138
|
-
Matplotlib colorname, hex string, or RGB value tuple.
|
|
139
|
-
amount : float, optional
|
|
140
|
-
Amount of lightening: >1 lightens, <1 darkens. Default is 0.5.
|
|
141
|
-
|
|
142
|
-
Returns
|
|
143
|
-
-------
|
|
144
|
-
tuple
|
|
145
|
-
RGB color tuple.
|
|
146
|
-
"""
|
|
147
|
-
try:
|
|
148
|
-
c = mc.cnames[color]
|
|
149
|
-
except KeyError:
|
|
150
|
-
c = color
|
|
151
|
-
c = colorsys.rgb_to_hls(*mc.to_rgb(c))
|
|
152
|
-
return colorsys.hls_to_rgb(c[0], max(0, min(1, amount * c[1])), c[2])
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
# === PLOT SLAB PROFILE =======================================================
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def slab_profile(instance):
|
|
159
|
-
"""Create bar chart of slab profile."""
|
|
160
|
-
# Plot Setup
|
|
161
|
-
plt.rcdefaults()
|
|
162
|
-
plt.rc("font", family="serif", size=10)
|
|
163
|
-
plt.rc("mathtext", fontset="cm")
|
|
164
|
-
|
|
165
|
-
# Create figure
|
|
166
|
-
fig = plt.figure(figsize=(8 / 3, 4))
|
|
167
|
-
ax1 = fig.gca()
|
|
168
|
-
|
|
169
|
-
# Initialize coordinates
|
|
170
|
-
x = []
|
|
171
|
-
y = []
|
|
172
|
-
total_heigth = 0
|
|
173
|
-
|
|
174
|
-
for line in np.flipud(instance.slab):
|
|
175
|
-
x.append(line[0])
|
|
176
|
-
x.append(line[0])
|
|
177
|
-
|
|
178
|
-
y.append(total_heigth)
|
|
179
|
-
total_heigth = total_heigth + line[1]
|
|
180
|
-
y.append(total_heigth)
|
|
181
|
-
|
|
182
|
-
# Set axis labels
|
|
183
|
-
ax1.set_xlabel(r"$\longleftarrow$ Density $\rho$ (kg/m$^3$)")
|
|
184
|
-
ax1.set_ylabel(r"Height above weak layer (mm) $\longrightarrow$")
|
|
185
|
-
|
|
186
|
-
ax1.set_xlim(500, 0)
|
|
187
|
-
|
|
188
|
-
ax1.fill_betweenx(y, 0, x)
|
|
189
|
-
|
|
190
|
-
# Save figure
|
|
191
|
-
save_plot(name="profile")
|
|
192
|
-
|
|
193
|
-
# Reset plot styles
|
|
194
|
-
plt.rcdefaults()
|
|
195
|
-
|
|
196
|
-
# Clear Canvas
|
|
197
|
-
plt.close()
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
# === DEFORMATION CONTOUR PLOT ================================================
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def deformed(
|
|
204
|
-
instance,
|
|
205
|
-
xsl,
|
|
206
|
-
xwl,
|
|
207
|
-
z,
|
|
208
|
-
phi,
|
|
209
|
-
dz=2,
|
|
210
|
-
scale=100,
|
|
211
|
-
window=np.inf,
|
|
212
|
-
pad=2,
|
|
213
|
-
levels=300,
|
|
214
|
-
aspect=2,
|
|
215
|
-
field="principal",
|
|
216
|
-
normalize=True,
|
|
217
|
-
dark=False,
|
|
218
|
-
filename="cont",
|
|
219
|
-
):
|
|
220
|
-
"""
|
|
221
|
-
Plot 2D deformed solution with displacement or stress fields.
|
|
222
|
-
|
|
223
|
-
Arguments
|
|
224
|
-
---------
|
|
225
|
-
instance : object
|
|
226
|
-
Instance of layered class.
|
|
227
|
-
xsl : ndarray
|
|
228
|
-
Discretized slab x-coordinates (mm).
|
|
229
|
-
xwl : ndarray
|
|
230
|
-
Discretized weak-layer x-coordinates (mm).
|
|
231
|
-
z : ndarray
|
|
232
|
-
Solution vectors at positions x as columns of matrix z.
|
|
233
|
-
phi : float
|
|
234
|
-
Inclination (degrees). Counterclockwise positive.
|
|
235
|
-
dz : float, optional
|
|
236
|
-
Element size along z-axis (mm) for stress plot. Default is 2 mm.
|
|
237
|
-
scale : int, optional
|
|
238
|
-
Scaling factor for the visualization of displacements. Default
|
|
239
|
-
is 100.
|
|
240
|
-
window : int, optional
|
|
241
|
-
Plot window (cm) around maximum vertical deflection. Default
|
|
242
|
-
is inf (full view).
|
|
243
|
-
pad : float, optional
|
|
244
|
-
Padding around shown geometry. Default is 2.
|
|
245
|
-
levels : int, optional
|
|
246
|
-
Number of isolevels. Default is 300.
|
|
247
|
-
aspect : int, optional
|
|
248
|
-
Aspect ratio of the displayed geometry. 1 is true to scale.
|
|
249
|
-
Default is 2.
|
|
250
|
-
field : {'u', 'w', 'Sxx', 'Txz', 'Szz', 'principal'}, optional
|
|
251
|
-
Field quantity for contour plot. Axial deformation 'u', vertical
|
|
252
|
-
deflection 'w', axial normal stress 'Sxx', shear stress 'Txz',
|
|
253
|
-
transverse normal stress 'Szz', or principal stresses 'principal'.
|
|
254
|
-
normalize : bool, optional
|
|
255
|
-
Toggle layerwise normalization of principal stresses to respective
|
|
256
|
-
strength. Only available with field='principal'. Default is True.
|
|
257
|
-
dark : bool, optional
|
|
258
|
-
Toggle display on dark figure background. Default is False.
|
|
259
|
-
|
|
260
|
-
Raises
|
|
261
|
-
------
|
|
262
|
-
ValueError
|
|
263
|
-
If invalid stress or displacement field is requested.
|
|
264
|
-
"""
|
|
265
|
-
# Plot Setup
|
|
266
|
-
plt.rcdefaults()
|
|
267
|
-
plt.rc("font", family="serif", size=10)
|
|
268
|
-
plt.rc("mathtext", fontset="cm")
|
|
269
|
-
|
|
270
|
-
# Set dark figure background if requested
|
|
271
|
-
if dark:
|
|
272
|
-
plt.style.use("dark_background")
|
|
273
|
-
fig = plt.figure()
|
|
274
|
-
ax = plt.gca()
|
|
275
|
-
fig.set_facecolor("#282c34")
|
|
276
|
-
ax.set_facecolor("white")
|
|
277
|
-
|
|
278
|
-
# Calculate top-to-bottom vertical positions (mm) in beam coordinate system
|
|
279
|
-
zi = instance.get_zmesh(dz=dz)[:, 0]
|
|
280
|
-
h = instance.h
|
|
281
|
-
|
|
282
|
-
# Compute slab displacements on grid (cm)
|
|
283
|
-
Usl = np.vstack([instance.u(z, z0=z0, unit="cm") for z0 in zi])
|
|
284
|
-
Wsl = np.vstack([instance.w(z, unit="cm") for _ in zi])
|
|
285
|
-
|
|
286
|
-
# Put coordinate origin at horizontal center
|
|
287
|
-
if instance.system in ["skier", "skiers"]:
|
|
288
|
-
xsl = xsl - max(xsl) / 2
|
|
289
|
-
xwl = xwl - max(xwl) / 2
|
|
290
|
-
|
|
291
|
-
# Compute slab grid coordinates with vertical origin at top surface (cm)
|
|
292
|
-
Xsl, Zsl = np.meshgrid(1e-1 * (xsl), 1e-1 * (zi + h / 2))
|
|
293
|
-
|
|
294
|
-
# Get x-coordinate of maximum deflection w (cm) and derive plot limits
|
|
295
|
-
xfocus = xsl[np.max(np.argmax(Wsl, axis=1))] / 10
|
|
296
|
-
xmax = np.min([np.max([Xsl, Xsl + scale * Usl]) + pad, xfocus + window / 2])
|
|
297
|
-
xmin = np.max([np.min([Xsl, Xsl + scale * Usl]) - pad, xfocus - window / 2])
|
|
298
|
-
|
|
299
|
-
# Scale shown weak-layer thickness with to max deflection and add padding
|
|
300
|
-
zmax = np.max(Zsl + scale * Wsl) + pad
|
|
301
|
-
zmin = np.min(Zsl) - pad
|
|
302
|
-
|
|
303
|
-
# Compute weak-layer grid coordinates (cm)
|
|
304
|
-
Xwl, Zwl = np.meshgrid(1e-1 * xwl, [1e-1 * (zi[-1] + h / 2), zmax])
|
|
305
|
-
|
|
306
|
-
# Assemble weak-layer displacement field (top and bottom)
|
|
307
|
-
Uwl = np.row_stack([Usl[-1, :], np.zeros(xwl.shape[0])])
|
|
308
|
-
Wwl = np.row_stack([Wsl[-1, :], np.zeros(xwl.shape[0])])
|
|
309
|
-
|
|
310
|
-
# Compute stress or displacement fields
|
|
311
|
-
match field:
|
|
312
|
-
# Horizontal displacements (um)
|
|
313
|
-
case "u":
|
|
314
|
-
slab = 1e4 * Usl
|
|
315
|
-
weak = 1e4 * Usl[-1, :]
|
|
316
|
-
label = r"$u$ ($\mu$m)"
|
|
317
|
-
# Vertical deflection (um)
|
|
318
|
-
case "w":
|
|
319
|
-
slab = 1e4 * Wsl
|
|
320
|
-
weak = 1e4 * Wsl[-1, :]
|
|
321
|
-
label = r"$w$ ($\mu$m)"
|
|
322
|
-
# Axial normal stresses (kPa)
|
|
323
|
-
case "Sxx":
|
|
324
|
-
slab = instance.Sxx(z, phi, dz=dz, unit="kPa")
|
|
325
|
-
weak = np.zeros(xwl.shape[0])
|
|
326
|
-
label = r"$\sigma_{xx}$ (kPa)"
|
|
327
|
-
# Shear stresses (kPa)
|
|
328
|
-
case "Txz":
|
|
329
|
-
slab = instance.Txz(z, phi, dz=dz, unit="kPa")
|
|
330
|
-
weak = instance.get_weaklayer_shearstress(x=xwl, z=z, unit="kPa")[1]
|
|
331
|
-
label = r"$\tau_{xz}$ (kPa)"
|
|
332
|
-
# Transverse normal stresses (kPa)
|
|
333
|
-
case "Szz":
|
|
334
|
-
slab = instance.Szz(z, phi, dz=dz, unit="kPa")
|
|
335
|
-
weak = instance.get_weaklayer_normalstress(x=xwl, z=z, unit="kPa")[1]
|
|
336
|
-
label = r"$\sigma_{zz}$ (kPa)"
|
|
337
|
-
# Principal stresses
|
|
338
|
-
case "principal":
|
|
339
|
-
slab = instance.principal_stress_slab(
|
|
340
|
-
z, phi, dz=dz, val="max", unit="kPa", normalize=normalize
|
|
341
|
-
)
|
|
342
|
-
weak = instance.principal_stress_weaklayer(
|
|
343
|
-
z, val="min", unit="kPa", normalize=normalize
|
|
344
|
-
)
|
|
345
|
-
if normalize:
|
|
346
|
-
label = (
|
|
347
|
-
r"$\sigma_\mathrm{I}/\sigma_+$ (slab), "
|
|
348
|
-
r"$\sigma_\mathrm{I\!I\!I}/\sigma_-$ (weak layer)"
|
|
349
|
-
)
|
|
350
|
-
else:
|
|
351
|
-
label = (
|
|
352
|
-
r"$\sigma_\mathrm{I}$ (kPa, slab), "
|
|
353
|
-
r"$\sigma_\mathrm{I\!I\!I}$ (kPa, weak layer)"
|
|
354
|
-
)
|
|
355
|
-
case _:
|
|
356
|
-
raise ValueError(
|
|
357
|
-
f"Invalid input '{field}' for field. Valid options are "
|
|
358
|
-
"'u', 'w', 'Sxx', 'Txz', 'Szz', or 'principal'"
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
# Complement label
|
|
362
|
-
label += r" $\longrightarrow$"
|
|
363
|
-
|
|
364
|
-
# Assemble weak-layer output on grid
|
|
365
|
-
weak = np.row_stack([weak, weak])
|
|
366
|
-
|
|
367
|
-
# Normalize colormap
|
|
368
|
-
absmax = np.nanmax(np.abs([slab.min(), slab.max(), weak.min(), weak.max()]))
|
|
369
|
-
clim = np.round(absmax, significant_digits(absmax))
|
|
370
|
-
levels = np.linspace(-clim, clim, num=levels + 1, endpoint=True)
|
|
371
|
-
# nanmax = np.nanmax([slab.max(), weak.max()])
|
|
372
|
-
# nanmin = np.nanmin([slab.min(), weak.min()])
|
|
373
|
-
# norm = MidpointNormalize(vmin=nanmin, vmax=nanmax)
|
|
374
|
-
|
|
375
|
-
# Plot baseline
|
|
376
|
-
plt.axhline(zmax, color="k", linewidth=1)
|
|
377
|
-
|
|
378
|
-
# Plot outlines of the undeformed and deformed slab
|
|
379
|
-
plt.plot(outline(Xsl), outline(Zsl), "k--", alpha=0.3, linewidth=1)
|
|
380
|
-
plt.plot(outline(Xsl + scale * Usl), outline(Zsl + scale * Wsl), "k", linewidth=1)
|
|
381
|
-
|
|
382
|
-
# Plot deformed weak-layer outline
|
|
383
|
-
if instance.system in ["-pst", "pst-", "-vpst", "vpst-"]:
|
|
384
|
-
nanmask = np.isfinite(xwl)
|
|
385
|
-
plt.plot(
|
|
386
|
-
outline(Xwl[:, nanmask] + scale * Uwl[:, nanmask]),
|
|
387
|
-
outline(Zwl[:, nanmask] + scale * Wwl[:, nanmask]),
|
|
388
|
-
"k",
|
|
389
|
-
linewidth=1,
|
|
390
|
-
)
|
|
391
|
-
|
|
392
|
-
# Colormap
|
|
393
|
-
cmap = plt.cm.RdBu_r
|
|
394
|
-
cmap.set_over(adjust_lightness(cmap(1.0), 0.9))
|
|
395
|
-
cmap.set_under(adjust_lightness(cmap(0.0), 0.9))
|
|
396
|
-
|
|
397
|
-
# Plot fields
|
|
398
|
-
plt.contourf(
|
|
399
|
-
Xsl + scale * Usl,
|
|
400
|
-
Zsl + scale * Wsl,
|
|
401
|
-
slab,
|
|
402
|
-
levels=levels, # norm=norm,
|
|
403
|
-
cmap=cmap,
|
|
404
|
-
extend="both",
|
|
405
|
-
)
|
|
406
|
-
plt.contourf(
|
|
407
|
-
Xwl + scale * Uwl,
|
|
408
|
-
Zwl + scale * Wwl,
|
|
409
|
-
weak,
|
|
410
|
-
levels=levels, # norm=norm,
|
|
411
|
-
cmap=cmap,
|
|
412
|
-
extend="both",
|
|
413
|
-
)
|
|
414
|
-
|
|
415
|
-
# Plot setup
|
|
416
|
-
plt.axis("scaled")
|
|
417
|
-
plt.xlim([xmin, xmax])
|
|
418
|
-
plt.ylim([zmin, zmax])
|
|
419
|
-
plt.gca().set_aspect(aspect)
|
|
420
|
-
plt.gca().invert_yaxis()
|
|
421
|
-
plt.gca().use_sticky_edges = False
|
|
422
|
-
|
|
423
|
-
# Plot labels
|
|
424
|
-
plt.gca().set_xlabel(r"lateral position $x$ (cm) $\longrightarrow$")
|
|
425
|
-
plt.gca().set_ylabel("depth below surface\n" + r"$\longleftarrow $ $d$ (cm)")
|
|
426
|
-
plt.title(rf"${scale}\!\times\!$ scaled deformations (cm)", size=10)
|
|
427
|
-
|
|
428
|
-
# Show colorbar
|
|
429
|
-
ticks = np.linspace(levels[0], levels[-1], num=11, endpoint=True)
|
|
430
|
-
plt.colorbar(orientation="horizontal", ticks=ticks, label=label, aspect=35)
|
|
431
|
-
|
|
432
|
-
# Save figure
|
|
433
|
-
save_plot(name=filename)
|
|
434
|
-
|
|
435
|
-
# Clear Canvas
|
|
436
|
-
plt.close()
|
|
437
|
-
|
|
438
|
-
# Reset plot styles
|
|
439
|
-
plt.rcdefaults()
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
# === BASE PLOT FUNCTION ======================================================
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
def plot_data(
|
|
446
|
-
name,
|
|
447
|
-
ax1data,
|
|
448
|
-
ax1label,
|
|
449
|
-
ax2data=None,
|
|
450
|
-
ax2label=None,
|
|
451
|
-
labelpos=None,
|
|
452
|
-
vlines=True,
|
|
453
|
-
li=False,
|
|
454
|
-
mi=False,
|
|
455
|
-
ki=False,
|
|
456
|
-
xlabel=r"Horizontal position $x$ (cm)",
|
|
457
|
-
):
|
|
458
|
-
"""Plot data. Base function."""
|
|
459
|
-
# Figure setup
|
|
460
|
-
plt.rcdefaults()
|
|
461
|
-
plt.rc("font", family="serif", size=10)
|
|
462
|
-
plt.rc("mathtext", fontset="cm")
|
|
463
|
-
|
|
464
|
-
# Plot styles
|
|
465
|
-
labelstyle, colors = set_plotstyles()
|
|
466
|
-
|
|
467
|
-
# Create figure
|
|
468
|
-
fig = plt.figure(figsize=(4, 8 / 3))
|
|
469
|
-
ax1 = fig.gca()
|
|
470
|
-
|
|
471
|
-
# Axis limits
|
|
472
|
-
ax1.autoscale(axis="x", tight=True)
|
|
473
|
-
|
|
474
|
-
# Set axis labels
|
|
475
|
-
ax1.set_xlabel(xlabel + r" $\longrightarrow$")
|
|
476
|
-
ax1.set_ylabel(ax1label + r" $\longrightarrow$")
|
|
477
|
-
|
|
478
|
-
# Plot x-axis
|
|
479
|
-
ax1.axhline(0, linewidth=0.5, color="gray")
|
|
480
|
-
|
|
481
|
-
# Plot vertical separators
|
|
482
|
-
if vlines:
|
|
483
|
-
ax1.axvline(0, linewidth=0.5, color="gray")
|
|
484
|
-
for i, f in enumerate(ki):
|
|
485
|
-
if not f:
|
|
486
|
-
ax1.axvspan(
|
|
487
|
-
sum(li[:i]) / 10,
|
|
488
|
-
sum(li[: i + 1]) / 10,
|
|
489
|
-
facecolor="gray",
|
|
490
|
-
alpha=0.05,
|
|
491
|
-
zorder=100,
|
|
492
|
-
)
|
|
493
|
-
for i, m in enumerate(mi, start=1):
|
|
494
|
-
if m > 0:
|
|
495
|
-
ax1.axvline(sum(li[:i]) / 10, linewidth=0.5, color="gray")
|
|
496
|
-
else:
|
|
497
|
-
ax1.autoscale(axis="y", tight=True)
|
|
498
|
-
|
|
499
|
-
# Calculate labelposition
|
|
500
|
-
if not labelpos:
|
|
501
|
-
x = ax1data[0][0]
|
|
502
|
-
labelpos = int(0.95 * len(x[~np.isnan(x)]))
|
|
503
|
-
|
|
504
|
-
# Fill left y-axis
|
|
505
|
-
i = 0
|
|
506
|
-
for x, y, label in ax1data:
|
|
507
|
-
i += 1
|
|
508
|
-
if label == "" or "FEA" in label:
|
|
509
|
-
# line, = ax1.plot(x, y, 'k:', linewidth=1)
|
|
510
|
-
ax1.plot(x, y, linewidth=3, color="white")
|
|
511
|
-
(line,) = ax1.plot(x, y, ":", linewidth=1) # , color='black'
|
|
512
|
-
thislabelpos = -2
|
|
513
|
-
x, y = x[~np.isnan(x)], y[~np.isnan(x)]
|
|
514
|
-
xtx = (x[thislabelpos - 1] + x[thislabelpos]) / 2
|
|
515
|
-
ytx = (y[thislabelpos - 1] + y[thislabelpos]) / 2
|
|
516
|
-
ax1.text(xtx, ytx, label, color=line.get_color(), **labelstyle)
|
|
517
|
-
else:
|
|
518
|
-
# Plot line
|
|
519
|
-
ax1.plot(x, y, linewidth=3, color="white")
|
|
520
|
-
(line,) = ax1.plot(x, y, linewidth=1)
|
|
521
|
-
# Line label
|
|
522
|
-
x, y = x[~np.isnan(x)], y[~np.isnan(x)]
|
|
523
|
-
if len(x) > 0:
|
|
524
|
-
xtx = (x[labelpos - 10 * i - 1] + x[labelpos - 10 * i]) / 2
|
|
525
|
-
ytx = (y[labelpos - 10 * i - 1] + y[labelpos - 10 * i]) / 2
|
|
526
|
-
ax1.text(xtx, ytx, label, color=line.get_color(), **labelstyle)
|
|
527
|
-
|
|
528
|
-
# Fill right y-axis
|
|
529
|
-
if ax2data:
|
|
530
|
-
# Create right y-axis
|
|
531
|
-
ax2 = ax1.twinx()
|
|
532
|
-
# Set axis label
|
|
533
|
-
ax2.set_ylabel(ax2label + r" $\longrightarrow$")
|
|
534
|
-
# Fill
|
|
535
|
-
for x, y, label in ax2data:
|
|
536
|
-
# Plot line
|
|
537
|
-
ax2.plot(x, y, linewidth=3, color="white")
|
|
538
|
-
(line,) = ax2.plot(x, y, linewidth=1, color=colors[8, 0])
|
|
539
|
-
# Line label
|
|
540
|
-
x, y = x[~np.isnan(x)], y[~np.isnan(x)]
|
|
541
|
-
xtx = (x[labelpos - 1] + x[labelpos]) / 2
|
|
542
|
-
ytx = (y[labelpos - 1] + y[labelpos]) / 2
|
|
543
|
-
ax2.text(xtx, ytx, label, color=line.get_color(), **labelstyle)
|
|
544
|
-
|
|
545
|
-
# Save figure
|
|
546
|
-
save_plot(name)
|
|
547
|
-
|
|
548
|
-
# Clear canvas
|
|
549
|
-
plt.close()
|
|
550
|
-
|
|
551
|
-
# Reset plot styles
|
|
552
|
-
plt.rcdefaults()
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
# === PLOT WRAPPERS ===========================================================
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
def displacements(instance, x, z, i="", **segments):
|
|
559
|
-
"""Wrap for dispalcements plot."""
|
|
560
|
-
data = [
|
|
561
|
-
[x / 10, instance.u(z, z0=0, unit="mm"), r"$u_0\ (\mathrm{mm})$"],
|
|
562
|
-
[x / 10, -instance.w(z, unit="mm"), r"$-w\ (\mathrm{mm})$"],
|
|
563
|
-
[x / 10, instance.psi(z, unit="degrees"), r"$\psi\ (^\circ)$ "],
|
|
564
|
-
]
|
|
565
|
-
plot_data(ax1label=r"Displacements", ax1data=data, name="disp" + str(i), **segments)
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
def section_forces(instance, x, z, i="", **segments):
|
|
569
|
-
"""Wrap section forces plot."""
|
|
570
|
-
data = [
|
|
571
|
-
[x / 10, instance.N(z), r"$N$"],
|
|
572
|
-
[x / 10, instance.M(z), r"$M$"],
|
|
573
|
-
[x / 10, instance.V(z), r"$V$"],
|
|
574
|
-
]
|
|
575
|
-
plot_data(ax1label=r"Section forces", ax1data=data, name="forc" + str(i), **segments)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
def stresses(instance, x, z, i="", **segments):
|
|
579
|
-
"""Wrap stress plot."""
|
|
580
|
-
data = [
|
|
581
|
-
[x / 10, instance.tau(z, unit="kPa"), r"$\tau$"],
|
|
582
|
-
[x / 10, instance.sig(z, unit="kPa"), r"$\sigma$"],
|
|
583
|
-
]
|
|
584
|
-
plot_data(ax1label=r"Stress (kPa)", ax1data=data, name="stress" + str(i), **segments)
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
def stress_criteria(x, stress, **segments):
|
|
588
|
-
"""Wrap plot of stress and energy criteria."""
|
|
589
|
-
data = [[x / 10, stress, r"$\sigma/\sigma_\mathrm{c}$"]]
|
|
590
|
-
plot_data(ax1label=r"Criteria", ax1data=data, name="crit", **segments)
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
def err_comp(da, Gdif, Ginc, mode=0):
|
|
594
|
-
"""Wrap energy release rate plot."""
|
|
595
|
-
data = [
|
|
596
|
-
[da / 10, 1e3 * Gdif[mode, :], r"$\mathcal{G}$"],
|
|
597
|
-
[da / 10, 1e3 * Ginc[mode, :], r"$\bar{\mathcal{G}}$"],
|
|
598
|
-
]
|
|
599
|
-
plot_data(
|
|
600
|
-
xlabel=r"Crack length $\Delta a$ (cm)",
|
|
601
|
-
ax1label=r"Energy release rate (J/m$^2$)",
|
|
602
|
-
ax1data=data,
|
|
603
|
-
name="err",
|
|
604
|
-
vlines=False,
|
|
605
|
-
)
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
def err_modes(da, G, kind="inc"):
|
|
609
|
-
"""Wrap energy release rate plot."""
|
|
610
|
-
label = r"$\bar{\mathcal{G}}$" if kind == "inc" else r"$\mathcal{G}$"
|
|
611
|
-
data = [
|
|
612
|
-
[da / 10, 1e3 * G[2, :], label + r"$_\mathrm{I\!I}$"],
|
|
613
|
-
[da / 10, 1e3 * G[1, :], label + r"$_\mathrm{I}$"],
|
|
614
|
-
[da / 10, 1e3 * G[0, :], label + r"$_\mathrm{I+I\!I}$"],
|
|
615
|
-
]
|
|
616
|
-
plot_data(
|
|
617
|
-
xlabel=r"Crack length $a$ (cm)",
|
|
618
|
-
ax1label=r"Energy release rate (J/m$^2$)",
|
|
619
|
-
ax1data=data,
|
|
620
|
-
name="modes",
|
|
621
|
-
vlines=False,
|
|
622
|
-
)
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
def fea_disp(instance, x, z, fea):
|
|
626
|
-
"""Wrap dispalcements plot."""
|
|
627
|
-
data = [
|
|
628
|
-
[fea[:, 0] / 10, -np.flipud(fea[:, 1]), r"FEA $u_0$"],
|
|
629
|
-
[fea[:, 0] / 10, np.flipud(fea[:, 2]), r"FEA $w_0$"],
|
|
630
|
-
# [fea[:, 0]/10, -np.flipud(fea[:, 3]), r'FEA $u(z=-h/2)$'],
|
|
631
|
-
# [fea[:, 0]/10, np.flipud(fea[:, 4]), r'FEA $w(z=-h/2)$'],
|
|
632
|
-
[fea[:, 0] / 10, np.flipud(np.rad2deg(fea[:, 5])), r"FEA $\psi$"],
|
|
633
|
-
[x / 10, instance.u(z, z0=0), r"$u_0$"],
|
|
634
|
-
[x / 10, -instance.w(z), r"$-w$"],
|
|
635
|
-
[x / 10, np.rad2deg(instance.psi(z)), r"$\psi$"],
|
|
636
|
-
]
|
|
637
|
-
plot_data(
|
|
638
|
-
ax1label=r"Displacements (mm)", ax1data=data, name="fea_disp", labelpos=-50
|
|
639
|
-
)
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
def fea_stress(instance, xb, zb, fea):
|
|
643
|
-
"""Wrap stress plot."""
|
|
644
|
-
data = [
|
|
645
|
-
[fea[:, 0] / 10, 1e3 * np.flipud(fea[:, 2]), r"FEA $\sigma_2$"],
|
|
646
|
-
[fea[:, 0] / 10, 1e3 * np.flipud(fea[:, 3]), r"FEA $\tau_{12}$"],
|
|
647
|
-
[xb / 10, instance.tau(zb, unit="kPa"), r"$\tau$"],
|
|
648
|
-
[xb / 10, instance.sig(zb, unit="kPa"), r"$\sigma$"],
|
|
649
|
-
]
|
|
650
|
-
plot_data(ax1label=r"Stress (kPa)", ax1data=data, name="fea_stress", labelpos=-50)
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
# === SAVE FUNCTION ===========================================================
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
def save_plot(name):
|
|
657
|
-
"""
|
|
658
|
-
Show or save plot depending on interpreter
|
|
659
|
-
|
|
660
|
-
Arguments
|
|
661
|
-
---------
|
|
662
|
-
name : string
|
|
663
|
-
Name for the figure.
|
|
664
|
-
"""
|
|
665
|
-
filename = name + ".png"
|
|
666
|
-
# Show figure if on jupyter notebook
|
|
667
|
-
if isnotebook():
|
|
668
|
-
plt.show()
|
|
669
|
-
# Save figure if on terminal
|
|
670
|
-
else:
|
|
671
|
-
# Make directory if not yet existing
|
|
672
|
-
if not os.path.isdir(os.path.join(os.getcwd(), "plots")):
|
|
673
|
-
os.mkdir("plots")
|
|
674
|
-
plt.savefig("plots/" + filename, bbox_inches="tight")
|
|
675
|
-
return
|