Technical Documentation: Refactoring Details¶
This document outlines the specific internal modifications made to classes.py in neurodesign-plus. It focuses on the new class variables, helper methods, and behavior changes in the Design, Experiment, and Optimisation classes.
Architecture Overview¶
The refactored code preserves a strict separation between the Experiment and the Design:
Experiment stores the fixed specification of the task container: HRF settings, whitening matrix inputs, ITI summaries, and duration specifications.
Design stores one concrete sampled realization: event order, ITIs, and optionally per-event sampled stimulus durations.
This separation is important because all designs in a population share the same experiment container and therefore the same whitening matrix. That keeps raw Fe and Fd values comparable across designs generated within that experiment.
1. Added Class Variables¶
Design Class¶
all_stim_durations(list of floats or None)Concrete per-trial stimulus durations for this design, including
t_preandt_post.Nonewhen all stimuli share the samestim_duration.When set,
designmatrix()uses these per-trial values instead ofexperiment.stim_duration.Sampled via
Experiment.sample_stim_durations()and passed at construction:Design(order=..., ITI=..., experiment=..., all_stim_durations=...).
Experiment Class¶
stimuli_durations(list of int/float or dicts, or None)A specification template – one entry per stimulus – defining how to sample each stimulus’s duration.
Each entry is either a scalar (fixed duration) or a dict specifying a distribution:
stimuli_durations = [ {"model": "fixed", "mean": 1.0}, {"model": "exponential", "mean": 2.0, "min": 1.0, "max": 5.0}, 1.5, ]
Length must equal
n_stimuli.Requires
trial_maxto be specified.If not provided, all stimuli use the original
stim_duration.
conditional_ITI(dict or None)Specification for transition-dependent ITI distributions.
Keys are
(prev_stim, curr_stim)tuples or"default".Values are dicts with
"model","mean", and optional"min","max","std".Example:
conditional_ITI = { (0, 1): {"model": "exponential", "mean": 2, "min": 1}, (1, 2): {"model": "fixed", "mean": 4}, "default": {"model": "exponential", "mean": 3, "min": 1}, }
order(list of ints or None)A user-provided fixed event order.
When provided,
order_fixedis set toTrue.The order is preserved across all designs during optimization – crossover and mutation do not modify it.
trial_max(float)The maximum raw stimulus duration across all conditions.
Required when
stimuli_durationsis provided.Used to compute the experiment container’s
trial_duration = trial_max + t_pre + t_post.
Sequence Generation Variables¶
order_keys(list of integer lists)– Sequences sampled as units.order_probabilities(list of floats)– Probabilities for each key.order_length(int)– Requested length of the final event-level order.
These three are inputs for sample_from_probabilities(). When order_probabilities is provided, the optimizer generates event orders by sampling from these sequence templates rather than using the original blocked, random, or m-sequence generators. Internally, the sampled templates are flattened and then truncated to the first order_length events.
2. New Functions¶
Experiment Class (all @staticmethod)¶
sample_stim_durations(order, stimuli_durations, t_pre, t_post)->list[float]Samples concrete per-trial stimulus durations for a given order, using the specifications in
stimuli_durations.Adds
t_pre + t_postto each sampled duration.Supports
"fixed","exponential","uniform", and"gaussian"models, as well as scalar values.
generate_iti(order, conditional_iti)->list[float]Samples a concrete ITI array from a transition-dependent ITI specification.
Uses the
"default"key for the first event and for any transition not explicitly listed.Supports
"fixed","exponential","uniform", and"gaussian"models.Returns a list of length
len(order).
calculate_duration(ITI, dur)->floatComputes total duration as the sum of all ITIs and all trial durations.
sample_from_probabilities(prob, key, length)->listSamples templates from
keywith weightsprob.Flattens the sampled templates into one event-level order.
Returns the first
lengthevents from that flattened order.Because the final list is truncated to
length, the last sampled template can be cut at the tail when templates have different lengths.
3. Modified Functions¶
Design Class¶
__init__(self, order, ITI, experiment, onsets=None, all_stim_durations=None)Added
all_stim_durationsfor per-trial variable stimulus durations.
designmatrix(self)Added support for variable stimulus durations via
self.all_stim_durations.When set, onset computation and design matrix construction use per-trial durations instead of a single shared
stim_duration.Returns
Falseif the design’s actual timing exceeds the experiment container.
crossover(self, other, seed)Added fixed-order support: when
order_fixed=True, offspring inherit the parent orders unchanged.When
stimuli_durationsis set and the order changes, per-trial durations are re-sampled to match the new order.When
order_fixed=True, per-trial durations are inherited.
mutation(self, q, seed)Added fixed-order support: mutation only changes the order when
order_fixed=Falseandorder_probabilities is None.Uses the same per-trial duration propagation logic as crossover.
Experiment Class¶
countstim(self)Computes the container duration from expected values:
n_trials x (trial_duration + ITImean).This behavior is kept even when
stimuli_durationsis set, so the whitening matrix dimensions stay stable across all designs in a population.
max_eff(self)Computes
FcMaxandFfMaxfrom a null design built inside the shared experiment container.The current implementation does not estimate
FeMaxorFdMaxinsidemax_eff().
Optimisation Class¶
add_new_designs(self, weights, R)Added order sampling paths:
fixed order -> self.exp.orderprobability-based order -> Experiment.sample_from_probabilities()default -> generate.order()Added ITI sampling paths:
conditional_ITI -> Experiment.generate_iti()default -> generate.iti()Added per-design sampling of stimulus durations when
stimuli_durationsis set.Keeps all generated designs attached to one shared experiment object.
clear(self)Propagates
all_stim_durationswhen preserving the best design across generation clears.
optimise(self)Skips Fe/Fd calibration pre-runs when the corresponding weight is zero or when the user has provided explicit FeMax/FdMax values.
to_next_generation(self, weights, seed, optimisation)When
order_fixed=True, skips mutation and crossover and uses immigration only.This focuses optimization on timing and other non-order parameters.