Extending#

Base classes and mixins for new strategies, warmups and evaluators. See the guide.

class TACTICS.thompson_sampling.strategies.base_strategy.SelectionStrategy(mode='maximize')[source]#

Abstract base class for reagent selection strategies

abstractmethod select_reagent(reagent_list, disallow_mask=None, **kwargs)[source]#

Select one reagent index from a component’s reagent list.

The sampler calls this once per component per cycle with the keyword context rng, component_idx, iteration, current_cycle and total_cycles; strategies read what they need from kwargs.

Parameters:
  • reagent_list (List) – Reagent objects with posterior mean/std/n_samples.

  • disallow_mask (set) – Indices that must not be selected (already sampled in combination with the other components’ current picks).

  • **kwargs – Per-cycle context from the sampler (see above).

Returns:

The selected index into reagent_list.

Return type:

int

select_batch(reagent_list, batch_size, disallow_mask=None, **kwargs)[source]#

Select batch_size reagent indices (with replacement).

Default implementation calls select_reagent() batch_size times. Not used by ThompsonSampler, which builds batches itself; kept for strategies used standalone.

Parameters:
  • reagent_list (List) – Reagent objects with posterior distributions.

  • batch_size (int) – Number of indices to return.

  • disallow_mask (set) – Indices to exclude from selection.

  • **kwargs – Passed through to select_reagent().

Returns:

Array of selected indices, length batch_size.

Return type:

ndarray

get_component_criticality(reagent_list)[source]#

Return criticality score for a component, or None if not supported.

Strategies with CATS (e.g., RouletteWheelSelection, BayesUCBSelection) override this to compute component criticality.

Returns:

Criticality score >= 0, or None if the strategy doesn’t compute it.

Return type:

float | None

get_component_state(reagent_list, component_idx, current_cycle, total_cycles)[source]#

Return full intermediate state for a component, or None if not supported.

CATS-aware strategies (RouletteWheelSelection, BayesUCBSelection) override this to expose the complete criticality + temperature/percentile pipeline.

Returns:

Dict with all intermediate values, or None if the strategy doesn’t compute component state.

Return type:

Dict[str, Any] | None

prepare_scores(reagent_list, rng)[source]#

Sample scores from posterior distributions

Return type:

ndarray

class TACTICS.thompson_sampling.strategies._thermal.ThermalCyclingMixin[source]#

Heated-component bookkeeping and rotation.

Subclasses call _init_thermal_cycling() from __init__ and implement _rotation_flexibility().

rotate_component(n_components)[source]#

Round-robin rotation to the next component.

rotate_component_weighted(n_components, reagent_lists, rng=None)[source]#

Rotate to the next heated component with flexibility-weighted probabilities.

Draws exactly one sample from rng after deterministic weight computation, so seeded runs are reproducible across strategies.

Parameters:
  • n_components (int) – Number of reaction components.

  • reagent_lists – One list of Reagent objects per component.

  • rngnumpy.random.Generator; a fresh default generator if None.

_rotation_flexibility(reagent_lists)[source]#

Per-component heating weights (higher = heated more often).

Return type:

ndarray

class TACTICS.thompson_sampling.strategies._thermal.GMICCriticalityMixin[source]#

Bases: ThermalCyclingMixin

GMIC criticality plus GMIC-weighted rotation.

GMIC = 0.5 * log(1 + var(posterior means) / mean(posterior variances)). High GMIC = critical component (clear winners among reagents); low GMIC = flexible component (reagents look alike). Flexible components are heated more often: weight = 1 / (1 + GMIC).

Subclasses call _init_gmic_state() from __init__.

_calculate_gmic_details(reagent_list)[source]#

Return (gmic, details) for one component.

details holds signal_var, mean_noise_var and n_active_reagents. GMIC is 0.0 when fewer than two reagents have been observed.

There is deliberately no minimum-observation gate here. One used to exist in TopTwoSelection (2026-06): it zeroed a component’s GMIC whenever its least-observed active reagent had fewer than N samples. On large components (adenine’s 688 isocyanides) a single straggler pinned GMIC to zero every cycle — on 25/28 benchmark libraries — which over-weighted that component in rotation. Removing the gate lifted adenine TT-TS top-100 recovery 87.1 -> 93.2 and halved its replicate variance (sd 18.4 -> 10.6), with no change on the libraries where the gate never fired.

Return type:

tuple

_calculate_gmic(reagent_list)[source]#

Gaussian Mutual Information Criticality for one component.

Return type:

float

get_component_criticality(reagent_list)[source]#

GMIC criticality (used by the sampler for rotation).

Return type:

float

class TACTICS.thompson_sampling.warmup.base.WarmupStrategy[source]#

Abstract base class for warmup strategies.

A warmup strategy determines how reagent combinations are generated during the warmup phase to initialize reagent posteriors.

abstractmethod generate_warmup_combinations(reagent_lists, num_warmup_trials, disallow_tracker)[source]#

Generate reagent combinations for warmup evaluation.

Parameters:#

reagent_listsList[List[Reagent]]

List of reagent lists, one for each component

num_warmup_trialsint

Number of trials per reagent

disallow_trackerDisallowTracker

Tracker to prevent duplicate combinations

Returns:#

List[List[int]]

List of combinations, where each combination is [idx_comp1, idx_comp2, …]

Return type:

List[List[int]]

get_expected_evaluations(reagent_lists, num_warmup_trials)[source]#

Calculate expected number of evaluations for this strategy.

Default implementation: sum of reagents × trials Override for strategies with different evaluation counts.

Parameters:#

reagent_listsList[List[Reagent]]

List of reagent lists

num_warmup_trialsint

Number of trials per reagent

Returns:#

int

Expected number of evaluations

Return type:

int

abstractmethod get_name()[source]#

Return strategy name for logging.

Returns:#

str

Human-readable strategy name

Return type:

str

get_description()[source]#

Return detailed strategy description.

Returns:#

str

Multi-line description of the strategy

Return type:

str

Package layout#

PEP 562 lazy re-exports for package __init__ modules.

The package hubs (TACTICS, TACTICS.thompson_sampling, .core, TACTICS.library_analysis) re-export names from their submodules for convenience. Importing those submodules eagerly makes import TACTICS – and any config-only import that passes through a hub – pay for RDKit, scipy, sqlitedict and the plotting stack up front. Each hub instead declares a {name: ".submodule"} map and calls install(), which resolves a name on first attribute access and caches it in the module namespace.

Submodule paths (TACTICS.thompson_sampling.core.evaluators) are unaffected: a lazy hub is still a regular package.

TACTICS._lazy.install(module, exports)[source]#

Give module a lazy __getattr__/__dir__ over exports.

Parameters:
  • module (ModuleType) – The package module (pass sys.modules[__name__]).

  • exports (Dict[str, str]) – {public_name: relative_submodule} – e.g. {"ThompsonSampler": ".core.sampler"}.