weac 2.6.4__py3-none-any.whl → 3.0.1__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/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