Skip to content

Typo-generation models

models

Define task, configuration, and result models for typo generation.

REPLACE_ONLY_TYPO_DISTRIBUTION module-attribute

REPLACE_ONLY_TYPO_DISTRIBUTION = TypoWeightDistribution(
    replace=1.0
)

Distribution that selects only replacement errors.

TRANSPOSE_ONLY_TYPO_DISTRIBUTION module-attribute

TRANSPOSE_ONLY_TYPO_DISTRIBUTION = TypoWeightDistribution(
    transpose=1.0
)

Distribution that selects only transposition errors.

DELETE_ONLY_TYPO_DISTRIBUTION module-attribute

DELETE_ONLY_TYPO_DISTRIBUTION = TypoWeightDistribution(
    delete=1.0
)

Distribution that selects only deletion errors.

INSERT_ONLY_TYPO_DISTRIBUTION module-attribute

INSERT_ONLY_TYPO_DISTRIBUTION = TypoWeightDistribution(
    insert=1.0
)

Distribution that selects only insertion errors.

DEFAULT_SINGLE_ERROR_TYPO_DISTRIBUTIONS module-attribute

Default single-error distributions, one for each supported operation type.

DEFAULT_MIXED_ERROR_TYPO_DISTRIBUTION module-attribute

DEFAULT_MIXED_ERROR_TYPO_DISTRIBUTION = (
    TypoWeightDistribution(
        replace=0.28,
        transpose=0.28,
        delete=0.28,
        insert=0.15,
    )
)

Mixed distribution mirroring MULTYPO's built-in operation weights.

TypoWeightDistribution dataclass

Represent the weight mapping of the MULTYPO typo operations.

Values are non-negative weights. MULTYPO normalizes the supplied mapping, so they do not need to sum to one.

Attributes:

Name Type Description
replace float

Replacement-operation weight.

transpose float

Transposition-operation weight.

delete float

Deletion-operation weight.

insert float

Insertion-operation weight.

Source code in hotstring\typo_generation\models.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@dataclass(frozen=True, slots=True)
class TypoWeightDistribution:
    """Represent the weight mapping of the MULTYPO typo operations.

    Values are non-negative weights. MULTYPO normalizes the supplied mapping,
    so they do not need to sum to one.

    Attributes:
        replace:
            Replacement-operation weight.
        transpose:
            Transposition-operation weight.
        delete:
            Deletion-operation weight.
        insert:
            Insertion-operation weight.
    """

    replace: float = 0.0
    transpose: float = 0.0
    delete: float = 0.0
    insert: float = 0.0

    def __post_init__(self) -> None:
        """Validate typo-distribution weights.

        Raises:
            TypeError:
                If a weight is not a real number or is a boolean.
            ValueError:
                If a weight is negative or all weights are zero.
        """
        values = (self.replace, self.transpose, self.delete, self.insert)
        for value in values:
            if isinstance(value, bool) or not isinstance(value, Real):
                raise TypeError("Typo distribution weights must be real numbers.")
            if value < 0:
                raise ValueError("Typo distribution weights cannot be negative.")

        if not any(value > 0 for value in values):
            raise ValueError("At least one typo distribution weight must be positive.")

    @property
    def distribution(self) -> dict[str, float]:
        """Return the mapping expected by `MultiTypoGenerator`.

        Returns:
            Typo-operation weight mapping.
        """
        return {
            "replace": float(self.replace),
            "transpose": float(self.transpose),
            "delete": float(self.delete),
            "insert": float(self.insert),
        }

distribution property

distribution: dict[str, float]

Return the mapping expected by MultiTypoGenerator.

Returns:

Type Description
dict[str, float]

Typo-operation weight mapping.

__post_init__

__post_init__() -> None

Validate typo-distribution weights.

Raises:

Type Description
TypeError

If a weight is not a real number or is a boolean.

ValueError

If a weight is negative or all weights are zero.

