A robot package: trust-aware hand-over#

This tutorial builds one robotics use case end to end, as a separate installable package: tutorials/trust_handover_package (pip install -e tutorials/trust_handover_package). A robot arm hands objects to a person and decides, before each hand-over, whether to hand over, hand over slowly, ask “Are you ready?” or wait. Its model of the person is Quantum Mind’s quantum-like trust model. The package adds populations of simulated people, a benchmark, a PyBullet simulation recorded as a GIF, a ROS 2 node and its own tests. All people are simulated.

The trust-aware hand-over was chosen among the robotics models because it is unique to Quantum Mind, quantum-like (no quantum computer is needed), a physical decision a robot takes many times a shift, and it has a classical counterpart to compare with: the same decision rule on a Markov trust model.

        flowchart LR
    accTitle: Structure of the quantum-handover package
    accDescr: Simulated people and the robot's trust model feed the decision rule; the decision drives the PyBullet arm or a real robot through ROS 2; outcomes and answers update the trust model.
    T["trust model<br/>quantum-like or Markov"]:::hum --> D["decision<br/>hand over · slow · ask · wait"]:::op
    D --> S["PyBullet arm<br/>(simulation)"]:::out
    D --> R["ROS 2 node<br/>/handover/decision"]:::out
    S & R -->|"taken · dropped · yes · no"| T
    P["simulated people<br/>two populations"]:::in --> S
    classDef in fill:#0b2a4a,stroke:#79c0ff,color:#e6edf3
    classDef op fill:#2a1b3d,stroke:#d2a8ff,color:#e6edf3
    classDef out fill:#0f2e1a,stroke:#7ee787,color:#e6edf3
    classDef hum fill:#3d1414,stroke:#ff7b72,color:#e6edf3
    
import sys, json
sys.path.insert(0, 'tutorials/trust_handover_package/src')     # or: pip install -e tutorials/trust_handover_package
import numpy as np
import matplotlib.pyplot as plt
from quantum_mind.viz import use_mpl_style
from quantum_handover import POLICIES, make_policy, run_benchmark, to_markdown
from quantum_handover.ros2_node import HandoverController
theme = use_mpl_style('dark')

1. One decision#

With the population-average trust model, a new person’s P(trust) is about 0.56. Handing over normally would risk a drop, so the policy compares the expected cost of every action.

d = make_policy('quantum-like').decide()
print('P(trust) = %.2f, best action: %s' % (d['p_trust'], d['action']))
for action, cost in sorted(d['costs'].items(), key=lambda kv: kv[1]):
    print('  %-14s expected cost %.2f' % (action, cost))
P(trust) = 0.56, best action: ask
  ask            expected cost 3.14
  slow_handover  expected cost 3.20
  wait           expected cost 3.50
  handover       expected cost 4.40

2. The benchmark#

Every policy meets 300 simulated people from each of two populations: people whose trust follows the quantum-like model and people whose trust follows the Markov model. The robot only knows population averages, never the person in front of it.

result = run_benchmark(n_people=300, n_steps=20, seed=0)
print(to_markdown(result))
| Simulated people | Policy | Mean session cost | Failures | Questions |
|---|---|---|---|---|
| quantum-like people | quantum-like | **39.8 ± 1.5** | 2.85 | 7.2 |
| quantum-like people | markov | 56.9 ± 1.0 | 4.73 | 4.6 |
| quantum-like people | always hand over | 93.9 ± 1.4 | 9.39 | 0.0 |
| quantum-like people | always slow | 68.9 ± 1.1 | 4.89 | 0.0 |
| Markov people | quantum-like | 27.8 ± 1.9 | 1.88 | 8.2 |
| Markov people | markov | **22.1 ± 1.9** | 1.71 | 1.9 |
| Markov people | always hand over | 104.2 ± 4.8 | 10.42 | 0.0 |
| Markov people | always slow | 41.0 ± 1.6 | 2.10 | 0.0 |
fig, ax = plt.subplots(figsize=(8, 3.6))
pops = list(result)
width = 0.2
for k, name in enumerate(POLICIES):
    means = [result[p][name]['cost'] for p in pops]
    errs = [result[p][name]['se'] for p in pops]
    ax.bar(np.arange(len(pops)) + (k - 1.5) * width, means, width, yerr=errs, label=name, color=theme['palette'][k])
