3
3
import typing
4
4
import warnings
5
5
6
- from scipy .optimize import root_scalar
7
-
8
6
from .steps import _ModelStep
9
7
from ._model_utils import get_gap_from_model
10
8
from ._step_utils import HeightOptimisationFunction , make_interpolation_func
@@ -81,6 +79,14 @@ class QuasiStaticStep(_ModelStep):
81
79
periodic_axes: tuple, optional ((False, False))
82
80
For each True value the corresponding axis will be solved by circular convolution, meaning the result is
83
81
periodic in that direction
82
+ periodic_im_repeats: tuple, optional (1,1)
83
+ The number of times the influence matrix should be wrapped along periodic dimensions, only used if at least one
84
+ of periodic axes is True. This is necessary to ensure truly periodic behaviour, no physical limit exists
85
+ method: {'auto', 'pk', 'double'}, optional ('auto')
86
+ The method by which the normal contact is solved, only used for load controlled contact.
87
+ 'pk' uses the Polonsky and Keer algorithm for elastic contact.
88
+ 'double' uses a double iteration procedure, suitable for elastic contact with a maximum pressure.
89
+ 'auto' automatically selects 'pk' if there is no maximum pressure and 'double' if there is.
84
90
max_it_interference: int, optional (100)
85
91
The maximum number of iterations used to find the interference between the surfaces, only used if
86
92
a total normal load is specified (Not used if contact is displacement controlled)
@@ -156,6 +162,7 @@ def __init__(self, step_name: str, number_of_steps: int, no_time: bool = False,
156
162
movement_interpolation_mode : str = 'linear' ,
157
163
profile_interpolation_mode : str = 'nearest' ,
158
164
periodic_geometry : bool = False , periodic_axes : tuple = (False , False ),
165
+ periodic_im_repeats : tuple = (1 , 1 ), method : str = 'auto' ,
159
166
max_it_interference : int = 100 , rtol_interference = 1e-3 ,
160
167
max_it_displacement : int = None , rtol_displacement = 1e-5 , no_update_warning : bool = True ,
161
168
upper : float = 4.0 ):
@@ -168,6 +175,7 @@ def __init__(self, step_name: str, number_of_steps: int, no_time: bool = False,
168
175
self .total_time = time_period
169
176
if not no_time and fast_ld :
170
177
raise ValueError ("Cannot have time dependence and fast_ld, either set no_time True or set fast_ld False" )
178
+ self ._periodic_im_repeats = periodic_im_repeats
171
179
self ._fast_ld = fast_ld
172
180
self ._relative_loading = relative_loading
173
181
self .profile_interpolation_mode = profile_interpolation_mode
@@ -178,6 +186,11 @@ def __init__(self, step_name: str, number_of_steps: int, no_time: bool = False,
178
186
self ._max_it_displacement = max_it_displacement
179
187
self ._rtol_displacement = rtol_displacement
180
188
self .number_of_steps = number_of_steps
189
+
190
+ if method not in {'auto' , 'pk' , 'double' }:
191
+ raise ValueError (f"Unrecognised method for step { step_name } : { method } " )
192
+
193
+ self ._method = method
181
194
self ._height_optimisation_func = None
182
195
self ._adhesion = adhesion
183
196
self ._unloading = unloading
@@ -256,7 +269,7 @@ def solve(self, previous_state, output_file):
256
269
else : # displacement controlled
257
270
update_func = self ._solve_displacement_controlled
258
271
259
- relative_time = np .linspace (0 , 1 , self .number_of_steps )
272
+ relative_time = np .linspace (0 , 1 , self .number_of_steps + 1 )[ 1 :]
260
273
just_touching_gap = None
261
274
262
275
original = dict ()
@@ -314,7 +327,7 @@ def solve(self, previous_state, output_file):
314
327
@property
315
328
def upper (self ):
316
329
if self ._upper is None :
317
- self ._upper = np .max (self ._just_touching_gap )* self ._upper_factor
330
+ self ._upper = np .max (self ._just_touching_gap ) * self ._upper_factor
318
331
return self ._upper
319
332
320
333
def update_movement (self , relative_time , original ):
@@ -327,47 +340,53 @@ def update_movement(self, relative_time, original):
327
340
def _solve_load_controlled (self , current_state ) -> dict :
328
341
# if there is time dependence or we don't already have one, make a new height optimiser
329
342
if not self ._no_time or self ._height_optimisation_func is None :
330
- h_opt_func = HeightOptimisationFunction (just_touching_gap = self ._just_touching_gap ,
331
- model = self .model ,
332
- adhesion_model = self ._adhesion_model ,
333
- initial_contact_nodes = self ._initial_contact_nodes ,
334
- max_it_inner = self ._max_it_displacement ,
335
- tol_inner = self ._rtol_displacement ,
336
- material_options = dict (),
337
- max_set_load = self .normal_load ,
338
- tolerance = self ._rtol_interference ,
339
- periodic_axes = self ._periodic_axes )
340
- self ._height_optimisation_func = h_opt_func
343
+ opt_func = HeightOptimisationFunction (just_touching_gap = self ._just_touching_gap ,
344
+ model = self .model ,
345
+ adhesion_model = self ._adhesion_model ,
346
+ initial_contact_nodes = self ._initial_contact_nodes ,
347
+ max_it_inner = self ._max_it_displacement ,
348
+ tol_inner = self ._rtol_displacement ,
349
+ material_options = dict (),
350
+ max_set_load = self .normal_load ,
351
+ tolerance = self ._rtol_interference ,
352
+ periodic_axes = self ._periodic_axes ,
353
+ periodic_im_repeats = self ._periodic_im_repeats )
354
+ self ._height_optimisation_func = opt_func
341
355
else :
342
- h_opt_func = self ._height_optimisation_func
356
+ opt_func = self ._height_optimisation_func
343
357
344
358
if self ._unloading and 'contact_nodes' in current_state :
345
359
contact_nodes = current_state ['contact_nodes' ]
346
360
else :
347
361
contact_nodes = None
348
362
# contact_nodes = np.ones(self._just_touching_gap.shape, dtype=np.bool)
363
+ if self ._method == 'auto' :
364
+ if np .isinf (opt_func .max_pressure ):
365
+ self ._method = 'pk'
366
+ else :
367
+ self ._method = 'double'
349
368
350
- h_opt_func .change_load (self .normal_load , contact_nodes )
351
-
352
- # need to set bounds and pick a sensible starting point
353
- upper = self .upper
354
- print (f'upper bound set at: { upper } ' )
355
- if self ._no_time :
356
- brackets = h_opt_func .get_bounds_from_cache (0 , upper )
369
+ if self ._method == 'pk' :
370
+ opt_func .contact_nodes = None
371
+ opt_func .p_and_k (self .normal_load )
357
372
else :
358
- brackets = (0 , upper )
359
- print (f'Bounds adjusted using cache to: { brackets } ' )
360
- print (f'Interference tolerance set to { self ._rtol_interference } Relative' )
373
+ opt_func .change_load (self .normal_load , contact_nodes )
374
+ # need to set bounds and pick a sensible starting point
375
+ upper = self .upper
376
+ print (f'upper bound set at: { upper } ' )
377
+ if self ._no_time :
378
+ brackets = opt_func .get_bounds_from_cache (0 , upper )
379
+ else :
380
+ brackets = (0 , upper )
381
+ print (f'Bounds adjusted using cache to: { brackets } ' )
382
+ print (f'Interference tolerance set to { self ._rtol_interference } Relative' )
383
+ opt_func .brent (0 , upper , r_tol = self ._rtol_interference , max_iter = self ._max_it_interference )
361
384
362
- opt_result = root_scalar (h_opt_func , bracket = brackets , rtol = self ._rtol_interference ,
363
- maxiter = self ._max_it_interference , args = (current_state ,))
364
385
# noinspection PyProtectedMember
365
- if h_opt_func ._results is None :
366
- h_opt_func ((brackets [0 ]+ brackets [1 ])/ 2 , current_state )
367
- results = h_opt_func .results
368
- results ['interference' ] = opt_result .root
369
- load_conv = (np .abs (results ['total_normal_load' ]- self .normal_load ) / self .normal_load ) < 0.05
370
- results ['converged' ] = bool (load_conv ) and not h_opt_func .last_call_failed
386
+
387
+ results = opt_func .results
388
+ load_conv = (np .abs (results ['total_normal_load' ] - self .normal_load ) / self .normal_load ) < 0.05
389
+ results ['converged' ] = bool (load_conv ) and not opt_func .last_call_failed
371
390
return results
372
391
373
392
def _solve_displacement_controlled (self , current_state ):
@@ -381,7 +400,8 @@ def _solve_displacement_controlled(self, current_state):
381
400
material_options = dict (),
382
401
max_set_load = 1 ,
383
402
tolerance = self ._rtol_interference ,
384
- periodic_axes = self ._periodic_axes )
403
+ periodic_axes = self ._periodic_axes ,
404
+ periodic_im_repeats = self ._periodic_im_repeats )
385
405
self ._height_optimisation_func = h_opt_func
386
406
else :
387
407
h_opt_func = self ._height_optimisation_func
0 commit comments