Source code in hotstring\typo_generation\models.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def __post_init__(self) -> None:
    """Validate typo-distribution weights.

    Raises:
        TypeError:
            If a weight is not a real number or is a boolean.
        ValueError:
            If a weight is negative or all weights are zero.
    """
    values = (self.replace, self.transpose, self.delete, self.insert)
    for value in values:
        if isinstance(value, bool) or not isinstance(value, Real):
            raise TypeError("Typo distribution weights must be real numbers.")
        if value < 0:
            raise ValueError("Typo distribution weights cannot be negative.")

    if not any(value > 0 for value in values):
        raise ValueError("At least one typo distribution weight must be positive.")

TypoGenerationTask dataclass

Describe one independently executable typo-generation task.

Attributes:

Name Type Description
distribution TypoWeightDistribution

Typo-operation distribution used by the task.

typo_rate float

MULTYPO typo rate passed directly to insert_typos.

generation_attempts_per_word int

Number of independent samples requested for each eligible word.

minimum_word_length int

Minimum source-word length eligible for this task.

Source code in hotstring\typo_generation\models.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
@dataclass(frozen=True, slots=True, kw_only=True)
class TypoGenerationTask:
    """Describe one independently executable typo-generation task.

    Attributes:
        distribution:
            Typo-operation distribution used by the task.
        typo_rate:
            MULTYPO typo rate passed directly to `insert_typos`.
        generation_attempts_per_word:
            Number of independent samples requested for each eligible word.
        minimum_word_length:
            Minimum source-word length eligible for this task.
    """

    distribution: TypoWeightDistribution
    typo_rate: float
    generation_attempts_per_word: int
    minimum_word_length: int

    def __post_init__(self) -> None:
        """Validate the task definition.

        Raises:
            TypeError:
                If a field has an invalid type.
            ValueError:
                If the typo rate, attempt count, or minimum length is invalid.
        """
        if not isinstance(self.distribution, TypoWeightDistribution):
            raise TypeError(
                "distribution must be a TypoWeightDistribution instance, "
                f"not {type(self.distribution).__name__}."
            )
        if isinstance(self.typo_rate, bool) or not isinstance(self.typo_rate, Real):
            raise TypeError("typo_rate must be a real number.")
        if self.typo_rate <= 0:
            raise ValueError("typo_rate must be greater than zero.")
        if isinstance(self.generation_attempts_per_word, bool) or not isinstance(
            self.generation_attempts_per_word, int
        ):
            raise TypeError("generation_attempts_per_word must be an integer.")
        if self.generation_attempts_per_word <= 0:
            raise ValueError("generation_attempts_per_word must be greater than zero.")
        if isinstance(self.minimum_word_length, bool) or not isinstance(
            self.minimum_word_length, int
        ):
            raise TypeError("minimum_word_length must be an integer.")
        if self.minimum_word_length < 2:
            raise ValueError("minimum_word_length must be at least 2.")

__post_init__

__post_init__() -> None

Validate the task definition.

Raises:

Type Description
TypeError

If a field has an invalid type.

ValueError

If the typo rate, attempt count, or minimum length is invalid.

Source code in hotstring\typo_generation\models.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def __post_init__(self) -> None:
    """Validate the task definition.

    Raises:
        TypeError:
            If a field has an invalid type.
        ValueError:
            If the typo rate, attempt count, or minimum length is invalid.
    """
    if not isinstance(self.distribution, TypoWeightDistribution):
        raise TypeError(
            "distribution must be a TypoWeightDistribution instance, "
            f"not {type(self.distribution).__name__}."
        )
    if isinstance(self.typo_rate, bool) or not isinstance(self.typo_rate, Real):
        raise TypeError("typo_rate must be a real number.")
    if self.typo_rate <= 0:
        raise ValueError("typo_rate must be greater than zero.")
    if isinstance(self.generation_attempts_per_word, bool) or not isinstance(
        self.generation_attempts_per_word, int
    ):
        raise TypeError("generation_attempts_per_word must be an integer.")
    if self.generation_attempts_per_word <= 0:
        raise ValueError("generation_attempts_per_word must be greater than zero.")
    if isinstance(self.minimum_word_length, bool) or not isinstance(
        self.minimum_word_length, int
    ):
        raise TypeError("minimum_word_length must be an integer.")
    if self.minimum_word_length < 2:
        raise ValueError("minimum_word_length must be at least 2.")

