Robotics#

Human-robot interaction: order-aware human models for robots that ask questions and track trust.

  • HRI_DOMAINS: three question pairs a robot asks people, with illustrative population parameters (object clarification, trust and hand-over, preference elicitation).

  • domain_models(): quantum-like, anchoring and Bayesian populations for a domain.

  • estimate_unprimed_rates(): questioning designs for learning what people think before the robot’s own questions influence them (fixed order, reversed-order probe, split order).

  • TRUST_PROTOCOL: hand-over outcomes and query designs for testing whether asking about trust changes trust.

  • HumanModelEnsemble: competing human models weighted by evidence (BIC weights); gives the robot a predicted answer distribution and its uncertainty, which can be passed to an orchestrator as a confidence signal.

  • ask_or_act(): decision rule for asking a person before acting.

  • HumanModelService: middleware-independent service wrapping the ensemble (used by the ROS 2 node in integrations/ros2).

The parameter values are illustrative (calibrated to answer rates of the size seen in survey data), not estimates from human-robot interaction studies. The simulation study that uses them is in the companion paper ‘Order-Aware Human Models for Robot Questioning and Trust’.

Classes

HumanModelEnsemble

Competing human models weighted by evidence.

HumanModelService

Middleware-independent human-model service a robot can run next to its planner.

Functions

domain_models

Simulated populations for one domain.

estimate_unprimed_rates

Estimate the unprimed "yes" rates from people who answer both questions.

ask_or_act

Decision rule for a robot that may ask a person before acting.

risk_aware_ask_or_act

Ask-or-act rule that scales with the hazard of the action and uses a lower confidence bound.

class HumanModelEnsemble(candidates=None, design=None)[source]#

Bases: object

Competing human models weighted by evidence.

The uncertainty is the entropy of the mixture prediction (bits) plus the disagreement between models (Jensen-Shannon divergence weighted by the model weights). A robot can use it to decide whether to ask, which question to ask first, and how much to trust an answer, and can pass it to an orchestrator as a confidence signal.

Parameters:
  • candidates (list of type, optional) – Model classes; default quantum-like (4D), Bayesian and anchoring.

  • design (iterable, optional) – Design passed to the models.

Variables:

Examples

>>> ens = HumanModelEnsemble()
>>> ens.update({'AB': [40, 10, 20, 30], 'BA': [30, 20, 10, 40]})
>>> p, unc = ens.predict('AB')
update(data, restarts=8, rng=None)[source]#

Fit every candidate model and compute BIC weights.

Parameters:
  • data (dict) – Answer counts {'AB': [yy, yn, ny, nn], 'BA': [...]}.

  • restarts (int, optional) – Restarts per fit.

  • rng (numpy.random.Generator, optional) – Random number generator.

Returns:

HumanModelEnsemble – self.

predict(condition)[source]#

Mixture prediction and its uncertainty.

Parameters:

condition (str) – Condition, for example 'AB'.

Returns:

  • prediction (numpy.ndarray) – Weighted mixture of the models’ predictions.

  • uncertainty (dict) – entropy_bits, model_disagreement_bits (weighted Jensen-Shannon divergence) and total_bits.

summary()[source]#

Ensemble weights.

Returns:

list of dict – One dict per model with model, weight and bic.

class HumanModelService(candidates=None, refit_every=20, min_answers=20, restarts=4, seed=0)[source]#

Bases: object

Middleware-independent human-model service a robot can run next to its planner.

It accumulates people’s answers to two questions asked in either order, refits a HumanModelEnsemble every refit_every answers, and returns a prediction with its uncertainty and an ask-or-act decision. Messages are plain, JSON-friendly dicts. The ROS 2 node in integrations/ros2 wraps this class.

Parameters:
  • candidates (list of type, optional) – Model classes of the ensemble.

  • refit_every (int, optional) – Number of new answers between refits.

  • min_answers (int, optional) – Answers needed before the first fit.

  • restarts (int, optional) – Restarts per fit.

  • seed (int, optional) – Seed of the fitting random number generator.

Examples

>>> from quantum_mind.applications.robotics import HumanModelService
>>> svc = HumanModelService()
>>> svc.add_answer({'order': 'AB', 'answers': [1, 0]})     # first answer, second answer (1 = yes)
{'n_answers': 1, 'refitted': False}
>>> svc.query({'order': 'AB'})['action']                   # too little data yet
'ask'
property n_answers#

number of answer pairs received.

Type:

int

add_answer(msg)[source]#

Record one person’s answers.

Parameters:

msg (dict) – {'order': 'AB' or 'BA', 'answers': [first, second]} with 1 = yes and 0 = no, in the order asked.

Returns:

dict – {'n_answers': int, 'refitted': bool}.

Raises:

ValueError – If the message is malformed.

query(msg=None)[source]#

