r'''
    Module with all structures for defining rings with operators.

    Let `\sigma: R \rightarrow R` be an additive homomorphism, i.e., for all elements `r,s \in R`,
    the map satisfies `\sigma(r+s) = \sigma(r) + \sigma(s)`. We define the *ring* `R` *with operator*
    `\sigma` as the pair `(R, \sigma)`.

    Similarly, if we have a set of additive maps `\sigma_1,\ldots,\sigma_n : R \rightarrow R`.
    Then we define the *ring* `R` *with operators* `(\sigma_1,\ldots,\sigma_n)` as the tuple
    `(R, (\sigma_1,\ldots,\sigma_n))`.

    This module provides the framework to define this type of rings with as many operators as
    the user wants and we also provide a Wrapper class so we can extend existing ring structures that
    already exist in `SageMath <https://www.sagemath.org>`_.

    The factory :func:`RingWithOperator` allows the creation of these rings with operators and will determine
    automatically in which specified category a ring will belong. For example, we can create the differential
    ring `(\mathbb{Q}[x], \partial_x)` or the difference ring `(\mathbb{Q}[x], x \mapsto x + 1)` with the
    following code::

        sage: from dalgebra import *
        sage: dQx = RingWithOperators(QQ[x], lambda p : p.derivative())
        sage: sQx = RingWithOperators(QQ[x], lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1))

    Once the rings are created, we can create elements within the ring and apply the corresponding operator::

        sage: x = dQx(x)
        sage: x.operation()
        1
        sage: x = sQx(x)
        sage: x.operation()
        x + 1

    We can also create the same ring with both operators together::

        sage: dsQx = RingWithOperators(QQ[x], lambda p : p.derivative(), lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1))
        sage: x = dsQx(x)
        sage: x.operation(operation=0)
        1
        sage: x.operation(operation=1)
        x + 1

    However, these operators have no structure by themselves: `SageMath`_ is not able to distinguish the type
    of the operators if they are defined using lambda expressions or callables. This can be seen by the fact that
    the factory can not detect the equality on two identical rings::

        sage: dQx is RingWithOperators(QQ[x], lambda p : p.derivative())
        False

    To avoid this behavior, we can set the types by providing an optional list called ``types`` whose elements are
    strings with values:

    * ``homomorphism``: the operator is interpret as a homomorphism/shift/difference operator.
    * ``derivation``: the operator is considered as a derivation.
    * ``skew``: the operator is considered as a skew-derivation.
    * ``none``: the operator will only be considered as an additive Map without further structure.

    We can see that, when setting this value, the ring is detected to be equal::

        sage: dQx = RingWithOperators(QQ[x], lambda p : p.derivative(), types=["derivation"])
        sage: dQx is RingWithOperators(QQ[x], lambda p : p.derivative(), types=["derivation"])
        True
        sage: # Since we have one variable, the built-in `diff` also work
        sage: dQx is RingWithOperators(QQ[x], diff, types=["derivation"])
        True
        sage: # We can also use elements in the derivation module
        sage: dQx is RingWithOperators(QQ[x], QQ[x].derivation_module().gens()[0], types=["derivation"])
        True

    Also, we can detect this equality when adding operators sequentially instead of at once::

        sage: dsQx = RingWithOperators(QQ[x],
        ....:     lambda p : p.derivative(),
        ....:     lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1),
        ....:     types = ["derivation", "homomorphism"]
        ....: )
        sage: dsQx is RingWithOperators(dQx, lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1), types=["homomorphism"])
        True

    For specific types of operators as *derivations* or *homomorphism*, there are other functions where the ``types`` argument can be skipped
    taking the corresponding value by default::

        sage: dQx is DifferentialRing(QQ[x], lambda p : p.derivative())
        True
        sage: dsQx is DifferenceRing(DifferentialRing(QQ[x], lambda p : p.derivative()), lambda p : QQ[x](p)(x=QQ[x].gens()[0] + 1))
        True

    We can also have more complexes structures with different types of operators::

        sage: R.<x,y> = QQ[] # x is the usual variable, y is an exponential
        sage: dx, dy = R.derivation_module().gens(); d = dx + y*dy
        sage: DR = DifferentialRing(R, d)
        sage: # We add a special homomorphism where the two generators are squared but QQ is fixed
        sage: DSR = DifferenceRing(DR, R.Hom(R)([x^2, y^2]))
        sage: DSR.noperators()
        2
        sage: DSR.operator_types()
        ('derivation', 'homomorphism')

    We can see that these operator **do not commute**::

        sage: x = DSR(x); y = DSR(y)
        sage: x.difference().derivative()
        2*x
        sage: x.derivative().difference()
        1
        sage: y.difference().derivative()
        2*y^2
        sage: y.derivative().difference()
        y^2

    Finally, this module also allows the definition of skew-derivations for any ring. This requires the use
    of derivation modules with twist (see :sageref:`sage.rings.derivations <rings/sage/rings/derivation>`)::

        sage: R.<x,y> = QQ[]
        sage: s = R.Hom(R)([x-y, x+y])
        sage: td = R.derivation_module(twist=s)(x-y)
        sage: tR = RingWithOperators(R, s, td, types=["homomorphism", "skew"])
        sage: x,y = tR.gens()
        sage: (x*y).skew() == x.skew()*y + x.shift()*y.skew()
        True
        sage: (x*y).skew() == x.skew()*y.shift() + x*y.skew()
        True

    AUTHORS:

    - Antonio Jimenez-Pastor (:git:`GitHub <Antonio-JP>`)
'''

# ****************************************************************************
#  Copyright (C) 2023 Antonio Jimenez-Pastor <ajpa@cs.aau.dk>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#                  https://www.gnu.org/licenses/
# ****************************************************************************

from __future__ import annotations

from typing import Callable, Collection
from sage.all import ZZ, latex, Parent
from sage.categories.all import Morphism, Category, Rings, CommutativeRings, CommutativeAdditiveGroups
from sage.categories.morphism import IdentityMorphism, SetMorphism # pylint: disable=no-name-in-module
from sage.categories.pushout import ConstructionFunctor, pushout
from sage.misc.all import abstract_method, cached_method
from sage.rings.morphism import RingHomomorphism_im_gens # pylint: disable=no-name-in-module
from sage.rings.ring import Ring, CommutativeRing #pylint: disable=no-name-in-module
from sage.rings.derivation import RingDerivationModule
from sage.structure.element import parent, Element #pylint: disable=no-name-in-module
from sage.structure.factory import UniqueFactory #pylint: disable=no-name-in-module
from sage.symbolic.ring import SR #pylint: disable=no-name-in-module

_Rings = Rings.__classcall__(Rings)
_CommutativeRings = CommutativeRings.__classcall__(CommutativeRings)
_CommutativeAdditiveGroups = CommutativeAdditiveGroups.__classcall__(CommutativeAdditiveGroups)

