예제 #1
0
    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)
예제 #3
0
    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)
예제 #4
0
    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)
예제 #6
0
    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
예제 #7
0
    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
예제 #8
0
    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)
예제 #9
0
    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
예제 #10
0
    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