# pylint: disable=unused-argument,abstract-method
import random
from collections import Counter
from ..field import extra_var
from .chain import ChainNetwork, ChainNode, ChainTrial, ChainTrialMaker
[docs]
class MCMCPNetwork(ChainNetwork):
"""
A Network class for MCMCP chains.
"""
[docs]
def make_definition(self):
return {}
[docs]
class MCMCPTrial(ChainTrial):
"""
A Network class for MCMCP.
Attributes
----------
first_stimulus
Definition of the first stimulus of the trial.
This definition corresponds to a setting
of the chain's free parameters.
second_stimulus
Definition of the second stimulus of the trial,
This definition corresponds to a setting
of the chain's free parameters.
"""
__extra_vars__ = ChainTrial.__extra_vars__.copy()
[docs]
def make_definition(self, experiment, participant):
"""
In MCMCP, a trial's definition is created by taking the
current state and the proposal from the source
:class:`~psynet.trial.mcmcp.MCMCPNode`
and adding a random ordering.
Parameters
----------
experiment
An instantiation of :class:`psynet.experiment.Experiment`,
corresponding to the current experiment.
participant
Optional participant with which to associate the trial.
Returns
-------
object
The trial's definition, equal to the node's definition
plus the random ordering.
"""
order = ["current_state", "proposal"]
random.shuffle(order)
definition = {
"current_state": self.node.definition["current_state"],
"proposal": self.node.definition["proposal"],
}
definition["ordered"] = [
{"role": role, "value": definition[role]} for role in order
]
return definition
@property
@extra_var(__extra_vars__)
def first_stimulus(self):
return self.definition["ordered"][0]["value"]
@property
@extra_var(__extra_vars__)
def second_stimulus(self):
return self.definition["ordered"][1]["value"]
[docs]
class MCMCPNode(ChainNode):
"""
A Node class for MCMCP chains.
"""
[docs]
def get_proposal(self, state, experiment, participant):
"""
Implements the proposal function for the MCMP chain.
Parameters
----------
state
The current state, with reference to which the proposal
state should be constructed.
experiment
An instantiation of :class:`psynet.experiment.Experiment`,
corresponding to the current experiment.
participant
An instantiation of :class:`psynet.participant.Participant`,
corresponding to the current participant.
Returns
-------
Object
The proposal state.
"""
raise NotImplementedError
[docs]
def summarize_trials(self, trials: list, experiment, participant):
"""
This method should summarize the answers to the provided trials.
A default method is implemented for cases when there is
just one trial per node; in this case, the method extracts and returns
the parameter values for the chosen stimulus, following the standard
definition of MCMCP. The method must be extended if it is to cope
with multiple trials per node.
Parameters
----------
trials
Trials to be summarized. By default only trials that are completed
(i.e. have received a response) and processed
(i.e. aren't waiting for an asynchronous process)
are provided here.
experiment
An instantiation of :class:`psynet.experiment.Experiment`,
corresponding to the current experiment.
participant
The participant who initiated the creation of the node.
Returns
-------
object
The derived seed. Should be suitable for serialisation to JSON.
"""
counts = Counter([t.answer["role"] for t in trials])
max_count = max(counts.values())
candidates = [item[0] for item in counts.items() if item[1] == max_count]
chosen_role = random.sample(candidates, 1)[0]
return self.definition[chosen_role]
[docs]
def create_definition_from_seed(self, seed, experiment, participant):
return {
"current_state": seed,
"proposal": self.get_proposal(seed, experiment, participant),
}
def create_initial_seed(self, experiment, participant):
raise NotImplementedError
[docs]
class MCMCPTrialMaker(ChainTrialMaker):
"""
A TrialMaker class for MCMCP chains;
see the documentation for
:class:`~psynet.trial.chain.ChainTrialMaker`
for usage instructions.
"""
[docs]
def finalize_trial(self, answer, trial, experiment, participant):
"""
Modifies ``answer`` so as to store three values:
* The position of the chosen stimulus;
* The role of the chosen stimulus (``"current_state"`` or ``"proposal"``);
* The value of the parameters underlying the chosen stimulus.
"""
# pylint: disable=unused-argument,no-self-use
position = int(answer)
answer = {
"position": position,
"role": trial.definition["ordered"][position]["role"],
"value": trial.definition["ordered"][position]["value"],
}
trial.answer = answer
super().finalize_trial(answer, trial, experiment, participant)
def get_answer_for_consistency_check(self, trial):
role = trial.answer["role"]
assert role in ["proposal", "current_state"]
return float(role == "proposal")
performance_check_type = "consistency"
consistency_check_type = "percent_agreement"
@property
def default_network_class(self):
return MCMCPNetwork