####################################################################################################
###
### DEFINING THE CATEGORY FOR RINGS WITH OPERATORS
###
####################################################################################################
class RingsWithOperators(Category):
    r'''
        Category for representing rings with operators.

        Let `\sigma: R \rightarrow R` be an additive homomorphism, i.e., for all elements `r,s \in R`,
        the map satisfies `\sigma(r+s) = \sigma(r) + \sigma(s)`. We define the *ring* `R` *with operator*
        `\sigma` as the pair `(R, \sigma)`.

        Similarly, if we have a set of additive maps `\sigma_1,\ldots,\sigma_n : R \rightarrow R`.
        Then we define the *ring* `R` *with operators* `(\sigma_1,\ldots,\sigma_n)` as the tuple
        `(R, (\sigma_1,\ldots,\sigma_n))`.

        This category defines the basic methods for these rings and their elements
    '''
    ## Defining a super-category
    def super_categories(self):
        return [_Rings]

    ## Defining methods for the Parent structures of this category
    class ParentMethods: #pylint: disable=no-member
        ##########################################################
        ### METHODS RELATED WITH THE OPERATORS
        ##########################################################
        ### 'generic'
        @abstract_method
        def operators(self) -> Collection[Morphism]:
            r'''
                Method to get the collection of operators that are defined over the ring.

                These operators are maps from ``self`` to ``self`` that compute the application
                of each operator over the elements of ``self``.
            '''
            raise NotImplementedError("Method 'operators' need to be implemented")

        def noperators(self) -> int:
            r'''
                Method to get the number of operators defined over a ring
            '''
            return len(self.operators())

        def operation(self, element : Element, operator : int = None) -> Element:
            r'''
                Method to apply an operator over an element.

                This method takes an element of ``self`` and applies one of the operators defined over ``self``
                over such element. This operator is given by its index, hence raising a :class:`IndexError` if
                the index is not in the valid range.

                INPUT:

                * ``element``: an element over the operator of this ring will be applied.
                * ``operator`` (`0` by default) the index of the operator that will be applied.

                OUTPUT:

                If the index is incorrect, an :class:`IndexError` is raised. Otherwise this method
                returns `f(x)` where `x` is the ``element`` and `f` is the operator defined by ``operator``.

                EXAMPLES::

                    sage: from dalgebra import *
                    sage: dQx = RingWithOperators(QQ[x], lambda p : p.derivative())
                    sage: sQx = RingWithOperators(QQ[x], lambda p : p(x=QQ[x].gens()[0] + 1))
                    sage: sdQx = RingWithOperators(QQ[x], lambda p : p(x=QQ[x].gens()[0] + 1), lambda p : p.derivative())
                    sage: p = QQ[x](x^3 - 3*x^2 + 3*x - 1)
                    sage: dQx.operation(p)
                    3*x^2 - 6*x + 3
                    sage: sQx.operation(p)
                    x^3
                    sage: sdQx.operation(p)
                    Traceback (most recent call last):
                    ...
                    IndexError: An index for the operation must be provided when having several operations
                    sage: sdQx.operation(p, 0)
                    x^3
                    sage: sdQx.operation(p, 1)
                    3*x^2 - 6*x + 3
                    sage: sdQx.operation(p, 2)
                    Traceback (most recent call last):
                    ...
                    IndexError: ... index out of range
            '''
            if operator is None and self.noperators() == 1: operator = 0
            elif operator is None: raise IndexError("An index for the operation must be provided when having several operations")
            return self.operators()[operator](element)

        @abstract_method
        def operator_types(self) -> tuple[str]:
            r'''
                Method to get the types of the operators.

                The only condition for `\sigma: R \rightarrow R` to be a valid operator is that it is
                an additive homomorphism. However, the behavior of `\sigma` with respect to the multiplication
                of `R` categorize `\sigma` into several possibilities:

                * "none": no condition is known over this method. This will disallow some extension operations.
                * "homomorphism": the map `\sigma` is an homomorphism, i.e., for all `r, s \in R` it satisfies
                  `\sigma(rs) = \sigma(r)\sigma(s)`.
                * "derivative": the map `\sigma` satisfies Leibniz rule, i.e., for all `r, s \in R` it satisfies
                  `\sigma(rs) = \sigma(r)s + r\sigma(s)`.
                * "skew": the map `\sigma` satisfies the skew-Leibniz rule, i.e., there is an homomorphism `\delta`
                  such for all `r, s \in R` it satisfies `\sigma(rs) = \sigma(r)s + \delta(r)\sigma(s)`.

                This method returns a tuple (sorted as the output of :func:`operators`) with the types of each of the
                operators.
            '''
            raise NotImplementedError("Method 'operator_types' need to be implemented")

        ### 'derivation'
        @cached_method
        def derivations(self) -> Collection[DerivationMap]:
            r'''
                Method to filter the derivations out of a ring with operators.

                Derivations are a particular type of operators. With this method we
                provide a similar interface as with the generic operators but just with
                derivation.

                Similarly, this class offers access to homomorphisms and skew derivations.

                When no derivation is declared for a ring, an empty tuple is returned.
            '''
            return tuple([operator for (operator, ttype) in zip(self.operators(), self.operator_types()) if ttype == "derivation"])

        def nderivations(self) -> int:
            r'''
                Method to get the number of derivations defined over a ring
            '''
            return len(self.derivations())

        def has_derivations(self) -> bool:
            r'''
                Method to know if there are derivations defined over the ring.
            '''
            return self.nderivations() > 0

        def is_differential(self) -> bool:
            r'''
                Method to check whether a ring is differential, i.e, all operators are derivations.
            '''
            return self.noperators() == self.nderivations()

        def derivative(self, element: Element, derivation: int = None) -> Element:
            r'''
                Method to apply a derivation over an element.

                This method applies a derivation over a given element in the same way an operator
                is applied by the method :func:`~RingsWithOperators.ParentMethods.operation`.
            '''
            if derivation is None and self.nderivations() == 1: derivation = 0
            elif derivation is None: raise IndexError("An index for the derivation must be provided when having several derivations")
            return self.derivations()[derivation](element)

        ### 'difference'
        @cached_method
        def differences(self) -> Collection[Morphism]:
            r'''
                Method to filter the differences out of a ring with operators.

                Differences are a particular type of operators. With this method we
                provide a similar interface as with the generic operators but just with
                difference.

                Similarly, this class offers access to derivations and skew derivations.

                When no difference is declared for a ring, an empty tuple is returned.
            '''
            return tuple([operator for (operator, ttype) in zip(self.operators(), self.operator_types()) if ttype == "homomorphism"])

        def ndifferences(self) -> int:
            r'''
                Method to get the number of differences defined over a ring
            '''
            return len(self.differences())

        def has_differences(self) -> bool:
            r'''
                Method to know if there are differences defined over the ring.
            '''
            return self.ndifferences() > 0

        def is_difference(self) -> bool:
            r'''
                Method to check whether a ring is difference, i.e, all operators are homomorphisms.
            '''
            return self.noperators() == self.ndifferences()

        def difference(self, element: Element, difference: int = None) -> Element:
            r'''
                Method to apply a difference over an element.

                This method applies a difference over a given element in the same way an operator
                is applied by the method :func:`~RingsWithOperators.ParentMethods.operation`.
            '''
            if difference is None and self.ndifferences() == 1: difference = 0
            elif difference is None: raise IndexError("An index for the difference must be provided when having several differences")
            return self.differences()[difference](element)

        def shift(self, element: Element, shift: int = None) -> Element:
            r'''
                Alias for :func:`~RingsWithOperators.ParentMethods.difference`.
            '''
            return self.difference(element, shift)

        ### 'skews'
        @cached_method
        def skews(self) -> Collection[Morphism]:
            r'''
                Method to filter the skew-derivations out of a ring with operators.

                Differences are a particular type of operators. With this method we
                provide a similar interface as with the generic operators but just with
                difference.

                Similarly, this class offers access to homomorphisms and derivations.

                When no skew-derivation is declared for a ring, an empty tuple is returned.
            '''
            return tuple([operator for (operator, ttype) in zip(self.operators(), self.operator_types()) if ttype == "skew"])

        def nskews(self) -> int:
            r'''
                Method to get the number of skew-derivations defined over a ring
            '''
            return len(self.skews())

        def has_skews(self) -> bool:
            r'''
                Method to know if there are skew-derivations defined over the ring.
            '''
            return self.ndifferences() > 0

        def is_skew(self) -> bool:
            r'''
                Method to check whether a ring is skewed, i.e, all operators are skew-derivations.
            '''
            return self.noperators() == self.nskews()

        def skew(self, element: Element, skew: int = None) -> Element:
            r'''
                Method to apply a skew-derivation over an element.

                This method applies a skew-derivation over a given element in the same way an operator
                is applied by the method :func:`~RingsWithOperators.ParentMethods.operation`.
            '''
            if skew is None and self.nskews() == 1: skew = 0
            elif skew is None: raise IndexError("An index for the skew must be provided when having several skews")
            return self.skews()[skew](element)

        ##########################################################
        ### OTHER METHODS
        ##########################################################
        @abstract_method
        def operator_ring(self) -> Ring:
            r'''
                Method to get the operator ring of ``self``.

                When we consider a ring with operators, we can always consider a new (usually non-commutative)
                ring where we extend ``self`` polynomially with all the operators and its elements represent
                new operators created from the operators defined over ``self``.

                This method return this new structure.
            '''
            raise NotImplementedError("Method 'operator_ring' need to be implemented")

        def operators_commute(self, op1: int, op2: int, points: int = 10, *args, **kwds) -> bool:
            r'''
                Method to check whether two operators of the ring commute.

                This method is not deterministic (meaning that it may return ``True`` even
                when the two operators do not fully commute) but it tries to check in a fix number
                of random elements if the two operators actually commute.

                It also try to see if the operators commute in the generators of the ring.

                INPUT:

                * ``op1``: index of the first operator to check.
                * ``op2``: index of the second operator to check.
                * ``points``: number of random points to be selected.
                * ``args``: arguments to be passed to the ``random_element`` method.
                * ``kwds``: arguments to be passed to the ``random_element`` method.

                OUTPUT:

                ``True`` if all the tests indicates the operators commute, ``False`` otherwise.
            '''
            op1 = self.operators()[op1]; op2 = self.operators()[op2]

            to_check = list(self.gens()); current = self.base()
            while current.ngens() > 0 and (not 1 in to_check):
                to_check.extend([self.element_class(self, el) for el in current.gens()])
                current = current.base()
            to_check.extend(self.random_element(*args, **kwds) for _ in range(points))

            return all(op1(op2(element)) == op2(op1(element)) for element in to_check)

        def all_operators_commute(self, points: int = 10, *args, **kwds):
            r'''
                Method to check whether all operators of the ring commute.

                This method is not deterministic (meaning that it may return ``True`` even
                when the two operators do not fully commute) but it tries to check in a fix number
                of random elements if the two operators actually commute.

                It also try to see if the operators commute in the generators of the ring.

                See :func:`operators_commute` for further information

                INPUT:

                * ``points``: number of random points to be selected.
                * ``args``: arguments to be passed to the ``random_element`` method.
                * ``kwds``: arguments to be passed to the ``random_element`` method.

                OUTPUT:

                ``True`` if all the tests indicates the operators commute, ``False`` otherwise.

                EXAMPLES::

                    sage: from dalgebra import *
                    sage: R.<x> = QQ[]; d = diff; s = R.Hom(R)(x+1)
                    sage: dsR = DifferenceRing(DifferentialRing(R, d), s)
                    sage: dsR.all_operators_commute()
                    True
                    sage: R.<x,y> = QQ[]
                    sage: dx,dy = R.derivation_module().gens(); d = dx + y*dy
                    sage: s = R.Hom(R)([x + 1, y^2])
                    sage: dsR = DifferenceRing(DifferentialRing(R, d), s)
                    sage: dsR.all_operators_commute()
                    False
            '''
            return all(
                self.operators_commute(i, j, points, *args, **kwds)
                for i in range(self.noperators())
                for j in range(i+1, self.noperators())
            )

        @abstract_method
        def constant_ring(self, operation: int = 0) -> Parent:
            r'''
                Method to obtain the constant ring of a given operation.

                The meaning of a ring of constants depends on the type of operator that
                we are considering:

                * "homomorphism": the elements that are fixed by the operator.
                * "derivation": the elements that goes to zero with the operator.
                * "skew": the elements that goes to zero with the operator.
                * "none": it makes no sense to talk about constant for these operators.
            '''
            raise NotImplementedError("Method 'constant_ring' not implemented")

    ## Defining methods for the Element structures of this category
    class ElementMethods: #pylint: disable=no-member
        ##########################################################
        ### APPLICATION METHODS
        ##########################################################
        def operation(self, operation : int = None, times : int = 1) -> Element:
            r'''
                Apply an operation to ``self`` a given amount of times.

                This method applies repeatedly an operation defined in the parent of ``self``.
                See :func:`~RingsWithOperators.ParentMethods.operation` for further information.
            '''
            if(not times in ZZ or times < 0):
                raise ValueError("The argument ``times`` must be a non-negative integer")

            if(times == 0):
                return self
            elif(times == 1):
                return self.parent().operation(self, operation)
            else:
                return self.parent().operation(self.operation(operation=operation, times=times-1), operation)

        def derivative(self, derivation: int = None, times: int = 1) -> Element:
            r'''
                Apply a derivation to ``self`` a given amount of times.

                This method applies repeatedly a derivation defined in the parent of ``self``.
                See :func:`~RingsWithOperators.ParentMethods.derivative` for further information.
            '''
            if(not times in ZZ or times < 0):
                raise ValueError("The argument ``times`` must be a non-negative integer")

            if(times == 0):
                return self
            elif(times == 1):
                return self.parent().derivative(self, derivation)
            else:
                return self.parent().derivative(self.derivative(derivation=derivation, times=times-1), derivation)

        def difference(self, difference: int = None, times: int = 1) -> Element:
            r'''
                Apply a difference to ``self`` a given amount of times.

                This method applies repeatedly a difference defined in the parent of ``self``.
                See :func:`~RingsWithOperators.ParentMethods.difference` for further information.
            '''
            if(not times in ZZ or times < 0):
                raise ValueError("The argument ``times`` must be a non-negative integer")

            if(times == 0):
                return self
            elif(times == 1):
                return self.parent().difference(self, difference)
            else:
                return self.parent().difference(self.difference(difference=difference, times=times-1), difference)

        def shift(self, shift: int = None, times: int = 1) -> Element:
            r'''
                Alias for :func:`~RingsWithOperators.ElementMethods.difference`.
            '''
            return self.difference(shift, times)

        def skew(self, skew: int = None, times: int = 1) -> Element:
            r'''
                Apply a skew-derivation to ``self`` a given amount of times.

                This method applies repeatedly a difference defined in the parent of ``self``.
                See :func:`~RingsWithOperators.ParentMethods.skew` for further information.
            '''
            if(not times in ZZ or times < 0):
                raise ValueError("The argument ``times`` must be a non-negative integer")

            if(times == 0):
                return self
            elif(times == 1):
                return self.parent().skew(self, skew)
            else:
                return self.parent().skew(self.skew(skew=skew, times=times-1), skew)

        ##########################################################
        ### BOOLEAN METHODS
        ##########################################################
        def is_constant(self, operation: int = 0):
            r'''
                Method to check whether an element is a constant with respect to one operator.

                INPUT:

                * ``operation``: index defining the operation we want to check.

                OUTPUT:

                A boolean value with ``True`` is the element is a constant (see
                :func:`~RingsWithOperators.ParentMethods.constant_ring` for further information
                on what is a constant depending on the type of operator).

                REMARK: this method do not require the implementation on :func:`~RingsWithOperators.ParentMethods.constant_ring`
                on its parent structure.

                EXAMPLES::

                    sage: from dalgebra import *
                    sage: R = DifferentialRing(QQ[x], diff)
                    sage: p = R(3)
                    sage: p.is_constant()
                    True
                    sage: p = R(x^3 - 3*x + 1)
                    sage: p.is_constant()
                    False

                Some interesting constants may arise unexpectedly when adding other derivations::

                    sage: R.<x,y> = QQ[]
                    sage: dx, dy = R.derivation_module().gens(); d = y*dx - x*dy
                    sage: dR = DifferentialRing(R, d)
                    sage: x,y = dR.gens()
                    sage: x.is_constant()
                    False
                    sage: y.is_constant()
                    False
                    sage: (x^2 + y^2).is_constant()
                    True
            '''
            ttype = self.parent().operator_types()[operation]
            if ttype == "homomorphism":
                result = self.operation(operation=operation) == self
            elif ttype in ("derivation", "skew"):
                result = self.operation(operation=operation) == self.parent().zero()
            else:
                raise ValueError(f"The operation {operation} has not a good type defined")

            return result

    # methods that all morphisms involving differential rings must implement
    class MorphismMethods:
        pass

