# coding: utf-8
from time import time
import numpy as np
from typing import Callable, Dict, Optional
import logging
from .meta_block import Block
def plastic(v: float, yield_strain: float = .005, rate: float = .02) -> float:
"""A basic plastic law given as an example."""
if v > yield_strain:
return rate * ((((v - yield_strain) / rate) ** 2 + 1) ** .5 - 1)
return 0
[docs]
class FakeMachine(Block):
"""This Block emulates the behavior of a tensile test machine.
It can emulate tensile tests, **not compression tests**. By default, it
assumes an elasto-plastic behavior of the tested sample. The main mechanical
parameters of the material are tunable.
This Block is meant to be driven like a :class:`~crappy.blocks.Machine`
Block. It receives speed or position commands from upstream Blocks, and
modifies the behavior of the emulated machine accordingly. Its outputs are
however different from the Machine Blocks, as it outputs the current force,
position, and strain of the emulated tensile test machine. The labels
carrying this data are : ``t(s), F(N), x(mm), Exx(%), Eyy(%)``.
This Block was originally designed for proposing examples that do not require
any hardware to run, but still display the possibilities of Crappy. It can
also be used to test a script without actually interacting with hardware.
.. versionadded:: 1.4.0
.. versionchanged:: 2.0.0 renamed from *Fake_machine* to *FakeMachine*
"""
[docs]
def __init__(self,
rigidity: float = 8.4E6,
l0: float = 200,
max_strain: float = 1.51,
sigma: Optional[Dict[str, float]] = None,
nu: float = 0.3,
plastic_law: Callable[[float], float] = plastic,
max_speed: float = 5,
mode: str = 'speed',
cmd_label: str = 'cmd',
freq: Optional[float] = 100,
display_freq: bool = False,
debug: Optional[bool] = False) -> None:
"""Sets the arguments and initializes the parent class.
Args:
rigidity: The rigidity of the material, in N, so that
:math:`force = rigidity * strain`.
.. versionchanged:: 2.0.0 renamed from *k* to *rigidity*
l0: The initial length of the fake sample to test, in mm.
max_strain: The maximum strain the material can withstand before
breaking.
.. versionchanged:: 1.5.10 renamed from *maxstrain* to *max_strain*
mode: Whether the command sent to the fake machine is a speed or a
position command. Can be ``'speed'`` or ``'position'``.
plastic_law: A callable taking the maximum reached strain and returning
the proportion of the current strain caused by plastic deformation.
sigma: A :obj:`dict` containing for each label the standard deviation for
adding noise to the signal. Can be given for part or all of the labels.
The deviation should be given not normalized, in the same unit as the
label to which it applies.
nu: Poisson's ratio of the material.
cmd_label: The label carrying the command of the fake machine.
freq: The target looping frequency for the Block. If :obj:`None`, loops
as fast as possible.
.. versionadded:: 1.5.10
display_freq: If :obj:`True`, displays the looping frequency of the
Block.
.. versionadded:: 1.5.10
.. versionchanged:: 2.0.0 renamed from *verbose* to *display_freq*
debug: If :obj:`True`, displays all the log messages including the
:obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log
messages with :obj:`~logging.INFO` level or higher. If :obj:`None`,
disables logging for this Block.
.. versionadded:: 2.0.0
"""
super().__init__()
self.freq = freq
self.display_freq = display_freq
self.debug = debug
# Setting the mechanical parameters of the material
self._rigidity = rigidity
self._l0 = l0
self._max_strain = max_strain / 100
self._nu = nu
self._max_speed = max_speed
self._sigma = {'F(N)': 50, 'x(mm)': 2e-3,
'Exx(%)': 1e-3, 'Eyy(%)': 1e-3} if sigma is None else sigma
self._plastic_law = plastic_law
self._mode = mode
self._cmd_label = cmd_label
# Creating the mechanical variables
self._current_pos = 0
self._prev_t = None
self._prev_broke_t = time()
self._plastic_elongation = 0
self._max_recorded_strain = 0
[docs]
def begin(self) -> None:
"""Sends a first value that should be 0."""
self._prev_t = self.t0
self._send_values()
[docs]
def loop(self) -> None:
"""Receives the latest command value, calculates the new speed and position
from it, checks whether the sample broke and what the plastic elongation
is, and finally returns the data."""
# Getting the latest command
data = self.recv_last_data(fill_missing=True)
if self._cmd_label not in data:
return
else:
cmd = data[self._cmd_label]
t = time()
delta_t = t - self._prev_t
self._prev_t = t
# Calculating the speed based on the command and the mode
if self._mode == 'speed':
speed = np.sign(cmd) * np.min((self._max_speed, np.abs(cmd)))
elif self._mode == 'position':
speed = np.sign(cmd - self._current_pos) * np.min(
(self._max_speed, np.abs(cmd - self._current_pos) / delta_t))
else:
raise ValueError(f'Invalid mode : {self._mode} !')
# Updating the current position
self._current_pos += speed * delta_t
# If the max strain is reached, consider that the sample broke
if self._current_pos / self._l0 > self._max_strain:
if time() - self._prev_broke_t > 1:
self._prev_broke_t = time()
self.log(logging.WARNING, "Sample broke !")
self._rigidity = 0
# Compute the plastic elongation separately
if self._current_pos / self._l0 > self._max_recorded_strain:
self._max_recorded_strain = self._current_pos / self._l0
self._plastic_elongation = self._plastic_law(
self._max_recorded_strain) * self._l0
# Finally, sending the values
self._send_values()
def _add_noise(self, to_send: Dict[str, float]) -> Dict[str, float]:
"""Adds noise to the data to be sent, according to the sigma values
provided by the user. Then returns the noised data."""
for label, value in to_send.items():
if label in self._sigma:
to_send[label] = np.random.normal(value, self._sigma[label])
return to_send
def _send_values(self) -> None:
"""Gathers all the information to be sent, adds noise and send it."""
to_send = {'t(s)': time() - self.t0,
'F(N)': (self._current_pos -
self._plastic_elongation) / self._l0 * self._rigidity,
'x(mm)': self._current_pos,
'Exx(%)': self._current_pos * 100 / self._l0,
'Eyy(%)': -self._nu * self._current_pos * 100 / self._l0}
self.send(self._add_noise(to_send))