Transmission efficiency

Tendons, pulleys and gears pass on less torque than a motor makes. A robot’s efficiency maps the torque commanded to each motor to the torque the motor delivers. This page explains why it is 1 by default, when it matters, and how to model and identify it.

Why the default is 1

Identified from the commanded torques \(u\), a robot’s inertia, damping and stiffness already include its transmission:

\[ M \ddot q + D \dot q + K q = B u . \]

The physical values are \(\eta M\), \(\eta D\) and \(\eta K\), and the robot receives \(\eta B u\). That is the same equation multiplied by \(\eta\), so the motion is the same. The templates’ stiffness and damping were identified this way. So every template has the efficiency 1: it receives each torque as the controller sends it. Controllers never divide their torques by the efficiency, on the real robot as in simulation.

When it matters: forces on the surroundings

An external force \(f\) is physical, so it does not scale with the transmission. In the same equation it enters divided by the efficiency:

\[ M \ddot q + D \dot q + K q = B u + J^\top f / \eta . \]

Estimating a contact force, or pressing with a chosen one, therefore needs \(\eta\). A virtual spring of stiffness \(K_v\) pressing on an object pushes with \(\eta K_v\) times its stretch, less what holds the robot bent. The calibrated values are in the templates, such as helyx.EFFICIENCY and adapt.MOTOR_EFFICIENCY, and are never the default.

A polynomial of the commanded torque

A constant \(\eta\) is the simplest model. vmc.Efficiency(c1, c2, ...) gives each motor’s delivered torque as a polynomial of its commanded torque,

\[ \tau = c_1 u + c_2 u^2 + \dots + c_n u^n , \]

with each coefficient one value for every motor or one per motor. A number passed as efficiency= is the linear coefficient \(c_1\). vmc.Efficiency(1.0) is the default, and Efficiency(..., friction=...) adds the static friction of the motors (Static friction). The coefficients are Params, to change, tune or identify:

import numpy as np
import virtualmodelcontrol as vmc
from virtualmodelcontrol.robots import helyx

cubic = vmc.Efficiency(0.10, 0.0, 0.40)  # made-up numbers, for all motors
arm = helyx.arm(efficiency=cubic)
arm.params["efficiency.c3"]
Param('c3', 0.4, unit='(N*m)^-2', scope='design')

A robot with an efficiency other than 1 receives the delivered torques, so its stiffness, damping and masses must be the physical ones, referred to the delivered torque: the values identified from the commanded torques, times \(\eta\), as above. In a simulator, vmc.sim.ModelPlant(robot, runtime=["*efficiency*"]) keeps the coefficients live.

Identify it from static measurements

The efficiency is measured in static conditions: hold each command until the robot settles, and measure what the motors deliver, for example with a load cell. plateaus finds the settled end of every hold in a log. fit_efficiency fits the coefficients by least squares through the origin: no command, no torque. Here a motor delivers less and less of its torque as the torque grows, and a log holds ten steps of 20 s:

from virtualmodelcontrol.identification import fit_efficiency, plateaus

truth = vmc.Efficiency(0.15, 0.0, -0.04)  # what the motor delivers
t = np.arange(0.0, 200.0, 0.01)  # [s], 100 Hz
command = np.repeat(np.linspace(0.1, 1.0, 10), t.size // 10)  # [N·m]
delivered, level = np.empty_like(t), 0.0
for i, goal in enumerate(truth(command)):  # time constant 1.5 s
    level += (goal - level) * 0.01 / 1.5
    delivered[i] = level
noise = np.random.default_rng(0).normal(0.0, 0.002, t.size)
measured = delivered + noise  # [N·m]

holds = plateaus(t, command, tolerance=1e-6)  # settled half of each hold
u = np.array([command[h].mean() for h in holds])
y = np.array([measured[h].mean() for h in holds])
linear = fit_efficiency(u, y)
cubic_fit = fit_efficiency(u, y, degree=3)
../_images/2e8e0c825eef34695249eb6a2f0fd03f560be4789bd4348a536b85493c727fef.svg

The linear fit gives a constant efficiency of 0.124 (\(R^2\) = 0.949) and misses the bend; the cubic one follows it (\(R^2\) = 1.0000, \(c_3\) = -0.040 against \(-0.04\)).

When one quantity is measured for several motors, such as a fingertip force along a direction, fit_efficiency(commanded, force, weights=w) takes the weights that map each motor’s delivered torque to it. They can come, for example, from the robot’s Jacobian. The finger’s adapt.MOTOR_EFFICIENCY comes from such a fit. With shared=True all motors get one polynomial.