_RingsWithOperators = RingsWithOperators.__classcall__(RingsWithOperators)
####################################################################################################
###
### DEFINING THE FACTORY FOR THE CREATION OF WRAPPED RINGS
###
####################################################################################################
class RingWithOperatorFactory(UniqueFactory):
    r'''
        Factory to create wrappers around existing rings.

        The :class:`RingsWithOperatorFactory` allows to create wrapper around existing rings
        with a predefined set of operators. For doing so, we have two possibilities:

        INPUT:

        * ``base``: a commutative ring to which we will add operators.
        * ``operators``: a list with operators that will be added to ``base``. It may be one of the following:
          - An additive callable: a :class:`AdditiveMap` will be created for it.
          - An additive homomorphism: a :class:`Morphism` with appropriate domain and codomain.
          - A ring homomorphism: a :class:`Morphism` in the appropriate Hom set.
          - A (skew)-derivation: an element of a module of (skew)-derivations. The corresponding :class:`SkewMap`
            will be created for it.
        * ``types`` (optional): if given, it must be a list with the corresponding types of the operators.
          We will use this information to create different types of :class:`Morphism`.

        SPECIAL CASES:

        If this is used over another wrapped ring, this Factory will create an extended version where the
        new operators are concatenated to the previous operators.

        OUTPUT:

        A :class:`RingWithOperators_Wrapper` with the new ring with operators.
    '''
    def create_key(self, base : CommutativeRing, *operators : Callable, **kwds):
        # checking the arguments
        if len(operators) < 1:
            raise ValueError("At least one operator must be given.")
        elif len(operators) == 1 and isinstance(operators[0], Collection):
            operators = operators[0]
        operators = list(operators)
        types = list(kwds.pop("types", len(operators)*["none"]))

        if isinstance(base, RingWithOperators_Wrapper):
            operators = list(base.construction()[0].operators) + operators
            types = list(base.construction()[0].types) + types
            base = base.wrapped

        # we convert the input into a common standard to create an appropriate key
        for (i, (operator, ttype)) in enumerate(zip(operators, types)):
            if ttype == "none":
                ## We decide the structure depending on the type of object
                if operator in base.Hom(base): # it is an homomorphism - we do nothing
                    types[i] = "homomorphism"
                    new_operator = operator
                elif isinstance(parent(operator), RingDerivationModule):
                    if operator.parent().twisting_morphism() is None: # derivation without twist
                        new_operator = DerivationMap(
                            base,
                            operator
                        )
                        types[i] = "derivation"
                    else:
                        new_operator = SkewMap(
                            base,
                            operator.parent().twisting_morphism(),
                            operator
                        )
                        types[i] = "skew"
                elif isinstance(operator, Callable):
                    new_operator = AdditiveMap(
                        base,
                        operator
                    )
                else:
                    raise TypeError(f"All operators must be callables. Found {operator}")
            elif ttype == "homomorphism":
                def hom_from_callable(base, func):
                    if base.ngens() > 0 and (not 1 in base.gens()):
                        base_map = hom_from_callable(base.base(), func)
                    else:
                        base_map = None
                    hom_set = base.Hom(base)
                    return hom_set([base(func(gen)) for gen in base.gens()], base_map = base_map)
                new_operator = hom_from_callable(base, operator)
            elif ttype == "derivation":
                der_module = base.derivation_module()
                new_operator = DerivationMap(
                    base,
                    sum((operator(base_gen)*der_gen for (base_gen,der_gen) in zip(base.gens(),der_module.gens())), der_module.zero())
                )
            elif ttype == "skew":
                if not isinstance(parent(operator), RingDerivationModule):
                    raise NotImplementedError("Building skew-derivation from callable not implemented")
                twist = operator.parent().twisting_morphism()
                twist = base.Hom(base).one() if twist is None else twist
                new_operator = SkewMap(base, twist, operator)

            if new_operator != operator:
                operators[i] = new_operator
        return tuple([base, tuple(operators), tuple(types)])

    def create_object(self, _, key):
        base, operators, types = key

        return RingWithOperators_Wrapper(base, *operators, types=types)
RingWithOperators = RingWithOperatorFactory("dalgebra.ring_w_operator.ring_w_operator.RingWithOperator")

def DifferentialRing(base : CommutativeRing, *operators : Callable):
    r'''
        Method that calls the :class:`RingWithOperatorFactory` with types always as "derivation".

        See documentation on :class:`RingWithOperatorFactory` for further information.
    '''
    # checking the arguments
    if len(operators) < 1:
        raise ValueError("At least one operator must be given.")
    elif len(operators) == 1 and isinstance(operators[0], Collection):
        operators = operators[0]

    return RingWithOperators(base, *operators, types=len(operators)*["derivation"])

def DifferenceRing(base: CommutativeRing, *operators : Callable):
    r'''
        Method that calls the :class:`RingWithOperatorFactory` with types always as "homomorphism".

        See documentation on :class:`RingWithOperatorFactory` for further information.
    '''
    # checking the arguments
    if len(operators) < 1:
        raise ValueError("At least one operator must be given.")
    elif len(operators) == 1 and isinstance(operators[0], Collection):
        operators = operators[0]

    return RingWithOperators(base, *operators, types=len(operators)*["homomorphism"])

####################################################################################################
###
### DEFINING THE ELEMENT AND PARENT FOR WRAPPED RINGS
###
####################################################################################################
class RingWithOperators_WrapperElement(Element):
    def __init__(self, parent, element):
        if(not isinstance(parent, RingWithOperators_Wrapper)):
            raise TypeError("An element created from a non-wrapper parent")
        elif(not element in parent.wrapped):
            raise TypeError("An element outside the parent [%s] is requested" %parent)

        Element.__init__(self, parent=parent)
        self.wrapped = element

    # Arithmetic methods
    def _add_(self, x) -> RingWithOperators_WrapperElement:
        if parent(x) != self.parent(): # this should not happened
            x = self.parent().element_class(self.parent(), self.parent().base()(x))
        return self.parent().element_class(self.parent(), self.wrapped + x.wrapped)
    def _sub_(self, x) -> RingWithOperators_WrapperElement:
        if parent(x) != self.parent(): # this should not happened
            x = self.parent().element_class(self.parent(), self.parent().base()(x))
        return self.parent().element_class(self.parent(), self.wrapped - x.wrapped)
    def _neg_(self) -> RingWithOperators_WrapperElement:
        return self.parent().element_class(self.parent(), -self.wrapped)
    def _mul_(self, x) -> RingWithOperators_WrapperElement:
        if parent(x) != self.parent(): # this should not happened
            x = self.parent().element_class(self.parent(), self.parent().base()(x))
        return self.parent().element_class(self.parent(), self.wrapped * x.wrapped)
    def _rmul_(self, x) -> RingWithOperators_WrapperElement:
        if parent(x) != self.parent(): # this should not happened
            x = self.parent().element_class(self.parent(), self.parent().base()(x))
        return self.parent().element_class(self.parent(), self.wrapped * x.wrapped)
    def _lmul_(self, x) -> RingWithOperators_WrapperElement:
        if parent(x) != self.parent(): # this should not happened
            x = self.parent().element_class(self.parent(), self.parent().base()(x))
        return self.parent().element_class(self.parent(), self.wrapped * x.wrapped)
    def __pow__(self, n) -> RingWithOperators_WrapperElement:
        return self.parent().element_class(self.parent(), self.wrapped ** n)

    def __eq__(self, x) -> bool:
        if x is None: return False

        r = pushout(self.parent(), parent(x))
        if isinstance(r, RingWithOperators_Wrapper):
            return self.wrapped == r(x).wrapped
        return r(self) == r(x)

    def is_zero(self) -> bool:
        return self.wrapped == 0
    def is_one(self) -> bool:
        return self.wrapped == 1

    ## Other magic methods
    def __hash__(self) -> int:
        return hash(self.wrapped)
    def __str__(self) -> str:
        return str(self.wrapped)
    def __repr__(self) -> str:
        return repr(self.wrapped)
    def _latex_(self) -> str:
        return latex(self.wrapped)

