Skip to content

Commit d6dbf5a

Browse files
committed
GPU rolling sliding solver added
1 parent 2276b96 commit d6dbf5a

40 files changed

+1948
-368
lines changed

examples/Quasi Static Steps - Normal contact with movement.ipynb

Lines changed: 64 additions & 4 deletions
Large diffs are not rendered by default.

examples/Recreating the hertz solution numerically (normal contact).ipynb

Lines changed: 10 additions & 10 deletions
Large diffs are not rendered by default.

slippy/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import multiprocessing
22
import os
3+
import numpy
34
"""Top-level package for SlipPY."""
45

56
__author__ = """Michael Watson"""
@@ -9,19 +10,25 @@
910
try:
1011
import cupy # noqa: F401
1112
CUDA = True
12-
asnumpy = cupy.asnumpy
1313
xp = cupy
1414
except ImportError:
1515
CUDA = False
16-
import numpy
17-
asnumpy = numpy.asarray
1816
xp = numpy
1917

18+
19+
def asnumpy(obj):
20+
if CUDA:
21+
return cupy.asnumpy(obj)
22+
return numpy.asarray(obj)
23+
24+
2025
CORES = multiprocessing.cpu_count()
2126
OUTPUT_DIR = os.getcwd()
2227
ERROR_IF_MISSING_MODEL = True
2328
ERROR_IF_MISSING_SUB_MODEL = True
2429
ERROR_IN_DATA_CHECK = True
30+
dtype = 'float64'
31+
material_names = []
2532

2633

2734
class OverRideCuda:

slippy/contact/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
from .lubrication_steps import IterSemiSystem
117117
from .models import ContactModel
118118
from .static_step import StaticStep
119-
# from .steps import InitialStep
119+
from .steps import _ModelStep, RepeatingStateStep
120120
from .unified_reynolds_solver import UnifiedReynoldsSolver
121121
from .quasi_static_step import QuasiStaticStep
122122
from . import sub_models
@@ -125,5 +125,5 @@
125125
'lubricant_models', 'IterSemiSystem', 'Elastic', 'Rigid', 'rigid', 'elastic_influence_matrix',
126126
'ContactModel', 'OutputRequest', 'OutputReader', 'OutputSaver', 'read_output',
127127
'StaticStep', 'UnifiedReynoldsSolver', 'sub_models', 'QuasiStaticStep', 'sub_models',
128-
'guess_loads_from_displacement', 'bccg', 'plan_convolve', 'plan_multi_convolve'
129-
]
128+
'guess_loads_from_displacement', 'bccg', 'plan_convolve', 'plan_multi_convolve', '_ModelStep',
129+
'RepeatingStateStep']

slippy/contact/_step_utils.py

Lines changed: 278 additions & 39 deletions
Large diffs are not rendered by default.