TypoGenerationConfig dataclass

Configure generator settings shared by every task in one run.

Attributes:

Name Type Description
language str

MULTYPO language identifier.

use_excluding_set bool

Whether MULTYPO's language excluding set is enabled.

horizontal_vs_vertical tuple[float, float]

Relative horizontal and vertical keyboard-neighbor weights.

Source code in hotstring\typo_generation\models.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
@dataclass(frozen=True, slots=True, kw_only=True)
class TypoGenerationConfig:
    """Configure generator settings shared by every task in one run.

    Attributes:
        language:
            MULTYPO language identifier.
        use_excluding_set:
            Whether MULTYPO's language excluding set is enabled.
        horizontal_vs_vertical:
            Relative horizontal and vertical keyboard-neighbor weights.
    """

    language: str = "english"
    use_excluding_set: bool = True
    horizontal_vs_vertical: tuple[float, float] = (9.0, 1.0)

    def __post_init__(self) -> None:
        """Validate shared typo-generation configuration."""
        if not isinstance(self.language, str):
            raise TypeError("language must be a string.")
        if not self.language:
            raise ValueError("language cannot be empty.")
        if not isinstance(self.use_excluding_set, bool):
            raise TypeError("use_excluding_set must be a boolean.")
        if not isinstance(self.horizontal_vs_vertical, tuple):
            raise TypeError("horizontal_vs_vertical must be a tuple.")
        if len(self.horizontal_vs_vertical) != 2:
            raise ValueError("horizontal_vs_vertical must contain exactly two weights.")
        for weight in self.horizontal_vs_vertical:
            if isinstance(weight, bool) or not isinstance(weight, Real):
                raise TypeError("Keyboard-neighbor weights must be real numbers.")
            if weight <= 0:
                raise ValueError("Keyboard-neighbor weights must be positive.")

__post_init__

__post_init__() -> None

Validate shared typo-generation configuration.

Source code in hotstring\typo_generation\models.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def __post_init__(self) -> None:
    """Validate shared typo-generation configuration."""
    if not isinstance(self.language, str):
        raise TypeError("language must be a string.")
    if not self.language:
        raise ValueError("language cannot be empty.")
    if not isinstance(self.use_excluding_set, bool):
        raise TypeError("use_excluding_set must be a boolean.")
    if not isinstance(self.horizontal_vs_vertical, tuple):
        raise TypeError("horizontal_vs_vertical must be a tuple.")
    if len(self.horizontal_vs_vertical) != 2:
        raise ValueError("horizontal_vs_vertical must contain exactly two weights.")
    for weight in self.horizontal_vs_vertical:
        if isinstance(weight, bool) or not isinstance(weight, Real):
            raise TypeError("Keyboard-neighbor weights must be real numbers.")
        if weight <= 0:
            raise ValueError("Keyboard-neighbor weights must be positive.")

RawTypoSample dataclass

Represent one successful noisy-word sample.

Attributes:

Name Type Description
noisy_word str

Generated typo candidate.

target_word str

Correct source word the typo should map back to.

Source code in hotstring\typo_generation\models.py
220
221
222
223
224
225
226
227
228
229
230
231
232
@dataclass(frozen=True, slots=True)
class RawTypoSample:
    """Represent one successful noisy-word sample.

    Attributes:
        noisy_word:
            Generated typo candidate.
        target_word:
            Correct source word the typo should map back to.
    """

    noisy_word: str
    target_word: str