class RingWithOperators_Wrapper(CommutativeRing):
    r'''
        Class for wrapping a Commutative ring and add operators over it.

        This class allows the user to translate a Commutative ring with some operations to
        the category of :class:`RingsWithOperators` preserving as many operations and properties
        of the original ring as possible, but adding the new functionality in the category.

        We do not recommend to use this class by itself. It should be created using the
        corresponding factory (see :class:`RingWithOperatorFactory` and its defined instance in
        ``dalgebra.ring_w_operator.ring_w_operator.RingWithOperators``).

        INPUT:

        * ``base``: the :class:`CommutativeRing` that will be wrapped.
        * ``operators``: a valid :class:`sage.categories.map.Map` to define an operator over ``self``.
        * ``types`` (optional): a list with the types (see :func:`RingsWithOperators.ParentMethods.operator_types`
          for further information). If nothing is given, the list will be automatically computed.
        * ``category`` (optional): argument from the category framework to allow further flexibility.
    '''
    Element = RingWithOperators_WrapperElement

    def __init__(self,
        base : CommutativeRing,
        *operators : Morphism | Collection[Morphism],
        types : Collection[str] = None,
        category = None
    ):
        #########################################################################################################
        ### CHECKING THE ARGUMENTS
        ### 'base'
        if not base in _CommutativeRings:
            raise TypeError("Only commutative rings can be wrapped as RingWithOperators")

        ### 'operators'
        if len(operators) == 1 and isinstance(operators[0], (list,tuple)):
            operators = operators[0]
        if any(not isinstance(operator, Morphism) for operator in operators):
            raise TypeError("All the given operators must be Maps")
        if any(operator.domain() != operator.codomain() or operator.domain() != base for operator in operators):
            raise TypeError("The operators must bu maps from and to the commutative ring given by 'base'")

        ### 'types'
        if types is None: # we compute the types using the maps
            types = []
            for operator in operators:
                if isinstance(operator, DerivationMap): types.append("derivation")
                elif isinstance(operator, SkewMap): types.append("skew")
                elif operator.category_for().is_subcategory(_CommutativeRings): types.append("homomorphism")
                else: types.append("none")
        else: # we check the operators behave as requested
            if not isinstance(types, (list, tuple)) or len(types) != len(operators):
                raise TypeError("The types must be a list of the same length of the operators")
            for operator, ttype in zip(operators, types):
                if ttype == "none":
                    if not operator.category_for().is_subcategory(_CommutativeAdditiveGroups):
                        raise ValueError(f"An operator invalid for type 'none' -> {operator}")
                elif ttype == "homomorphism":
                    if not operator.category_for().is_subcategory(_CommutativeRings):
                        raise ValueError(f"An operator invalid for type 'homomorphism' -> {operator}")
                elif ttype == "derivation":
                    if not isinstance(operator, DerivationMap):
                        raise ValueError(f"An operator invalid for type 'derivation' -> {operator}")
                elif ttype == "skew":
                    if not isinstance(operator, SkewMap):
                        raise ValueError(f"An operator invalid for type 'skew' -> {operator}")
                else:
                    raise ValueError(f"Invalid type provided -> {ttype}")

        self.__types = tuple(types)

        #########################################################################################################
        # CREATING CATEGORIES
        categories = [_RingsWithOperators, base.category()]
        if(isinstance(category, (list, tuple))):
            categories += list(category)
        elif(category != None):
            categories.append(category)

        #########################################################################################################
        ### CALLING THE SUPER AND ARRANGING SOME CONVERSIONS
        super().__init__(base.base(), category=tuple(categories))
        self.__wrapped = base

        # registering conversion to simpler structures
        current = self.base()
        morph = RingWithOperators_Wrapper_SimpleMorphism(self, current)
        current.register_conversion(morph)
        while(not(current.base() == current)):
            current = current.base()
            morph = RingWithOperators_Wrapper_SimpleMorphism(self, current)
            current.register_conversion(morph)

        #########################################################################################################
        ### CREATING THE NEW OPERATORS FOR THE CORRECT STRUCTURE
        self.__operators : tuple[WrappedMap] = tuple([WrappedMap(self, operator) for operator in operators])

    @property
    def wrapped(self) -> CommutativeRing: return self.__wrapped

    def operators(self) -> tuple[WrappedMap]: return self.__operators

    def operator_types(self) -> tuple[str]: return self.__types

    ## Coercion methods
    def _has_coerce_map_from(self, S) -> bool:
        r'''
            Return ``True`` if it is possible to have a coercion map from `S` to ``self``.
        '''
        if isinstance(S, RingWithOperators_Wrapper):
            return self.wrapped._has_coerce_map_from(S.wrapped) # the operators do not matter for coercing elements
        else:
            return self.wrapped._has_coerce_map_from(S)

    def _element_constructor_(self, x) -> RingWithOperators_WrapperElement:
        r'''
            Extended definition of :func:`_element_constructor_`.
        '''
        if x in SR:
            # conversion from symbolic ring --> using its string representation
            x = str(x)
        elif isinstance(parent(x), RingWithOperators_Wrapper):
            # conversion from other wrapped rings with operators --> we convert the element within
            x = x.wrapped
        p = self.wrapped._element_constructor_(x)
        return self.element_class(self, p)

    def _is_valid_homomorphism_(self, codomain, im_gens, base_map=None) -> bool:
        return self.wrapped._is_valid_homomorphism_(codomain, im_gens, base_map)

    def construction(self) -> RingWithOperatorsFunctor:
        return RingWithOperatorsFunctor([operator.function for operator in self.operators()], self.operator_types()), self.wrapped

    # Rings methods
    def characteristic(self) -> int:
        return self.wrapped.characteristic()

    def gens(self) -> tuple[RingWithOperators_WrapperElement]:
        return tuple([self.element_class(self, gen) for gen in self.wrapped.gens()])

    def ngens(self) -> int:
        return self.wrapped.ngens()

    ## Representation methods
    def __repr__(self) -> str:
        begin = "Differential " if self.is_differential() else "Difference " if self.is_difference() else ""
        return f"{begin}Ring [[{self.wrapped}], {repr(self.operators())}]"

    def __str__(self) -> str:
        return repr(self)

    def _latex_(self) -> str:
        return "".join((
            r"\left(",
            latex(self.wrapped),
            ", ",
            latex(self.operators()) if self.noperators() > 1 else latex(self.operators()[0]),
            r"\right)"
        ))

    ## Element generation
    def one(self) -> RingWithOperators_WrapperElement:
        r'''
            Return the one element in ``self``.

            EXAMPLES::

                sage: from dalgebra import *
                sage: R = RingWithOperators(QQ['x'], diff)
                sage: R.one()
                1
        '''
        return self.element_class(self, self.wrapped.one())

    def zero(self) -> RingWithOperators_WrapperElement:
        r'''
            Return the zero element in ``self``.

            EXAMPLES::

                sage: from dalgebra import *
                sage: R = RingWithOperators(QQ['x'], diff)
                sage: R.zero()
                0
        '''
        return self.element_class(self, self.wrapped.zero())

    def random_element(self,*args,**kwds) -> RingWithOperators_WrapperElement:
        r'''
            Creates a random element in this ring.

            This method creates a random element in the base infinite polynomial ring and
            cast it into an element of ``self``.
        '''
        p = self.wrapped.random_element(*args,**kwds)
        return self.element_class(self, p)

####################################################################################################
###
### DEFINING THE CONSTRUCTION FUNCTOR AND SIMPLE MORPHISM
###
####################################################################################################
class RingWithOperatorsFunctor(ConstructionFunctor):
    def __init__(self, operators: Collection[Morphism], types: Collection[str]):
        if len(operators) != len(types):
            raise ValueError("The length of the operators and types must coincide.")
        self.__operators = tuple(operators)
        self.__types = tuple(types)

        super().__init__(_CommutativeRings, _RingsWithOperators)

    ### Methods to implement
    def _coerce_into_domain(self, x: Element) -> Element:
        if not x in self.domain():
            raise TypeError(f"The object [{x}] is not an element of [{self.domain()}]")

    def _apply_functor(self, x):
        return RingWithOperators(x, self.__operators, self.__types)

    def _repr_(self):
        return f"RingWithOperators(*,{self.__operators}])"

    def __eq__(self, other):
        return self.__class__ == other.__class__ and self.__operators == other.__operators and self.__types == other.__types

    def merge(self, other):
        if isinstance(other, RingWithOperatorsFunctor):
            return RingWithOperatorsFunctor(self.__operators + other.__operators, self.__types + other.__types)
        else:
            raise NotImplementedError(f"{self} can only be merged with other RingWithOperatorsFunctor")

    @property
    def operators(self) -> Collection[Morphism]:  return self.__operators
    @property
    def types(self): return self.__types

class RingWithOperators_Wrapper_SimpleMorphism(Morphism):
    r'''
        Class representing maps to simpler rings.

        This map allows the coercion system to detect that some elements in a
        :class:`RingWithOperator_Wrapper` are included in simpler rings.
    '''
    def __init__(self, domain, codomain):
        super().__init__(domain, codomain)

    def _call_(self, p):
        return self.codomain()(p.wrapped)

####################################################################################################
###
### DEFINING THE REQUIRED MAPS FOR THIS MODULE
###
####################################################################################################
class AdditiveMap(SetMorphism):
    def __init__(self, domain : Parent, function : Callable):
        # We create the appropriate Hom set
        hom = domain.Hom(domain, category=_CommutativeAdditiveGroups)
        self.function = function
        super().__init__(hom, function)

    def __str__(self) -> str:
        return f"Additive Map [{repr(self)}]\n\t- From: {self.domain()}\n\t- To  : {self.codomain()}"

    def __repr__(self) -> str:
        return f"{repr(self.function)}"

    def _latex_(self) -> str:
        return latex(self.function)

    def __eq__(self, other) -> bool:
        return isinstance(other, AdditiveMap) and self.domain() == other.domain() and self.function == other.function

    def __hash__(self) -> int:
        return self.function.__hash__()

class SkewMap(AdditiveMap):
    def __init__(self, domain : Parent, twist : Morphism, function : Callable):
        # we check the input
        if not twist in domain.Hom(domain):
            raise TypeError("The twist for a skew derivation must be an homomorphism.")
        tw_der_module = domain.derivation_module(twist=twist)
        if not function in tw_der_module:
            raise TypeError("The function for a skew derivation must be in the corresponding module")
        self.twist = twist
        super().__init__(domain, function)

    def __str__(self) -> str:
        return f"Skew Derivation [{repr(self)}] over (({self.domain()}))"

class DerivationMap(SkewMap):
    def __init__(self, domain, function : Callable):
        super().__init__(domain, domain.Hom(domain).one(), function)

    def __str__(self) -> str:
        return f"Derivation [{repr(self)}] over (({self.domain()}))"