Predict answers and decide whether to ask.

Parameters:

msg (dict, optional) – {'order': 'AB', 'ask_cost': 1.0, 'error_cost': 5.0, 'max_disagreement_bits': 0.05} (all keys optional).

Returns:

dict – prediction [yy, yn, ny, nn], p_first_yes, uncertainty, weights and the ask_or_act() fields. Before enough answers have arrived, action is 'ask' with the reason “not enough data”.

Raises:

ValueError – If the order is not 'AB' or 'BA'.

domain_models(domain)[source]#

Simulated populations for one domain.

Parameters:

domain (str) – Key of HRI_DOMAINS.

Returns:

dict – {'QL': QuantumOrderModel, 'Anchoring': AnchoringOrderModel, 'Bayes': BayesOrderModel}. The Bayesian population reproduces the quantum-like model’s A-first joint distribution without an order effect.

estimate_unprimed_rates(design, generator, n, rng=None, model=None, probe=0.1)[source]#

Estimate the unprimed “yes” rates from people who answer both questions.

The unprimed rate of a question is its “yes” rate when it is asked first, before the other question can influence the answer.

Parameters:
  • design ({'fixed', 'probe', 'split'}) – 'fixed': everyone answers A then B, and the B rate is read from the second answers (ignores order effects). 'probe': a fraction probe answers B first, and model fitted to all answers gives both rates. 'split': half answer each order and each rate comes from first answers only (model-free; the robust default).

  • generator (Model) – Population that produces the answers (for example from domain_models()).

  • n (int) – Number of people.

  • rng (numpy.random.Generator, optional) – Random number generator.

  • model (type, optional) – Model class fitted in the 'probe' design.

  • probe (float, optional) – Fraction asked in the reversed order in the 'probe' design.

Returns:

tuple of float – Estimated (pi_A, pi_B).

ask_or_act(p_success, uncertainty=None, ask_cost=1.0, error_cost=5.0, max_disagreement_bits=0.05)[source]#

Decision rule for a robot that may ask a person before acting.

Acting costs error_cost * (1 - p_success) in expectation; asking costs ask_cost and is assumed to resolve the doubt. The robot also asks when the human models disagree by more than max_disagreement_bits, because then p_success itself is unreliable. The rule is framework-agnostic: call it from a ROS 2 node, a behaviour tree or a planner.

Parameters:
  • p_success (float) – Probability that acting now is right (for example the ensemble’s predicted “yes” rate for the robot’s current hypothesis).

  • uncertainty (dict, optional) – Output of HumanModelEnsemble.predict(); its model_disagreement_bits is used.

  • ask_cost (float, optional) – Cost of asking.

  • error_cost (float, optional) – Cost of acting wrongly.

  • max_disagreement_bits (float, optional) – Model disagreement above which the robot always asks.

Returns:

dict – action ('ask' or 'act'), expected_cost_act and a human-readable reason.

Raises:

ValueError – If p_success is not in [0, 1].

Examples

>>> from quantum_mind.applications.robotics import ask_or_act
>>> ask_or_act(0.9, ask_cost=1.0, error_cost=5.0)['action']
'act'
>>> ask_or_act(0.6, ask_cost=1.0, error_cost=5.0)['action']
'ask'
risk_aware_ask_or_act(p_success, hazard='medium', ask_cost=1.0, p_lower=None, uncertainty=None, max_disagreement_bits=0.05, max_risk=None)[source]#

Ask-or-act rule that scales with the hazard of the action and uses a lower confidence bound.

Handing over a cup and handing over a knife need different thresholds. The cost of a wrong action is taken from the hazard level, and when a lower bound on the success probability is available (for example from CalibrationMonitor or a conformal predictor) the decision uses it instead of the point estimate, so an unreliable confidence makes the robot ask.

Parameters:
  • p_success (float) – Estimated probability that acting now is right.

  • hazard (str or float, optional) – A key of HAZARD_COSTS ('low', 'medium', 'high', 'critical') or a numeric cost of a wrong action.

  • ask_cost (float, optional) – Cost of asking.

  • p_lower (float, optional) – Lower confidence bound on the success probability; used in place of p_success when given.

  • uncertainty (dict, optional) – Output of HumanModelEnsemble.predict(); disagreement between human models forces asking.

  • max_disagreement_bits (float, optional) – Disagreement threshold.

  • max_risk (float, optional) – Hard limit on the failure probability: above it the robot asks whatever the costs.

Returns:

dict – The fields of ask_or_act(), plus hazard_cost and p_used.

Examples

>>> from quantum_mind.applications.robotics import risk_aware_ask_or_act
>>> risk_aware_ask_or_act(0.95, 'low')['action']          # cup: act
'act'
>>> risk_aware_ask_or_act(0.95, 'high')['action']         # knife: ask
'ask'