elasticipy 4.0.0__py3-none-any.whl → 4.1.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.
- Elasticipy/gui.py +1 -1
- Elasticipy/interfaces/FEPX.py +119 -0
- Elasticipy/interfaces/PRISMS.py +103 -0
- Elasticipy/plasticity.py +34 -1
- Elasticipy/polefigure.py +21 -0
- Elasticipy/spherical_function.py +2 -14
- Elasticipy/tensors/elasticity.py +171 -79
- Elasticipy/tensors/fourth_order.py +126 -55
- Elasticipy/tensors/mapping.py +44 -0
- Elasticipy/tensors/second_order.py +107 -3
- Elasticipy/tensors/stress_strain.py +20 -4
- {elasticipy-4.0.0.dist-info → elasticipy-4.1.0.dist-info}/METADATA +22 -4
- elasticipy-4.1.0.dist-info/RECORD +23 -0
- {elasticipy-4.0.0.dist-info → elasticipy-4.1.0.dist-info}/WHEEL +1 -1
- elasticipy-4.0.0.dist-info/RECORD +0 -20
- {elasticipy-4.0.0.dist-info → elasticipy-4.1.0.dist-info}/licenses/LICENSE +0 -0
- {elasticipy-4.0.0.dist-info → elasticipy-4.1.0.dist-info}/top_level.txt +0 -0
Elasticipy/gui.py
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from Elasticipy.tensors.second_order import SymmetricSecondOrderTensor, SecondOrderTensor, \
|
|
2
|
+
SkewSymmetricSecondOrderTensor
|
|
3
|
+
from Elasticipy.tensors.stress_strain import StressTensor, StrainTensor
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
DTYPES={'stress':StressTensor,
|
|
11
|
+
'strain':StrainTensor,
|
|
12
|
+
'strain_el':StrainTensor,
|
|
13
|
+
'strain_pl':StrainTensor,
|
|
14
|
+
'velgrad':SecondOrderTensor,
|
|
15
|
+
'defrate':SymmetricSecondOrderTensor,
|
|
16
|
+
'defrate_pl':SymmetricSecondOrderTensor,
|
|
17
|
+
'spinrate':SkewSymmetricSecondOrderTensor,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _list_valid_filenames(folder, startswith='strain'):
|
|
22
|
+
file_list = os.listdir(folder)
|
|
23
|
+
pattern = r'{}\.step\d+'.format(startswith)
|
|
24
|
+
return [f for f in file_list if re.fullmatch(pattern, f)]
|
|
25
|
+
|
|
26
|
+
def from_step_file(file, dtype=None):
|
|
27
|
+
"""
|
|
28
|
+
Import data from a single step file given by FEPX.
|
|
29
|
+
|
|
30
|
+
The type of returns is inferred from the data one wants to parse
|
|
31
|
+
(according the `FEPX documentation <https://fepx.info/doc/output.html>`_ ).
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
file : str
|
|
36
|
+
Path to the file to read
|
|
37
|
+
dtype : type, optional
|
|
38
|
+
If provided, sets the type of returned array. It can be:
|
|
39
|
+
- float
|
|
40
|
+
- SecondOrderTensor
|
|
41
|
+
- SymmetricSecondOrderTensor
|
|
42
|
+
- SkewSymmetricSecondOrderTensor
|
|
43
|
+
- stressTensor
|
|
44
|
+
- strainTensor
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
SecondOrderTensor or numpy.ndarray
|
|
49
|
+
Array of second-order tensors built from the read data. The array will be of shape (n,), where n is the number
|
|
50
|
+
of elements in the mesh.
|
|
51
|
+
"""
|
|
52
|
+
data = pd.read_csv(file, header=None, sep=' ')
|
|
53
|
+
array = data.to_numpy()
|
|
54
|
+
base_name = os.path.splitext(os.path.basename(file))[0]
|
|
55
|
+
if dtype is None:
|
|
56
|
+
if base_name in DTYPES:
|
|
57
|
+
dtype = DTYPES[base_name]
|
|
58
|
+
else:
|
|
59
|
+
dtype = float
|
|
60
|
+
if issubclass(dtype,SymmetricSecondOrderTensor):
|
|
61
|
+
return dtype.from_Voigt(array, voigt_map=[1,1,1,1,1,1])
|
|
62
|
+
elif dtype == SkewSymmetricSecondOrderTensor:
|
|
63
|
+
zeros = np.zeros(array.shape[0])
|
|
64
|
+
mat = np.array([[ zeros, array[:, 0], array[:, 1]],
|
|
65
|
+
[-array[:, 0], zeros, array[:, 2]],
|
|
66
|
+
[-array[:, 1], -array[:, 2], zeros ]]).transpose((2, 0, 1))
|
|
67
|
+
return SkewSymmetricSecondOrderTensor(mat)
|
|
68
|
+
elif dtype == SecondOrderTensor:
|
|
69
|
+
length = array.shape[0]
|
|
70
|
+
return SecondOrderTensor(array.reshape((length,3,3)))
|
|
71
|
+
elif dtype == float:
|
|
72
|
+
return array
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def from_results_folder(folder, dtype=None):
|
|
77
|
+
"""
|
|
78
|
+
Import all data of a given field from FEPX results folder.
|
|
79
|
+
|
|
80
|
+
The type of returns is inferred from the data one wants to parse
|
|
81
|
+
(according the `FEPX documentation <https://fepx.info/doc/output.html>`_ ).
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
folder : str
|
|
86
|
+
Path to the folder to read the results from
|
|
87
|
+
dtype : type, optional
|
|
88
|
+
If provided, sets the type of returned array. It can be:
|
|
89
|
+
- float
|
|
90
|
+
- SecondOrderTensor
|
|
91
|
+
- SymmetricSecondOrderTensor
|
|
92
|
+
- SkewSymmetricSecondOrderTensor
|
|
93
|
+
- stressTensor
|
|
94
|
+
- strainTensor
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
SecondOrderTensor or numpy.ndarray
|
|
99
|
+
Array of second-order tensors built from the read data. The array will be of shape (m, n), where m is the number
|
|
100
|
+
of time increment n is the number of elements in the mesh.
|
|
101
|
+
"""
|
|
102
|
+
dir_path = Path(folder)
|
|
103
|
+
folder_name = dir_path.name
|
|
104
|
+
if not dir_path.is_dir():
|
|
105
|
+
raise ValueError(f"{folder} is not a valid directory.")
|
|
106
|
+
constructor = None
|
|
107
|
+
array = []
|
|
108
|
+
for file in dir_path.iterdir():
|
|
109
|
+
if file.is_file() and file.name.startswith(folder_name):
|
|
110
|
+
data_file = from_step_file(str(file), dtype=dtype)
|
|
111
|
+
if constructor is None:
|
|
112
|
+
constructor = type(data_file)
|
|
113
|
+
elif constructor != type(data_file):
|
|
114
|
+
raise ValueError('The types of data contained in {} seem to be inconsistent.'.format(folder))
|
|
115
|
+
array.append(data_file)
|
|
116
|
+
if constructor == np.ndarray:
|
|
117
|
+
return np.stack(array, axis=0)
|
|
118
|
+
else:
|
|
119
|
+
return constructor.stack(array)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from Elasticipy.tensors.second_order import SecondOrderTensor, SymmetricSecondOrderTensor
|
|
2
|
+
from Elasticipy.tensors.stress_strain import StressTensor
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
def _rebuild_tensor(F11, F22, F33, F12, F13, F21, F23, F31, F32):
|
|
7
|
+
return np.array([[F11, F12, F13], [F21, F22, F23], [F31, F32, F33]]).transpose((2,0,1))
|
|
8
|
+
|
|
9
|
+
def from_quadrature_file(file, returns='stress'):
|
|
10
|
+
"""
|
|
11
|
+
Read data from quadrature output file generated by PRISMS plasticity.
|
|
12
|
+
|
|
13
|
+
These files, usually named QuadratureOutputsXXX.csv (where XXX denotes the time increment), contain large amount of
|
|
14
|
+
data, such as gradient components, stress values, grain ID etc.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
file : str
|
|
19
|
+
Path to quadrature file
|
|
20
|
+
returns : str or list of str or tuple of str
|
|
21
|
+
name(s) of requested field(s). They can be:
|
|
22
|
+
- grain ID
|
|
23
|
+
- phase ID
|
|
24
|
+
- det(J)
|
|
25
|
+
- twin
|
|
26
|
+
- coordinates
|
|
27
|
+
- orientation
|
|
28
|
+
- elastic gradient
|
|
29
|
+
- plastic gradient
|
|
30
|
+
- stress
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
ndarray or SecondOrderTensor or StressTensor
|
|
34
|
+
The number of returned values depends on the requested fields.
|
|
35
|
+
"""
|
|
36
|
+
data=pd.read_csv(file, header=None, dtype=float, usecols=range(0,37))
|
|
37
|
+
grainID, phaseID, detJ, twin, x, y, z, rot1, rot2, rot3, *other = data.T.to_numpy()
|
|
38
|
+
Fe11, Fe22, Fe33, Fe12, Fe13, Fe21, Fe23, Fe31, Fe32, *FpStress = other
|
|
39
|
+
Fp11, Fp22, Fp33, Fp12, Fp13, Fp21, Fp23, Fp31, Fp32, *stress_compo = FpStress
|
|
40
|
+
if isinstance(returns, str):
|
|
41
|
+
returns_list = (returns,)
|
|
42
|
+
else:
|
|
43
|
+
returns_list = returns
|
|
44
|
+
returned_values = []
|
|
45
|
+
for r in returns_list:
|
|
46
|
+
rl = r.lower()
|
|
47
|
+
if (rl == 'grain id') or (rl == 'grainid'):
|
|
48
|
+
returned_values.append(grainID)
|
|
49
|
+
elif (rl == 'phase id') or (rl == 'phaseid'):
|
|
50
|
+
returned_values.append(phaseID)
|
|
51
|
+
elif rl == 'det(j)':
|
|
52
|
+
returned_values.append(detJ)
|
|
53
|
+
elif rl == 'twin':
|
|
54
|
+
returned_values.append(twin)
|
|
55
|
+
elif rl == 'coordinates':
|
|
56
|
+
returned_values.append(np.array([x, y, z]).T)
|
|
57
|
+
elif rl == 'orientation':
|
|
58
|
+
returned_values.append(np.array([rot1, rot2, rot3]).T)
|
|
59
|
+
elif rl == 'elastic gradient':
|
|
60
|
+
Fe = _rebuild_tensor(Fe11, Fe22, Fe33, Fe12, Fe13, Fe21, Fe23, Fe31, Fe32)
|
|
61
|
+
returned_values.append(SecondOrderTensor(Fe))
|
|
62
|
+
elif rl == 'plastic gradient':
|
|
63
|
+
Fp = _rebuild_tensor(Fp11, Fp22, Fp33, Fp12, Fp13, Fp21, Fp23, Fp31, Fp32)
|
|
64
|
+
returned_values.append(SecondOrderTensor(Fp))
|
|
65
|
+
elif rl == 'stress':
|
|
66
|
+
stress = _rebuild_tensor(*stress_compo)
|
|
67
|
+
returned_values.append(StressTensor(stress))
|
|
68
|
+
else:
|
|
69
|
+
raise ValueError('Unknown return type')
|
|
70
|
+
if isinstance(returns, str):
|
|
71
|
+
return returned_values[0]
|
|
72
|
+
else:
|
|
73
|
+
return tuple(returned_values)
|
|
74
|
+
|
|
75
|
+
def from_stressstrain_file(file):
|
|
76
|
+
"""
|
|
77
|
+
Read data from stress-strain file generated by PRISMS plasticity.
|
|
78
|
+
|
|
79
|
+
This file is usually named stressstrain.txt
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
file : str
|
|
84
|
+
Path to stress-strain file
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
SymmetricSecondOrderTensor
|
|
89
|
+
Average Green-Lagrange strain tensor
|
|
90
|
+
StressTensor
|
|
91
|
+
Average stress tensor
|
|
92
|
+
"""
|
|
93
|
+
data = pd.read_csv(file, sep='\t')
|
|
94
|
+
Exx, Exy, Exz = data['Exx'], data['Exy'], data['Exz']
|
|
95
|
+
Eyy, Eyz = data['Eyy'], data['Eyz']
|
|
96
|
+
Ezz = data['Ezz']
|
|
97
|
+
E = np.array([[Exx, Exy, Exz], [Exy, Eyy, Eyz], [Exz, Eyz, Ezz]]).transpose((2,0,1))
|
|
98
|
+
Txx, Txy, Txz = data['Txx'], data['Txy'], data['Txz']
|
|
99
|
+
Tyy, Tyz = data['Tyy'], data['Tyz']
|
|
100
|
+
Tzz = data['Tzz']
|
|
101
|
+
T = np.array([[Txx, Txy, Txz], [Txy, Tyy, Tyz], [Txz, Tyz, Tzz]]).transpose((2, 0, 1))
|
|
102
|
+
return SymmetricSecondOrderTensor(E), StressTensor(T)
|
|
103
|
+
|
Elasticipy/plasticity.py
CHANGED
|
@@ -6,6 +6,9 @@ class IsotropicHardening:
|
|
|
6
6
|
"""
|
|
7
7
|
Template class for isotropic hardening plasticity models
|
|
8
8
|
"""
|
|
9
|
+
type = "Isotropic"
|
|
10
|
+
name = 'Generic'
|
|
11
|
+
|
|
9
12
|
def __init__(self, criterion='von Mises'):
|
|
10
13
|
"""
|
|
11
14
|
Create an instance of a plastic model, assuming isotropic hardening
|
|
@@ -27,6 +30,12 @@ class IsotropicHardening:
|
|
|
27
30
|
self.criterion = criterion
|
|
28
31
|
self.plastic_strain = 0.0
|
|
29
32
|
|
|
33
|
+
def __repr__(self):
|
|
34
|
+
return (('{} plasticity model\n'.format(self.name) +
|
|
35
|
+
' type: {}\n'.format(self.type)) +
|
|
36
|
+
' criterion: {}\n'.format(self.criterion.name) +
|
|
37
|
+
' current strain: {}'.format(self.plastic_strain))
|
|
38
|
+
|
|
30
39
|
def flow_stress(self, strain, **kwargs):
|
|
31
40
|
pass
|
|
32
41
|
|
|
@@ -67,9 +76,14 @@ class IsotropicHardening:
|
|
|
67
76
|
|
|
68
77
|
|
|
69
78
|
class JohnsonCook(IsotropicHardening):
|
|
79
|
+
"""
|
|
80
|
+
Special case of isotropic hardening with an underlying Johnson Cook hardening evolution rule
|
|
81
|
+
"""
|
|
82
|
+
name = "Johnson-Cook"
|
|
83
|
+
|
|
70
84
|
def __init__(self, A, B, n, C=None, eps_dot_ref=1.0, m=None, T0=25, Tm=None, criterion='von Mises'):
|
|
71
85
|
"""
|
|
72
|
-
Constructor for a
|
|
86
|
+
Constructor for a Johnson-Cook (JC) model.
|
|
73
87
|
|
|
74
88
|
The JC model is an exponential-law strain hardening model, which can take into account strain-rate sensibility
|
|
75
89
|
and temperature-dependence (although they are not mandatory). See notes for details.
|
|
@@ -235,6 +249,11 @@ class JohnsonCook(IsotropicHardening):
|
|
|
235
249
|
|
|
236
250
|
|
|
237
251
|
class PlasticityCriterion:
|
|
252
|
+
"""
|
|
253
|
+
Template class for plasticity criteria
|
|
254
|
+
"""
|
|
255
|
+
name = 'generic'
|
|
256
|
+
|
|
238
257
|
@staticmethod
|
|
239
258
|
def eq_stress(stress, **kwargs):
|
|
240
259
|
"""
|
|
@@ -271,6 +290,10 @@ class PlasticityCriterion:
|
|
|
271
290
|
pass
|
|
272
291
|
|
|
273
292
|
class VonMisesPlasticity(PlasticityCriterion):
|
|
293
|
+
"""
|
|
294
|
+
von Mises plasticity criterion, with associated normality rule
|
|
295
|
+
"""
|
|
296
|
+
name = 'von Mises'
|
|
274
297
|
@staticmethod
|
|
275
298
|
def eq_stress(stress, **kwargs):
|
|
276
299
|
return stress.vonMises()
|
|
@@ -283,6 +306,11 @@ class VonMisesPlasticity(PlasticityCriterion):
|
|
|
283
306
|
return StrainTensor(3 / 2 * gradient_tensor.matrix)
|
|
284
307
|
|
|
285
308
|
class TrescaPlasticity(PlasticityCriterion):
|
|
309
|
+
"""
|
|
310
|
+
Tresca plasticity criterion, with associated normality rule
|
|
311
|
+
"""
|
|
312
|
+
name = 'Tresca'
|
|
313
|
+
|
|
286
314
|
@staticmethod
|
|
287
315
|
def eq_stress(stress, **kwargs):
|
|
288
316
|
return stress.Tresca()
|
|
@@ -305,6 +333,11 @@ class TrescaPlasticity(PlasticityCriterion):
|
|
|
305
333
|
return strain / strain.eq_strain()
|
|
306
334
|
|
|
307
335
|
class DruckerPrager(PlasticityCriterion):
|
|
336
|
+
"""
|
|
337
|
+
Drucker-Prager pressure-dependent plasticity criterion, with associated normality rule
|
|
338
|
+
"""
|
|
339
|
+
name = 'Drucker-Prager'
|
|
340
|
+
|
|
308
341
|
def __init__(self, alpha):
|
|
309
342
|
"""
|
|
310
343
|
Create a Drucker-Prager (DG) plasticity criterion.
|
Elasticipy/polefigure.py
CHANGED
|
@@ -101,6 +101,27 @@ class LambertScale(mscale.ScaleBase):
|
|
|
101
101
|
mscale.register_scale(LambertScale)
|
|
102
102
|
|
|
103
103
|
def add_polefigure(fig, *args, projection='stereographic', **kwargs):
|
|
104
|
+
"""
|
|
105
|
+
Add a pole figure to the figure object.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
fig : matplotlib.figure.Figure
|
|
110
|
+
Handle to an existing figure
|
|
111
|
+
args : list
|
|
112
|
+
Positional arguments to pass to the subplot constructor
|
|
113
|
+
projection : str, optional
|
|
114
|
+
Projection to use. I can be 'stereographic' (default), 'equal area' or 'lambert' (which is equivalent to equal
|
|
115
|
+
area).
|
|
116
|
+
|
|
117
|
+
kwargs : dict
|
|
118
|
+
Keyword arguments to pass to the subplot constructor
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
matplotlib.axes._subplots.AxesSubplot
|
|
123
|
+
Handle to the added axes
|
|
124
|
+
"""
|
|
104
125
|
if projection.lower() == 'equal area':
|
|
105
126
|
projection = 'lambert'
|
|
106
127
|
ax = fig.add_subplot(*args, projection='polar', **kwargs)
|
Elasticipy/spherical_function.py
CHANGED
|
@@ -541,8 +541,6 @@ class SphericalFunction:
|
|
|
541
541
|
u, evals = self.evaluate_on_spherical_grid((n_phi, n_theta), return_in_spherical=False, use_symmetry=False)
|
|
542
542
|
ax = _plot3D(new_fig, u, evals, **kwargs)
|
|
543
543
|
ax.axis('equal')
|
|
544
|
-
if fig is None:
|
|
545
|
-
plt.show()
|
|
546
544
|
return new_fig, ax
|
|
547
545
|
|
|
548
546
|
def plot_xyz_sections(self, n_theta=500, fig=None, axs=None, **kwargs):
|
|
@@ -590,12 +588,10 @@ class SphericalFunction:
|
|
|
590
588
|
r = self.eval_spherical(angles)
|
|
591
589
|
ax.plot(theta_polar, r, **kwargs)
|
|
592
590
|
axs_new.append(ax)
|
|
593
|
-
if fig is None:
|
|
594
|
-
new_fig.show()
|
|
595
591
|
return new_fig, axs_new
|
|
596
592
|
|
|
597
593
|
def plot_as_pole_figure(self, n_theta=50, n_phi=200, projection='lambert',
|
|
598
|
-
fig=None, plot_type='imshow',
|
|
594
|
+
fig=None, plot_type='imshow', title=None,
|
|
599
595
|
subplot_args=(), subplot_kwargs=None, **kwargs):
|
|
600
596
|
"""
|
|
601
597
|
Plots a pole figure visualization of spherical data using specified parameters and plot types.
|
|
@@ -617,7 +613,7 @@ class SphericalFunction:
|
|
|
617
613
|
plot_type : str, optional
|
|
618
614
|
The type of plot to generate: 'imshow', 'contourf', or 'contour', by default 'imshow'.
|
|
619
615
|
title : str, optional
|
|
620
|
-
Title to add to the current axis.
|
|
616
|
+
Title to add to the current axis. Default is None.
|
|
621
617
|
subplot_args : tuple
|
|
622
618
|
List of arguments to pass to the subplot function, by default ()
|
|
623
619
|
subplot_kwargs : dict
|
|
@@ -656,8 +652,6 @@ class SphericalFunction:
|
|
|
656
652
|
ax.set_rlim(*self.domain[1])
|
|
657
653
|
ax.set_title(title)
|
|
658
654
|
new_fig.colorbar(sc)
|
|
659
|
-
if show:
|
|
660
|
-
plt.show()
|
|
661
655
|
return new_fig, ax
|
|
662
656
|
|
|
663
657
|
|
|
@@ -886,8 +880,6 @@ class HyperSphericalFunction(SphericalFunction):
|
|
|
886
880
|
else:
|
|
887
881
|
r_grid = np.mean(values, axis=2)
|
|
888
882
|
ax = _plot3D(new_fig, u[:, :, 0, :], r_grid, **kwargs)
|
|
889
|
-
if fig is None:
|
|
890
|
-
plt.show()
|
|
891
883
|
return new_fig, ax
|
|
892
884
|
|
|
893
885
|
def plot_xyz_sections(self, n_theta=500, n_psi=100, color_minmax='blue', alpha_minmax=0.2, color_mean='red',
|
|
@@ -950,8 +942,6 @@ class HyperSphericalFunction(SphericalFunction):
|
|
|
950
942
|
handles.extend([line, area])
|
|
951
943
|
labels.extend([line.get_label(), area.get_label()])
|
|
952
944
|
new_fig.legend(handles, labels, loc='upper center', ncol=2, bbox_to_anchor=(0.5, 0.95))
|
|
953
|
-
if fig is None:
|
|
954
|
-
new_fig.show()
|
|
955
945
|
return new_fig, axs
|
|
956
946
|
|
|
957
947
|
def plot_as_pole_figure(self, n_theta=50, n_phi=200, n_psi=50, which='mean', projection='lambert', fig=None,
|
|
@@ -1033,6 +1023,4 @@ class HyperSphericalFunction(SphericalFunction):
|
|
|
1033
1023
|
ax.set_rlim(*self.domain[1])
|
|
1034
1024
|
ax.set_title(title)
|
|
1035
1025
|
fig.colorbar(sc)
|
|
1036
|
-
if show:
|
|
1037
|
-
plt.show()
|
|
1038
1026
|
return fig, ax
|