Source code for model.template

from typing import List, Dict, Union, Optional
from pydantic import BaseModel, model_validator, GetCoreSchemaHandler
from pydantic_core import core_schema

[docs] class Variable(BaseModel): """Unified class for defining variables in puzzles. This class supports two mutually exclusive ways of defining variables: - By specifying `type` and `domain`. - By providing a custom formula in `formula`. """ type: Optional[str] = None """The type of the variable. Example: - `"int"`: Represents an integer variable. - `"bool"`: Represents a boolean variable. """ domain: Optional[str] = None """The domain of the variable. Example: - `"[1, 10]"`: Represents integers from 1 to 10. - `"[True, False]"`: Represents boolean values. - `"['red', 'blue', 'green']"`: Represents a selection from the given options. """ formula: Optional[str] = None """The formula used to initialize the variable. Example: - `"randint(1,6) + randint(1,6)"`: Represents the sum of two dice rolls. Notes: - Custom operators defined in the `custom_operator` field of the puzzle template can be used in the formula. - If `formula` is defined, `type` and `domain` must not be defined. """
[docs] @model_validator(mode='after') def validate_exclusive_fields(self): """Validates the mutually exclusive constraints between fields. Raises: ValueError: - If both `formula` and `type`/`domain` are defined. - If `formula` is not defined, but `type` or `domain` is missing. Example: >>> DefinedVar(formula="x+y", type="int") # Raises ValueError >>> DefinedVar(type="int") # Raises ValueError (missing `domain`) >>> DefinedVar(type="int", domain="[1, 10]") # Valid >>> DefinedVar(formula="randint(1,6)") # Valid """ if self.formula: if self.type or self.domain: raise ValueError("`formula` cannot be defined along with `type` or `domain`.") else: if not (self.type and self.domain): raise ValueError("When `formula` is not defined, both `type` and `domain` must be defined.") return self
[docs] class DefinedSymbol(BaseModel): """Base class for defining symbol templates used in puzzle generation. This class provides the fundamental structure for creating basic symbol elements in puzzles. Note: The base data structure is a dictionary, where the keys are the symbol names (or tuples when len(source) > 1) and the values are the Z3 symbols. If only the list of Z3 symbols are needed, use `<dict_name>.to_list()` or `list(<dict_name>)` method to convert the dictionary values to a list. """ source: List[str] """Initialization expressions for the symbol, serving as primary keys. Examples: - When length is 1: The primary key is a string. Example: source = ["children"] with children = ["Alice", "Bob"] will generate two symbols with primary keys "Alice" and "Bob". - When length > 1: The primary key is a tuple. Example: source = ["children", "adults"] with children = ["Alice", "Bob"], adults = ["Chris"] will generate symbols with primary keys ("Alice", "Chris") and ("Bob", "Chris"). """ attr: Optional[List[str]] = None """List of symbol attributes defining additional characteristics. Examples: - ["color", "size"] indicates the symbol has color and size attributes. Notes: - When None (default), symbols can be accessed directly via dictionary keys (event[key]). - When specified, symbols must be accessed using get() method (event[key].get('color')). """ type: Union[str, List[str]] """Type definition rules for the symbol. Valid Values: 'int' (default), 'bool', 'float', and 'enum'. Examples: - Single type string when attr is None: "Bool" - Type list matching attr length when attr exists: ["int", "bool"] """ desc: Optional[Union[str, List[str]]] = None """Description template for the symbol. Examples: - Single string when no attributes: "Basic proposition symbol" - List matching attr length when attributes exist: ["Color description", "Size description"] """
[docs] @model_validator(mode='before') def validate_type_desc_based_on_attr(cls, values): """Validates consistency between attributes, types and descriptions. Raises: ValueError: - ``attr`` is ``None``, but ``type`` is a list. - ``attr`` exists, but ``type`` is not a list. - The format of ``desc`` does not match the presence of ``attr``. Examples: >>> DefinedSymbol(source=["A"], attr=["color"], type="Str") # Raises ValueError >>> DefinedSymbol(source=["A"], desc=["desc1", "desc2"]) # Raises ValueError """ attr = values.get('attr') type_val = values.get('type') desc_val = values.get('desc') if attr is None: # 当 attr 为 None 时,type 应该是 str,desc 应该是 Optional[str] if isinstance(type_val, list): raise ValueError("When attr is None, type must be str") if desc_val is not None and isinstance(desc_val, list): raise ValueError("When attr is None, desc must be str or None") else: # 当 attr 是 list 时,type 和 desc 也应该是 list if not isinstance(type_val, list): raise ValueError("When attr is list, type must be list") if desc_val is not None and not isinstance(desc_val, list): raise ValueError("When attr is list, desc must be list or None") return values
[docs] class DerivedSymbol(BaseModel): """Class defining rules for generating derived symbols from existing ones. The derivation works by first randomly selecting a number of values from the `source` list and creating new symbols with the selected values. Selection Process (before optimization): 1. For each domain (total count specified by ``domain``): a. For each dimension (count specified by ``dim``): i. For each data source in ``source``: - Select ``amount[k]`` items from ``source[k]`` following ``order`` and ``duplicate`` rules b. Verify dimension-level conditions (``dim_cond`` and custom conditions with scope='dim') 2. Verify domain-level conditions (``domain_cond`` and custom conditions with scope='domain') Example .. code-block:: python "max_num": { "source": ["range(p_num)", "range(2, 11)"], "domain": "3", "custom_cond": [{ "scope": "domain", "fields": [0, 1], "constraint": "lambda l: all([item[0][1][0] < money // prices[item[0][0][0]] for item in l])" }], "formula": "num[names[_sym[0]]] <= _sym[1]", "desc": "The maximum number of Award {names[_sym[0]]} that a person can take is {_sym[1]}." } This configuration will: - Generate 3 symbols (``domain = 3``) - Each symbol has 1 dimension (default ``dim = 1``) - Each dimension uses 2 data sources (``len(source) = 2``) - The first source provides integers from 0 to ``p_num - 1`` - The second source provides integers from 2 to 10 - Selects 1 item from each source (default when ``amount`` is None) - Applies custom domain-level constraint on the selected values """ source: List[str] """Data sources for the selection. Each element must be a string representing either: - A stringified list of literals (e.g., '[True, False]') - A variable name containing the data """ amount: Optional[list] = None """Number of items selected from each data source. Examples: - ["2", "1"]: Select 2 items from first source, 1 from second - None: Select exactly 1 item from each source (default) Note: - Length must match ``source`` - Each value must be a string (literal or variable name) """ order: Optional[List[bool]] = None """Permutation configuration per data source during selection. Controls whether selection order matters: - True: Permutation (order matters) - False: Combination (order doesn't matter) Default: All True (order matters for all sources) Note: Length must match ``source`` """ duplicate: Optional[List[bool]] = None """Selection repetition rules per data source during selection. Controls whether duplicates are allowed: - True: Allow duplicate selections - False: Disallow duplicates Default: All False (no duplicates allowed) Note: Length must match ``source`` """ domain: Optional[str] = None """Total number of selections. Examples: - "5": Make 5 selections from the data sources - "n": Use variable `n` to determine count Note: Must be a string representing a literal or variable name """ domain_cond: bool = True """Global repetition rule for symbol selection: Controls whether identical symbol combinations are allowed: - True: Disallow identical combinations - False: Allow duplicates Default: True (no identical combinations) """ dim: int = 1 """Number of dimensions for the derived symbol. Example: - 2 generates two-dimensional symbol matrix (useful for statements with multiple clauses). """ dim_cond: Optional[list] = [] """Inter-dimensional constraints (list of conditions). Example: - [[0, 1], [2]] means values from `source[0]` and `source[1]` cannot be identical at the same time AND values from `source[2]` cannot be identical. Note: - Cannot contain duplicate indices. Default: - [[0, 1, ..., `len(source)`]] (i.e., all selections must be different in at least one source). """ custom_cond: Optional[list] = [] """Custom constraint dictionaries containing: - scope: Application level ('domain'/'dim') - fields: List of field indices from source - constraint: Constraint logic expression. Must be a valid Python lambda function string. - When scope="domain", the input is a 4-dimensional list, where the selected values can be fetched by `l[domain_index][dim_index][source_index (in 'fields')][amount_index]`. - When scope="dim", the input is a 3-dimensional list, where the selected values can be fetched by `l[dim_index][source_index (in 'fields')][amount_index]`. """ formula: Optional[str] = None """Symbol generation formula using Python syntax.""" desc: str = "" """Symbol collection description text for puzzle generation."""
[docs] class DerivedSymbols(BaseModel): """Container for multiple derived symbol templates with random counts.""" total: str """Total number of symbols to generate.""" templates: List[DerivedSymbol] """List of symbol templates for generation."""
[docs] class StaticCondition(BaseModel): """Base constraint definition for puzzle rules.""" formula: str """Constraint logic expression using Python syntax. Example: "x + y < 10" """ desc: Optional[str] = None """Natural language description for puzzle text generation. Example: "Sum of two numbers must be less than 10" """
[docs] class DynamicCondition(StaticCondition): """Extended constraints with multi-dimensional parameters.""" source: list """Data sources (same format as DerivedSymbol).""" amount: Optional[list] = None """Number of selections per data source. (same format as DerivedSymbol)""" order: Optional[List[bool]] = None """Permutation configuration (default all True).""" duplicate: Optional[List[bool]] = None """Repetition rule configuration (default all False).""" domain: Optional[str] = None """Total condition count. Must be a range string "[min, max]". If None, one condition will be generated. Example: - "[1, 5]": Generate between 1 and 5 conditions. """ domain_cond: bool = True """Global repetition rule.""" custom_cond: Optional[list] = [] """Custom constraints (same format as DerivedSymbol)."""
[docs] class PostGen(BaseModel): """ Initialization after computing the problem solution for the first time. (Applicable for scenarios where parameters in the actual problem need to be computed using z3) """ post_gen_vars: Optional[Dict[str, str]] = None """Extracting the values of symbols from _sol (the selected solution) as new variables. Key: The new variable name. Value: The expression to compute the variable value, which can be a string of a Python expression. """ post_gen_conditions: Optional[Dict[str, StaticCondition]] = None """New constraints to add after initial solution. Key: The new constraint name. Value: A string of the formula for the constraint. """
[docs] class Optimize(BaseModel): """Optimization target definition (for optimization problems only).""" type: str """Optimization type ("minimize" or "maximize").""" formula: str """Formula to optimize."""
[docs] class QueryBase(BaseModel): """Base class for question definitions.""" desc: str """Question description text (may contain placeholders). Example: "Which option satisfies the condition?" """
[docs] class QuerySelectionBase(QueryBase): """Multiple-choice question definition.""" query_type: str = "single_choice" """Question type: - 'single_choice': Single correct answer - 'multiple_choice': Multiple correct answers """ select_type: bool = True """Whether to select the correct or incorrect option(s): - True: Select the correct option(s) - False: Select the incorrect option(s) """ opt_num: Optional[int] = 4 """Total number of options to present (default 4)."""
[docs] class QuerySelectionTemplate(BaseModel): """Template for multiple-choice options. The option generation process works by randomly selecting a number of values from the `source` list to create options. """ source: list """Data source. (Same format as DerivedSymbol.source)""" amount: Optional[list] = None """Number of selections per source. (Same format as DerivedSymbol.amount)""" order: Optional[List[bool]] = None """Permutation configuration (default first dimension False, others True).""" duplicate: Optional[List[bool]] = None """Repetition rule configuration. (default all False)""" cond: str = 'any' """Constraint scope: - 'any': At least one solution satisfies - 'all': All solutions satisfy """ opt_formula: str """Option correctness evaluation expression. Example: "x % 2 == 0" """ opt_text: Optional[str] = None """Option display template (may contain placeholders). Example: "{_opt[0][0]}" Note: - Automatically prefixed with ABCD, no need to include in string. """ custom_cond: Optional[list] = [] """Custom constraints (same format as DerivedSymbol.custom_cond)."""
[docs] class QuerySelectionWithSingleTemplate(QuerySelectionBase, QuerySelectionTemplate): """Single template for multiple-choice questions.""" pass
[docs] class QuerySelectionWithMultipleTemplates(QuerySelectionBase): """Multiple templates for multiple-choice questions.""" templates: List[QuerySelectionTemplate] """List of option templates."""
[docs] class Query(QueryBase): """Open-ended question definition.""" ans_formula: str """Answer generation formula.""" ans_text: str """Answer text format.""" ans_assertion: Optional[str] = "len(_solutions) == 1" """Assertion for answer validation."""
[docs] class PuzzleTemplate(BaseModel): """Main puzzle template structure integrating all components.""" custom_operator: Optional[Dict[str, str]] = None """Dictionary of custom operators. Key: Operator name. Value: Python expression string defining the operator OR the path to a Python file containing the operator definition. Example: {"double": "lambda x: x * 2", "reformat": "customs/mathexpr_generator.py"} """ variables: Dict[str, Variable] """Dictionary of variable definitions (name: definition).""" symbols: Optional[Dict[str, Union[DefinedSymbol, DerivedSymbols, DerivedSymbol]]] = None """Dictionary of symbol definitions (name: definition).""" conditions: Optional[Dict[str, Union[StaticCondition, DynamicCondition]]] = None """Dictionary of conditions.""" calc_solution: bool = True """Whether to compute solutions (default True).""" max_solution: int = 6000 """Maximum number of solutions to generate. Note: - Here, "solution" refers to a valid configuration of all symbols that satisfies all constraints. It does NOT mean the number of valid answers to the final question. - If the number of solutions exceeds this limit, the solver will stop and raise an exception. """ post_generation: Optional[PostGen] = None """Post-generation configuration including: - post_gen_vars: New variables from solutions - post_gen_conditions: Additional constraints """ optimize: Optional[Optimize] = None """Optimization target (for optimization problems only).""" queries: Optional[Dict[str, Union[QuerySelectionWithMultipleTemplates, QuerySelectionWithSingleTemplate, Query]]] = None """Dictionary of question definitions.""" desc: str """Overall template description for puzzle introduction."""