Skip to content

Commit

Permalink
Retire the cooldown factor (#253)
Browse files Browse the repository at this point in the history
It turned out that not much effect can be expected from the
cooldown mechanism. Thus removing the cooldown factor from both
the API and CLI arguments. The CooldownModel is renamed to
WeightedModel to keep the slightly separate weight-adjusting
functionality.
  • Loading branch information
akosthekiss authored Nov 24, 2024
1 parent d6d95ce commit 5c7128d
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 144 deletions.
27 changes: 7 additions & 20 deletions docs/guide/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,32 +111,19 @@ models are:
return super().charset(node, idx, non_zero_chars)
return super().charset(node, idx, chars)
3. :class:`grammarinator.runtime.CooldownModel`: This model enhances the
behavior of another model by introducing a cooldown mechanism. It manages
cooldown multipliers that adjust the weights of the alternatives
selected by the underlying model. This adjustment helps increasing grammar
coverage by reducing the probability of alternatives that have already
been generated.

The cooldown factor can be set using the ``--cooldown`` argument of the
:ref:`grammarinator-generate<grammarinator-generate>` script or through
the constructor of :class:`grammarinator.tool.DefaultGeneratorFactory`.
By default, the multiplier of each alternative starts from 1, unless
custom values are assigned to specific alternatives. This assignment
can happen through the constructor of CooldownModel (when using the API)
3. :class:`grammarinator.runtime.WeightedModel`: This model modifies the
behavior of another model by adjusting (pre-multiplying) the weights of
alternatives. By default, the multiplier of each alternative starts from 1,
unless custom values are assigned to specific alternatives. This assignment
can happen through the constructor of WeightedModel (when using the API)
or with the ``--weigths`` CLI option of the
:ref:`grammarinator-generate<grammarinator-generate>` utility by providing
a file containing the weights. Whenever an alternative is selected during
generation, the model scales down its multiplier with a cooldown factor
from the [0,1] interval that can be set using the ``--cooldown`` argument
of the :ref:`grammarinator-generate<grammarinator-generate>` script or
through the constructor of
:class:`grammarinator.tool.DefaultGeneratorFactory`.
a file containing the weights.

The expected format of the weights differs depending on whether
Grammarinator is used from API or from CLI. When using the API, a compact
representation is used, which is not JSON serializable. For API usage,
refer to the documention of :class:`grammarinator.runtime.CooldownModel`.
refer to the documention of :class:`grammarinator.runtime.WeightedModel`.
When providing weights from the CLI, then the input JSON file should have
the following format:

Expand Down
4 changes: 2 additions & 2 deletions docs/guide/test_generation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ substituted by the generator. Alternatively, if the ``--stdout`` argument is
provided, the test cases will be printed to the standard output.

The behavior of the generator can be customized using :doc:`models <models>`
(``--model`` and ``--cooldown``), :doc:`listeners <listeners>`
(``--listener``), :doc:`transformers <transformers>` (``--transform``), and
(``--model`` and ``--weights``), :doc:`listeners <listeners>` (``--listener``),
:doc:`transformers <transformers>` (``--transform``), and
:doc:`serializers <serializers>` (``--serialize``).

If a directory containing Grammarinator trees is specified using the
Expand Down
18 changes: 3 additions & 15 deletions grammarinator/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import os
import random

from argparse import ArgumentParser, ArgumentTypeError, SUPPRESS
from argparse import ArgumentParser, SUPPRESS
from functools import partial
from itertools import count
from math import inf
Expand All @@ -25,13 +25,6 @@
from .tool import DefaultGeneratorFactory, DefaultPopulation, GeneratorTool


def restricted_float(value):
value = float(value)
if value <= 0.0 or value > 1.0:
raise ArgumentTypeError(f'{value!r} not in range (0.0, 1.0]')
return value


def process_args(args):
args.generator = import_object(args.generator)
args.model = import_object(args.model)
Expand Down Expand Up @@ -60,11 +53,9 @@ def process_args(args):
def generator_tool_helper(args, weights, lock):
return GeneratorTool(generator_factory=DefaultGeneratorFactory(args.generator,
model_class=args.model,
cooldown=args.cooldown,
weights=weights,
lock=lock,
listener_classes=args.listener),
rule=args.rule, out_format=args.out,
rule=args.rule, out_format=args.out, lock=lock,
limit=RuleSize(depth=args.max_depth, tokens=args.max_tokens),
population=DefaultPopulation(args.population, args.tree_extension, args.tree_codec) if args.population else None,
generate=args.generate, mutate=args.mutate, recombine=args.recombine, keep_trees=args.keep_trees,
Expand Down Expand Up @@ -101,9 +92,6 @@ def execute():
help='maximum recursion depth during generation (default: %(default)f).')
parser.add_argument('--max-tokens', default=RuleSize.max.tokens, type=int, metavar='NUM',
help='maximum token number during generation (default: %(default)f).')
parser.add_argument('-c', '--cooldown', default=1.0, type=restricted_float, metavar='NUM',
help='cool-down factor defines how much the probability of an alternative should decrease '
'after it has been chosen (interval: (0, 1]; default: %(default)f).')
parser.add_argument('-w', '--weights', metavar='FILE',
help='JSON file defining custom weights for alternatives.')

Expand Down Expand Up @@ -152,7 +140,7 @@ def execute():

if args.jobs > 1:
with Manager() as manager:
with generator_tool_helper(args, weights=manager.dict(args.weights), lock=manager.Lock()) as generator_tool: # pylint: disable=no-member
with generator_tool_helper(args, weights=args.weights, lock=manager.Lock()) as generator_tool:
parallel_create_test = partial(create_test, generator_tool, seed=args.random_seed)
with Pool(args.jobs) as pool:
for _ in pool.imap_unordered(parallel_create_test, count(0) if args.n == inf else range(args.n)):
Expand Down
2 changes: 1 addition & 1 deletion grammarinator/runtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
# This file may not be copied, modified, or distributed except
# according to those terms.

from .cooldown_model import CooldownModel
from .default_model import DefaultModel
from .dispatching_listener import DispatchingListener
from .dispatching_model import DispatchingModel
Expand All @@ -15,3 +14,4 @@
from .population import Annotations, Individual, Population
from .rule import ParentRule, Rule, UnlexerRule, UnparserRule, UnparserRuleAlternative, UnparserRuleQuantified, UnparserRuleQuantifier
from .serializer import simple_space_serializer
from .weighted_model import WeightedModel
64 changes: 0 additions & 64 deletions grammarinator/runtime/cooldown_model.py

This file was deleted.

51 changes: 51 additions & 0 deletions grammarinator/runtime/weighted_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright (c) 2020-2024 Renata Hodovan, Akos Kiss.
#
# Licensed under the BSD 3-Clause License
# <LICENSE.rst or https://opensource.org/licenses/BSD-3-Clause>.
# This file may not be copied, modified, or distributed except
# according to those terms.

from .model import Model


class WeightedModel(Model):
"""
Custom model (or model wrapper) that pre-multiplies the weights of
alternatives before calling the underlying model.
"""

def __init__(self, model, *, weights=None):
"""
:param Model model: The underlying model.
:param dict[tuple,float] weights: Multipliers of alternatives. The keys
of the dictionary are tuples in the form of ``(str, int, int)``,
each denoting an alternative: the first element specifies the name
of the rule that contains the alternative, the second element
specifies the index of the alternation containing the alternative
within the rule, and the third element specifies the index of the
alternative within the alternation (both indices start counting from
0). The first and second elements correspond to the ``node`` and
``idx`` parameters of :meth:`choice`, while the third element
corresponds to the indices of the ``weights`` parameter.
"""
self._model = model
self._weights = weights or {}

def choice(self, node, idx, weights):
"""
Transitively calls the ``choice`` method of the underlying model with
multipliers applied to ``weights`` first.
"""
return self._model.choice(node, idx, [w * self._weights.get((node.name, idx, i), 1) for i, w in enumerate(weights)])

def quantify(self, node, idx, cnt, start, stop):
"""
Trampoline to the ``quantify`` method of the underlying model.
"""
return self._model.quantify(node, idx, cnt, start, stop)

def charset(self, node, idx, chars):
"""
Trampoline to the ``charset`` method of the underlying model.
"""
return self._model.charset(node, idx, chars)
19 changes: 5 additions & 14 deletions grammarinator/tool/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from os.path import abspath, dirname
from shutil import rmtree

from ..runtime import CooldownModel, DefaultModel, RuleSize
from ..runtime import DefaultModel, RuleSize, WeightedModel

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -74,32 +74,23 @@ class DefaultGeneratorFactory(GeneratorFactory):
"""

def __init__(self, generator_class, *,
model_class=None, cooldown=1.0, weights=None, lock=None,
model_class=None, weights=None,
listener_classes=None):
"""
:param type[~grammarinator.runtime.Generator] generator_class: The class
of the generator to instantiate.
:param type[~grammarinator.runtime.Model] model_class: The class of the
model to instantiate. The model instance is used to instantiate the
generator.
:param float cooldown: Cooldown factor. Used to instantiate a
:class:`~grammarinator.runtime.CooldownModel` wrapper around the
model.
:param dict[tuple,float] weights: Initial multipliers of alternatives.
Used to instantiate a :class:`~grammarinator.runtime.CooldownModel`
Used to instantiate a :class:`~grammarinator.runtime.WeightedModel`
wrapper around the model.
:param multiprocessing.Lock lock: Lock object when generating in
parallel. Used to instantiate a
:class:`~grammarinator.runtime.CooldownModel` wrapper around the
model.
:param list[type[~grammarinator.runtime.Listener]] listener_classes:
List of listener classes to instantiate and attach to the generator.
"""
super().__init__(generator_class)
self._model_class = model_class or DefaultModel
self._cooldown = cooldown
self._weights = weights
self._lock = lock
self._listener_classes = listener_classes or []

def __call__(self, limit=None):
Expand All @@ -117,8 +108,8 @@ def __call__(self, limit=None):
:rtype: ~grammarinator.runtime.Generator
"""
model = self._model_class()
if self._cooldown < 1 or self._weights:
model = CooldownModel(model, cooldown=self._cooldown, weights=self._weights, lock=self._lock)
if self._weights:
model = WeightedModel(model, weights=self._weights)

listeners = []
for listener_class in self._listener_classes:
Expand Down
28 changes: 0 additions & 28 deletions tests/grammars/Alternatives.g4

This file was deleted.

0 comments on commit 5c7128d

Please sign in to comment.