class WrappedMap(AdditiveMap):
    def __init__(self, domain : RingWithOperators_Wrapper, function : Morphism):
        if not isinstance(domain, RingWithOperators_Wrapper):
            raise TypeError("A WrappedMap can only be created for a 'RingWithOperators_Wrapper'")

        if function.domain() != domain.wrapped:
            raise ValueError(f"The map to be wrapped must have appropriate domain: ({domain.wrapped}) instead of ({function.domain()})")

        super().__init__(domain, lambda p : domain(function(domain(p).wrapped)))
        self.function = function

    def __repr__(self) -> str:
        if isinstance(self.function, RingHomomorphism_im_gens):
            im_gens = {v: im for (v,im) in zip(self.function.domain().gens(), self.function.im_gens())}
            return f"Hom({im_gens})"
        elif isinstance(self.function, IdentityMorphism):
            return "Id"
        else:
            return super().__repr__()

    def __str__(self) -> str:
        return f"Wrapped [{repr(self)}] over (({self.domain()}))"

    def _latex_(self) -> str:
        if isinstance(self.function, RingHomomorphism_im_gens):
            im_gens = {v: im for (v,im) in zip(self.function.domain().gens(), self.function.im_gens())}
            return r"\sigma\left(" + r", ".join(f"{latex(v)} \\mapsto {latex(im)}" for (v,im) in im_gens.items()) + r"\right)"
        elif isinstance(self.function, IdentityMorphism):
            return r"\text{id}"
        return super()._latex_()

__all__ = ["RingsWithOperators", "RingWithOperators", "DifferentialRing", "DifferenceRing"]


class BearDestroysDiv2:
	def sumUp(self, W, H, MOD):
		mask = (1 << W) - 1
		# prevFallDp[i] is how many ways current row is filled with fall trees. 1 filled, 0 not filled.
		prevFall = [0 for x in range(mask + 1)]
		prevFall[0] = 1
		result = 0
		for row in range(0, H):
			nextFall = [0 for x in range(mask + 1)]
			curSum = 0
			for curMask in range(0, mask + 1):
				# curMask is the mask of 'S', 'E' in current row. 0 is 'S', 1 is 'E'.
				for fall in range(0, mask + 1):
					# fall is prev Fall situation.
					fallCount = prevFall[fall]
					if fallCount == 0:
						continue
					curFall = fall
					nfall = 0
					for i in range(0, W):
						if (curFall & (1 << i)) != 0:
							continue
						if (curMask & (1 << i)) == 0:
							# Prefer 'S'.
							if row != H - 1:
								nfall |= 1 << i
								curSum += fallCount
							elif i != W - 1 and (curFall & (1 << (i + 1))) == 0:
								curFall |= 1 << (i + 1)
								curSum += fallCount
						else:
							# Prefer 'E'.
							if i != W - 1 and (curFall & (1 << (i + 1))) == 0:
								curFall |= 1 << (i + 1)
								curSum += fallCount
							elif row != H - 1:
								nfall |= 1 << i
								curSum += fallCount
					nextFall[nfall] = (nextFall[nfall] + fallCount) % MOD
			prevFall = nextFall
			result = (result + curSum * pow(2, (H - 1 - row) * W) % MOD) % MOD
		return result


import numpy as np
import itertools
import multiprocessing
import threading
import subprocess
import time
import sys
import os

if len(sys.argv) != 3:
    print("input data_folder thread_num")
    exit(0)

folder = sys.argv[1]
max_spawn = int(sys.argv[2])

if not os.path.exists(folder):
    os.mkdir(folder)
os.chdir(folder)

if os.name == "nt":
    proc = "../bin/throughput.exe"
else:
    proc = "../bin/throughput.out"

task_list = []
width = [7]
n_inst = [10000]
trial = [10]
ano_prob = [0] + list(np.logspace(-6,-4, 20))
ano_life = [100, 1000]

task_list = itertools.product(width, n_inst, trial, ano_prob, ano_life)
task_list = list(task_list)
task_list = [list(map(str, val)) for val in task_list]
print(task_list)
task_list = task_list * 1000
print("max_spawn = ", max_spawn)

task_lock = threading.Lock()
task_index = 0

def spawn_and_wait(thread_id):
    global task_list
    global task_lock
    global task_index
    while True:
        finish_flag = False
        my_task_index = 0

        task_lock.acquire()
        try:
            if task_index == len(task_list):
                finish_flag = True
            else:
                my_task_index = task_index
                task_index += 1
        finally:
            task_lock.release()

        if finish_flag:
            print("thread{:2} exits".format(thread_id))
            break
        else:
            fname = "result{}.txt".format(thread_id)
            arg = [proc, fname] + task_list[my_task_index]
            print(arg)
            process = subprocess.Popen(arg)
            # process = subprocess.Popen(arg, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            start = time.time()
            print("thread{:2} : start task {}: {}".format(thread_id, my_task_index, arg))
            process.wait()
            elapsed = time.time() - start
            print("thread{:2} : finish task {}: elp:{}".format(thread_id, my_task_index, elapsed))

thread_pool = []
for ind in range(max_spawn):
    thread = threading.Thread(target=spawn_and_wait, args=[ind])
    thread_pool.append(thread)
for thread in thread_pool:
    thread.start()
for thread in thread_pool:
    thread.join()


import os
import re
from shutil import copyfile
import math
import random
import pandas as pd

SOURCE_PATH = "data/aug"
DEST_PATH = "data"
TRAIN_DIR = os.path.join(DEST_PATH, 'train')
TEST_DIR = os.path.join(DEST_PATH, 'test')

ratio = 0.1

if not os.path.exists(TRAIN_DIR):
    os.makedirs(TRAIN_DIR)
if not os.path.exists(TEST_DIR):
    os.makedirs(TEST_DIR)

annotations = pd.read_csv('data/annotation/annotations.csv')

rows_test = []
rows_train = []

images = [f for f in os.listdir(SOURCE_PATH)
          if re.search(r'([a-zA-Z0-9\s_\\.\-\(\):])+(.jpg|.jpeg|.png)$', f)]

num_images = len(images)
num_test_images = math.ceil(ratio*num_images)

for i in range(num_test_images):
    idx = random.randint(0, len(images)-1)
    filename = images[idx]
    copyfile(os.path.join(SOURCE_PATH, filename),
             os.path.join(TEST_DIR, filename))

    print(filename)
    annotation = annotations.loc[annotations['filename'] == filename].values[0].tolist()

    rows_test.append({
                'filename': annotation[0],
                'width': annotation[1],
                'height': annotation[2],
                'class': annotation[3],
                'xmin': annotation[4],
                'ymin': annotation[5],
                'xmax': annotation[6],
                'ymax': annotation[7]
            })

    images.remove(images[idx])

for filename in images:
    copyfile(os.path.join(SOURCE_PATH, filename),
             os.path.join(TRAIN_DIR, filename))

    annotation = annotations.loc[annotations['filename'] == filename].values[0].tolist()

    rows_train.append({
                'filename': annotation[0],
                'width': annotation[1],
                'height': annotation[2],
                'class': annotation[3],
                'xmin': annotation[4],
                'ymin': annotation[5],
                'xmax': annotation[6],
                'ymax': annotation[7]
            })

pd.DataFrame(rows_test).to_csv('data/annotation/annotations_test.csv', header=True, index=None)
pd.DataFrame(rows_train).to_csv('data/annotation/annotations_train.csv', header=True, index=None)


from random import random
import matplotlib.pyplot as plt

class AlgoritmoGenetico():
    def __init__(self, tamanho_populacao):
        self.tamanho_populacao = tamanho_populacao
        self.populacao = []
        self.geracao = 0
        self.melhor_solucao = 0
        self.lista_solucoes = []

    def inicializa_populacao(self, espacos, valores, limite_espacos):
        for i in range(self.tamanho_populacao):
            self.populacao.append(Individuo(espacos, valores, limite_espacos))
        self.melhor_solucao = self.populacao[0]

    def ordena_populacao(self):
        self.populacao = sorted(self.populacao,
                                key = lambda populacao: populacao.nota_avaliacao,
                                reverse = True)

    def melhor_individuo(self, individuo):
        if individuo.nota_avaliacao > self.melhor_solucao.nota_avaliacao:
            self.melhor_solucao = individuo

    def soma_avaliacoes(self):
        soma = 0
        for individuo in self.populacao:
           soma += individuo.nota_avaliacao
        return soma

    def seleciona_pai(self, soma_avaliacao):
        pai = -1
        valor_sorteado = random() * soma_avaliacao
        soma = 0
        i = 0
        while i < len(self.populacao) and soma < valor_sorteado:
            soma += self.populacao[i].nota_avaliacao
            pai += 1
            i += 1
        return pai

    def visualiza_geracao(self):
        melhor = self.populacao[0]
        print("G:%s -> Valor: %s Espaço: %s Cromossomo: %s" % (self.populacao[0].geracao,
                                                               melhor.nota_avaliacao,
                                                               melhor.espaco_usado,
                                                               melhor.cromossomo))

    def resolver(self, taxa_mutacao, numero_geracoes, espacos, valores, limite_espacos):
        self.inicializa_populacao(espacos, valores, limite_espacos)

        for individuo in self.populacao:
            individuo.avaliacao()

        self.ordena_populacao()
        self.melhor_solucao = self.populacao[0]
        self.lista_solucoes.append(self.melhor_solucao.nota_avaliacao)

        self.visualiza_geracao()

        for geracao in range(numero_geracoes):
            soma_avaliacao = self.soma_avaliacoes()
            nova_populacao = []

            for individuos_gerados in range(0, self.tamanho_populacao, 2):
                pai1 = self.seleciona_pai(soma_avaliacao)
                pai2 = self.seleciona_pai(soma_avaliacao)

                filhos = self.populacao[pai1].crossover(self.populacao[pai2])

                nova_populacao.append(filhos[0].mutacao(taxa_mutacao))
                nova_populacao.append(filhos[1].mutacao(taxa_mutacao))

            self.populacao = list(nova_populacao)

            for individuo in self.populacao:
                individuo.avaliacao()

            self.ordena_populacao()

            self.visualiza_geracao()

            melhor = self.populacao[0]
            self.lista_solucoes.append(melhor.nota_avaliacao)
            self.melhor_individuo(melhor)

        print("\nMelhor solução -> G: %s Valor: %s Espaço: %s Cromossomo: %s" %
              (self.melhor_solucao.geracao,
               self.melhor_solucao.nota_avaliacao,
               self.melhor_solucao.espaco_usado,
               self.melhor_solucao.cromossomo))

        return self.melhor_solucao.cromossomo