TypoGenerationResult dataclass

Represent aggregated output of the typo-generation stage.

Attributes:

Name Type Description
config TypoGenerationConfig

Shared generator configuration used for generation.

tasks tuple[TypoGenerationTask, ...]

Ordered generation tasks that were executed.

source_word_count int

Number of source words supplied before per-task length filtering.

generated_sample_count int

Number of successful raw samples before deduplication.

candidates dict[str, str]

Unique noisy-word to target-word mappings.

clashes dict[str, tuple[str, ...]]

Noisy words that mapped to more than one target word.

Source code in hotstring\typo_generation\models.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
@dataclass(frozen=True, slots=True)
class TypoGenerationResult:
    """Represent aggregated output of the typo-generation stage.

    Attributes:
        config:
            Shared generator configuration used for generation.
        tasks:
            Ordered generation tasks that were executed.
        source_word_count:
            Number of source words supplied before per-task length filtering.
        generated_sample_count:
            Number of successful raw samples before deduplication.
        candidates:
            Unique noisy-word to target-word mappings.
        clashes:
            Noisy words that mapped to more than one target word.
    """

    config: TypoGenerationConfig
    tasks: tuple[TypoGenerationTask, ...]
    source_word_count: int
    generated_sample_count: int
    candidates: dict[str, str]
    clashes: dict[str, tuple[str, ...]]

    @property
    def unique_noisy_word_count(self) -> int:
        """Return the number of unique noisy forms after aggregation.

        Returns:
            Candidate count plus internal clash count.
        """
        return len(self.candidates) + len(self.clashes)

unique_noisy_word_count property

unique_noisy_word_count: int

Return the number of unique noisy forms after aggregation.

Returns:

Type Description
int

Candidate count plus internal clash count.

create_default_typo_generation_tasks

create_default_typo_generation_tasks(
    *,
    single_error_attempts_per_word: int,
    multi_error_attempts_per_word: int,
    multi_error_minimum_word_length: int,
) -> tuple[TypoGenerationTask, ...]

Create the project's default single- and two-error task set.

Parameters:

Name Type Description Default
single_error_attempts_per_word int

Sampling attempts per eligible word for each forced single-error task.

required
multi_error_attempts_per_word int

Sampling attempts per eligible word for the mixed two-error task.

required
multi_error_minimum_word_length int

Minimum word length for the mixed two-error task.

required

Returns:

Type Description
tuple[TypoGenerationTask, ...]

Ordered default task tuple.

Source code in hotstring\typo_generation\models.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def create_default_typo_generation_tasks(
    *,
    single_error_attempts_per_word: int,
    multi_error_attempts_per_word: int,
    multi_error_minimum_word_length: int,
) -> tuple[TypoGenerationTask, ...]:
    """Create the project's default single- and two-error task set.

    Args:
        single_error_attempts_per_word:
            Sampling attempts per eligible word for each forced single-error task.
        multi_error_attempts_per_word:
            Sampling attempts per eligible word for the mixed two-error task.
        multi_error_minimum_word_length:
            Minimum word length for the mixed two-error task.

    Returns:
        Ordered default task tuple.
    """
    single_error_tasks = tuple(
        TypoGenerationTask(
            distribution=distribution,
            typo_rate=1.0,
            generation_attempts_per_word=single_error_attempts_per_word,
            minimum_word_length=2,
        )
        for distribution in DEFAULT_SINGLE_ERROR_TYPO_DISTRIBUTIONS
    )
    multi_error_task = TypoGenerationTask(
        distribution=DEFAULT_MIXED_ERROR_TYPO_DISTRIBUTION,
        typo_rate=2.0,
        generation_attempts_per_word=multi_error_attempts_per_word,
        minimum_word_length=multi_error_minimum_word_length,
    )
    return (*single_error_tasks, multi_error_task)