slippy/contact/hertz.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,6 @@ def hertz_full(r1: typing.Union[typing.Sequence, float], r2: typing.Union[typing
646646
zeta = [0 if v1 <= 0.1938 else 0.223 + 2.321 * v1 - 2.397 * v1 ** 2 for v1 in v]
647647
results['max_von_mises_stress'] = [c1 * p0 for c1 in c]
648648
results['max_von_mises_depth'] = [a * zeta1 for zeta1 in zeta]
649-
results['total_deflection'] = 1 / np.pi / e_star * load * (np.log(4 * np.pi * e_star * r / load) - 1)
650649

651650
# The following is taken from deeg
652651

slippy/contact/lubrication_steps.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class IterSemiSystem(_ModelStep):
8585
periodic in that direction. NOTE: this only controls the deformation result from the materials, ensure that the
8686
reynolds equation solver used supports periodic solutions and is set to produce a periodic solution to the
8787
pressure equation. Additionally, ensure that the axes of periodicity match.
88+
periodic_im_repeats: tuple, optional (1,1)
89+
The number of times the influence matrix should be wrapped along periodic dimensions, only used if at least one
90+
of periodic axes is True. This is necessary to ensure truly periodic behaviour, no physical limit exists
8891
max_it_pressure: int, optional (100)
8992
The maximum number of iterations in the fluid pressure calculation loop
9093
rtol_pressure: float, optional (1e-7)
@@ -203,6 +206,7 @@ def __init__(self, step_name: str, reynolds_solver: _NonDimensionalReynoldSolver
203206
movement_interpolation_mode: str = 'linear',
204207
profile_interpolation_mode: str = 'nearest',
205208
periodic_geometry: bool = False, periodic_axes: tuple = (False, False),
209+
periodic_im_repeats: tuple = (1, 1),
206210
max_it_pressure: int = 5000, rtol_pressure: float = 2e-6,
207211
max_it_interference: int = 5000,
208212
rtol_interference: float = 1e-4,
@@ -218,7 +222,7 @@ def __init__(self, step_name: str, reynolds_solver: _NonDimensionalReynoldSolver
218222
self.profile_interpolation_mode = profile_interpolation_mode
219223
self._periodic_profile = periodic_geometry
220224
self._periodic_axes = periodic_axes
221-
225+
self._periodic_im_repeats = periodic_im_repeats
222226
self._max_it_pressure = max_it_pressure
223227
self._max_it_interference = max_it_interference
224228
self._rtol_pressure = rtol_pressure
@@ -367,7 +371,7 @@ def solve(self, previous_state: dict, output_file):
367371
for s in self.sub_models:
368372
s.no_time = self._no_time
369373

370-
relative_time = np.linspace(0, 1, self.number_of_steps)
374+
relative_time = np.linspace(0, 1, self.number_of_steps+1)[1:]
371375
just_touching_gap = None
372376

373377
original = dict()
@@ -396,14 +400,14 @@ def solve(self, previous_state: dict, output_file):
396400

397401
# make a new loads function if we need it
398402
if (previous_gap_shape is None or previous_gap_shape != just_touching_gap.shape) and im_mats:
399-
span = just_touching_gap.shape
403+
span = tuple([s*(2-pa) for s, pa in zip(just_touching_gap.shape, self._periodic_axes)])
400404
max_pressure = self.reynolds.dimensionalise_pressure(min([surf_1_material.max_load,
401405
surf_2_material.max_load]), True)
402406
self._nd_max_pressure = max_pressure
403-
im1 = surf_1_material.influence_matrix(span=span, grid_spacing=[gs] * 2,
404-
components=['zz'])['zz']
405-
im2 = surf_2_material.influence_matrix(span=span, grid_spacing=[gs] * 2,
406-
components=['zz'])['zz']
407+
im1 = surf_1_material.influence_matrix(components=['zz'], grid_spacing=[gs] * 2,
408+
span=span, periodic_strides=self._periodic_im_repeats)['zz']
409+
im2 = surf_2_material.influence_matrix(components=['zz'], grid_spacing=[gs] * 2, span=span,
410+
periodic_strides=self._periodic_im_repeats)['zz']
407411
total_im = im1 + im2
408412
loads_func = plan_convolve(just_touching_gap, total_im, circular=self._periodic_axes)
409413
previous_gap_shape = just_touching_gap.shape

slippy/contact/models.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ def __init__(self, name: str, surface_1: _SurfaceABC, surface_2: _SurfaceABC = N
7171
if lubricant is not None:
7272
self.lubricant_model = lubricant
7373
self.steps = OrderedDict({'Initial': InitialStep()})
74+
self.current_step = None
75+
self.current_step_start_time = None
7476
if output_dir is not None:
7577
if not os.path.isdir(output_dir):
7678
try:
@@ -207,7 +209,7 @@ def solve(self, verbose: bool = False, skip_data_check: bool = False):
207209
if os.path.exists(self.log_file_name):
208210
os.remove(self.log_file_name)
209211

210-
current_state = None
212+
current_state = {'time': 0.0}
211213

212214
with ExitStack() as stack:
213215
output_writer = stack.enter_context(OutputSaver(self.name))
@@ -222,6 +224,8 @@ def solve(self, verbose: bool = False, skip_data_check: bool = False):
222224

223225
for this_step in self.steps:
224226
print(f"Solving step {this_step}")
227+
self.current_step = self.steps[this_step]
228+
self.current_step_start_time = current_state['time']
225229
for output in self.steps[this_step].outputs:
226230
output.new_step(current_state['time'])
227231
current_state = self.steps[this_step].solve(current_state, output_writer)

slippy/contact/quasi_static_step.py

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import typing
44
import warnings
55

6-
from scipy.optimize import root_scalar
7-
86
from .steps import _ModelStep
97
from ._model_utils import get_gap_from_model
108
from ._step_utils import HeightOptimisationFunction, make_interpolation_func
@@ -81,6 +79,14 @@ class QuasiStaticStep(_ModelStep):
8179
periodic_axes: tuple, optional ((False, False))
8280
For each True value the corresponding axis will be solved by circular convolution, meaning the result is
8381
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.
8490
max_it_interference: int, optional (100)
8591
The maximum number of iterations used to find the interference between the surfaces, only used if
8692
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,
156162
movement_interpolation_mode: str = 'linear',
157163
profile_interpolation_mode: str = 'nearest',
158164
periodic_geometry: bool = False, periodic_axes: tuple = (False, False),
165+
periodic_im_repeats: tuple = (1, 1), method: str = 'auto',
159166
max_it_interference: int = 100, rtol_interference=1e-3,
160167
max_it_displacement: int = None, rtol_displacement=1e-5, no_update_warning: bool = True,
161168
upper: float = 4.0):
@@ -168,6 +175,7 @@ def __init__(self, step_name: str, number_of_steps: int, no_time: bool = False,
168175
self.total_time = time_period
169176
if not no_time and fast_ld:
170177
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
171179
self._fast_ld = fast_ld
172180
self._relative_loading = relative_loading
173181
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,
178186
self._max_it_displacement = max_it_displacement
179187
self._rtol_displacement = rtol_displacement
180188
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
181194
self._height_optimisation_func = None
182195
self._adhesion = adhesion
183196
self._unloading = unloading
@@ -256,7 +269,7 @@ def solve(self, previous_state, output_file):
256269
else: # displacement controlled
257270
update_func = self._solve_displacement_controlled
258271

259-
relative_time = np.linspace(0, 1, self.number_of_steps)
272+
relative_time = np.linspace(0, 1, self.number_of_steps + 1)[1:]
260273
just_touching_gap = None
261274

262275
original = dict()
@@ -314,7 +327,7 @@ def solve(self, previous_state, output_file):
314327
@property
315328
def upper(self):
316329
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
318331
return self._upper
319332

320333
def update_movement(self, relative_time, original):
@@ -327,47 +340,53 @@ def update_movement(self, relative_time, original):
327340
def _solve_load_controlled(self, current_state) -> dict:
328341
# if there is time dependence or we don't already have one, make a new height optimiser
329342
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
341355
else:
342-
h_opt_func = self._height_optimisation_func
356+
opt_func = self._height_optimisation_func
343357

344358
if self._unloading and 'contact_nodes' in current_state:
345359
contact_nodes = current_state['contact_nodes']
346360
else:
347361
contact_nodes = None
348362
# 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'
349368

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)
357372
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)
361384

362-
opt_result = root_scalar(h_opt_func, bracket=brackets, rtol=self._rtol_interference,
363-
maxiter=self._max_it_interference, args=(current_state,))
364385
# 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
371390
return results
372391

373392
def _solve_displacement_controlled(self, current_state):
@@ -381,7 +400,8 @@ def _solve_displacement_controlled(self, current_state):
381400
material_options=dict(),
382401
max_set_load=1,
383402
tolerance=self._rtol_interference,
384-
periodic_axes=self._periodic_axes)
403+
periodic_axes=self._periodic_axes,
404+
periodic_im_repeats=self._periodic_im_repeats)
385405
self._height_optimisation_func = h_opt_func
386406
else:
387407
h_opt_func = self._height_optimisation_func

0 commit comments

Comments
 (0)