if __name__ == '__main__':

    limite = 3
    tamanho_populacao = 20
    taxa_mutacao = 0.01
    numero_geracoes = 100
    ag = AlgoritmoGenetico(tamanho_populacao)
    resultado = ag.resolver(taxa_mutacao, numero_geracoes, espacos, valores, limite)
    for i in range(len(lista_produtos)):
        if resultado[i] == '1':
            print("Nome: %s R$ %s " % (lista_produtos[i].nome,
                                       lista_produtos[i].valor))

    #for valor in ag.lista_solucoes:
    #    print(valor)
    plt.plot(ag.lista_solucoes)
    plt.title("Acompanhamento dos valores")
    plt.show()

curso/Algoritmos Genéticos em Python/13.py


# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

from __future__ import annotations
import pandas as pd
import numpy as np
from typing import Iterable, Callable, Any

def unpack_column(
    table: pd.DataFrame, column_description: dict, table_name: str
) -> pd.DataFrame:
    """Validates a column in a pandas DataFrame

    Args:
        table (pandas.DataFrame): A table containing the column at the index
            specified by column_description
        column_description (dict): A dictionary with the following supported
            keys:

             - name: the name of the column, which is assigned to the column
                     label to the result table returned by this function
             - index: the zero based index of the column in the table's
                      ordered columns
             - type: (optional) the column will be converted (if necessary to
                     this type) If the conversion is not possible for a value
                     at any row, an error is raised.
             - min_value: (optional) inclusive minimum value constraint.
             - max_value: (optional) inclusive maximum value constraint.

        table_name (str): the name of the table being processed, purely
            for error feedback when an error occurs.

    Raises:
        ValueError: the values in the column were not convertable to the
            specified column description type
        ValueError: a min_value or max_value was specified without specifying
            type in column description
        ValueError: the min_value or max_value constraint was violated by the
            value in the column.

    Returns:
        pandas.DataFrame: the resulting table
    """
    data = table.iloc[:, column_description["index"]].copy()
    col_name = column_description["name"]
    if "type" in column_description:
        if data.isna().any():
            try:
                data = data.astype("object")
                data.loc[data.notna()] = data.loc[data.notna()].astype(
                    column_description["type"]
                )
            except ValueError:
                raise ValueError(
                    f"{table_name} table, column: '{col_name}' contains "
                    "values that cannot be converted to: "
                    f"'{column_description['type']}'"
                )
        else:
            data = data.astype(column_description["type"])
    if "min_value" in column_description:
        if "type" not in column_description:
            raise ValueError("type required with min_value")
        min_value = column_description["min_value"]
        if len(data[data < min_value]):
            raise ValueError(
                f"{table_name} table, column: '{col_name}' contains values "
                f"less than the minimum allowed value: {min_value}"
            )
    if "max_value" in column_description:
        if "type" not in column_description:
            raise ValueError("type required with max_value")
        max_value = column_description["max_value"]
        if len(data[data > max_value]):
            raise ValueError(
                f"{table_name} table, column: '{col_name}' contains values "
                f"greater than the maximum allowed value: {max_value}"
            )
    return data

def _list_duplicates(seq: Iterable) -> list:
    """
    get the list of duplicate values in the specified sequence
    https://stackoverflow.com/questions/9835762/how-do-i-find-the-duplicates-in-a-list-and-create-another-list-with-them

    Args:
        seq (iterable): a sequence of comparable values

    Returns:
        list: the list of values that appear at least 2 times in the input
    """
    seen = set()
    seen_add = seen.add
    # adds all elements it doesn't know yet to seen and all other to seen_twice
    seen_twice = set(x for x in seq if x in seen or seen_add(x))
    # turn the set into a list (as requested)
    return list(seen_twice)

def unpack_table(
    table: pd.DataFrame, column_descriptions: list[dict], table_name: str
) -> pd.DataFrame:
    """Validates and assigns column names to a column-ordered table using
    the specified list of column descriptions. Any existing column labels
    on the specified table are ignored.

    Args:
        table (pandas.DataFrame): a column ordered table to validate
        column_descriptions (list): a list of dictionaries with describing the
            columns. See :py:func:`unpack_column` for how this is used
        table_name (str): the name of the table being processed, purely
            for error feedback when an error occurs.

    Raises:
        ValueError: a duplicate column name was detected

    Returns:
        pandas.DataFrame: a type-validated table with columns replaced with
            the contents of column_descriptions.
    """
    cols = [x["name"] for x in column_descriptions]
    duplicates = _list_duplicates(cols)
    if duplicates:
        # this could potentially happen if a classifier is named the same
        # thing as another column
        raise ValueError(f"duplicate column names detected: {duplicates}")
    data = {
        x["name"]: unpack_column(table, x, table_name)
        for x in column_descriptions
    }
    return pd.DataFrame(columns=cols, data=data)

def _try_get_int(s) -> int:
    """
    Checks if the specified value is an integer, and returns a result

    Args:
        s (any): a value to test

    Returns:
        tuple: (int(s), True) if the value can be converted to an integer,
            and otherwise (None, False)
    """
    try:
        i = int(s)
        return i, True
    except ValueError:
        return None, False

def get_parse_bool_func(
    table_name: str, colname: str
) -> Callable[[Any], bool]:
    """gets a boolean-like value to boolean parse function according to the
    SIT specification.  The parameters are used to create a friendly error
    message when a parse failure occurs.

    Args:
        table_name (str): Table name to be used in failure error message
        colname (str): Column name to be used in failure error message

    Returns:
        func: a boolean-like value to bool parse function
    """

    def parse_bool(x: Any) -> bool:
        """Converts the specified value to a boolean according to SIT
        specification, or raises an error.

        Args:
            x (varies): a value to convert to boolean

        Raises:
            ValueError: The specified value was not convertable to boolean

        Returns:
            boolean: The converted value
        """
        if isinstance(x, bool):
            return x
        elif isinstance(x, int):
            # the sit format treats negatives as False for boolean fields
            return x > 0
        else:
            str_x = str(x).lower()
            int_x, success = _try_get_int(str_x)
            if success:
                return int_x > 0
            if str_x in ["true", "t", "y"]:
                return True
            elif str_x in ["false", "f", "n"]:
                return False
            else:
                raise ValueError(
                    f"{table_name}: cannot parse value: '{x}' in "
                    f"column: '{colname}' as a boolean"
                )

    return parse_bool

def substitute_using_age_class_rows(
    rows: pd.DataFrame,
    parse_bool_func: Callable[[Any], bool],
    age_classes: pd.DataFrame,
) -> pd.DataFrame:
    """Substitute age class criteria values that appear in SIT transition
    rules or disturbance events data into age values.

    Checks that min softwood age equals min hardwood age and max softwood
    age equals max hardwood age since CBM does not carry separate HW/SW ages.

    Args:
        rows (pandas.DataFrame): sit data containing columns that describe age
            eligibility:

            - using_age_class
            - min_softwood_age
            - min_hardwood_age
            - max_softwood_age
            - max_hardwood_age

        parse_bool_func (func): a function that maps boolean-like values to
            boolean.  Passed to the pandas.Series.map function for the
            using_age_class column.
        age_classes (pandas.DataFrame): [description]

    Raises:
        ValueError: values found in the age eligibility columns are not
            defined identifiers in the specified age classes table.
        ValueError: hardwood and softwood age criteria were not identical.

    Returns:
        pandas.DataFrame: the input table with age values criteria substituted
            for age class criteria.
    """

    rows.using_age_class = rows.using_age_class.map(parse_bool_func)
    non_using_age_class_rows = rows.loc[~rows.using_age_class]
    using_age_class_rows = rows.loc[rows.using_age_class].copy()

    for age_class_criteria_col in [
        "min_softwood_age",
        "min_hardwood_age",
        "max_softwood_age",
        "max_hardwood_age",
    ]:
        valid_age_classes = np.concatenate(
            [age_classes.name.unique(), np.array(["-1"])]
        )
        age_class_ids = (
            using_age_class_rows[age_class_criteria_col].astype(str).unique()
        )
        undefined_age_classes = np.setdiff1d(age_class_ids, valid_age_classes)
        if len(undefined_age_classes) > 0:
            raise ValueError(
                f"In column {age_class_criteria_col}, the following age class "
                f"identifiers: {undefined_age_classes} are not defined in SIT "
                "age classes."
            )

    age_class_start_year_map = {
        x.name: int(x.start_year) for x in age_classes.itertuples()
    }
    age_class_end_year_map = {
        x.name: int(x.end_year) for x in age_classes.itertuples()
    }
    using_age_class_rows.min_softwood_age = (
        using_age_class_rows.min_softwood_age.astype(str).map(
            age_class_start_year_map
        )
    )
    using_age_class_rows.min_hardwood_age = (
        using_age_class_rows.min_hardwood_age.astype(str).map(
            age_class_start_year_map
        )
    )
    using_age_class_rows.max_softwood_age = (
        using_age_class_rows.max_softwood_age.astype(str).map(
            age_class_end_year_map
        )
    )
    using_age_class_rows.max_hardwood_age = (
        using_age_class_rows.max_hardwood_age.astype(str).map(
            age_class_end_year_map
        )
    )

    # if the above mapping fails, it results in Nan values in the failed rows,
    # this replaces those with -1
    using_age_class_rows.min_softwood_age = (
        using_age_class_rows.min_softwood_age.fillna(-1)
    )
    using_age_class_rows.min_hardwood_age = (
        using_age_class_rows.min_hardwood_age.fillna(-1)
    )
    using_age_class_rows.max_softwood_age = (
        using_age_class_rows.max_softwood_age.fillna(-1)
    )
    using_age_class_rows.max_hardwood_age = (
        using_age_class_rows.max_hardwood_age.fillna(-1)
    )

    # return the final substituted rows
    result = pd.concat(
        [non_using_age_class_rows, using_age_class_rows]
    ).reset_index(drop=True)

    # convert to float then to int in case the columns are stored as
    # strings in float format (which fails on astype(int))
    result.min_softwood_age = result.min_softwood_age.astype(float).astype(int)
    result.min_hardwood_age = result.min_hardwood_age.astype(float).astype(int)
    result.max_softwood_age = result.max_softwood_age.astype(float).astype(int)
    result.max_hardwood_age = result.max_hardwood_age.astype(float).astype(int)

    # check that all age criteria are identical between SW and HW (since CBM
    # has only a stand age)
    has_null_min_age_criteria = (result.min_softwood_age < 0) | (
        result.min_hardwood_age < 0
    )
    has_null_max_age_criteria = (result.max_softwood_age < 0) | (
        result.max_hardwood_age < 0
    )
    differing_age_criteria = result.loc[
        (
            (result.min_softwood_age != result.min_hardwood_age)
            & ~has_null_min_age_criteria
        )
        | (
            (result.max_softwood_age != result.max_hardwood_age)
            & ~has_null_max_age_criteria
        )
    ]
    if len(differing_age_criteria) > 0:
        raise ValueError(
            "Values of column min_softwood_age must equal values of column "
            "min_hardwood_age, and values of column max_softwood_age must "
            "equal values of column max_hardwood_age since CBM defines only "
            "a stand age and does not track hardwood and softwood age "
            "seperately."
        )

    return result


