def _solve(self, current_state: dict, **kwargs) -> dict: span = [ (2 - pa) * s for pa, s in zip(self._periodic_axes, current_state['maximum_tangential_force'].shape) ] if not self._pre_solve_checks or span != self._last_span: self._check(span) self._last_span = span domain = current_state['contact_nodes'] conv_func = plan_convolve(current_state['maximum_tangential_force'], self._im_total, domain, circular=self._periodic_axes) # if the displacements are provided by another sub model or we have a set displacement we just have one set # of bccg iterations: if self.displacement_from_sub_model: displacement = current_state['rigid_body_displacement_' + self.direction] else: displacement = kwargs[f'rigid_body_displacement_{self.direction}'] set_displacement = float(displacement) * np.ones( current_state['maximum_tangential_force'].shape) x0 = self.previous_result if self.previous_result is not None else \ current_state['maximum_tangential_force']/2 min_pressure = np.array( -1 * current_state['maximum_tangential_force'][domain]) loads_in_domain, failed = bccg( conv_func, set_displacement[domain], self._tol, self._max_it, x0[domain], min_pressure, current_state['maximum_tangential_force'][domain]) loads_in_domain = slippy.asnumpy(loads_in_domain) full_loads = np.zeros_like(current_state['maximum_tangential_force']) full_loads[domain] = loads_in_domain stick_nodes = np.logical_and( domain, full_loads < (0.99 * current_state['maximum_tangential_force'])) rtn_dict = dict() rtn_dict['stick_nodes'] = stick_nodes tangential_deformation = slippy.asnumpy( conv_func(loads_in_domain, True)) rtn_dict['loads_' + self.component[0]] = full_loads if 'total_displacement_' + self.component[0] in current_state: rtn_dict['total_displacement_' + self.component[0]] += tangential_deformation else: rtn_dict['total_displacement_' + self.component[0]] = tangential_deformation slip_distance = set_displacement - tangential_deformation slip_distance[stick_nodes] = 0 slip_distance[np.logical_not(domain)] = 0 rtn_dict['slip_distance'] = slip_distance return rtn_dict
def test_elastic_coupled(): mat = core.Elastic('steel_6', {'E': 200e9, 'v': 0.3}) np.random.seed(0) loads1 = np.random.rand(16, 16) loads2 = np.random.rand(16, 16) directions = 'xyzx' for i in range(3): dir_1 = directions[i] dir_2 = directions[i + 1] loads_in_direction = {dir_1: loads1, dir_2: loads2} displacement = mat.displacement_from_surface_loads(loads_in_direction, grid_spacing=0.01, simple=True) loads_calc = mat.loads_from_surface_displacement( displacements=displacement, grid_spacing=0.01, simple=True) for direction in [dir_1, dir_2]: npt.assert_allclose(loads_in_direction[direction], slippy.asnumpy(loads_calc[direction]), atol=0.02) displacement = mat.displacement_from_surface_loads(loads_in_direction, grid_spacing=0.01, simple=False) loads_calc = mat.loads_from_surface_displacement( displacements=displacement, grid_spacing=0.01, simple=False) for direction in [dir_1, dir_2]: npt.assert_allclose(loads_in_direction[direction], slippy.asnumpy(loads_calc[direction]), atol=0.02)
def p_and_k(self, target_load: float, pressure_guess: np.ndarray = None, add_to_cache: bool = True): """ Solve the set contact problem using the Polonsky and Keer algorithm Parameters ---------- target_load: float The target total load pressure_guess: array, optional (None) The initial guess for the pressure solution, if none is supplied the last converged solution is used if there is no last converged solution a flat array (ones) is used. add_to_cache: bool If True the result will be cached Returns ------- None Notes ----- The results from this method can be accessed by accessing the results property of this class, this will automatically fill in displacement for both surfaces and the rigid body interference result. The contact nodes property must be set to None before using this method, this remakes the convolution function. """ if self.max_pressure < np.inf: raise ValueError( "Polonsky and Keer algorithm cannot be used with a maximum pressure" ) if pressure_guess is None: pressure_guess = self._last_converged_loads or np.ones_like( self._just_touching_gap) failed, pressure, gap = polonsky_and_keer( self.conv_func, pressure_guess, self._just_touching_gap, target_load, self._grid_spacing, self._tol, self._max_it) total_displacement = self.conv_func(pressure) self._results = { 'loads_z': pressure, 'total_displacement_z': total_displacement, 'interference': np.mean((slippy.asnumpy(self._just_touching_gap) + slippy.asnumpy(total_displacement))[slippy.asnumpy( pressure > 0)]), 'converged': not failed } if add_to_cache: self.add_to_cache(self._results['interference'], target_load, pressure, failed)
def solve(self, current_state: dict) -> dict: span = current_state['maximum_tangential_force'].shape if not self._pre_solve_checks or span != self._last_span: self._check(span) self._last_span = span domain = current_state['contact_nodes'] conv_func_full = plan_convolve(self._im_total, self._im_total, domain, circular=self._periodic_axes) # if the displacements are provided by another sub model or we have a set displacement we just have one set # of bccg iterations: if not self.load_controlled: if self.update_displacement: set_displacement = self.displacement_upd(current_state['time']) elif self.displacement_from_sub_model: set_displacement = current_state['rigid_body_displacement'] else: set_displacement = self.displacement try: set_displacement = float(set_displacement)*np.ones_like(current_state['maximum_tangential_force']) except TypeError: pass x0 = self.previous_result if self.previous_result is not None else set_displacement/np.sum(self._im_total) loads_in_domain, failed = bccg(conv_func_full, set_displacement[domain], self._tol, self._max_it, x0[domain], 0, current_state['maximum_tangential_force'][domain]) loads_in_domain = slippy.asnumpy(loads_in_domain) full_loads = np.zeros_like(current_state['maximum_tangential_force']) full_loads[domain] = loads_in_domain stick_nodes = np.logical_and(domain, full_loads < (0.99 * current_state['maximum_tangential_force'])) current_state['stick_nodes'] = stick_nodes tangential_deformation = slippy.asnumpy(conv_func_full(loads_in_domain, True)) loads = current_state['loads']._asdict() if 'loads' in current_state else dict() loads[self.component[0]] = full_loads current_state['loads'] = Loads(**loads) td = 'total_displacement' all_displacements = current_state[td]._asdict() if td in current_state else dict() if self.component[0] in all_displacements and all_displacements[self.component[0]] is not None: all_displacements[self.component[0]] += tangential_deformation else: all_displacements[self.component[0]] = tangential_deformation current_state[td] = Displacements(**all_displacements) slip_distance = set_displacement-tangential_deformation slip_distance[stick_nodes] = 0 slip_distance[np.logical_not(domain)] = 0 current_state['slip_distance'] = slip_distance return current_state else: raise NotImplementedError('Load controlled partial slip is not yet implemented')
def test_materials_basic(): # check that one of influence matrix or displacement from loading is given for material in core.materials._IMMaterial._subclass_registry: if material in exceptions: continue try: mat_params = material_parameters[material.material_type] except KeyError: raise AssertionError( f"Material test parameters are not specified, for material {material.material_type}" ) mat_instance = material(**mat_params[0]) max_load = mat_params[1].pop('_max_load', 1) np.random.seed(0) loads = np.random.rand(16, 16) * max_load # check that the loads and displacement functions are inverse of each other for direction in {'x', 'y', 'z'}: load_in_direction = {direction: loads} displacement = mat_instance.displacement_from_surface_loads( load_in_direction, **mat_params[1]) set_disp = displacement[direction] loads_calc = mat_instance.loads_from_surface_displacement( displacements={direction: set_disp}, **mat_params[2]) npt.assert_allclose(loads, slippy.asnumpy(loads_calc[direction]), atol=max_load * 0.02)
def solve(self, current_state: dict) -> dict: if 'converged' in current_state and not current_state['converged']: print( f"SUB MODEL: {self.name}, Solution did not converge, no wear") return current_state if self.no_time: just_touching_gap = current_state['just_touching_gap'] if self.plastic_def_this_step is None or ( 'new_step' in current_state and current_state['new_step']): self.plastic_def_this_step = np.zeros_like(just_touching_gap) # need to sort out the discrepancy between the current just touching gap and the one used for the model gap = (just_touching_gap - current_state['interference'] + current_state['total_displacement_z'] + self.plastic_def_this_step) else: # just use the current just touching gap and interference gap = slippy.asnumpy(current_state['gap']) # just_touching_gap = current_state['just_touching_gap'] # gap = (just_touching_gap - current_state['interference'] + current_state['total_displacement_z']) max_load = min(self.model.surface_1.material.max_load, self.model.surface_2.material.max_load) idx = np.logical_and( current_state['loads_z'] >= max_load, np.logical_and(gap < 0, current_state['contact_nodes'])) total_wear = -gap[idx] if self.no_time: self.plastic_def_this_step[idx] += total_wear if total_wear.size: tpd = np.sum(total_wear) * self.model.surface_1.grid_spacing**2 * ( self.p_surf_1 + self.p_surf_2) else: tpd = np.array(0.0) results = {'total_plastic_deformation': tpd} if self.p_surf_1 > 0: y_pts = current_state['surface_1_points'][0][idx] x_pts = current_state['surface_1_points'][1][idx] surface_1_wear = total_wear * self.p_surf_1 results['wear_plastic_surface_1'] = surface_1_wear self.model.surface_1.wear(self.name, x_pts, y_pts, surface_1_wear) if self.p_surf_2 > 0: y_pts = current_state['surface_2_points'][0][idx] x_pts = current_state['surface_2_points'][1][idx] surface_2_wear = total_wear * self.p_surf_2 results['wear_plastic_surface_2'] = surface_2_wear self.model.surface_2.wear(self.name, x_pts, y_pts, surface_2_wear) print(f"SUB MODEL: {self.name}, total deformation: {tpd}") return results
def __call__(self, height, current_state=None): # If the height guess is in the cache that can be out load guess height = float(height) if height in self.cache_heights: total_load = self.cache_total_load[self.cache_heights.index( height)] print( f"Returning bound value from cache: height: {height:.4}, total_load {total_load:.4}" ) return total_load - self._set_load pressure_initial_guess = self.get_loads_from_cache(height) self.it += 1 # if im mats we can save some time here mostly by not moving data to and from the gpu if self.im_mats: z = -self._just_touching_gap + height if not self.set_contact_nodes: self.contact_nodes = z > 0 # this will remake the conv function as a side effect contact_nodes = self.contact_nodes if not np.any(contact_nodes): print('no contact nodes') total_load = 0 full_loads = np.zeros(contact_nodes.shape) failed = False else: if pressure_initial_guess is None: pressure_initial_guess = np.zeros(z.shape) z_in = z[contact_nodes] pressure_guess_in = pressure_initial_guess[contact_nodes] loads_in_domain, failed = bccg(self.conv_func, z_in, self._tol, self._max_it, pressure_guess_in, 0, self.max_pressure) loads_in_domain = slippy.asnumpy(loads_in_domain) self._results = { 'loads_in_domain': loads_in_domain, 'domain': self.contact_nodes, 'interference': height } total_load = float( np.sum(loads_in_domain) * self._grid_spacing**2) if not failed: full_loads = np.zeros(contact_nodes.shape) full_loads[contact_nodes] = loads_in_domain self._last_converged_loads = full_loads else: full_loads = None self.add_to_cache(height, total_load, full_loads, failed) if failed: # noinspection PyUnboundLocalVariable print( f'Failed: total load: {total_load}, height {height}, max_load {np.max(loads_in_domain)}' ) self.last_call_failed = True else: print( f'Solved: interference: {height}\tTotal load: {total_load}\tTarget load: {self._set_load}' ) self.last_call_failed = False return total_load - self._set_load # else use the basic form loads, total_disp, disp_1, disp_2, contact_nodes, failed = \ solve_normal_interference(height, gap=self._just_touching_gap, model=self._model, current_state=current_state, adhesive_pressure=self._adhesion_model, contact_nodes=self.contact_nodes, max_iter=self._max_it, material_options=self._material_options, tol=self._tol, initial_guess_loads=pressure_initial_guess) self._results['loads_z'] = loads self._results['total_displacement'] = total_disp self._results['surface_1_displacement'] = disp_1 self._results['surface_2_displacement'] = disp_2 self._results['contact_nodes'] = contact_nodes self._results['interference'] = height total_load = np.sum(loads.flatten()) * self._grid_spacing**2 self._results['total_normal_load'] = total_load if failed: self.last_call_failed = True print( f'Failed: total load: {total_load}, height {height}, max_load {np.max(loads.flatten())}' ) else: self.last_call_failed = False print( f'Interference is: {height}\tTotal load is: {total_load}\tTarget load is: {self._set_load}' ) self.add_to_cache(height, total_load, loads, failed) return total_load - self._set_load
def rey(self, target_load: float = None, target_mean_gap: float = None, add_to_cache: bool = True): """ Solve the contact problem using the Rey algorithm Parameters ---------- target_load: float The target total load target_mean_gap: float The target mean gap add_to_cache: bool If True the result will be cached Returns ------- None Notes ----- The results from this method can be accessed by accessing the results property of this class, this will automatically fill in displacement for both surfaces and the rigid body interference result. The contact nodes property must be set to None before using this method, this remakes the convolution function. """ if not self._zero_frequency_zero: raise ValueError( "Rey solver requires a zero frequency value of 0, set in material definitions" ) if not all(self._periodic_axes): raise ValueError( "Rey solver requires fully periodic contact, set periodic axes to True in step definition" ) if self.max_pressure < np.inf: raise ValueError( "Rey algorithm cannot be used with a maximum pressure") if target_load is None: target_mean_pressure = None else: target_mean_pressure = target_load / ( self._just_touching_gap.size * self._grid_spacing**2) failed, pressure, gap, total_displacement, contact_nodes = rey( self._just_touching_gap, self.conv_func, self._adhesion_model, self._tol, self._max_it, target_mean_gap, target_mean_pressure) self._results = { 'loads_z': pressure, 'total_displacement_z': total_displacement, 'interference': np.mean((slippy.asnumpy(self._just_touching_gap) + slippy.asnumpy(total_displacement))[slippy.asnumpy( pressure > 0)]), 'converged': not failed, 'gap': gap, 'contact_nodes': contact_nodes } if add_to_cache: self.add_to_cache(self._results['interference'], target_load, pressure, failed)
def results(self): if self._results is None: print("No results found in height opt func") if slippy.CUDA: xp = cp else: xp = np if self.im_mats and 'surface_1_displacement' not in self._results: # need to put the loads into an np array of right shape # find the full displacements (and convert to np array) # find disp on surface 1 and surface 2 surf_1 = self._model.surface_1 surf_2 = self._model.surface_2 span = tuple([ s * (2 - pa) for s, pa in zip(self._just_touching_gap.shape, self._periodic_axes) ]) # noinspection PyUnresolvedReferences im1 = surf_1.material.influence_matrix( span=span, grid_spacing=[surf_1.grid_spacing] * 2, components=['zz'])['zz'] # noinspection PyUnresolvedReferences im2 = surf_2.material.influence_matrix( span=span, grid_spacing=[surf_1.grid_spacing] * 2, components=['zz'])['zz'] if 'domain' in self._results and 'loads_in_domain' in self._results: full_loads = xp.zeros(self._just_touching_gap.shape) full_loads[ self._results['domain']] = self._results['loads_in_domain'] full_disp = slippy.asnumpy( self.conv_func(self._results['loads_in_domain'], ignore_domain=True)) full_loads = slippy.asnumpy(full_loads) elif 'loads_z' in self._results: full_loads = slippy.asnumpy(self._results['loads_z']) full_disp = slippy.asnumpy( self._results['total_displacement_z']) else: raise ValueError("Results not properly set") conv_func_1 = plan_convolve(full_loads, im1, None, circular=self._periodic_axes) conv_func_2 = plan_convolve(full_loads, im2, None, circular=self._periodic_axes) disp_1 = conv_func_1(full_loads) disp_2 = conv_func_2(full_loads) if slippy.CUDA: disp_1, disp_2 = xp.asnumpy(disp_1), xp.asnumpy(disp_2) full_loads = xp.asnumpy(full_loads) total_load = float(np.sum(full_loads) * self._grid_spacing**2) if 'contact_nodes' in self._results: contact_nodes = self._results['contact_nodes'] else: contact_nodes = full_loads > 0 if 'gap' in self._results: gap = self._results['gap'] else: gap = self._just_touching_gap - self._results[ 'interference'] + full_disp results = { 'loads_z': full_loads, 'total_displacement_z': full_disp, 'surface_1_displacement_z': disp_1, 'surface_2_displacement_z': disp_2, 'contact_nodes': contact_nodes, 'total_normal_load': total_load, 'interference': self._results['interference'], 'gap': gap } return results else: return self._results
def __init__(self, just_touching_gap: np.ndarray, model: _ContactModelABC, adhesion_model: _AdhesionModelABC, initial_contact_nodes: np.ndarray, max_it: int, rtol: float, material_options: typing.Union[typing.Sequence[dict], dict], max_set_load: float, rtol_outer: float = 0, max_it_outer: int = 0, use_cache: bool = True, cache_loads=True, periodic_axes: typing.Tuple[bool] = (False, False)): if slippy.CUDA: xp = cp cache_loads = False else: xp = np self._grid_spacing = model.surface_1.grid_spacing self._just_touching_gap = np.asarray(just_touching_gap) self._model = model self._adhesion_model = adhesion_model self.initial_contact_nodes = initial_contact_nodes self._max_it = max_it self._tol = rtol self._max_it_outer = max_it_outer self._tol_outer = rtol_outer self._material_options = material_options self._original_set_load = max_set_load self._set_load = float(max_set_load) self._periodic_axes = periodic_axes self.cache_heights = [0.0] self.cache_total_load = [0.0] self.cache_surface_loads = [xp.zeros(just_touching_gap.shape)] self.it = 0 self.use_cache = use_cache self.use_loads_cache = cache_loads surf_1 = model.surface_1 surf_2 = model.surface_2 self.im_mats = False self.conv_func: ConvolutionFunction = None self.cache_max = False self.last_call_failed = False self._last_converged_loads = None if isinstance(surf_1.material, _IMMaterial) and isinstance( surf_2.material, _IMMaterial): self.im_mats = True self._zero_frequency_zero = ( surf_1.material.zero_frequency_value == 0 and surf_2.material.zero_frequency_value == 0) span = tuple([ s * (2 - pa) for s, pa in zip(just_touching_gap.shape, periodic_axes) ]) max_pressure = min( [surf_1.material.max_load, surf_2.material.max_load]) self.max_pressure = max_pressure im1 = surf_1.material.influence_matrix( components=['zz'], grid_spacing=[surf_1.grid_spacing] * 2, span=span)['zz'] im2 = surf_2.material.influence_matrix( components=['zz'], grid_spacing=[surf_1.grid_spacing] * 2, span=span)['zz'] total_im = im1 + im2 self.total_im = xp.asarray(total_im) self.contact_nodes = initial_contact_nodes if use_cache and max_pressure != np.inf: max_loads = max_pressure * xp.ones(just_touching_gap.shape) self.cache_total_load.append(max_pressure * just_touching_gap.size * surf_1.grid_spacing**2) max_elastic_disp = self.conv_func(max_loads) self.cache_heights.append( xp.max(max_elastic_disp + xp.asarray(just_touching_gap))) if cache_loads: self.cache_surface_loads.append(slippy.asnumpy(max_loads)) else: self.contact_nodes = initial_contact_nodes