<commit_before>
Domains
=======

.. currentmodule:: haydi

This section introduces the core structure of *Haydi*: *domains* and basic operations on
them. Advanced domains and canonical forms are covered in a separate section:
:doc:`cnfs`.


Elementary Domains
------------------

One of basic structures in Haydi is :class:`Domain` that represents a generic
collection of arbitrary objects. Elements in the domain can be iterated or
randomly generated.

There are four *elementary domains* shipped with Haydi: :class:`Range` (range of
integers), :class:`Values` (domain of explicitly listed Python objects),
:class:`ASet` (set of atoms) and :class:`Boolean` (two-element domain).
Examples::

  >>> import haydi as hd

  >>> hd.Range(4)  # Domain of four integers
  <Range size=4 {0, 1, 2, 3}>

  >>> hd.Values(["Haystack", "diver"])
  <Values size=2 {'Haystack', 'diver'}>

  >>> hd.ASet(3, "a")  # A set of three atoms
  <ASet id=1 size=3 name=a>

  >>> hd.Boolean()
  <Boolean size=2 {False, True}>


:class:`ASet` is a little bit special and it is designed for enumerating
non-isomorphic structures. It is covered in :doc:`cnfs`, we will not use it in
this section. For more information about each elementary domain, see their API
documentations.


Composition
-----------

New domains can be created by composing existing ones. There are the following
compositions: *product*, *sequences*, *subsets*, *mappings*, and *join*.


Cartesian product :math:`(A \times B)`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:class:`Product` creates a domain of all ordered tuples, for example::

    >>> import haydi as hd
    >>> a = hd.Range(2)
    >>> b = hd.Values(("a", "b", "c"))
    >>> hd.Product((a, b))
    <Product size=6 {(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), ...}>

alternatively, the same thing can be written by using the infix operator ``*``::

    >>> a * b
    <Product size=6 {(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), ...}>

The product can be created on more than two domains::

    >>> hd.Product((a, b, hd.Values["x", "y"]))
    <Product size=12{(0, 'a', 'x'), (0, 'a', 'y'), (0, 'b', 'x'), ...}>

    >>> a * b * hd.Values["x", "y"])
    <Product size=12{(0, 'a', 'x'), (0, 'a', 'y'), (0, 'b', 'x'), ...}>


Sequences :math:`(A^n)`
~~~~~~~~~~~~~~~~~~~~~~~

:class:`Sequences` is a shortcut for a product over the same domain.
Sequences of a given length can be defined as::

     >>> import haydi as hd
     >>> a = hd.Range(2)
     >>> hd.Sequences(a, 3)  # Sequences of length 3
     <Sequences size=8 {(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), ...}>

or sequences with a length in a given range::

     >>> hd.Sequences(a, 0, 2)  # Sequences of length 0 to 2
     <Sequences size=7 {(), (0,), (1,), (0, 0), ...}>


Subsets :math:`(\mathcal{P}(A))`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The :class:`Subsets` domain creates sets of elements of a given domain;
the following example creates the power set::

      >>> import haydi as hd
      >>> hd.Subsets(hd.Range(2))
      <Subsets size=4 {{}, {0}, {0, 1}, {1}}>

When one argument is provided, it is used to limit subsets to a given size::

      >>> hd.Subsets(hd.Range(3), 2)  # Subsets of size 2
      <Subsets size=3 {{0, 1}, {0, 2}, {1, 2}}>

Two arguments limit the subsets to a size in a given range::

      >>> hd.Subsets(hd.Range(3), 0, 1)  # Subsets of size between 0 and 1
      <Subsets size=4 {{}, {0}, {1}, {2}}>

.. note ::

   Type of elements created by :class:`Subsets` is *not* the standard Python
   ``set``, but :class:`haydi.Set`. For more information, see :doc:`btypes`.