from edc_appointment.appointment_creator import AppointmentCreator as BaseAppointmentCreator

from ..models import DispenseSchedule
from ..print_profile import DispenseProfileSelector

class AppointmentCreator(BaseAppointmentCreator):

    """Creates dispense timepoints and update.
    """

    appointment_model = 'edc_pharma.appointment'
    profile_selector_cls = DispenseProfileSelector

    def __init__(self, schedule_name=None, schedule_plan=None,
                 subject_identifier=None, schedule=None, timepoints=None):
        self.schedule = schedule
        self.schedule_name = schedule_name
        self.schedule_plan = schedule_plan
        self.subject_identifier = subject_identifier
        self.timepoints = timepoints

    def create(self):
        profile_selector = self.profile_selector_cls(
            subject_identifier=self.subject_identifier,
            schedule_name=self.schedule_name,
            schedule_plan=self.schedule_plan)
        appointments = []
        for code in self.timepoints:
            timepoint = self.timepoints.get(code)
            try:
                appointment = DispenseAppointment.objects.get(
                    schedule=self.schedule,
                    appt_datetime=timepoint.timepoint_datetime,
                    profile_label=profile_selector.profile.label)
            except DispenseAppointment.DoesNotExist:
                appointment = DispenseAppointment.objects.create(
                    schedule=self.schedule,
                    profile_label=profile_selector.profile.label,
                    appt_datetime=timepoint.timepoint_datetime)
            appointments.append(appointment)
        return appointments

class DispenseScheduleCreator:

    """Creates dispense schedule.
    """

    def __init__(self, schedule=None, schedule_plan=None, subject_identifier=None,
                 sequence=None, arm=None):
        self.schedule = schedule
        self.schedule_plan = schedule_plan
        self.subject_identifier = subject_identifier
        self.sequence = sequence
        self.arm = arm

    def create(self):
        try:
            obj = DispenseSchedule.objects.get(
                subject_identifier=self.subject_identifier,
                name=self.schedule.name,
                start_datetime=self.schedule.period.start_datetime,
                end_datetime=self.schedule.period.end_datetime)
        except DispenseSchedule.DoesNotExist:
            obj = DispenseSchedule.objects.create(
                subject_identifier=self.subject_identifier,
                name=self.schedule.name,
                sequence=self.sequence,
                description=self.schedule.description,
                arm=self.arm,
                start_datetime=self.schedule.period.start_datetime,
                end_datetime=self.schedule.period.end_datetime)
        return obj


#!/usr/bin/python
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

def down_sample(a):
    tmp=[]
    for t in a:
        tmp.append(t[3::4])
    return tmp[3::4]

def draw_velocity(time, output_dir, data_dir):
    v = np.fromfile(data_dir + '/bin_data/atm_v_time_{}_iter_n_layer_0.bin'.format(time),'<f8')
    w = np.fromfile(data_dir + '/bin_data/atm_w_time_{}_iter_n_layer_0.bin'.format(time),'<f8')
    h = np.fromfile(data_dir + '/bin_data/atm_h_time_{}_iter_n_layer_0.bin'.format(time),'<f8')

    vm = np.sqrt(v**2+w**2)
    vm[vm==0] = 1
    v = v/vm
    w = w/vm

    x = np.linspace(-180, 180, 361)
    y = np.linspace(-90, 90, 181)
    xv, yv = np.meshgrid(x, y)
    #print xv
    #print yv

    figure = plt.figure(figsize=(15, 8))
    m = Basemap(llcrnrlon=-180,llcrnrlat=-90,urcrnrlon=180,urcrnrlat=90,projection='cyl', lon_0=0)

    xi, yi = m(xv.flatten(), yv.flatten())

    xi = xi.reshape((181,361))
    yi = yi.reshape((181,361))
    vm = vm.reshape((181,361))
    w = w.reshape((181,361))
    v = v.reshape((181,361))
    h = h.reshape((181,361))

    wn = down_sample(w)
    vn = down_sample(-v)
    wn = wn / np.sqrt(np.square(wn) + np.square(vn))
    vn = vn / np.sqrt(np.square(wn) + np.square(vn))
    cs = m.quiver(down_sample(xi), down_sample(yi), wn, vn, down_sample(vm), width=0.001,
         headlength=7, headwidth=5, pivot='tail', clim=[0, 1.2], cmap='jet')

    m.contour( xi, yi, h, colors ='k', linewidths= 0.3 )
    m.drawparallels(np.arange(-90., 90., 10.), labels=[1,0,0,0], fontsize=10)
    m.drawmeridians(np.arange(-180., 180., 45.), labels=[0,0,0,1], fontsize=10)

    cbar = m.colorbar(cs, location='bottom', pad="10%", label='Velocity (m/s)')
    plt.title("Atmospheric Velocity at {0}Ma".format(time))
    plt.savefig(output_dir + '/{}Ma_velocity.png'.format(time), bbox_inches='tight')
    plt.close()

if  __name__ == "__main__":
    draw_velocity(0, '../benchmark/output/atm_maps/velocity/', '../benchmark/output/')
    draw_velocity(5, '../benchmark/output/atm_maps/velocity/', '../benchmark/output/')


__author__ = 'Advik-B' # advik.b@gmail.com

import os
import sys
from fnmatch import fnmatch
# Third party modules
from send2trash import send2trash as delete
from termcolor import cprint

DEL = False

try:
    cwd = sys.argv[1]
except IndexError:
    cwd = os.getcwd()

if_allowed_files = os.path.isfile(os.path.join(cwd,'allowed-ext.txt'))
if_allowed_dirs = os.path.isdir(os.path.join(cwd,'.keep'))

instructions = """
steps:
    - create a file named 'allowed-ext.txt' in the current directory you can keep all the allowed extensions here
    - run this script
optional:
    - you can also pass the path of the directory as an argument to the script
"""

endmsg = """
if I deleted some important files, you can find them in the trash-folder/bin.
also next time make sure you have the 'allowed-ext.txt' file in the current directory. and all the white-listed extensions in it.
"""

if if_allowed_files == False:
    cprint('allowed-ext.txt NOT found in current directory.\
        \nPlease follow the below instructions before cleanup to avoid loss of files', 'red')
    for line in instructions.split('\n'):
        cprint(line, 'yellow')
    sys.exit(2)
elif if_allowed_files== True:
    cprint('Clean-up is starting.', 'green')
    allowed_ext = ['*.md', '*.txt'] # list of allowed extensions
    allowed_fil = []

    with open(os.path.join(cwd,'allowed-ext.txt'), 'r') as f:
        for line in f:
            allowed_ext.append(line.strip())
        for path, subdirs, files in os.walk(cwd):
            for name in files:
                for pattern in allowed_ext:
                    if fnmatch(name.casefold(), pattern) or '.git' in os.path.join(path, name) or '.vscode' in os.path.join(path, name) or '.keep' in os.path.join(path, name):
                        allowed_fil.append(os.path.join(path, name))

    for path, subdirs, files in os.walk(cwd):
        for name in files:
            if fnmatch(name, '*.*') and os.path.join(path, name) not in allowed_fil:
                pth = os.path.join(path, name)
                cprint('Deleting: '+pth, color='cyan')
                delete(pth)
                DEL = True
    cprint('Clean-up is done.', 'green')
    if DEL:
        for line in endmsg.split('\n'):
            cprint(line, 'yellow')


# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 David Townshend
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 675 Mass Ave, Cambridge, MA 02139, USA.

from norman._six import assert_raises

from norman import NotSet, Field, Store, Index

try:
    from unittest.mock import Mock, patch
except ImportError:
    from mock import Mock, patch

##### Index tests

class TestIndex_Ordered(object):
    'Tests starting with an empty index'

    def setup(self):
        field = Mock(key=lambda x: x)
        self.i = Index(field)
        self.orecords = ['M' + str(i) for i in range(6)]
        self.urecords = ['U0', 'U1', 'U2']
        self.i._ordered = ([0, 1, 2, 3, 3, 4], [r for r in self.orecords])
        self.ordered = ([0, 1, 2, 3, 3, 4], [r for r in self.orecords])
        self.unordered = self.i._unordered.copy()
        self.i._unordered[-1] = [('1', self.urecords[0]),
                                 ('1', self.urecords[1])]
        self.i._unordered[-2] = [('2', self.urecords[2])]
        self.unordered[-1] = [('1', self.urecords[0]),
                              ('1', self.urecords[1])]
        self.unordered[-2] = [('2', self.urecords[2])]

    def test_insert(self):
        r = Mock()
        self.i.insert(1, r)
        self.orecords.insert(2, r)
        assert self.i._ordered == ([0, 1, 1, 2, 3, 3, 4], self.orecords)
        assert self.i._unordered == self.unordered

    def test_len(self):
        assert len(self.i) == 9, len(self.i)

    def test_remove(self):
        self.i.remove(2, self.orecords[2])
        del self.orecords[2]
        expect = ([0, 1, 3, 3, 4], self.orecords)
        assert self.i._ordered == expect
        assert self.i._unordered == self.unordered

    def test_iter_eq(self):
        got = set(self.i == 3)
        expect = set(self.orecords[3:5])
        assert got == expect, (got, expect)

    def test_iter_ne(self):
        expect = set(self.orecords[:3] + self.orecords[5:] + self.urecords)
        got = set(self.i != 3)
        assert got == expect, (got, expect)

    def test_iter_lt(self):
        got = set(self.i < 3)
        expect = set(self.orecords[:3])
        assert got == expect, (got, expect)

    def test_iter_le(self):
        got = set(self.i <= 3)
        expect = set(self.orecords[:5])
        assert got == expect, (got, expect)

    def test_iter_gt(self):
        got = set(self.i > 2)
        expect = set(self.orecords[3:])
        assert got == expect, (got, expect)

    def test_iter_ge(self):
        got = set(self.i >= 2)
        expect = set(self.orecords[2:])
        assert got == expect, (got, expect)