ax.set_xticks(range(len(pops)), pops)
ax.set_ylabel('mean session cost (lower is better)')
ax.set_title('Hand-over policies against two kinds of simulated people')
ax.legend(fontsize=8, ncol=2)
plt.show()
Figure: Hand-over policies against two kinds of simulated people

The statements below are checked by this cell every time the documentation is built:

ql, mk = result['quantum-like people'], result['Markov people']
fixed = ('always hand over', 'always slow')
for rows in (ql, mk):                                      # model-based beats fixed, for both kinds of people
    assert max(rows['quantum-like']['cost'], rows['markov']['cost']) < min(rows[f]['cost'] for f in fixed)
assert ql['quantum-like']['cost'] < ql['markov']['cost']    # each model wins on its own kind of people
assert mk['markov']['cost'] < mk['quantum-like']['cost']
regret_ql = mk['quantum-like']['cost'] - mk['markov']['cost']
regret_mk = ql['markov']['cost'] - ql['quantum-like']['cost']
assert regret_ql < regret_mk                               # the quantum-like policy loses less when wrong
print('loss when the model is wrong: quantum-like %.1f, Markov %.1f' % (regret_ql, regret_mk))
loss when the model is wrong: quantum-like 5.7, Markov 17.1

Result. Both model-based policies beat both fixed policies. Each model wins on the people who behave like it, and the quantum-like policy loses less when it is wrong, so it is the safer choice when the robot does not know which kind of person it faces. Which kind real people are is what a user study has to establish.

3. The simulation#

quantum-handover animate runs one session in PyBullet: a KUKA iiwa arm picks a cube from a stand and hands it to the person; when the person does not take it, the cube falls under gravity. Left, the quantum-like policy; right, always handing over; same simulated person (initial P(trust) 0.34) and the same random draws. The quantum-like robot asks when trust is low and hands over slowly after a “no”. In this session it dropped 2 cubes (cost 27.0) against 5 (cost 50.0).

Quantum-like policy (left) and always handing over (right) in PyBullet

The GIF is made by the package (it needs pybullet and pillow, which the documentation build does not install):

pip install -e "tutorials/trust_handover_package[sim]"
quantum-handover animate --out handover.gif

4. On a robot (ROS 2)#

The ROS 2 node is a thin layer over HandoverController, which turns events into the next decision. A motion controller publishes what happened on /handover/events; the node answers on /handover/decision. Here the controller runs without ROS:

ctl = HandoverController('quantum-like')
print('start      ', ctl.decision())
for event in ({'answer': 'yes'}, {'outcome': 'taken'}, {'outcome': 'dropped'}, {'answer': 'no'}):
    print('%-11s' % list(event.values())[0], ctl.handle(json.dumps(event)))
start       {"action": "ask", "p_trust": 0.5603, "costs": {"handover": 4.3975, "slow_handover": 3.1987, "ask": 3.1385, "wait": 3.4987}}
yes         {"action": "handover", "p_trust": 1.0, "costs": {"handover": 0.0, "slow_handover": 1.0, "ask": 0.5, "wait": 0.3}}
taken       {"action": "ask", "p_trust": 0.8484, "costs": {"handover": 1.5165, "slow_handover": 1.7582, "ask": 1.4099, "wait": 1.8165}}
dropped     {"action": "ask", "p_trust": 0.8602, "costs": {"handover": 1.3976, "slow_handover": 1.6988, "ask": 1.3386, "wait": 1.6976}}
no          {"action": "slow_handover", "p_trust": 0.0, "costs": {"handover": 10.0, "slow_handover": 6.0, "ask": 6.5, "wait": 6.3}}

On a ROS 2 machine: python -m quantum_handover.ros2_node --policy quantum-like.

A limitation of the trust model, visible above. After the “yes”, trust is certain (1.0). The successful hand-over then lowers it to about 0.85, and the drop that follows raises it slightly. In this model every success and failure turns the trust state by a fixed angle, so right after an answer has made trust certain, any event moves it away from certainty, and a failure can turn it back towards it. The benchmark results include this behaviour; whether people’s trust behaves this way is one of the things a study with real people should test.

Summary. The package is a plain Python package with an optional simulation and an optional ROS 2 node, its requirements are in tutorials/trust_handover_package/requirements.txt, and its six tests run with pytest tutorials/trust_handover_package/tests. The results are for simulated people only.