Scientific components¶
Backend contract and adapters¶
Provider-neutral backend contracts.
- class p2p.backends.base.Backend(*args, **kwargs)[source]¶
Minimal interface implemented by backend adapters.
- class p2p.backends.base.BackendResponse(answer_text, usage=<factory>)[source]¶
Normalized response returned by every backend adapter.
- Parameters:
answer_text (str)
usage (TokenUsage)
- exception p2p.backends.base.NoAnswerTextError[source]¶
The provider returned no answer text: a refusal, block, or filter.
The message names the provider’s stated reason so that it survives into the recorded
[ERROR]answer when every retry fails.
- class p2p.backends.base.TokenUsage(input_tokens=0, output_tokens=0, cached_input_tokens=0, reasoning_tokens=0)[source]¶
Normalized token counts returned by every backend.
- Parameters:
input_tokens (int)
output_tokens (int)
cached_input_tokens (int)
reasoning_tokens (int)
Deterministic offline backend for tests and local smoke runs.
- class p2p.backends.dummy.DummyBackend(*, model_name='dummy', answer_text='DummyBackend called.', usage=None, **_)[source]¶
Return a configured response without making an external call.
- Parameters:
model_name (str)
answer_text (str)
usage (TokenUsage | None)
_ (Any)
Native local Ollama backend adapter.
- class p2p.backends.ollama.OllamaBackend(*, model_name='gemma3:4b', base_url='http://localhost:11434', temperature=0.0, max_tokens=128, timeout=60.0, think=False, keep_alive=None, options=None, transport=None)[source]¶
Call Ollama’s native non-streaming chat endpoint.
- Parameters:
model_name (str)
base_url (str)
temperature (float | None)
max_tokens (int | None)
timeout (float)
think (bool | str | None)
keep_alive (str | int | None)
options (Mapping[str, Any] | None)
transport (Transport | None)
OpenAI backend adapter: Chat Completions or Responses, chosen explicitly.
- class p2p.backends.openai.OpenAIBackend(*, model_name, temperature=0.7, max_tokens=1024, transport='chat_completions', store=False, client=None, api_key=None)[source]¶
Normalize OpenAI text responses.
transportnames the API:chat_completions(the default) orresponses. It is never switched silently. P2P’s provider-neutralmax_tokensis sent asmax_completion_tokens(Chat Completions) ormax_output_tokens(Responses); both bound visible and reasoning tokens.storeis sent on every request because both APIs may store responses by default; P2P never retrieves them, so it defaults to false.- Parameters:
model_name (str)
temperature (float | None)
max_tokens (int | None)
transport (str)
store (bool)
api_key (str | None)
DeepSeek V4 Chat Completions backend adapter.
- class p2p.backends.deepseek.DeepSeekBackend(*, model_name='deepseek-v4-flash', temperature=0.7, max_tokens=1024, thinking='disabled', reasoning_effort=None, base_url=None, client=None, api_key=None)[source]¶
Normalize DeepSeek’s OpenAI-compatible chat API.
Thinking is disabled by default because P2P’s survey and persona workflows generally require short, directly parseable responses. Set
thinking="enabled"explicitly for reasoning workloads.- Parameters:
model_name (str)
temperature (float | None)
max_tokens (int | None)
thinking (str)
reasoning_effort (str | None)
base_url (str | None)
api_key (str | None)
Google Gemini backend adapter.
- class p2p.backends.gemini.GeminiBackend(*, model_name, temperature=0.7, max_tokens=1024, system_instruction=None, client=None, config_factory=None, api_key=None)[source]¶
Normalize Gemini GenerateContent responses.
The model ID has no default: Gemini models are retired on a published schedule, so every run names the model it uses.
- Parameters:
model_name (str)
temperature (float | None)
max_tokens (int | None)
system_instruction (str | None)
config_factory (Callable[..., Any] | None)
api_key (str | None)
Simulated respondents: a deterministic, rule-based backend. Not a language model.
It lets Stage 1 run end to end offline (persona generation, attribute
discovery, survey answers), for demonstrations and tests. Behaviour comes from
a declarative world:
world:
setting: a fictional town # used in persona text (optional)
attributes:
everyday priority: # attribute name
relevance: survey # offered by survey-level discovery
values:
green and outdoors: # value; appears verbatim in personas
weight: 2.0 # how strongly it pulls answers
prefers: [A shared garden, Protected cycle lanes]
describe: grows vegetables and cycles everywhere # optional
evenings and culture:
prefers: [A night market]
age group:
values: {20s: {}, 40s: {}, 60s: {}}
Answers. For a question offered as text ([A] option, [B] option, ...), each
option scores the summed weight of the persona’s values that prefer it, plus
deterministic noise (Gumbel, scale noise) from a hash of the persona, the
question and the option. The highest score is answered. With no matching
preferences the answer is the noise alone, so any survey can be answered.
Personas. A request to vary named attributes gets one value per attribute that the world knows, drawn with a seeded generator; unknown attributes are named but left unvalued. Attribute discovery returns the world’s attributes, those whose values prefer an option of the question first.
Usage is reported as zero tokens: nothing is metered.
- class p2p.backends.simulated.SimulatedBackend(*, model_name='simulated-respondents', world=None, seed=0, noise=1.0)[source]¶
Answer P2P’s Stage-1 prompts with deterministic rules (see module docstring).
- Parameters:
model_name (str)
world (Mapping[str, Any] | None)
seed (int)
noise (float)
Agents and conductors¶
Backend-neutral survey agent.
- class p2p.agents.base.Agent(*, eid, backend, endowment='', formality='')[source]¶
Combine persona instructions with an injected backend.
- Parameters:
eid (str)
endowment (str)
formality (str)
- class p2p.agents.base.AgentResponse(eid, answer_text, usage, model_name)[source]¶
Normalized response returned to a survey conductor.
- Parameters:
eid (str)
answer_text (str)
usage (TokenUsage)
model_name (str)
Configuration for backend-neutral survey agents.
- class p2p.agents.config.AgentConfig(agent_type, model_name, formality='', agent_kwargs=<factory>, delay=0.0)[source]¶
Settings used by an injected agent/conductor factory.
- Parameters:
agent_type (str)
model_name (str)
formality (str)
agent_kwargs (Mapping[str, Any])
delay (float)
Factories connecting agents to the single backend registry.
- p2p.agents.factory.create_agent(*, config, eid, endowment='', backend=None, backend_factory=None)[source]¶
Create one generic agent using an injected or registered backend.
- Parameters:
config (AgentConfig)
eid (str)
endowment (str)
backend_factory (Callable[[...], Any] | None)
- Return type:
- p2p.agents.factory.create_backend(*, backend_type, model_name, **backend_kwargs)[source]¶
Create a backend through
p2p.registry.BACKENDS.- Parameters:
backend_type (str)
model_name (str)
backend_kwargs (Any)
Survey conduction over backend-neutral agents.
- class p2p.agents.conductor.SurveyConductor(*, survey, endowments, output_path=None, agent_type='dummy', model_name='dummy', formality='', cleaner=None, clean=False, verbose=True, max_retries=3, retry_delay=0.5, token_tracker=None, agent_factory=<function create_agent>)[source]¶
Elicit survey answers and account for normalized usage once.
- Parameters:
output_path (str | None)
agent_type (str)
model_name (str)
formality (str)
clean (bool)
verbose (bool)
max_retries (int)
retry_delay (float)
- run(*, save=False, parallel_worker=False, **agent_kwargs)[source]¶
Run the survey sequentially over all endowments.
- Parameters:
save (bool)
parallel_worker (bool)
agent_kwargs (Any)
- Return type:
list[dict[str, Any]]
Survey, responses, and endowments¶
- class p2p.survey.converter.Survey(csv_path, config_path)[source]¶
Load, manage, split, and save survey questions.
The YAML schema maps the logical
question_id,question_text,code_to_answer, and optionalsplitfields to CSV column names.- csv_path¶
Path to the survey CSV.
- config¶
Loaded YAML field mapping.
- questions¶
Parsed question dictionaries. Each record contains an ID, question text, code/answer mappings, and an optional split.
- split_questions(method='manual', split_map=None, train_ratio=0.7, valid_ratio=0.15, seed=None, save=False, save_path=None)[source]¶
Assigns ‘split’ attribute to each question.
Parameters: - method: “manual” or “from_map” - split_map: dict mapping question IDs to split labels - train_ratio, valid_ratio: used only if method=”manual” - seed: random seed for reproducibility - save: if True, write the split info back to a CSV - save_path: custom output path; defaults to original csv_path if None
- save(filepath=None)[source]¶
Saves the current survey state (including splits if present) to a CSV. Column names are derived from the YAML config. If splits exist but no split column is specified in the config, a column named ‘split’ is added.
- report_splits(show_percentage=True)[source]¶
Prints a summary of the split distribution among survey questions.
Parameters: - show_percentage (bool): If True, also print percentages.
- classmethod from_questions(questions, config=None, csv_path=None, config_path=None)[source]¶
Instantiate a Survey object from a list of preloaded questions.
- Parameters:
questions (list) – List of question dicts. Must include keys like ‘id’, ‘question’, ‘code_to_answer’, etc.
config (dict, optional) – YAML-style config dict specifying field names.
csv_path (str, optional) – Placeholder CSV path, if needed for compatibility.
config_path (str, optional) – Placeholder config path.
- Returns:
An instantiated Survey object with preloaded questions.
- Return type:
- class p2p.survey.converter.BinaryExtendedSurvey(csv_path, config_path)[source]¶
- class p2p.responses.converter.Responses(source, survey, output_format='code', clean=True, cleaner=None)[source]¶
Container class for loading, accessing, and saving a matrix of survey responses. Supports matrix-style CSV input or list-of-dict input, and handles answer-to-code mapping.
- output_format¶
Determines how responses are stored. - ‘code’: responses are mapped to numeric or symbolic codes. - ‘answer’: responses are kept as raw (cleaned) answer text.
- Type:
str
- questions¶
Mapping from question ID to metadata (extracted from the survey).
- Type:
dict
- responses¶
Nested dictionary where responses[qid][eid] = value (code or answer).
- Type:
dict
- clean¶
If True, apply normalization and heuristic cleaning to free-form agent responses.
- Type:
bool
- cleaner¶
Optional cleaner instance to use for response normalization and disambiguation. If None and clean=True, a default ResponseCleaner will be instantiated using the survey.
- Type:
ResponseCleaner
- get(qid, eid)[source]¶
Retrieve the response of a specific endowment to a specific question.
- Parameters:
qid (str) – Question ID.
eid (str) – Endowment ID.
- Returns:
Encoded or raw response, or None if not found.
- Return type:
str or None
- get_question_vector(qid)[source]¶
Get all responses to a specific question.
- Parameters:
qid (str) – Question ID.
- Returns:
Mapping from eid to response value.
- Return type:
dict
- get_agent_vector(eid)[source]¶
Get all responses given by a specific agent.
- Parameters:
eid (str) – Endowment ID.
- Returns:
Mapping from qid to response value.
- Return type:
dict
- get_matrix_by_split(split, survey=None, dropna=False)[source]¶
Returns a [qid × eid] matrix of responses for a given question split.
- Parameters:
split (str) – Split name (e.g., ‘train’, ‘valid’, ‘test’).
survey (Survey, optional) – Defaults to self.survey if not specified.
dropna (bool) – If True, drop rows with any missing values.
- Returns:
Response matrix for the specified question split.
- Return type:
pd.DataFrame
- sample_fraction(fraction, seed=101)[source]¶
Samples a fraction of questions while preserving the original split proportions.
- Parameters:
fraction (float) – Fraction of total questions to retain (0 < fraction <= 1).
seed (int) – Random seed for reproducibility.
- Returns:
A new Responses object containing only the sampled questions.
- Return type:
- sample_trainvalid_fraction(fraction, seed=101)[source]¶
Samples a fraction of questions from the train and valid splits while keeping the test split fully intact. Preserves original train/valid proportions.
- Parameters:
fraction (float) – Fraction of train+valid questions to retain (0 < fraction <= 1).
seed (int) – Random seed for reproducibility.
- Returns:
A new Responses object containing the sampled questions.
- Return type:
- save(path)[source]¶
Save the response matrix to a matrix-style CSV file.
- Parameters:
path (str) – Output file path.
- classmethod from_agent_records(agent_records, survey, output_format='answer', clean=True, cleaner=None)[source]¶
Construct a Responses object from a list of agent records.
- Parameters:
agent_records (list[dict]) – Each record should have: - “agent_id”: str, the EID - “responses”: list of answers (aligned with survey.questions)
survey (Survey) – Survey instance (with questions metadata).
output_format (str) – “answer” (default) or “code”.
clean (bool) – Whether to apply cleaning and normalization.
cleaner (ResponseCleaner, optional) – Optional custom cleaner.
- Returns:
A populated Responses instance.
- Return type:
- clone_with_subset(subset_qids, subset_survey=None)[source]¶
Returns a new Responses object restricted to a subset of question IDs.
- Parameters:
subset_qids (list[str]) – List of question IDs to retain.
subset_survey (Survey, optional) – A survey object that contains only the selected questions. If not provided, the existing survey will be filtered.
- Returns:
A new Responses instance with restricted question-response matrix.
- Return type:
- class p2p.responses.converter.BinaryExtendedResponses(source, survey, output_format='code', clean=True, cleaner=None)[source]¶
Extension of the Responses class for binary-extended surveys.
Automatically maps multiclass responses into binary format by expanding each original question into one binary sub-question per answer option.
- original_questions_by_id¶
Mapping of original (pre-expansion) question IDs to metadata.
- Type:
dict
- clean¶
Whether to apply response cleaning and normalization (inherited from Responses).
- Type:
bool
- cleaner¶
Instance used for cleaning answers (inherited from Responses).
- Type:
ResponseCleaner
- clone_with_subset(subset_qids, subset_survey=None)[source]¶
Clone a BinaryExtendedResponses object with only a subset of binary question IDs.
- Parameters:
subset_qids (list[str]) – List of binary question IDs to retain.
subset_survey (BinaryExtendedSurvey, optional) – A filtered survey instance. If None, it will be inferred from the current survey.
- Returns:
New response object with restricted binary questions.
- Return type:
- class p2p.endowments.manager.Endowments(csv_path=None)[source]¶
A structured container and access layer for managing a pool of LLM agent endowments.
Each endowment defines a persona used to condition agent behaviour in simulation or empirical experiments.
- csv_path¶
Path to the endowment CSV file
- Type:
str
- endowments¶
List of all endowment entries, each a dictionary with: - eid (str): Unique identifier - endow_text (str): Identity-conditioning prompt passed to LLM - role (str): Either ‘ground_truth’ or ‘proxy’ - weight (float or None): Importance weight (sampling or regression)
- Type:
list[dict]
- index¶
Maps eid to endowment dictionary for fast lookup
- Type:
dict
- Key Methods:
load(cls, path): Class method for instantiating from CSV
get_endowments(): Return all endowment entries
get_endowment_by_eid(eid): Lookup a single endowment
get_endowments_by_role(role): Filter by role (‘ground_truth’ or ‘proxy’)
get_eids_by_role(role): List eids matching a role
update_weights(weights): Partially update weights by eid
save(path): Save the endowments back to a CSV file
- classmethod load(path)[source]¶
Load an Endowments instance from a CSV file. This method can be extended for validation, logging, or alternative formats.
- update_weights(weights)[source]¶
Update weights for a subset of endowments.
- Parameters:
weights (dict) – Mapping from eid (str) to new weight (float or None)
- Behaviour:
Only updates entries whose eid is present in the weights dict
Ignores eids that do not exist in the current pool
Supports partial updating (e.g., just ground_truth or just proxy)
- initialize_ground_truth_weights(seed=None, save=False, save_path=None)[source]¶
Randomly initialize normalized weights for ‘ground_truth’ endowments.
- Parameters:
seed (int, optional) – Random seed for reproducibility.
save (bool) – Whether to save the updated weights to CSV.
save_path (str, optional) – Path to save the updated CSV. Defaults to self.csv_path.
- Raises:
ValueError – If no ‘ground_truth’ endowments exist.
Warning – If weights already exist and save=False.
- report_role_assignment(show_percentage=True)[source]¶
Prints a summary of the role distribution among survey questions.
Parameters: - show_percentage (bool): If True, also print percentages.
- clone_with_fraction(fraction=1.0, seed=101)[source]¶
Returns a new ActiveEndowments object with a sampled subset of endowments, preserving the proxy/ground_truth ratio.
- Parameters:
fraction (float) – Fraction of endowments to retain (0 < fraction ≤ 1).
seed (int) – Random seed for reproducibility.
- Returns:
A new object with sampled endowments.
- Return type:
- clone_with_proxy_fraction(fraction=1.0, seed=101)[source]¶
Returns a new Endowments object with a subsampled set of proxy agents, while retaining all ground_truth agents. The sampling preserves the proxy structure but shrinks its size.
- Parameters:
fraction (float) – Fraction of proxy endowments to retain (0 < fraction ≤ 1).
seed (int) – Random seed for reproducibility.
- Returns:
A new object with modified endowments.
- Return type:
- renormalize_ground_truth_weights()[source]¶
Re-normalizes the weights of ground truth endowments so they sum to 1.
Preserves relative weights across ground truth agents. Raises an error if total weight is zero.
This is useful after subsampling endowments, since dropped agents may invalidate the original normalization.
- class p2p.endowments.manager.ActiveEndowments(csv_path=None)[source]¶
Subclass of Endowments that supports mode-aware experiments. Each endowment is expected to include: - ‘mode’: a tuple of theme labels, e.g., (‘core’, ‘economics’) - ‘attributes’: list of attribute names used during generation
- get_entropy_by_mode(responses, survey, normalize=True, top_k=None)[source]¶
Computes normalized entropy for each mode based on responses from agents sharing that mode.
- Parameters:
- Returns:
Sorted list of (mode, entropy score, number of endowments), descending by entropy.
- Return type:
list[tuple]
- clone_with_proxy_modes(included_modes, n_proxies=30, seed=None)[source]¶
Clone a subset of the endowments retaining: - All ground truth agents. - A random subset of proxy agents from the included_modes (default: 30).
- Parameters:
included_modes (list[str]) – Modes to include when sampling proxy agents.
n_proxies (int) – Number of proxy agents to sample (default: 30).
seed (int, optional) – Random seed for reproducibility.
- Returns:
New object containing ground truth + sampled proxy agents.
- Return type:
Generation¶
- class p2p.generators.active_endowment_generator.ActiveEndowmentGenerator(survey, endowment_model, attribute_learner, agent_config, attribute_bank, conductor_factory=None, variability_tracker=None, target_n=100, initial_n=10, num_update_steps=4, seed=None, parallel=False, max_workers=10, verbose=True, question_patch=None, logger=None, token_tracker=None, enable_survey_attributes=True, checkpoint_callback=None, resume_state=None)[source]¶
Generate endowments adaptively to increase response variability in simulated survey experiments.
The generator owns a deep-copied, run-local attribute bank. Attributes discovered during a run use the reserved modes
survey.globalandquestion.<QID>. Response simulation is delegated to an injected conductor factory, keeping backend and agent integration outside this module.- Parameters:
agent_config (Any)
attribute_bank (dict)
conductor_factory (Callable[[...], Any] | None)
question_patch (dict | None)
enable_survey_attributes (bool)
checkpoint_callback (Callable[[dict[str, Any]], Any] | None)
resume_state (dict[str, Any] | None)
- survey¶
Survey containing question definitions and metadata.
- endowment_model¶
Object that generates endowments from attribute lists.
- attribute_learner¶
Object that discovers survey and question attributes.
- agent_config¶
Agent settings consumed by the injected conductor.
- attribute_bank¶
Run-local copy of the injected attribute bank.
- conductor_factory¶
Optional callable that constructs survey conductors.
- tracker¶
Variability tracker used for entropy-guided sampling.
- target_n¶
Total number of endowments to generate.
- initial_n¶
Number sampled per initial mode.
- num_update_steps¶
Maximum number of adaptive expansion steps.
- parallel¶
Whether sampling and simulation may use worker threads.
- max_workers¶
Maximum number of worker threads.
- question_patch¶
Question-targeting allocation settings.
- seed¶
Random seed used by the generator.
- response_log¶
Collected agent-level response records.
- mode_history¶
Modes and attributes used during sampling.
- token_tracker¶
Optional injected usage tracker.
- checkpoint_state()[source]¶
Return JSON-safe state sufficient for an exact paid-run resume.
- Return type:
dict[str, Any]
- add_survey_attributes()[source]¶
Register attributes related to the overall survey under the reserved
survey.globalmode.
- add_question_attributes(qid, max_attributes=None)[source]¶
Register attributes for a question under its reserved
question.<QID>mode.
- generate()[source]¶
Orchestrates the full active generation loop. Starts with initial sampling, then repeatedly expands the pool using entropy-weighted sampling until target_n endowments are generated.
- Returns:
An ActiveEndowments manager with all generated endowments.
- export_mode_summary(path)[source]¶
Saves a summary of endowments generated per mode to CSV.
- Parameters:
path (str) – Path to save the summary file.
- export_responses_to_csv(path, format='code')[source]¶
Converts and saves all collected agent responses to a CSV file.
- Parameters:
path (str) – Path to save the file.
format – Output format to use in Responses class (e.g., “code” or “text”).
Backend-neutral attribute discovery for active generation.
- exception p2p.generators.attribute_learner.AttributeParseError[source]¶
Raised when an attribute response contains no usable values.
- class p2p.generators.attribute_learner.AttributeLearner(*, survey, backend, tracker=None, max_attributes=None, max_retries=2, retry_base_delay=0.5)[source]¶
Discover concise survey attributes through one backend.
- Parameters:
max_attributes (int | None)
max_retries (int)
retry_base_delay (float)
Backend-neutral persona generation for active endowments.
- class p2p.generators.endowment_model.EndowmentModel(*, backend, tracker=None, batch_size=10, delay=0.5, retry_failed=True, randomize_attributes=True, max_attributes=12, seed=None, max_retries=1, retry_base_delay=0.0)[source]¶
Generate normalized persona records through one backend.
- Parameters:
batch_size (int)
delay (float)
retry_failed (bool)
randomize_attributes (bool)
max_attributes (int)
seed (int | None)
max_retries (int)
retry_base_delay (float)
- class p2p.generators.theme_variability_tracker.ThemeVariabilityTracker(questions, smoothing=0.1)[source]¶
Tracks and updates the entropy of survey question responses and the overall response diversity (variability) of agent modes across update steps.
This tracker maintains a running log of agent responses by question and by mode, calculates normalized entropy per question, computes weighted variability per mode, and generates adaptive sampling weights based on response diversity.
It supports plotting of question entropy trajectories, theme variability over time, and current entropy levels to assist in diagnosing diversity and guiding active sampling.
- Parameters:
questions (list[dict])
smoothing (float)
- property tracked_qids¶
Returns: list[str]: The list of question IDs being tracked.
- static categorical_entropy(counts)[source]¶
Computes normalized entropy for a categorical distribution.
- Parameters:
counts (list[int]) – Counts of each category label.
- Returns:
Normalized entropy ∈ [0, 1], with 0 = pure, 1 = uniform.
- Return type:
float
- compute_entropy(qid, responses)[source]¶
Computes normalized entropy for a specific question using its response history.
- Parameters:
qid (str) – Question ID.
responses (list[str]) – List of response strings.
- Returns:
Normalized entropy for the given question and responses.
- Return type:
float
- update_from_records(agent_records)[source]¶
Updates internal response logs and entropy/variability metrics based on a batch of responses.
- Parameters:
agent_records (list[dict]) – List of agent records. Each must contain: - ‘mode’: tuple of attribute labels - ‘responses’: list of answers aligned with self.qid_order
- filter_records(agent_records, survey)[source]¶
Filters agent responses to retain only those for tracked question IDs.
- Parameters:
agent_records (list[dict]) – List of agent records with full response lists.
survey (Survey) – Survey object with all questions.
- Returns:
Filtered agent records with responses only for tracked questions.
- Return type:
list[dict]
- compute_global_question_entropy()[source]¶
Computes the normalized entropy for each tracked question based on all responses.
- Returns:
qid -> normalized entropy
- Return type:
dict
- compute_inverse_entropy_weights()[source]¶
Computes normalized inverse entropy weights over questions for use in weighted variability scoring.
- Returns:
qid -> normalized inverse entropy weight (sums to 1)
- Return type:
dict
- compute_theme_variability(mode)[source]¶
Computes the weighted average entropy across all tracked questions for a given agent mode.
- Parameters:
mode (tuple) – Mode label (e.g. (‘core’, ‘economics’))
- Returns:
Variability score ∈ [0, 1]
- Return type:
float
- get_scores()[source]¶
Computes variability scores with optional smoothing.
- Returns:
mode_tuple -> smoothed variability score
- Return type:
dict
- get_softmax_weights(temperature=0.3)[source]¶
Converts theme variability scores to softmax probabilities for adaptive sampling.
- Parameters:
temperature (float) – Softmax temperature. 0 = greedy, ↑ = smoother.
- Returns:
mode_tuple -> sampling probability
- Return type:
dict
- get_low_entropy_questions(top_k=3)[source]¶
Returns a list of top_k questions with the lowest normalized entropy.
- Parameters:
top_k (int) – Number of low-entropy questions to return.
- Returns:
Question IDs with lowest entropy.
- Return type:
list[str]
- plot_question_entropy_trajectories(base_height=6, width_per_step=1, save_path=None)[source]¶
Plots the entropy trajectory over time for each tracked question, color-coded by trend (rising, falling, stable).
- Parameters:
base_height (int) – Height of plot in inches.
width_per_step (int) – Width scaling factor per update step.
save_path (str) – Path for the saved figure. Default is None.
- plot_theme_variability_trajectories(attribute_bank, base_height=6, width_per_step=1, save_path=None)[source]¶
Plots variability trajectories for each mode over update steps, color-coded by template group using palettes from the attribute bank.
- Parameters:
attribute_bank (dict) – YAML-style config of attribute templates.
base_height (int) – Height of plot.
width_per_step (int) – Width scaling factor per update step.
save_path (str) – Path for the saved figure. Default is None.
- plot_theme_variability_trajectories_legacy(attribute_bank, base_height=6, width_per_step=1, save_path=None)[source]¶
Plots variability trajectories for each mode over update steps, color-coded by template group using palettes from the attribute bank.
- Parameters:
attribute_bank (dict) – YAML-style config of attribute templates.
base_height (int) – Height of plot.
width_per_step (int) – Width scaling factor per update step.
save_path (str) – Path for the saved figure. Default is None.
- plot_entropy_bar_chart(sort=True, figsize=(16, 5), top_n=None, save_path=None)[source]¶
Plots a bar chart of normalized entropy across all tracked questions.
- Parameters:
sort (bool) – Whether to sort bars by entropy.
figsize (tuple) – Size of the plot.
top_n (int or None) – If set, limits display to top-N questions by entropy.
save_path (str) – Path for the saved figure. Default is None.
- class p2p.generators.vanilla_endowment_generator.EndowmentGenerator(backend_type='openai', model_name='gpt-4o-mini', temperature=0.9, max_tokens=1024, batch_size=10, delay=0.5, system_prompt='You are an expert assistant trained to generate realistic, diverse, and demographically plausible personas for social science surveys.\n\nEach persona should include:\n- `eid`: a short, lowercase, variable-safe identifier that encodes key traits (e.g., urban_liberal_30s_female). No punctuation or spaces.\n- `endow_text`: a natural language description of the persona (1–5 sentences), written as if describing a survey respondent.\n\nInstructions:\n- Represent a wide range of age, gender, race/ethnicity, education, region, and political ideology.\n- Avoid repetition of phrasing or demographic combinations across personas.\n- Do not include explanations or formatting outside of the JSON array.\n', retry_failed=True, clear_after_run=False, token_tracker=None, verbose=True, max_retries=10, retry_base_delay=10.0, backend=None, backend_factory=None, **backend_kwargs)[source]¶
Generate baseline personas without entropy guidance or learned attributes.
Backend resolution is injected so this module remains independent of provider registries and agent integrations. Callers may provide either an initialized backend or a factory that resolves one. Backend calls must return the normalized
BackendResponsecontract.- Parameters:
backend_type (str)
model_name (str)
temperature (float)
max_tokens (int)
batch_size (int)
delay (float)
system_prompt (str)
retry_failed (bool)
clear_after_run (bool)
token_tracker (Any | None)
verbose (bool)
max_retries (int)
retry_base_delay (float)
backend (Any | None)
backend_factory (Callable[[...], Any] | None)
- backend_type¶
Provider identifier passed to an injected backend factory.
- model_name¶
Model identifier used for generation and usage reporting.
- temperature¶
Sampling temperature.
- max_tokens¶
Maximum output-token setting passed to a backend factory.
- batch_size¶
Number of personas requested per backend call.
- delay¶
Delay between generation batches.
- system_prompt¶
System instruction for persona generation.
- retry_failed¶
Whether failed prompts should be retried.
- clear_after_run¶
Whether accumulated state is cleared after generation.
- token_tracker¶
Optional injected object exposing normalized
log(...).
- backend¶
Injected or factory-created object exposing
chat(...).
Models¶
- class p2p.models.base.Model(verbose=False)[source]¶
- Parameters:
verbose (bool)
- score(A, y)[source]¶
Return mean squared error.
- Parameters:
A (DataFrame)
y (ndarray)
- Return type:
float
- static safe_df_to_numpy(df)[source]¶
Safely converts a DataFrame to a float64 NumPy array after ensuring all values are numeric.
- Parameters:
df (DataFrame) – Pandas DataFrame to convert.
- Returns:
A NumPy ndarray of float64 values.
- Raises:
ValueError if any value cannot be converted or if NaNs are found. –
- Return type:
ndarray
- class p2p.models.constrained_model.ConstrainedModel(constraint_groups=None, group_total=1.0, nonneg=False, sum_to_one=False, verbose=False, soft_group_sum=True, group_penalty_strength=100.0, solver_fallbacks=('CLARABEL', 'OSQP', 'SCS'), intercept=False)[source]¶
- Parameters:
constraint_groups (dict | None)
group_total (float)
nonneg (bool)
sum_to_one (bool)
verbose (bool)
soft_group_sum (bool)
group_penalty_strength (float)
solver_fallbacks (tuple[str, ...])
intercept (bool)
- solve_with_fallback(problem, beta, label)[source]¶
Solve, falling back to another solver if the configured one fails.
A first-order solver can fail on an individual cross-validation fold while succeeding on every neighbouring one – the stiff group-mass penalty scales with the number of questions, and some folds land badly conditioned. Without a fallback, one such fold aborts an entire run.
Falling back only happens after a genuine failure, so a solve that already succeeds is untouched.
solver_used_records what answered.- Parameters:
problem (Problem)
beta (Variable)
label (str)
Grouped cross-validation for constrained models.
- class p2p.models.constrained_cv.ConstrainedModelCV(*args, fold_groups=None, fold_method='hashed', **kwargs)[source]¶
Cross-validation where constraints follow observation groups.
- Parameters:
fold_groups (Mapping[str, Sequence[str]] | None)
fold_method (str)
- fit(A, y, *, fold_assignments=None)[source]¶
Cross-validate over question-grouped folds.
Folds come from
fold_assignmentswhen given (replay), otherwise fromfold_methodwithrandom_stateas the seed.- Parameters:
A (DataFrame)
y (Series | ndarray)
fold_assignments (FoldAssignment | Mapping[str, int] | None)
- p2p.models.constrained_cv.build_group_folds(groups, *, n_splits, assignments=None, method='hashed', seed=101)[source]¶
Build grouped train/validation indices, one pair per fold.
groupslabels each observation with its question. Without explicitassignmentsthe folds are generated bymethod(seep2p.models.folds); an explicitFoldAssignmentor plain mapping of question -> fold index is used as given, after validation.- Parameters:
n_splits (int)
assignments (FoldAssignment | Mapping[str, int] | None)
method (str)
seed (int | None)
- Return type:
tuple[tuple[ndarray, ndarray], …]
Constrained Lasso model.
- class p2p.models.constrained_lasso.ConstrainedLasso(alpha=1.0, zero_threshold=1e-05, solver=None, **kwargs)[source]¶
Lasso regression with optional constraints and explicit solver choice.
- Parameters:
alpha (float)
zero_threshold (float)
solver (str | None)
Constrained ElasticNet model.
- class p2p.models.constrained_elasticnet.ConstrainedElasticNet(alpha=1.0, l1_ratio=0.5, zero_threshold=1e-05, solver=None, **kwargs)[source]¶
ElasticNet regression with constraints and explicit solver choice.
- Parameters:
alpha (float)
l1_ratio (float)
zero_threshold (float)
solver (str | None)
- class p2p.models.weighted_constrained_lasso.WeightedConstrainedLasso(question_weights=None, **kwargs)[source]¶
Weighted Constrained Lasso. Overrides only the loss term. Constraints, penalties, coefficient thresholding, and metadata storage unchanged.
- class p2p.models.weighted_constrained_elasticnet.WeightedConstrainedElasticNet(question_weights=None, **kwargs)[source]¶
Weighted Constrained ElasticNet. Overrides only the loss term.
Stage 2 without regression: the uniform average of the proxy panel’s answers.
- class p2p.models.simple_average.SimpleAverage(verbose=False)[source]¶
Give every agent in the design the same weight, 1/n.
Nothing is fitted and the targets are not used:
fitonly reads the agent columns. On P2P’s one-hot design, where every retained agent answered every question (column-drop cleaning), a question’s predicted option masses are the shares of agents who chose each option, so they sum to one. This is the no-regression baseline: the same agents and the same evaluation as a fitted model, without the weights.- Parameters:
verbose (bool)
Stage 2 fitted around a fixed reference distribution.
A reference distribution a (one value per option row, summing to one within
each question) enters every prediction with weight one:
prediction = a + X @ beta
The agents therefore explain each target’s deviation from the reference
rather than its level. Internally the estimator is fitted to y - a with the
question-mass target lowered by one, so that each question’s prediction still
sums to the configured total; when every agent answers every question this
makes the agents’ weights sum to zero. The penalty, the cross-validation and
the scoring are those of the wrapped estimator: score and predict work
on the full prediction, so the error of y - a against X @ beta and the
error of y against a + X @ beta are the same number.
The drift refit of a survey across waves is the case where a is the earlier
wave’s marginals.
- p2p.models.reference_distribution.check_reference_masses(reference, constraint_groups, rows)[source]¶
Every question’s reference mass over its modelled rows must be one.
- Parameters:
reference (Mapping[str, float])
constraint_groups (Mapping[str, list[str]])
rows (Index)
- Return type:
None
- p2p.models.reference_distribution.reference_vector(reference, rows)[source]¶
The reference value of every option row, in row order.
- Parameters:
reference (Mapping[str, float])
rows (Index)
- Return type:
ndarray
- p2p.models.reference_distribution.with_reference_distribution(model_cls, reference)[source]¶
Return
model_clsfitted aroundreference(see the module docstring).referencemaps option-row ids to values. The returned class takes the same keyword arguments;group_totalkeeps its meaning as the total of each question’s full prediction.- Parameters:
model_cls (type)
reference (Mapping[str, float])
- Return type:
type
Cross-validation fold assignments: generate, record and replay.
A question is the indivisible cross-validation unit: all of its option rows go
to the same fold. A fold assignment maps every question to a fold index
0 .. n_splits - 1 and fully determines the folds.
Three methods produce an assignment:
hashed(default)Questions are placed largest first; ties between equal-size questions are broken by
sha256("{seed}:{question_id}"). Each question goes to the fold with the fewest option rows so far, the lowest fold index winning a tie. Only the Python standard library decides the order, so the result is identical on every machine and library version.sklearnWhatever the installed
sklearn.model_selection.GroupKFolddoes, without shuffling. scikit-learn up to 1.7 broke ties with an unstable sort whose order depended on the installation (scikit-learn issue #29495), so this is for quick checks, not for reproducible results.replayThe assignment is read from a fold record written by an earlier run.
Every assignment carries a partition_digest that identifies which questions
share a fold, ignoring fold numbering: renumbering folds cannot change any
result, because each penalty is scored by the average error over the folds.
- class p2p.models.folds.FoldAssignment(n_splits, method, seed, assignment, question_sizes, producer, source=None, fold_row_counts=None, origin=None)[source]¶
A complete question -> fold assignment and its provenance.
- Parameters:
n_splits (int)
method (str)
seed (int | None)
assignment (Mapping[str, int])
question_sizes (Mapping[str, int] | None)
producer (Mapping[str, str])
source (str | None)
fold_row_counts (tuple[int, ...] | None)
origin (Mapping[str, Any] | None)
- classmethod from_mapping(assignment, *, n_splits, source=None)[source]¶
Wrap an explicit mapping, e.g. one passed in by a test.
- Parameters:
assignment (Mapping[str, Any])
n_splits (int)
source (str | None)
- Return type:
- property partition_digest: str¶
SHA-256 of the sorted folds, each a sorted list of question ids.
Fold numbering is dropped, so two assignments share a digest exactly when they put the same questions together.
- indices(group_labels)[source]¶
Training and validation row indices for each fold, in fold order.
The labels must name exactly the assigned questions. When the assignment knows its question sizes (or, for schema-1 records, its per-fold row totals), the labels must reproduce those too: a mismatch means the assignment was made for a differently cleaned design.
- Parameters:
group_labels (Sequence[str])
- Return type:
tuple[tuple[ndarray, ndarray], …]
- p2p.models.folds.assign_folds(sizes, *, n_splits, method='hashed', seed=101)[source]¶
Generate a fold assignment for questions of the given sizes.
- Parameters:
sizes (Mapping[str, int])
n_splits (int)
method (str)
seed (int | None)
- Return type:
- p2p.models.folds.count_question_sizes(group_labels)[source]¶
Count the option rows of each question in a per-row label sequence.
- Parameters:
group_labels (Sequence[str])
- Return type:
dict[str, int]
- p2p.models.folds.read_fold_record(path)[source]¶
Read a schema-1 or schema-2 fold record for replay.
Schema 1 is the session-4 parity-fixture format (
validation_groups, optional per-foldvalidation_rows); itsvalidation_qids_digesthas no documented definition and is ignored. Schema 2 is what P2P writes; itspartition_digestis recomputed and must match.- Parameters:
path (str | PathLike[str])
- Return type:
- p2p.models.folds.resolve_fold_assignment(group_labels, *, n_splits, assignments=None, method='hashed', seed=101)[source]¶
Return the assignment a cross-validation run will use.
An explicit
FoldAssignmentor plain mapping wins; otherwise one is generated from the labels withmethodandseed.- Parameters:
group_labels (Sequence[str])
n_splits (int)
assignments (FoldAssignment | Mapping[str, Any] | None)
method (str)
seed (int | None)
- Return type:
- p2p.models.folds.write_fold_record(path, assignment)[source]¶
Write
assignmentas a schema-2 JSON fold record.- Parameters:
path (str | PathLike[str])
assignment (FoldAssignment)
- Return type:
Path