class TestIndex_UnOrdered(object):
    'Tests starting with an empty index'

    def setup(self):
        def key(value):
            raise TypeError
        field = Mock(key=key)
        self.i = Index(field)
        self.orecords = ['M' + str(i) for i in range(6)]
        self.urecords = ['U0', 'U1', 'U2']
        self.i._ordered = ([0, 1, 2, 3, 3, 4], [r for r in self.orecords])
        self.ordered = ([0, 1, 2, 3, 3, 4], [r for r in self.orecords])
        self.unordered = self.i._unordered.copy()
        self.i._unordered[-1] = [('1', self.urecords[0]),
                                 ('1', self.urecords[1])]
        self.i._unordered[-2] = [('2', self.urecords[2])]
        self.unordered[-1] = [('1', self.urecords[0]),
                              ('1', self.urecords[1])]
        self.unordered[-2] = [('2', self.urecords[2])]

        def mockhash(v):
            try:
                return -int(v)
            except ValueError:
                raise TypeError
        patch('norman._store.hash', mockhash, create=True).start()
        patch('norman._store.id', lambda v: 10, create=True).start()

    def teardown(self):
        patch.stopall()

    def test_insert1(self):
        r = Mock()
        self.i.insert('4', r)
        self.unordered[-4] = [('4', r)]
        assert self.i._unordered == self.unordered
        assert self.i._ordered == self.ordered

    def test_insert2(self):
        r = Mock()
        self.i.insert('2', r)
        self.unordered[-2].append(('2', r))
        assert self.i._unordered == self.unordered
        assert self.i._ordered == self.ordered

    def test_insert_id(self):
        r = Mock()
        self.i.insert('a', r)
        self.unordered[10] = [('a', r)]
        assert self.i._unordered == self.unordered
        assert self.i._ordered == self.ordered

    def test_insert_NotSet(self):
        r = Mock()
        self.i.insert(NotSet, r)
        self.unordered[NotSet] = [(NotSet, r)]
        assert self.i._unordered == self.unordered
        assert self.i._ordered == self.ordered

    def test_remove(self):
        self.i.remove('1', self.urecords[1])
        self.unordered[-1].pop()
        assert self.i._unordered == self.unordered
        assert self.i._ordered == self.ordered

    def test_remove_id(self):
        self.i.remove('2', self.urecords[2])
        del self.unordered[-2]
        assert self.i._unordered == self.unordered
        assert self.i._ordered == self.ordered

    def test_iter_eq(self):
        expect = set(self.urecords[:2])
        got = set(self.i == '1')
        assert got == expect, (got, expect)

    def test_iter_ne(self):
        expect = set([self.urecords[2]] + self.orecords)
        got = set(self.i != '1')
        assert got == expect, (got, expect)

    def test_comparison(self):
        with assert_raises(TypeError):
            set(self.i < '1')
        with assert_raises(TypeError):
            set(self.i <= '1')
        with assert_raises(TypeError):
            set(self.i > '1')
        with assert_raises(TypeError):
            set(self.i >= '1')

class TestIndexCornerCases(object):

    def test_notset(self):
        field = Field()
        i = Index(field)
        r = Mock()
        i.insert(NotSet, r)
        assert i._unordered[NotSet] == [(NotSet, r)]

class TestStore(object):

    def setup(self):
        self.full = Field(default=NotSet)
        self.sparse = Field(default= -1)
        self.missing = Field(default=NotSet)
        self.full._name = 'full'
        self.sparse._name = 'sparse'
        self.missing._name = 'missing'
        self.store = Store()
        self.store.add_field(self.full)
        self.store.add_field(self.sparse)

    def populate(self):
        for i in range(5):
            self.store.add_record(str(i))
            self.store.set(str(i), self.full, i)
        for i in [1, 3]:
            self.store.set(str(i), self.sparse, i)

    def test_add_field(self):
        self.populate()
        self.store.add_field(self.missing)
        assert self.store.get('0', self.missing) == NotSet

    def test_add_record(self):
        self.store.add_record('new')
        assert self.store.has_record('new')

    def test_clear(self):
        self.populate()
        self.store.clear()
        assert self.store.record_count() == 0
        for index in self.store.indexes.values():
            assert len(index) == 0

    def test_get(self):
        self.populate()
        assert self.store.get('0', self.full) == 0

    def test_has_record(self):
        self.populate()
        assert self.store.has_record('0')
        assert not self.store.has_record('not a record')

    def test_iter_field_full(self):
        self.populate()
        got = set(self.store.iter_field(self.full))
        expect = set([(str(i), i) for i in range(5)])
        assert got == expect, got

    def test_iter_field_sparse(self):
        self.populate()
        got = set(self.store.iter_field(self.sparse))
        expect = set([('0', -1), ('1', 1), ('2', -1), ('3', 3), ('4', -1)])
        assert got == expect, got

    def test_iter_records(self):
        self.populate()
        it = self.store.iter_records()
        assert set(it) == set('012324')

    def test_record_count(self):
        self.populate()
        assert self.store.record_count() == 5

    def test_remove_record(self):
        self.populate()
        self.store.remove_record('3')
        assert set(self.store.iter_records()) == set('0124')

    def test_remove_field(self):
        # This merely tests that it runs.
        self.populate()
        self.store.remove_field(self.full)

    def test_set_overwrite(self):
        self.populate()
        self.store.set('1', self.sparse, 'new value')
        assert self.store.get('1', self.sparse) == 'new value'

class TestStoreIndex(object):

    def setup(self):
        self.f = Field()
        self.f._name = 'f'
        self.store = Store()
        self.store.add_field(self.f)
        self.index = self.store.indexes[self.f]

    def test_add_record(self):
        self.store.add_record('new')
        assert self.index._unordered[NotSet] == [(NotSet, 'new')]
        assert self.index._ordered == ([], [])

    def test_remove_record(self):
        self.store.add_record('new')
        self.store.remove_record('new')
        assert self.index._unordered == {}
        assert self.index._ordered == ([], [])

    def test_set(self):
        self.store.add_record('new')
        self.store.set('new', self.f, 'value')
        assert self.index._unordered == {}
        assert self.index._ordered == ([('1str', 'value')], ['new'])


from beewin_module.database.beewinDB import Member, Result
from beewin_module.models.dataClass import MemberData
from flask_restful import Resource,  request, output_json
from common.argsParser import uid_parser, member_parser

class MemberResource(Resource):

    def get(self, uid):
        if uid == 'all':
            res: Result = Member.MemberLength()
            return res.to_dict()
        else:
            res: Result = Member.read(uid)
            return res.to_dict()

    def post(self):
        arg = member_parser.parse_args()
        _member = MemberData.from_dict(arg)
        res: Result = Member.create(_member.uid, _member)
        return res.to_dict()

    def put(self):
        arg = member_parser.parse_args()
        _member = MemberData.from_dict(arg)
        res: Result = Member.update(_member.uid, _member)
        return res.to_dict()

    def delete(self):
        arg = uid_parser.parse_args()
        res: Result = Member.delete(arg['uid'])
        return res.to_dict()

if __name__ == '__main__':
    pass


import numpy as np
import matplotlib.pyplot as plt
import iris.plot as iplt
from irise import convert, diagnostics, variable
from myscripts.models.um import case_studies
import tropopause

pvtrop = 3.5
pvname = 'ertel_potential_vorticity'
dz = np.linspace(-2000, 2000, 21)

def main(cubes):
    """
    """
    # Calulate N^2
    theta = convert.calc('air_potential_temperature', cubes)
    nsq = variable.N_sq(theta)

    # Find the tropopause
    ztrop, fold_t, fold_b = tropopause.height(cubes)

    # Mask ridges and troughs
    ridges, troughs = tropopause.ridges_troughs(cubes)

    # Create profile of N_sq vs tropopause
    for name, mask in [('troughs', ridges), ('ridges', troughs)]:
        cube = diagnostics.profile(nsq, ztrop, dz, mask=mask)[0]
        iplt.plot(cube, cube.coords()[0], label=name)

    plt.axhline(color='k')
    plt.xlabel(r'$N^2$ $s^{-1}$')
    plt.ylabel('Distance from the tropopause')
    plt.legend(loc='best')
    plt.title('Tropopause PV = %.1f' % pvtrop)
    plt.show()

if __name__ == '__main__':
    forecast = case_studies.iop8.copy()
    cubes = forecast.set_lead_time(hours=24)
    main(cubes)


N, M = map(int, input().split())
mx = [0, 0]
for i in range(1, 10):
    tmp = 0
    for j in range(1, N+1):
        tmp = tmp*10 + i
        tmp %= M
        if tmp == 0:
            mx = max(mx, [j, i])

if mx[0] != 0:
    print(str(mx[1])*mx[0])
else:
    print(-1)


""" Forms Motors. """
from datetime import date

from django import forms

class MotorForm(forms.Form):
    """ Formularios para oferta de mobliliarios. """
    FUEL_CHOICES = [
        ('Nafta', 'Nafta'),
        ('Diesel', 'Diesel'),
        ('Alcohol', 'Alcohol'),
        ('Flex', 'Flex'),
        ('Eléctrico', 'Eléctrico'),
    ]

    TRANSMISSION_CHOICES = [
        ('manual', 'Manual'),
        ('automatic', 'Automática'),
    ]

    YEAR_CHOICES = [(year, year)
                    for year in range(1886, date.today().year + 1)]

    # Unico de Motor
    brand = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Marca',
        max_length=255,
        min_length=1,
        required=True,
    )
    model = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Modelo',
        max_length=255,
        min_length=1,
        required=True,
    )
    fuel = forms.ChoiceField(
        widget=forms.Select(attrs={'class': 'form-control'}),
        label='Combustible',
        choices=FUEL_CHOICES,
        required=True,
    )
    transmission = forms.ChoiceField(
        widget=forms.Select(attrs={'class': 'form-control'}),
        label='Transmisión',
        choices=TRANSMISSION_CHOICES,
        required=True,
    )
    year = forms.ChoiceField(
        widget=forms.Select(attrs={'class': 'form-control'}),
        label='Año',
        required=True,
        choices=YEAR_CHOICES,
        initial=date.today().year,
    )
    color = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Color',
        max_length=255,
        min_length=4,
        required=True,
    )

    # Compartido con BaseClass
    title = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Título',
        max_length=255,
        min_length=1,
        required=True,
    )

    description = forms.CharField(
        widget=forms.Textarea(attrs={'class': 'form-control'}),
        label='Descripción',
        max_length=255,
        min_length=1,
        required=True,
    )

    location = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Ubicación',
        max_length=255,
        min_length=4,
        required=True,
    )

    price = forms.IntegerField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Precio',
        min_value=1,
        required=True,
        error_messages={
            'invalid': 'Por favor, ingrese un número válido para el precio.',
        }
    )

    phone1 = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Celular 1',
        min_length=8,
        required=True,
        error_messages={
            'invalid': 'Por favor, ingrese un número de contacto válido',
        }
    )

    phone2 = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Celular 2',
        min_length=8,
        required=False
    )

    email = forms.EmailField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Email',
        error_messages={
            'invalid': 'Por favor, ingrese un email válido.',
        }
    )

    images = forms.ImageField(
    widget=forms.FileInput(attrs={'class': 'form-control'}),
    label='Imagen',
    required=False,
    )