Mappings :math:`(A \rightarrow B)`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The domain :class:`Mappings` creates all mappings from a domain to another
domain::

     >>> import haydi as hd
     >>> a = hd.Range(2)
     >>> b = hd.Values(["a", "b"])
     >>> hd.Mappings(a, b)
     <Mappings size=4 {{0: 'a'; 1: 'a'}, {0: 'a'; 1: 'b'}, {0: 'b'; , ...}>

.. note ::

   Type of elements created by :class:`Mappings` is *not* the standard Python
   ``dict``, but :class:`haydi.Map`. For more information, see :doc:`btypes`.



Join :math:`(A \uplus B)`
~~~~~~~~~~~~~~~~~~~~~~~~~

Join operations creates a new domain that contains elements of all given
domains (disjoint union)::

  >>> import haydi as hd
  >>> a = hd.Range(2)
  >>> b = hd.Values(["abc", "ikl", "xyz"])
  >>> c = hd.Values([123])

  >>> hd.Join((a, b, c))
  <Join size=6 {0, 1, 'abc', 'ikl', ...}>

The same behavior can be also achieved by `+` operator on domains::

  >>> a + b + c
  <Join size=6 {0, 1, 'abc', 'ikl', ...}>

Note that Join does not collapse the same elements in the joined domains::

  >>> a = hd.Range(2)
  >>> b = hd.Range(3)
  >>> a + b
  <Join size=5 {0, 1, 0, 1, 2}><commit_msg>DOC: Documentation for map_class and set_class<commit_after>
Domains
=======

.. currentmodule:: haydi

This section introduces the core structure of *Haydi*: *domains* and basic operations on
them. Advanced domains and canonical forms are covered in a separate section:
:doc:`cnfs`.


Elementary Domains
------------------

One of basic structures in Haydi is :class:`Domain` that represents a generic
collection of arbitrary objects. Elements in the domain can be iterated or
randomly generated.

There are four *elementary domains* shipped with Haydi: :class:`Range` (range of
integers), :class:`Values` (domain of explicitly listed Python objects),
:class:`ASet` (set of atoms) and :class:`Boolean` (two-element domain).
Examples::

  >>> import haydi as hd

  >>> hd.Range(4)  # Domain of four integers
  <Range size=4 {0, 1, 2, 3}>

  >>> hd.Values(["Haystack", "diver"])
  <Values size=2 {'Haystack', 'diver'}>

  >>> hd.ASet(3, "a")  # A set of three atoms
  <ASet id=1 size=3 name=a>

  >>> hd.Boolean()
  <Boolean size=2 {False, True}>


:class:`ASet` is a little bit special and it is designed for enumerating
non-isomorphic structures. It is covered in :doc:`cnfs`, we will not use it in
this section. For more information about each elementary domain, see their API
documentations.


Composition
-----------

New domains can be created by composing existing ones. There are the following
compositions: *product*, *sequences*, *subsets*, *mappings*, and *join*.


Cartesian product :math:`(A \times B)`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:class:`Product` creates a domain of all ordered tuples, for example::

    >>> import haydi as hd
    >>> a = hd.Range(2)
    >>> b = hd.Values(("a", "b", "c"))
    >>> hd.Product((a, b))
    <Product size=6 {(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), ...}>

alternatively, the same thing can be written by using the infix operator ``*``::

    >>> a * b
    <Product size=6 {(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), ...}>

The product can be created on more than two domains::

    >>> hd.Product((a, b, hd.Values["x", "y"]))
    <Product size=12{(0, 'a', 'x'), (0, 'a', 'y'), (0, 'b', 'x'), ...}>

    >>> a * b * hd.Values["x", "y"])
    <Product size=12{(0, 'a', 'x'), (0, 'a', 'y'), (0, 'b', 'x'), ...}>


Sequences :math:`(A^n)`
~~~~~~~~~~~~~~~~~~~~~~~

:class:`Sequences` is a shortcut for a product over the same domain.
Sequences of a given length can be defined as::

     >>> import haydi as hd
     >>> a = hd.Range(2)
     >>> hd.Sequences(a, 3)  # Sequences of length 3
     <Sequences size=8 {(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), ...}>

or sequences with a length in a given range::

     >>> hd.Sequences(a, 0, 2)  # Sequences of length 0 to 2
     <Sequences size=7 {(), (0,), (1,), (0, 0), ...}>


Subsets :math:`(\mathcal{P}(A))`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The :class:`Subsets` domain creates sets of elements of a given domain;
the following example creates the power set::

      >>> import haydi as hd
      >>> hd.Subsets(hd.Range(2))
      <Subsets size=4 {{}, {0}, {0, 1}, {1}}>

When one argument is provided, it is used to limit subsets to a given size::

      >>> hd.Subsets(hd.Range(3), 2)  # Subsets of size 2
      <Subsets size=3 {{0, 1}, {0, 2}, {1, 2}}>

Two arguments limit the subsets to a size in a given range::

      >>> hd.Subsets(hd.Range(3), 0, 1)  # Subsets of size between 0 and 1
      <Subsets size=4 {{}, {0}, {1}, {2}}>

.. note ::

   Type of elements created by :class:`Subsets` is *not* the standard Python
   ``set``, but :class:`haydi.Set`. For more information, see :doc:`btypes`.
   This behavior can be overridden by argument ``set_class`::

     >>> hd.Subsets(hd.Range(2), set_class=frozenset)
     <Subsets size=4 {frozenset([]), frozenset([0]), frozenset([0, 1]), ...}>
 

Mappings :math:`(A \rightarrow B)`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The domain :class:`Mappings` creates all mappings from a domain to another
domain::

     >>> import haydi as hd
     >>> a = hd.Range(2)
     >>> b = hd.Values(["a", "b"])
     >>> hd.Mappings(a, b)
     <Mappings size=4 {{0: 'a'; 1: 'a'}, {0: 'a'; 1: 'b'}, {0:  ... a'}, ...}>


.. note ::

   Type of elements created by :class:`Mappings` is *not* the standard Python
   ``dict``, but :class:`haydi.Map`. For more information, see :doc:`btypes`.
   This behavior can be overridden by argument ``map_class`::

     >>> hd.Mappings(a, b, map_class=dict)
     <Mappings size=4 {{0: 'a', 1: 'a'}, {0: 'a', 1: 'b'}, {0:  ... a'}, ...}>
    




Join :math:`(A \uplus B)`
~~~~~~~~~~~~~~~~~~~~~~~~~

Join operations creates a new domain that contains elements of all given
domains (disjoint union)::

  >>> import haydi as hd
  >>> a = hd.Range(2)
  >>> b = hd.Values(["abc", "ikl", "xyz"])
  >>> c = hd.Values([123])

  >>> hd.Join((a, b, c))
  <Join size=6 {0, 1, 'abc', 'ikl', ...}>

The same behavior can be also achieved by `+` operator on domains::

  >>> a + b + c
  <Join size=6 {0, 1, 'abc', 'ikl', ...}>

Note that Join does not collapse the same elements in the joined domains::

  >>> a = hd.Range(2)
  >>> b = hd.Range(3)
  >>> a + b
  <Join size=5 {0, 1, 0, 1, 2}>
{'fraction_non_alphanumeric': 0.21939602756384272, 'fraction_numerical': 0.022800972841507904, 'mean_word_length': 3.4474988733663814, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 243, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1514985', 'n_tokens_mistral': 3578, 'n_tokens_neox': 3358, 'n_words': 1253}