Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://www.mooseframework.org
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #include "CrystalPlasticityStressUpdateBase.h"
11 :
12 : #include "libmesh/utility.h"
13 : #include "libmesh/int_range.h"
14 : #include "Conversion.h"
15 : #include "MooseException.h"
16 :
17 : InputParameters
18 603 : CrystalPlasticityStressUpdateBase::validParams()
19 : {
20 603 : InputParameters params = Material::validParams();
21 1206 : params.addParam<std::string>(
22 : "base_name",
23 : "Optional parameter that allows the user to define multiple crystal plasticity mechanisms");
24 603 : params.addClassDescription(
25 : "Crystal Plasticity base class: handles the Newton iteration over the stress residual and "
26 : "calculates the Jacobian based on constitutive laws provided by inheriting classes");
27 :
28 : // The return stress increment classes are intended to be iterative materials, so must set compute
29 : // = false for all inheriting classes
30 603 : params.set<bool>("compute") = false;
31 603 : params.suppressParameter<bool>("compute");
32 :
33 1206 : params.addParam<MooseEnum>(
34 : "crystal_lattice_type",
35 1809 : MooseEnum("BCC FCC HCP", "FCC"),
36 : "Crystal lattice type or representative unit cell, i.e., BCC, FCC, HCP, etc.");
37 :
38 2412 : params.addRangeCheckedParam<std::vector<Real>>(
39 : "unit_cell_dimension",
40 1206 : std::vector<Real>{1.0, 1.0, 1.0},
41 : "unit_cell_dimension_size = 3",
42 : "The dimension of the unit cell along three directions, where a cubic unit cell is assumed "
43 : "for cubic crystals and a hexagonal unit cell (a, a, c) is assumed for HCP crystals. These "
44 : "dimensions will be taken into account while computing the slip systems."
45 : " Default size is 1.0 along all three directions.");
46 :
47 1206 : params.addRequiredParam<unsigned int>(
48 : "number_slip_systems",
49 : "The total number of possible active slip systems for the crystalline material");
50 1206 : params.addRequiredParam<FileName>(
51 : "slip_sys_file_name",
52 : "Name of the file containing the slip systems, one slip system per row, with the slip plane "
53 : "normal given before the slip plane direction.");
54 1206 : params.addParam<Real>("number_cross_slip_directions",
55 1206 : 0,
56 : "Quanity of unique slip directions, used to determine cross slip familes");
57 1206 : params.addParam<Real>("number_cross_slip_planes",
58 1206 : 0,
59 : "Quanity of slip planes belonging to a single cross slip direction; used "
60 : "to determine cross slip families");
61 1206 : params.addParam<Real>(
62 : "slip_increment_tolerance",
63 1206 : 2e-2,
64 : "Maximum allowable slip in an increment for each individual constitutive model");
65 1206 : params.addParam<Real>(
66 1206 : "stol", 1e-2, "Constitutive internal state variable relative change tolerance");
67 1206 : params.addParam<Real>("resistance_tol",
68 1206 : 1.0e-2,
69 : "Constitutive slip system resistance relative residual tolerance for each "
70 : "individual constitutive model");
71 1206 : params.addParam<Real>("zero_tol",
72 1206 : 1e-12,
73 : "Tolerance for residual check when variable value is zero for each "
74 : "individual constitutive model");
75 1206 : params.addParam<bool>(
76 : "print_state_variable_convergence_error_messages",
77 1206 : false,
78 : "Whether or not to print warning messages from the crystal plasticity specific convergence "
79 : "checks on both the constiutive model internal state variables.");
80 603 : return params;
81 0 : }
82 :
83 455 : CrystalPlasticityStressUpdateBase::CrystalPlasticityStressUpdateBase(
84 455 : const InputParameters & parameters)
85 : : Material(parameters),
86 599 : _base_name(isParamValid("base_name") ? getParam<std::string>("base_name") + "_" : ""),
87 455 : _crystal_lattice_type(
88 455 : getParam<MooseEnum>("crystal_lattice_type").getEnum<CrystalLatticeType>()),
89 910 : _unit_cell_dimension(getParam<std::vector<Real>>("unit_cell_dimension")),
90 910 : _number_slip_systems(getParam<unsigned int>("number_slip_systems")),
91 910 : _slip_sys_file_name(getParam<FileName>("slip_sys_file_name")),
92 910 : _number_cross_slip_directions(getParam<Real>("number_cross_slip_directions")),
93 910 : _number_cross_slip_planes(getParam<Real>("number_cross_slip_planes")),
94 :
95 910 : _rel_state_var_tol(getParam<Real>("stol")),
96 910 : _slip_incr_tol(getParam<Real>("slip_increment_tolerance")),
97 910 : _resistance_tol(getParam<Real>("resistance_tol")),
98 910 : _zero_tol(getParam<Real>("zero_tol")),
99 :
100 455 : _slip_resistance(declareProperty<std::vector<Real>>(_base_name + "slip_resistance")),
101 910 : _slip_resistance_old(getMaterialPropertyOld<std::vector<Real>>(_base_name + "slip_resistance")),
102 455 : _slip_increment(declareProperty<std::vector<Real>>(_base_name + "slip_increment")),
103 :
104 455 : _slip_direction(_number_slip_systems),
105 455 : _slip_plane_normal(_number_slip_systems),
106 455 : _flow_direction(declareProperty<std::vector<RankTwoTensor>>(_base_name + "flow_direction")),
107 455 : _tau(declareProperty<std::vector<Real>>(_base_name + "applied_shear_stress")),
108 1365 : _print_convergence_message(getParam<bool>("print_state_variable_convergence_error_messages"))
109 : {
110 455 : getSlipSystems();
111 451 : sortCrossSlipFamilies();
112 :
113 902 : if (parameters.isParamSetByUser("number_cross_slip_directions"))
114 0 : _calculate_cross_slip = true;
115 : else
116 451 : _calculate_cross_slip = false;
117 451 : }
118 :
119 : void
120 25344 : CrystalPlasticityStressUpdateBase::initQpStatefulProperties()
121 : {
122 25344 : _tau[_qp].resize(_number_slip_systems);
123 :
124 25344 : _flow_direction[_qp].resize(_number_slip_systems);
125 323904 : for (const auto i : make_range(_number_slip_systems))
126 : {
127 298560 : _flow_direction[_qp][i].zero();
128 298560 : _tau[_qp][i] = 0.0;
129 : }
130 :
131 25344 : _slip_resistance[_qp].resize(_number_slip_systems);
132 25344 : _slip_increment[_qp].resize(_number_slip_systems);
133 25344 : }
134 :
135 : void
136 455 : CrystalPlasticityStressUpdateBase::getSlipSystems()
137 : {
138 : bool orthonormal_error = false;
139 :
140 : // read in the slip system data from auxiliary text file
141 455 : MooseUtils::DelimitedFileReader _reader(_slip_sys_file_name);
142 : _reader.setFormatFlag(MooseUtils::DelimitedFileReader::FormatFlag::ROWS);
143 455 : _reader.read();
144 :
145 : // check the size of the input
146 455 : if (_reader.getData().size() != _number_slip_systems)
147 0 : paramError(
148 : "number_slip_systems",
149 : "The number of rows in the slip system file should match the number of slip system.");
150 :
151 5758 : for (const auto i : make_range(_number_slip_systems))
152 : {
153 : // initialize to zero
154 5303 : _slip_direction[i].zero();
155 : _slip_plane_normal[i].zero();
156 : }
157 :
158 455 : if (_crystal_lattice_type == CrystalLatticeType::HCP)
159 128 : transformHexagonalMillerBravaisSlipSystems(_reader);
160 327 : else if (_crystal_lattice_type == CrystalLatticeType::BCC ||
161 : _crystal_lattice_type == CrystalLatticeType::FCC)
162 : {
163 4107 : for (const auto i : make_range(_number_slip_systems))
164 : {
165 : // directly grab the raw data and scale it by the unit cell dimension
166 26460 : for (const auto j : index_range(_reader.getData(i)))
167 : {
168 22680 : if (j < LIBMESH_DIM)
169 11340 : _slip_plane_normal[i](j) = _reader.getData(i)[j] / _unit_cell_dimension[j];
170 : else
171 11340 : _slip_direction[i](j - LIBMESH_DIM) =
172 11340 : _reader.getData(i)[j] * _unit_cell_dimension[j - LIBMESH_DIM];
173 : }
174 : }
175 : }
176 :
177 5731 : for (const auto i : make_range(_number_slip_systems))
178 : {
179 : // normalize
180 5280 : _slip_plane_normal[i] /= _slip_plane_normal[i].norm();
181 5280 : _slip_direction[i] /= _slip_direction[i].norm();
182 :
183 5280 : if (_crystal_lattice_type != CrystalLatticeType::HCP)
184 : {
185 : const auto magnitude = _slip_plane_normal[i] * _slip_direction[i];
186 3780 : if (std::abs(magnitude) > libMesh::TOLERANCE)
187 : {
188 : orthonormal_error = true;
189 : break;
190 : }
191 : }
192 : }
193 :
194 451 : if (orthonormal_error)
195 0 : mooseError("CrystalPlasticityStressUpdateBase Error: The slip system file contains a slip "
196 : "direction and plane normal pair that are not orthonormal in the Cartesian "
197 : "coordinate system.");
198 451 : }
199 :
200 : void
201 128 : CrystalPlasticityStressUpdateBase::transformHexagonalMillerBravaisSlipSystems(
202 : const MooseUtils::DelimitedFileReader & reader)
203 : {
204 : const unsigned int miller_bravais_indices = 4;
205 : RealVectorValue temporary_slip_direction, temporary_slip_plane;
206 : // temporary_slip_plane.resize(LIBMESH_DIM);
207 : // temporary_slip_direction.resize(LIBMESH_DIM);
208 :
209 128 : if (_unit_cell_dimension[0] != _unit_cell_dimension[1] ||
210 128 : _unit_cell_dimension[0] == _unit_cell_dimension[2])
211 1 : mooseError("CrystalPlasticityStressUpdateBase Error: The specified unit cell dimensions are "
212 : "not consistent with expectations for "
213 : "HCP crystal hexagonal lattices.");
214 127 : else if (reader.getData(0).size() != miller_bravais_indices * 2)
215 1 : mooseError("CrystalPlasticityStressUpdateBase Error: The number of entries in the first row of "
216 : "the slip system file is not consistent with the expectations for the 4-index "
217 : "Miller-Bravais assumption for HCP crystals. This file should represent both the "
218 : "slip plane normal and the slip direction with 4-indices each.");
219 :
220 : // set up the tranformation matrices
221 126 : RankTwoTensor transform_matrix;
222 : transform_matrix.zero();
223 126 : transform_matrix(0, 0) = 1.0 / _unit_cell_dimension[0];
224 126 : transform_matrix(1, 0) = 1.0 / (_unit_cell_dimension[0] * std::sqrt(3.0));
225 126 : transform_matrix(1, 1) = 2.0 / (_unit_cell_dimension[0] * std::sqrt(3.0));
226 126 : transform_matrix(2, 2) = 1.0 / (_unit_cell_dimension[2]);
227 :
228 1626 : for (const auto i : make_range(_number_slip_systems))
229 : {
230 : // read in raw data from file and store in the temporary vectors
231 13502 : for (const auto j : index_range(reader.getData(i)))
232 : {
233 : // Check that the slip plane normal indices of the basal plane sum to zero for consistency
234 : Real basal_pl_sum = 0.0;
235 48008 : for (const auto k : make_range(LIBMESH_DIM))
236 36006 : basal_pl_sum += reader.getData(i)[k];
237 :
238 12002 : if (basal_pl_sum > _zero_tol)
239 1 : mooseError(
240 : "CrystalPlasticityStressUpdateBase Error: The specified HCP basal plane Miller-Bravais "
241 : "indices do not sum to zero. Check the values supplied in the associated text file.");
242 :
243 : // Check that the slip direction indices of the basal plane sum to zero for consistency
244 : Real basal_dir_sum = 0.0;
245 48004 : for (const auto k : make_range(miller_bravais_indices, miller_bravais_indices + LIBMESH_DIM))
246 36003 : basal_dir_sum += reader.getData(i)[k];
247 :
248 12001 : if (basal_dir_sum > _zero_tol)
249 1 : mooseError("CrystalPlasticityStressUpdateBase Error: The specified HCP slip direction "
250 : "Miller-Bravais indices in the basal plane (U, V, and T) do not sum to zero "
251 : "within the user specified tolerance (try loosing zero_tol if using the default "
252 : "value). Check the values supplied in the associated text file.");
253 :
254 12000 : if (j < miller_bravais_indices)
255 : {
256 : // Planes are directly copied over, per a_1 = x convention used here:
257 : // Store the first two indices for the basal plane, (h and k), and drop
258 : // the redundant third basal plane index (i)
259 6000 : if (j < 2)
260 3000 : temporary_slip_plane(j) = reader.getData(i)[j];
261 : // Store the c-axis index as the third entry in the orthorombic index convention
262 3000 : else if (j == 3)
263 1500 : temporary_slip_plane(j - 1) = reader.getData(i)[j];
264 : }
265 : else
266 : {
267 6000 : const auto direction_j = j - miller_bravais_indices;
268 : // Store the first two indices for the slip direction in the basal plane,
269 : //(U, V), and drop the redundant third basal plane index (T)
270 6000 : if (direction_j < 2)
271 3000 : temporary_slip_direction(direction_j) = reader.getData(i)[j];
272 : // Store the c-axis index as the third entry in the orthorombic index convention
273 3000 : else if (direction_j == 3)
274 1500 : temporary_slip_direction(direction_j - 1) = reader.getData(i)[j];
275 : }
276 : }
277 :
278 : // perform transformation calculation
279 1500 : _slip_direction[i] = transform_matrix * temporary_slip_direction;
280 1500 : _slip_plane_normal[i] = transform_matrix * temporary_slip_plane;
281 : }
282 124 : }
283 :
284 : void
285 451 : CrystalPlasticityStressUpdateBase::sortCrossSlipFamilies()
286 : {
287 451 : if (_number_cross_slip_directions == 0)
288 : {
289 451 : _cross_slip_familes.resize(0);
290 451 : return;
291 : }
292 :
293 : // If cross slip does occur, then set up the system of vectors for the families
294 0 : _cross_slip_familes.resize(_number_cross_slip_directions);
295 : // and set the first index of each inner vector
296 0 : for (unsigned int i = 0; i < _number_cross_slip_directions; ++i)
297 0 : _cross_slip_familes[i].resize(1);
298 :
299 : // Sort the index of the slip system based vectors into separte families
300 : unsigned int family_counter = 1;
301 0 : _cross_slip_familes[0][0] = 0;
302 :
303 0 : for (unsigned int i = 1; i < _number_slip_systems; ++i)
304 : {
305 0 : for (unsigned int j = 0; j < family_counter; ++j)
306 : {
307 : // check to see if the slip system direction i matches any of the existing slip directions
308 : // First calculate the dot product
309 : Real dot_product = 0.0;
310 0 : for (const auto k : make_range(Moose::dim))
311 : {
312 0 : unsigned int check_family_index = _cross_slip_familes[j][0];
313 0 : dot_product += std::abs(_slip_direction[check_family_index](k) - _slip_direction[i](k));
314 : }
315 : // Then check if the dot product is one, if yes, add to family and break
316 0 : if (MooseUtils::absoluteFuzzyEqual(dot_product, 0.0))
317 : {
318 0 : _cross_slip_familes[j].push_back(i);
319 0 : if (_cross_slip_familes[j].size() > _number_cross_slip_planes)
320 0 : mooseError(
321 : "Exceeded the number of cross slip planes allowed in a single cross slip family");
322 :
323 : break; // exit the loop over the exisiting cross slip families and move to the next slip
324 : // direction
325 : }
326 : // The slip system in question does not belong to an existing family
327 0 : else if (j == (family_counter - 1) && !MooseUtils::absoluteFuzzyEqual(dot_product, 0.0))
328 : {
329 0 : if (family_counter > _number_cross_slip_directions)
330 0 : mooseError("Exceeds the number of cross slip directions specified for this material");
331 :
332 0 : _cross_slip_familes[family_counter][0] = i;
333 0 : family_counter++;
334 0 : break;
335 : }
336 : }
337 : }
338 :
339 0 : if (_print_convergence_message)
340 : {
341 0 : mooseWarning("Checking the slip system ordering now:");
342 0 : for (unsigned int i = 0; i < _number_cross_slip_directions; ++i)
343 : {
344 : Moose::out << "In cross slip family " << i << std::endl;
345 0 : for (unsigned int j = 0; j < _number_cross_slip_planes; ++j)
346 0 : Moose::out << " is the slip direction number " << _cross_slip_familes[i][j] << std::endl;
347 : }
348 : }
349 : }
350 :
351 : unsigned int
352 0 : CrystalPlasticityStressUpdateBase::identifyCrossSlipFamily(const unsigned int index)
353 : {
354 0 : for (unsigned int i = 0; i < _number_cross_slip_directions; ++i)
355 0 : for (unsigned int j = 0; j < _number_cross_slip_planes; ++j)
356 0 : if (_cross_slip_familes[i][j] == index)
357 0 : return i;
358 :
359 : // Should never reach this statement
360 0 : mooseError("The supplied slip system index is not among the slip system families sorted.");
361 : }
362 :
363 : void
364 935877 : CrystalPlasticityStressUpdateBase::calculateFlowDirection(const RankTwoTensor & crysrot)
365 : {
366 935877 : calculateSchmidTensor(
367 935877 : _number_slip_systems, _slip_plane_normal, _slip_direction, _flow_direction[_qp], crysrot);
368 935877 : }
369 :
370 : void
371 935877 : CrystalPlasticityStressUpdateBase::calculateSchmidTensor(
372 : const unsigned int & number_slip_systems,
373 : const std::vector<RealVectorValue> & plane_normal_vector,
374 : const std::vector<RealVectorValue> & direction_vector,
375 : std::vector<RankTwoTensor> & schmid_tensor,
376 : const RankTwoTensor & crysrot)
377 : {
378 : std::vector<RealVectorValue> local_direction_vector, local_plane_normal;
379 935877 : local_direction_vector.resize(number_slip_systems);
380 935877 : local_plane_normal.resize(number_slip_systems);
381 :
382 : // Update slip direction and normal with crystal orientation
383 12129703 : for (const auto i : make_range(_number_slip_systems))
384 : {
385 11193826 : local_direction_vector[i].zero();
386 : local_plane_normal[i].zero();
387 :
388 44775304 : for (const auto j : make_range(LIBMESH_DIM))
389 134325912 : for (const auto k : make_range(LIBMESH_DIM))
390 : {
391 100744434 : local_direction_vector[i](j) =
392 100744434 : local_direction_vector[i](j) + crysrot(j, k) * direction_vector[i](k);
393 :
394 100744434 : local_plane_normal[i](j) =
395 100744434 : local_plane_normal[i](j) + crysrot(j, k) * plane_normal_vector[i](k);
396 : }
397 :
398 : // Calculate Schmid tensor
399 44775304 : for (const auto j : make_range(LIBMESH_DIM))
400 134325912 : for (const auto k : make_range(LIBMESH_DIM))
401 : {
402 100744434 : schmid_tensor[i](j, k) = local_direction_vector[i](j) * local_plane_normal[i](k);
403 : }
404 : }
405 935877 : }
406 :
407 : void
408 7112211 : CrystalPlasticityStressUpdateBase::calculateShearStress(
409 : const RankTwoTensor & pk2,
410 : const RankTwoTensor & inverse_eigenstrain_deformation_grad,
411 : const unsigned int & num_eigenstrains)
412 : {
413 7112211 : if (!num_eigenstrains)
414 : {
415 50074207 : for (const auto i : make_range(_number_slip_systems))
416 45506180 : _tau[_qp][i] = pk2.doubleContraction(_flow_direction[_qp][i]);
417 :
418 4568027 : return;
419 : }
420 :
421 2544184 : RankTwoTensor eigenstrain_deformation_grad = inverse_eigenstrain_deformation_grad.inverse();
422 37450840 : for (const auto i : make_range(_number_slip_systems))
423 : {
424 : // compute PK2_hat using deformation gradient
425 34906656 : RankTwoTensor pk2_hat = eigenstrain_deformation_grad.det() *
426 34906656 : eigenstrain_deformation_grad.transpose() * pk2 *
427 34906656 : inverse_eigenstrain_deformation_grad.transpose();
428 34906656 : _tau[_qp][i] = pk2_hat.doubleContraction(_flow_direction[_qp][i]);
429 : }
430 : }
431 :
432 : void
433 7000428 : CrystalPlasticityStressUpdateBase::calculateTotalPlasticDeformationGradientDerivative(
434 : RankFourTensor & dfpinvdpk2,
435 : const RankTwoTensor & inverse_plastic_deformation_grad_old,
436 : const RankTwoTensor & inverse_eigenstrain_deformation_grad_old,
437 : const unsigned int & num_eigenstrains)
438 : {
439 7000428 : std::vector<Real> dslip_dtau(_number_slip_systems, 0.0);
440 7000428 : std::vector<RankTwoTensor> dtaudpk2(_number_slip_systems);
441 7000428 : std::vector<RankTwoTensor> dfpinvdslip(_number_slip_systems);
442 :
443 7000428 : calculateConstitutiveSlipDerivative(dslip_dtau);
444 :
445 85856807 : for (const auto j : make_range(_number_slip_systems))
446 : {
447 78856379 : if (num_eigenstrains)
448 : {
449 : RankTwoTensor eigenstrain_deformation_grad_old =
450 33368448 : inverse_eigenstrain_deformation_grad_old.inverse();
451 33368448 : dtaudpk2[j] = eigenstrain_deformation_grad_old.det() * eigenstrain_deformation_grad_old *
452 33368448 : _flow_direction[_qp][j] * inverse_eigenstrain_deformation_grad_old;
453 : }
454 : else
455 45487931 : dtaudpk2[j] = _flow_direction[_qp][j];
456 78856379 : dfpinvdslip[j] = -inverse_plastic_deformation_grad_old * _flow_direction[_qp][j];
457 78856379 : dfpinvdpk2 += (dfpinvdslip[j] * dslip_dtau[j] * _substep_dt).outerProduct(dtaudpk2[j]);
458 : }
459 7000428 : }
460 :
461 : void
462 6496831 : CrystalPlasticityStressUpdateBase::calculateEquivalentSlipIncrement(
463 : RankTwoTensor & equivalent_slip_increment)
464 : {
465 : // Sum up the slip increments to find the equivalent plastic strain due to slip
466 78930351 : for (const auto i : make_range(_number_slip_systems))
467 72433520 : equivalent_slip_increment += _flow_direction[_qp][i] * _slip_increment[_qp][i] * _substep_dt;
468 6496831 : }
469 :
470 : void
471 949829 : CrystalPlasticityStressUpdateBase::setQp(const unsigned int & qp)
472 : {
473 949829 : _qp = qp;
474 949829 : }
475 :
476 : void
477 1025267 : CrystalPlasticityStressUpdateBase::setSubstepDt(const Real & substep_dt)
478 : {
479 1025267 : _substep_dt = substep_dt;
480 1025267 : }
481 :
482 : bool
483 2077013 : CrystalPlasticityStressUpdateBase::isConstitutiveStateVariableConverged(
484 : const std::vector<Real> & current_var,
485 : const std::vector<Real> & var_before_update,
486 : const std::vector<Real> & previous_substep_var,
487 : const Real & tolerance)
488 : {
489 : // sometimes the state variable size may not equal to the number of slip systems
490 2077013 : unsigned int sz = current_var.size();
491 : mooseAssert(current_var.size() == sz, "Current variable size does not match");
492 : mooseAssert(var_before_update.size() == sz, "Variable before update size does not match");
493 : mooseAssert(previous_substep_var.size() == sz, "Previous substep variable size does not match");
494 :
495 : bool is_converged = true;
496 :
497 : Real diff_val = 0.0;
498 : Real abs_prev_substep_val = 0.0;
499 27644837 : for (const auto i : make_range(sz))
500 : {
501 25567824 : diff_val = std::abs(var_before_update[i] - current_var[i]);
502 25567824 : abs_prev_substep_val = std::abs(previous_substep_var[i]);
503 :
504 : // set to false if the state variable is not converged
505 25567824 : if (abs_prev_substep_val < _zero_tol && diff_val > _zero_tol)
506 : is_converged = false;
507 25492094 : else if (abs_prev_substep_val > _zero_tol && diff_val > tolerance * abs_prev_substep_val)
508 : is_converged = false;
509 : }
510 2077013 : return is_converged;
511 : }
|