EconomicDecisionMaking#

class braintools.cogtask.EconomicDecisionMaking(t_fixation=Quantity(1500., 'ms'), t_offer=(Quantity(1000., 'ms'), Quantity(2000., 'ms')), t_decision=Quantity(750., 'ms'), offers=((0, 1), (1, 3), (1, 2), (1, 1), (2, 1), (3, 1), (4, 1), (6, 1), (2, 0)), relative_value=2.2, quantity_scale=0.2, noise_sigma=Quantity(0., 'ms^0.5'), **kwargs)[source]#

Economic (value-based) decision-making task.

On each trial two offers are shown, one of juice A and one of juice B, in varying quantities (e.g. “1A vs 3B”); the subject must choose the offer of greater subjective value. Juice A is preferred: one drop of A is worth relative_value drops of B. This is the orbitofrontal-cortex juice choice paradigm of Padoa-Schioppa and Assad [1], as implemented by the EconomicDecisionMaking environment of NeuroGym [2] (itself adapted from the reward-based RNN training of Song et al. [3]).

Structure: Fixation >> Offer >> Decision

Parameters:
  • t_fixation (Quantity) – Fixation period (default: 1500 ms).

  • t_offer (Any) – Offer period. A Quantity gives a fixed duration; a (min, max) tuple of Quantities draws it uniformly per trial (default: (1000, 2000) ms, as in NeuroGym).

  • t_decision (Quantity) – Decision period (default: 750 ms).

  • offers (Sequence[Tuple[int, int]]) – Offer types as (nB, nA) pairs, the number of drops of juice B and of juice A (default: NeuroGym’s (0, 1), (1, 3), (1, 2), (1, 1), (2, 1), (3, 1), (4, 1), (6, 1), (2, 0)).

  • relative_value (float) – Value of one drop of A in drops of B, i.e. 1A = relative_value B (default: 2.2; NeuroGym’s B_to_A = 1 / 2.2). Must be positive.

  • quantity_scale (float) – Factor applied to the drop counts in the quantity inputs (default: 1 / 5).

  • noise_sigma (Array | ndarray | bool | number | bool | int | float | complex | Quantity) – Standard deviation of Gaussian input noise on the quantity channels during the offer, in units of ms ** 0.5 and scaled by 1 / sqrt(dt) (default: 0, noise-free offers).

  • **kwargs – Forwarded to Task (e.g. seed, dt).

Raises:

ValueError – If offers is empty, not of shape (K, 2) or negative, relative_value <= 0, or t_offer is a malformed tuple.

See also

PerceptualDecisionMaking

Two-alternative choice from sensory evidence.

Notes

Inputs (7 channels, NeuroGym’s order fixation, a1, b1, a2, b2, n1, n2): fixation (1); juice1 and juice2 (2 each), one-hot identity [A, B] of the juice in offer 1 and offer 2; quantity (2), quantity_scale times the drop counts [n1, n2]. Which offer slot holds juice A is random (juice_order 0: A is offer 1, 1: A is offer 2). The fixation input is 1 during fixation and offer and 0 during the decision.

Ground truth. The offer values, in units of B, are

\[V_A = \rho \, n_A, \qquad V_B = n_B,\]

with \(\rho\) = relative_value. The correct choice is the offer of juice A if \(V_A > V_B\) and of juice B if \(V_B > V_A\); a tie is broken at random (tie is set in the trial state).

Labels (categorical, 3 classes): 0 (fixate) during fixation and offer, ground_truth + 1 during the decision, where ground_truth is the chosen offer slot (0 = offer 1, 1 = offer 2).

Trial state: nA, nB, juice_order, value_A, value_B, choose_A, tie, ground_truth and, when sampled, offer_duration (ms).

With the default uniform t_offer the task runs in packed (variable-length) mode (see max_trial_duration()); batch_sample runs under jax.jit and vmap either way.

Differences from NeuroGym. NeuroGym’s environment is trained by reward only (the reward is the chosen juice’s value) and defines no ground truth; here the higher-value offer is the supervised label and ties are broken at random. Phase names are 'fixation', 'offer' and 'decision' (NeuroGym: 'offer_on' for the offer). The optional input noise is an addition.

References

Examples

>>> import brainunit as u
>>> import braintools
>>> task = braintools.cogtask.EconomicDecisionMaking(
...     seed=0, dt=10.0 * u.ms, t_offer=1000.0 * u.ms)
>>> task.num_inputs, task.num_outputs
(7, 3)
>>> X, Y, info = task.sample_trial(0)
>>> X.shape, Y.shape
((325, 7), (325,))
>>> s = info['trial_state']
>>> correct_is_A = bool(s['value_A'] > s['value_B'])
>>> bool(s['choose_A']) == correct_is_A or bool(s['tie'])
True
>>> chosen_slot = int(s['juice_order']) if bool(s['choose_A']) else 1 - int(s['juice_order'])
>>> int(Y[-1]) == chosen_slot + 1
True
>>>
>>> X, Y = task.batch_sample(4)
>>> X.shape, Y.shape
((325, 4, 7), (325, 4))
define_features()[source]#

Define input and output features.

Override in subclass for class-based task definition.

Return type:

Tuple

Returns:

  • input_features (Feature or FeatureSet) – Input feature definitions.

  • output_features (Feature or FeatureSet) – Output feature definitions.

define_phases()[source]#

Define the phase structure.

Override in subclass for class-based task definition.

Returns:

The task phase structure (single phase or composition).

Return type:

Phase

trial_init(ctx)[source]#

Initialize trial-level state.

Override in subclass to set up trial parameters like ground_truth, stimulus indices, etc.

Parameters:

ctx (Context) – Trial context to populate with state.

Return type:

None