#
# Princeton University licenses this file to You under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.  You may obtain a copy of the License at:
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
#
# ****************************************   DISTRIBUTION FUNCTIONS   **************************************************
"""

* `NormalDist`
* `UniformToNormalDist`
* `ExponentialDist`
* `UniformDist`
* `GammaDist`
* `WaldDist`

Overview
--------

Functions that return one or more samples from a distribution.

"""
from enum import IntEnum

import numpy as np
import typecheck as tc

from psyneulink.core.components.functions.function import Function_Base, FunctionError
from psyneulink.core.globals.keywords import \
    ADDITIVE_PARAM, DIST_FUNCTION_TYPE, BETA, DIST_MEAN, DIST_SHAPE, DRIFT_DIFFUSION_ANALYTICAL_FUNCTION, \
    EXPONENTIAL_DIST_FUNCTION, GAMMA_DIST_FUNCTION, HIGH, LOW, MULTIPLICATIVE_PARAM, NOISE, NORMAL_DIST_FUNCTION, \
    SCALE, STANDARD_DEVIATION, THRESHOLD, UNIFORM_DIST_FUNCTION, WALD_DIST_FUNCTION
from psyneulink.core.globals.context import ContextFlags
from psyneulink.core.globals.utilities import parameter_spec
from psyneulink.core.globals.preferences.basepreferenceset import is_pref_set

from psyneulink.core.globals.parameters import Parameter

__all__ = [
    'DistributionFunction', 'DRIFT_RATE', 'DRIFT_RATE_VARIABILITY', 'DriftDiffusionAnalytical', 'ExponentialDist',
    'GammaDist', 'NON_DECISION_TIME', 'NormalDist', 'STARTING_POINT', 'STARTING_POINT_VARIABILITY',
    'THRESHOLD_VARIABILITY', 'UniformDist', 'UniformToNormalDist', 'WaldDist',
]

class DistributionFunction(Function_Base):
    componentType = DIST_FUNCTION_TYPE

class NormalDist(DistributionFunction):
    """
    NormalDist(                      \
             mean=0.0,               \
             standard_deviation=1.0, \
             params=None,            \
             owner=None,             \
             prefs=None              \
             )

    .. _NormalDist:

    Return a random sample from a normal distribution using numpy.random.normal;

    *Modulatory Parameters:*

    | *MULTIPLICATIVE_PARAM:* `standard_deviation <NormalDist.standard_deviation>`
    | *ADDITIVE_PARAM:* `mean <NormalDist.mean>`
    |

    Arguments
    ---------

    mean : float : default 0.0
        The mean or center of the normal distribution

    standard_deviation : float : default 1.0
        Standard deviation of the normal distribution. Must be > 0.0

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    Attributes
    ----------

    mean : float : default 0.0
        The mean or center of the normal distribution.

    standard_deviation : float : default 1.0
        Standard deviation of the normal distribution; if it is 0.0, returns `mean <NormalDist.mean>`.

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    """

    componentName = NORMAL_DIST_FUNCTION

    paramClassDefaults = Function_Base.paramClassDefaults.copy()

    class Parameters(DistributionFunction.Parameters):
        """
            Attributes
            ----------

                mean
                    see `mean <NormalDist.mean>`

                    :default value: 0.0
                    :type: float

                standard_deviation
                    see `standard_deviation <NormalDist.standard_deviation>`

                    :default value: 1.0
                    :type: float

        """
        mean = Parameter(0.0, modulable=True, aliases=[ADDITIVE_PARAM])
        standard_deviation = Parameter(1.0, modulable=True, aliases=[MULTIPLICATIVE_PARAM])

    @tc.typecheck
    def __init__(self,
                 default_variable=None,
                 mean=0.0,
                 standard_deviation=1.0,
                 params=None,
                 owner=None,
                 prefs: is_pref_set = None):
        # Assign args to params and functionParams dicts
        params = self._assign_args_to_param_dicts(mean=mean,
                                                  standard_deviation=standard_deviation,
                                                  params=params)

        super().__init__(default_variable=default_variable,
                         params=params,
                         owner=owner,
                         prefs=prefs,
                         )

    def _validate_params(self, request_set, target_set=None, context=None):
        super()._validate_params(request_set=request_set, target_set=target_set, context=context)

        if STANDARD_DEVIATION in target_set:
            if target_set[STANDARD_DEVIATION] < 0.0:
                raise FunctionError("The standard_deviation parameter ({}) of {} must be greater than zero.".
                                    format(target_set[STANDARD_DEVIATION], self.name))

    def _function(self,
                 variable=None,
                 context=None,
                 params=None,
                 ):
        mean = self.get_current_function_param(DIST_MEAN, context)
        standard_deviation = self.get_current_function_param(STANDARD_DEVIATION, context)

        result = np.random.normal(mean, standard_deviation)

        return self.convert_output_type(result)

class UniformToNormalDist(DistributionFunction):
    """
    UniformToNormalDist(             \
             mean=0.0,               \
             standard_deviation=1.0, \
             params=None,            \
             owner=None,             \
             prefs=None              \
             )

    .. _UniformToNormalDist:

    Return a random sample from a normal distribution using first np.random.rand(1) to generate a sample from a uniform
    distribution, and then converting that sample to a sample from a normal distribution with the following equation:

    .. math::

        normal\\_sample = \\sqrt{2} \\cdot standard\\_dev \\cdot scipy.special.erfinv(2 \\cdot uniform\\_sample - 1)  + mean

    The uniform --> normal conversion allows for a more direct comparison with MATLAB scripts.

    .. note::

        This function requires `SciPy <https://pypi.python.org/pypi/scipy>`_.

    (https://github.com/jonasrauber/randn-matlab-python)

    *Modulatory Parameters:*

    | *MULTIPLICATIVE_PARAM:* `standard_deviation <UniformToNormalDist.standard_deviation>`
    | *ADDITIVE_PARAM:* `mean <UniformToNormalDist.mean>`
    |

    Arguments
    ---------

    mean : float : default 0.0
        The mean or center of the normal distribution

    standard_deviation : float : default 1.0
        Standard deviation of the normal distribution

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    Attributes
    ----------

    mean : float : default 0.0
        The mean or center of the normal distribution

    standard_deviation : float : default 1.0
        Standard deviation of the normal distribution

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    """

    componentName = NORMAL_DIST_FUNCTION

    class Parameters(DistributionFunction.Parameters):
        """
            Attributes
            ----------

                variable
                    see `variable <UniformToNormalDist.variable>`

                    :default value: numpy.array([0])
                    :type: numpy.ndarray
                    :read only: True

                mean
                    see `mean <UniformToNormalDist.mean>`

                    :default value: 0.0
                    :type: float

                standard_deviation
                    see `standard_deviation <UniformToNormalDist.standard_deviation>`

                    :default value: 1.0
                    :type: float

        """
        variable = Parameter(np.array([0]), read_only=True, pnl_internal=True, constructor_argument='default_variable')
        mean = Parameter(0.0, modulable=True, aliases=[ADDITIVE_PARAM])
        standard_deviation = Parameter(1.0, modulable=True, aliases=[MULTIPLICATIVE_PARAM])

    paramClassDefaults = Function_Base.paramClassDefaults.copy()

    @tc.typecheck
    def __init__(self,
                 default_variable=None,
                 mean=0.0,
                 standard_deviation=1.0,
                 params=None,
                 owner=None,
                 prefs: is_pref_set = None):
        # Assign args to params and functionParams dicts
        params = self._assign_args_to_param_dicts(mean=mean,
                                                  standard_deviation=standard_deviation,
                                                  params=params)

        super().__init__(default_variable=default_variable,
                         params=params,
                         owner=owner,
                         prefs=prefs,
                         )

    def _function(self,
                 variable=None,
                 context=None,
                 params=None,
                 ):

        try:
            from scipy.special import erfinv
        except:
            raise FunctionError("The UniformToNormalDist function requires the SciPy package.")

        mean = self.get_current_function_param(DIST_MEAN, context)
        standard_deviation = self.get_current_function_param(STANDARD_DEVIATION, context)

        sample = np.random.rand(1)[0]
        result = ((np.sqrt(2) * erfinv(2 * sample - 1)) * standard_deviation) + mean

        return self.convert_output_type(result)

class ExponentialDist(DistributionFunction):
    """
    ExponentialDist(                \
             beta=1.0,              \
             params=None,           \
             owner=None,            \
             prefs=None             \
             )

    .. _ExponentialDist:

    Return a random sample from a exponential distribution using numpy.random.exponential

    *Modulatory Parameters:*

    | *MULTIPLICATIVE_PARAM:* `beta <ExponentialDist.beta>`
    |

    Arguments
    ---------

    beta : float : default 1.0
        The scale parameter of the exponential distribution

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    Attributes
    ----------

    beta : float : default 1.0
        The scale parameter of the exponential distribution

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    """
    componentName = EXPONENTIAL_DIST_FUNCTION

    paramClassDefaults = Function_Base.paramClassDefaults.copy()

    class Parameters(DistributionFunction.Parameters):
        """
            Attributes
            ----------

                beta
                    see `beta <ExponentialDist.beta>`

                    :default value: 1.0
                    :type: float

        """
        beta = Parameter(1.0, modulable=True, aliases=[MULTIPLICATIVE_PARAM])

    @tc.typecheck
    def __init__(self,
                 default_variable=None,
                 beta=1.0,
                 params=None,
                 owner=None,
                 prefs: is_pref_set = None):
        # Assign args to params and functionParams dicts
        params = self._assign_args_to_param_dicts(beta=beta,
                                                  params=params)

        super().__init__(default_variable=default_variable,
                         params=params,
                         owner=owner,
                         prefs=prefs,
                         )

    def _function(self,
                 variable=None,
                 context=None,
                 params=None,
                 ):

        beta = self.get_current_function_param(BETA, context)
        result = np.random.exponential(beta)

        return self.convert_output_type(result)

class UniformDist(DistributionFunction):
    """
    UniformDist(                      \
             low=0.0,             \
             high=1.0,             \
             params=None,           \
             owner=None,            \
             prefs=None             \
             )

    .. _UniformDist:

    Return a random sample from a uniform distribution using numpy.random.uniform

    Arguments
    ---------

    low : float : default 0.0
        Lower bound of the uniform distribution

    high : float : default 1.0
        Upper bound of the uniform distribution

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    Attributes
    ----------

    low : float : default 0.0
        Lower bound of the uniform distribution

    high : float : default 1.0
        Upper bound of the uniform distribution

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    """
    componentName = UNIFORM_DIST_FUNCTION

    paramClassDefaults = Function_Base.paramClassDefaults.copy()

    class Parameters(DistributionFunction.Parameters):
        """
            Attributes
            ----------

                high
                    see `high <UniformDist.high>`

                    :default value: 1.0
                    :type: float

                low
                    see `low <UniformDist.low>`

                    :default value: 0.0
                    :type: float

        """
        low = Parameter(0.0, modulable=True)
        high = Parameter(1.0, modulable=True)

    @tc.typecheck
    def __init__(self,
                 default_variable=None,
                 low=0.0,
                 high=1.0,
                 params=None,
                 owner=None,
                 prefs: is_pref_set = None):
        # Assign args to params and functionParams dicts
        params = self._assign_args_to_param_dicts(low=low,
                                                  high=high,
                                                  params=params)

        super().__init__(default_variable=default_variable,
                         params=params,
                         owner=owner,
                         prefs=prefs,
                         )

    def _function(self,
                 variable=None,
                 context=None,
                 params=None,
                 ):

        low = self.get_current_function_param(LOW, context)
        high = self.get_current_function_param(HIGH, context)
        result = np.random.uniform(low, high)

        return self.convert_output_type(result)

class GammaDist(DistributionFunction):
    """
    GammaDist(\
             scale=1.0,\
             dist_shape=1.0,\
             params=None,\
             owner=None,\
             prefs=None\
             )

    .. _GammaDist:

    Return a random sample from a gamma distribution using numpy.random.gamma

    *Modulatory Parameters:*

    | *MULTIPLICATIVE_PARAM:* `scale <GammaDist.scale>`
    | *ADDITIVE_PARAM:* `dist_shape <GammaDist.dist_shape>`
    |

    Arguments
    ---------

    scale : float : default 1.0
        The scale of the gamma distribution. Should be greater than zero.

    dist_shape : float : default 1.0
        The shape of the gamma distribution. Should be greater than zero.

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    Attributes
    ----------

    scale : float : default 1.0
        The scale of the gamma distribution. Should be greater than zero.

    dist_shape : float : default 1.0
        The shape of the gamma distribution. Should be greater than zero.

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    """

    componentName = GAMMA_DIST_FUNCTION

    paramClassDefaults = Function_Base.paramClassDefaults.copy()

    class Parameters(DistributionFunction.Parameters):
        """
            Attributes
            ----------

                dist_shape
                    see `dist_shape <GammaDist.dist_shape>`

                    :default value: 1.0
                    :type: float

                scale
                    see `scale <GammaDist.scale>`

                    :default value: 1.0
                    :type: float

        """
        scale = Parameter(1.0, modulable=True, aliases=[MULTIPLICATIVE_PARAM])
        dist_shape = Parameter(1.0, modulable=True, aliases=[ADDITIVE_PARAM])

    @tc.typecheck
    def __init__(self,
                 default_variable=None,
                 scale=1.0,
                 dist_shape=1.0,
                 params=None,
                 owner=None,
                 prefs: is_pref_set = None):
        # Assign args to params and functionParams dicts
        params = self._assign_args_to_param_dicts(scale=scale,
                                                  dist_shape=dist_shape,
                                                  params=params)

        super().__init__(default_variable=default_variable,
                         params=params,
                         owner=owner,
                         prefs=prefs,
                         )

    def _function(self,
                 variable=None,
                 context=None,
                 params=None,
                 ):

        scale = self.get_current_function_param(SCALE, context)
        dist_shape = self.get_current_function_param(DIST_SHAPE, context)

        result = np.random.gamma(dist_shape, scale)

        return self.convert_output_type(result)

class WaldDist(DistributionFunction):
    """
     WaldDist(             \
              scale=1.0,\
              mean=1.0,\
              params=None,\
              owner=None,\
              prefs=None\
              )

     .. _WaldDist:

     Return a random sample from a Wald distribution using numpy.random.wald

    *Modulatory Parameters:*

    | *MULTIPLICATIVE_PARAM:* `scale <WaldDist.scale>`
    | *ADDITIVE_PARAM:* `mean <WaldDist.mean>`
    |

     Arguments
     ---------

     scale : float : default 1.0
         Scale parameter of the Wald distribution. Should be greater than zero.

     mean : float : default 1.0
         Mean of the Wald distribution. Should be greater than or equal to zero.

     params : Dict[param keyword: param value] : default None
         a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
         function.  Values specified for parameters in the dictionary override any assigned to those parameters in
         arguments of the constructor.

     owner : Component
         `component <Component>` to which to assign the Function.

     prefs : PreferenceSet or specification dict : default Function.classPreferences
         the `PreferenceSet` for the Function. If it is not specified, a default is assigned using `classPreferences`
         defined in __init__.py (see :doc:`PreferenceSet <LINK>` for details).

     Attributes
     ----------

     scale : float : default 1.0
         Scale parameter of the Wald distribution. Should be greater than zero.

     mean : float : default 1.0
         Mean of the Wald distribution. Should be greater than or equal to zero.

     params : Dict[param keyword: param value] : default None
         a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
         function.  Values specified for parameters in the dictionary override any assigned to those parameters in
         arguments of the constructor.

     owner : Component
         `component <Component>` to which to assign the Function.

     prefs : PreferenceSet or specification dict : default Function.classPreferences
         the `PreferenceSet` for the Function. If it is not specified, a default is assigned using `classPreferences`
         defined in __init__.py (see :doc:`PreferenceSet <LINK>` for details).

     """

    componentName = WALD_DIST_FUNCTION

    paramClassDefaults = Function_Base.paramClassDefaults.copy()

    class Parameters(DistributionFunction.Parameters):
        """
            Attributes
            ----------

                mean
                    see `mean <WaldDist.mean>`

                    :default value: 1.0
                    :type: float

                scale
                    see `scale <WaldDist.scale>`

                    :default value: 1.0
                    :type: float

        """
        scale = Parameter(1.0, modulable=True, aliases=[MULTIPLICATIVE_PARAM])
        mean = Parameter(1.0, modulable=True, aliases=[ADDITIVE_PARAM])

    @tc.typecheck
    def __init__(self,
                 default_variable=None,
                 scale=1.0,
                 mean=1.0,
                 params=None,
                 owner=None,
                 prefs: is_pref_set = None):
        # Assign args to params and functionParams dicts
        params = self._assign_args_to_param_dicts(scale=scale,
                                                  mean=mean,
                                                  params=params)

        super().__init__(default_variable=default_variable,
                         params=params,
                         owner=owner,
                         prefs=prefs,
                         )

    def _function(self,
                 variable=None,
                 context=None,
                 params=None,
                 ):

        scale = self.get_current_function_param(SCALE, context)
        mean = self.get_current_function_param(DIST_MEAN, context)

        result = np.random.wald(mean, scale)

        return self.convert_output_type(result)

# Note:  For any of these that correspond to args, value must match the name of the corresponding arg in __init__()
DRIFT_RATE = 'drift_rate'
DRIFT_RATE_VARIABILITY = 'DDM_DriftRateVariability'
THRESHOLD_VARIABILITY = 'DDM_ThresholdRateVariability'
STARTING_POINT = 'starting_point'
STARTING_POINT_VARIABILITY = "DDM_StartingPointVariability"
NON_DECISION_TIME = 't0'

def _DriftDiffusionAnalytical_bias_getter(owning_component=None, context=None):
    starting_point = owning_component.parameters.starting_point._get(context)
    threshold = owning_component.parameters.threshold._get(context)
    try:
        return (starting_point + threshold) / (2 * threshold)
    except TypeError:
        return None

# QUESTION: IF VARIABLE IS AN ARRAY, DOES IT RETURN AN ARRAY FOR EACH RETURN VALUE (RT, ER, ETC.)
class DriftDiffusionAnalytical(DistributionFunction):  # -------------------------------------------------------------------------------
    """
    DriftDiffusionAnalytical(   \
        default_variable=None,  \
        drift_rate=1.0,         \
        threshold=1.0,          \
        starting_point=0.0,     \
        t0=0.2                  \
        noise=0.5,              \
        params=None,            \
        owner=None,             \
        prefs=None              \
        )

    .. _DriftDiffusionAnalytical:

    Return terminal value of decision variable, mean accuracy, and mean response time computed analytically for the
    drift diffusion process as described in `Bogacz et al (2006) <https://www.ncbi.nlm.nih.gov/pubmed/17014301>`_.

    *Modulatory Parameters:*

    | *MULTIPLICATIVE_PARAM:* `drift_rate <DriftDiffusionAnalytical.drift_rate>`
    | *ADDITIVE_PARAM:* `starting_point <DriftDiffusionAnalytical.starting_point>`
    |

    Arguments
    ---------

    default_variable : number, list or array : default class_defaults.variable
        specifies a template for decision variable(s);  if it is list or array, a separate solution is computed
        independently for each element.

    drift_rate : float, list or 1d array : default 1.0
        specifies the drift_rate of the drift diffusion process.  If it is a list or array,
        it must be the same length as `default_variable <DriftDiffusionAnalytical.default_variable>`.

    threshold : float, list or 1d array : default 1.0
        specifies the threshold (boundary) of the drift diffusion process.  If it is a list or array,
        it must be the same length as `default_variable <DriftDiffusionAnalytical.default_variable>`.

    starting_point : float, list or 1d array : default 1.0
        specifies the initial value of the decision variable for the drift diffusion process.  If it is a list or
        array, it must be the same length as `default_variable <DriftDiffusionAnalytical.default_variable>`.

    noise : float, list or 1d array : default 0.0
        specifies the noise term (corresponding to the diffusion component) of the drift diffusion process.
        If it is a float, it must be a number from 0 to 1.  If it is a list or array, it must be the same length as
        `default_variable <DriftDiffusionAnalytical.default_variable>` and all elements must be floats from 0 to 1.

    t0 : float, list or 1d array : default 0.2
        specifies the non-decision time for solution. If it is a float, it must be a number from 0 to 1.  If it is a
        list or array, it must be the same length as  `default_variable <DriftDiffusionAnalytical.default_variable>` and all
        elements must be floats from 0 to 1.

    params : Dict[param keyword: param value] : default None
        a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
        function.  Values specified for parameters in the dictionary override any assigned to those parameters in
        arguments of the constructor.

    owner : Component
        `component <Component>` to which to assign the Function.

    name : str : default see `name <Function.name>`
        specifies the name of the Function.

    prefs : PreferenceSet or specification dict : default Function.classPreferences
        specifies the `PreferenceSet` for the Function (see `prefs <Function_Base.prefs>` for details).

    shenhav_et_al_compat_mode: bool : default False
        whether Shenhav et al. compatibility mode is set. See shenhav_et_al_compat_mode property.

    Attributes
    ----------

    variable : number or 1d array
        holds initial value assigned to :keyword:`default_variable` argument;
        ignored by `function <BogaczEtal.function>`.

    drift_rate : float or 1d array
        determines the drift component of the drift diffusion process.

    threshold : float or 1d array
        determines the threshold (boundary) of the drift diffusion process (i.e., at which the integration
        process is assumed to terminate).

    starting_point : float or 1d array
        determines the initial value of the decision variable for the drift diffusion process.

    noise : float or 1d array
        determines the diffusion component of the drift diffusion process (used to specify the variance of a
        Gaussian random process).

    t0 : float or 1d array
        determines the assumed non-decision time to determine the response time returned by the solution.

    bias : float or 1d array
        normalized starting point:
        (`starting_point <DriftDiffusionAnalytical.starting_point>` + `threshold <DriftDiffusionAnalytical.threshold>`) /
        (2 * `threshold <DriftDiffusionAnalytical.threshold>`)

    owner : Component
        `component <Component>` to which the Function has been assigned.

    name : str
        the name of the Function; if it is not specified in the **name** argument of the constructor, a
        default is assigned by FunctionRegistry (see `Naming` for conventions used for default and duplicate names).

    prefs : PreferenceSet or specification dict : Function.classPreferences
        the `PreferenceSet` for function; if it is not specified in the **prefs** argument of the Function's
        constructor, a default is assigned using `classPreferences` defined in __init__.py (see :doc:`PreferenceSet
        <LINK>` for details).

    """

    componentName = DRIFT_DIFFUSION_ANALYTICAL_FUNCTION

    paramClassDefaults = Function_Base.paramClassDefaults.copy()

    class Parameters(DistributionFunction.Parameters):
        """
            Attributes
            ----------

                bias
                    see `bias <DriftDiffusionAnalytical.bias>`

                    :default value: 0.5
                    :type: float
                    :read only: True

                drift_rate
                    see `drift_rate <DriftDiffusionAnalytical.drift_rate>`

                    :default value: 1.0
                    :type: float

                noise
                    see `noise <DriftDiffusionAnalytical.noise>`

                    :default value: 0.5
                    :type: float

                starting_point
                    see `starting_point <DriftDiffusionAnalytical.starting_point>`

                    :default value: 0.0
                    :type: float

                t0
                    see `t0 <DriftDiffusionAnalytical.t0>`

                    :default value: 0.2
                    :type: float

                threshold
                    see `threshold <DriftDiffusionAnalytical.threshold>`

                    :default value: 1.0
                    :type: float

        """
        drift_rate = Parameter(1.0, modulable=True, aliases=[MULTIPLICATIVE_PARAM])
        starting_point = Parameter(0.0, modulable=True, aliases=[ADDITIVE_PARAM])
        threshold = Parameter(1.0, modulable=True)
        noise = Parameter(0.5, modulable=True)
        t0 = .200
        bias = Parameter(0.5, read_only=True, getter=_DriftDiffusionAnalytical_bias_getter)

    @tc.typecheck
    def __init__(self,
                 default_variable=None,
                 drift_rate: parameter_spec = 1.0,
                 starting_point: parameter_spec = 0.0,
                 threshold: parameter_spec = 1.0,
                 noise: parameter_spec = 0.5,
                 t0: parameter_spec = .200,
                 params=None,
                 owner=None,
                 prefs: is_pref_set = None,
                 shenhav_et_al_compat_mode=False):

        self._shenhav_et_al_compat_mode = shenhav_et_al_compat_mode

        # Assign args to params and functionParams dicts
        params = self._assign_args_to_param_dicts(drift_rate=drift_rate,
                                                  starting_point=starting_point,
                                                  threshold=threshold,
                                                  noise=noise,
                                                  t0=t0,
                                                  params=params)

        super().__init__(default_variable=default_variable,
                         params=params,
                         owner=owner,
                         prefs=prefs,
                         )

    @property
    def output_type(self):
        return self._output_type

    @output_type.setter
    def output_type(self, value):
        # disabled because it happens during normal execution, may be confusing
        # warnings.warn('output_type conversion disabled for {0}'.format(self.__class__.__name__))
        self._output_type = None

    @property
    def shenhav_et_al_compat_mode(self):
        """
        Get whether the function is set to Shenhav et al. compatibility mode. This mode allows
        the analytic computations of mean error rate and reaction time to match exactly the
        computations made in the MATLAB DDM code (Matlab/ddmSimFRG.m). These compatibility changes
        should only effect edges cases that involve the following cases:

            - Floating point overflows and underflows are ignored when computing mean RT and mean ER
            - Exponential expressions used in cacluating mean RT and mean ER are bounded by 1e-12 to 1e12.
            - Decision time is not permitted to be negative and will be set to 0 in these cases. Thus RT
              will be RT = non-decision-time in these cases.

        Returns
        -------
        Shenhav et al. compatible mode setting : (bool)

        """
        return self._shenhav_et_al_compat_mode

    @shenhav_et_al_compat_mode.setter
    def shenhav_et_al_compat_mode(self, value):
        """
        Set whether the function is set to Shenhav et al. compatibility mode. This mode allows
        the analytic computations of mean error rate and reaction time to match exactly the
        computations made in the MATLAB DDM code (Matlab/ddmSimFRG.m). These compatibility chages
        should only effect edges cases that involve the following cases:

            - Floating point overflows and underflows are ignored when computing mean RT and mean ER
            - Exponential expressions used in cacluating mean RT and mean ER are bounded by 1e-12 to 1e12.
            - Decision time is not permitted to be negative and will be set to 0 in these cases. Thus RT
              will be RT = non-decision-time in these cases.

        Arguments
        ---------

        value : bool
            Set True to turn on Shenhav et al. compatibility mode, False for off.
        """
        self._shenhav_et_al_compat_mode = value

    def _function(self,
                 variable=None,
                 context=None,
                 params=None,
                 ):
        """
        Return: terminal value of decision variable (equal to threshold), mean accuracy (error rate; ER) and mean
        response time (RT)

        Arguments
        ---------

        variable : 2d array
            ignored.

        params : Dict[param keyword: param value] : default None
            a `parameter dictionary <ParameterPort_Specification>` that specifies the parameters for the
            function.  Values specified for parameters in the dictionary override any assigned to those parameters in
            arguments of the constructor.

        Returns
        -------
        Decision variable, mean ER, mean RT : (float, float, float)

        """

        attentional_drift_rate = float(self.get_current_function_param(DRIFT_RATE, context))
        stimulus_drift_rate = float(variable)
        drift_rate = attentional_drift_rate * stimulus_drift_rate
        threshold = self.get_current_function_param(THRESHOLD, context)
        starting_point = float(self.get_current_function_param(STARTING_POINT, context))
        noise = float(self.get_current_function_param(NOISE, context))
        t0 = float(self.get_current_function_param(NON_DECISION_TIME, context))

        # drift_rate = float(self.drift_rate) * float(variable)
        # threshold = float(self.threshold)
        # starting_point = float(self.starting_point)
        # noise = float(self.noise)
        # t0 = float(self.t0)

        bias = (starting_point + threshold) / (2 * threshold)

        # Prevents div by 0 issue below:
        if bias <= 0:
            bias = 1e-8
        if bias >= 1:
            bias = 1 - 1e-8

        # drift_rate close to or at 0 (avoid float comparison)
        if np.abs(drift_rate) < 1e-8:
            # back to absolute bias in order to apply limit
            bias_abs = bias * 2 * threshold - threshold
            # use expression for limit a->0 from Srivastava et al. 2016
            rt = t0 + (threshold ** 2 - bias_abs ** 2) / (noise ** 2)
            er = (threshold - bias_abs) / (2 * threshold)
        else:
            drift_rate_normed = np.abs(drift_rate)
            ztilde = threshold / drift_rate_normed
            atilde = (drift_rate_normed / noise) ** 2

            is_neg_drift = drift_rate < 0
            bias_adj = (is_neg_drift == 1) * (1 - bias) + (is_neg_drift == 0) * bias
            y0tilde = ((noise ** 2) / 2) * np.log(bias_adj / (1 - bias_adj))
            if np.abs(y0tilde) > threshold:
                # First difference between Shenhav et al. DDM code and PNL's.
                if self.shenhav_et_al_compat_mode:
                    y0tilde = -1 * (y0tilde < 0) * threshold + (y0tilde >=0 ) * threshold
                else:
                    y0tilde = -1 * (is_neg_drift == 1) * threshold + (is_neg_drift == 0) * threshold

            x0tilde = y0tilde / drift_rate_normed

            # Whether we should ignore or raise floating point over and underflow exceptions.
            # Shenhav et al. MATLAB code ignores them.
            ignore_or_raise = "raise"
            if self.shenhav_et_al_compat_mode:
                ignore_or_raise = "ignore"

            with np.errstate(over=ignore_or_raise, under=ignore_or_raise):
                try:
                    # Lets precompute these common sub-expressions
                    exp_neg2_x0tilde_atilde = np.exp(-2 * x0tilde * atilde)
                    exp_2_ztilde_atilde = np.exp(2 * ztilde * atilde)
                    exp_neg2_ztilde_atilde = np.exp(-2 * ztilde * atilde)

                    if self.shenhav_et_al_compat_mode:
                        exp_neg2_x0tilde_atilde = np.nanmax([1e-12, exp_neg2_x0tilde_atilde])
                        exp_2_ztilde_atilde = np.nanmin([1e12, exp_2_ztilde_atilde])
                        exp_neg2_ztilde_atilde = np.nanmax([1e-12, exp_neg2_ztilde_atilde])

                    rt = ztilde * np.tanh(ztilde * atilde) + \
                         ((2 * ztilde * (1 - exp_neg2_x0tilde_atilde)) / (
                                 exp_2_ztilde_atilde - exp_neg2_ztilde_atilde) - x0tilde)
                    er = 1 / (1 + exp_2_ztilde_atilde) - \
                         ((1 - exp_neg2_x0tilde_atilde) / (exp_2_ztilde_atilde - exp_neg2_ztilde_atilde))

                    # Fail safe to prevent negative mean RT's. Shenhav et al. do this.
                    if self.shenhav_et_al_compat_mode:
                        if rt < 0:
                            rt = 0

                    rt = rt + t0

                except FloatingPointError:
                    # Per Mike Shvartsman:
                    # If ±2*ztilde*atilde (~ 2*z*a/(c^2) gets very large, the diffusion vanishes relative to drift
                    # and the problem is near-deterministic. Without diffusion, error rate goes to 0 or 1
                    # depending on the sign of the drift, and so decision time goes to a point mass on z/a – x0, and
                    # generates a "RuntimeWarning: overflow encountered in exp"
                    er = 0
                    rt = ztilde / atilde - x0tilde + t0

            # This last line makes it report back in terms of a fixed reference point
            #    (i.e., closer to 1 always means higher p(upper boundary))
            # If you comment this out it will report errors in the reference frame of the drift rate
            #    (i.e., reports p(upper) if drift is positive, and p(lower if drift is negative)
            er = (is_neg_drift == 1) * (1 - er) + (is_neg_drift == 0) * (er)

        # Compute moments (mean, variance, skew) of condiational response time distributions
        moments = DriftDiffusionAnalytical._compute_conditional_rt_moments(drift_rate, noise, threshold, bias, t0)

        return rt, er, \
               moments['mean_rt_plus'], moments['var_rt_plus'], moments['skew_rt_plus'], \
               moments['mean_rt_minus'], moments['var_rt_minus'], moments['skew_rt_minus']

    @staticmethod
    def _compute_conditional_rt_moments(drift_rate, noise, threshold, starting_point, t0):
        """
        This is a helper function for computing the conditional decison time moments for the DDM.
        It is based completely off of Matlab\\DDMFunctions\\ddm_metrics_cond_Mat.m.

        :param drift_rate: The drift rate of the DDM
        :param noise: The diffusion rate.
        :param threshold: The symmetric threshold of the DDM
        :param starting_point: The initial condition.
        :param t0: The non decision time.
        :return: A dictionary containing the following key value pairs:
         mean_rt_plus: The mean RT of positive responses.
         mean_rt_minus: The mean RT of negative responses.
         var_rt_plus: The variance of RT of positive responses.
         var_rt_minus: The variance of RT of negative responses.
         skew_rt_plus: The skew of RT of positive responses.
         skew_rt_minus: The skew of RT of negative responses.
        """

        #  transform starting point to be centered at 0
        starting_point = (starting_point - 0.5) * 2.0 * threshold

        if abs(drift_rate) < 0.01:
            drift_rate = 0.01

        X = drift_rate * starting_point / noise**2
        Z = drift_rate * threshold / noise**2

        X = max(-100, min(100, X))

        Z = max(-100, min(100, Z))

        if abs(Z) < 0.0001:
            Z = 0.0001

        def coth(x):
            return 1 / np.tanh(x)

        def csch(x):
            return 1 / np.sinh(x)

        moments = {}

        # Lets ignore any divide by zeros we get or NaN errors. This will allow the NaN's to propogate.
        with np.errstate(divide='ignore', invalid='ignore'):
            moments["mean_rt_plus"] = noise**2. / (drift_rate**2) * (2 * Z * coth(2 * Z) - (X + Z) * coth(X + Z))

            moments["mean_rt_minus"] = noise**2. / (drift_rate**2) * (2 * Z * coth(2 * Z) - (-X + Z) * coth(-X + Z))

            moments["var_rt_plus"] = noise**4. / (drift_rate**4) * \
                              (4 * Z**2. * (csch(2 * Z))**2 + 2 * Z * coth(2 * Z) - (Z + X)**2. *
                               (csch(Z + X))**2 - (Z + X) * coth(Z + X))

            moments["var_rt_minus"] = noise**4. / (drift_rate**4) * \
                               (4 * Z**2. * (csch(2 * Z)) ** 2 + 2 * Z * coth(2 * Z) - (Z - X)**2. *
                                (csch(Z - X))**2 - (Z - X) * coth(Z - X))

            moments["skew_rt_plus"] = noise**6. / (drift_rate** 6) * \
                               (12 * Z**2. * (csch(2 * Z))**2 + 16 * Z**3. * coth(2 * Z) * (csch(2 * Z))**2 +
                                6 * Z * coth(2 * Z) - 3 * (Z + X)**2. * (csch(Z + X))**2 -
                                2 * (Z + X)**3. * coth(Z + X) * (csch(Z + X))**2 - 3 * (Z + X) * coth(Z + X))

            moments["skew_rt_minus"] = noise**6. / (drift_rate**6) * \
                                (12 * Z**2. * (csch(2 * Z))**2 + 16 * Z**3. * coth(2 * Z) *
                                 (csch(2 * Z))**2 + 6 * Z * coth(2 * Z) - 3 * (Z - X)**2. *
                                 (csch(Z - X))**2 - 2 * (Z - X)**3. * coth(Z - X) *
                                 (csch(Z - X))**2 - 3 * (Z - X) * coth(Z - X))

            # divide third central moment by var_rt**1.5 to get skewness
            moments['skew_rt_plus'] /= moments['var_rt_plus']**1.5
            moments['skew_rt_minus'] /= moments['var_rt_minus']**1.5

            # Add the non-decision time to the mean RTs
            moments['mean_rt_plus'] += t0
            moments['mean_rt_minus'] += t0

        return moments

    def derivative(self, output=None, input=None, context=None):
        """
        derivative(output, input)

        Calculate the derivative of :math:`\\frac{1}{reward rate}` with respect to the threshold (**output** arg)
        and drift_rate (**input** arg).  Reward rate (:math:`RR`) is assumed to be:

            :math:`RR = delay_{ITI} + \\frac{Z}{A} + ED`;

        the derivative of :math:`\\frac{1}{RR}` with respect to the `threshold <DriftDiffusionAnalytical.threshold>` is:

            :math:`\\frac{1}{A} - \\frac{E}{A} - 2\\frac{A}{c^2}ED`;

        and the derivative of 1/RR with respect to the `drift_rate <DriftDiffusionAnalytical.drift_rate>` is:

            :math:`-\\frac{Z}{A^2} + \\frac{Z}{A^2}E - \\frac{2Z}{c^2}ED`

        where:

            *A* = `drift_rate <DriftDiffusionAnalytical.drift_rate>`,

            *Z* = `threshold <DriftDiffusionAnalytical.threshold>`,

            *c* = `noise <DriftDiffusionAnalytical.noise>`,

            *E* = :math:`e^{-2\\frac{ZA}{c^2}}`,

            *D* = :math:`delay_{ITI} + delay_{penalty} - \\frac{Z}{A}`,

            :math:`delay_{ITI}` is the intertrial interval and :math:`delay_{penalty}` is a penalty delay.

        Returns
        -------

        derivatives :  List[float, float)
            of :math:`\\frac{1}{RR}` with respect to `threshold <DriftDiffusionAnalytical.threshold>` and `drift_rate
            <DriftDiffusionAnalytical.drift_rate>`.

        """
        Z = output or self.get_current_function_param(THRESHOLD, context)
        A = input or self.get_current_function_param(DRIFT_RATE, context)
        c = self.get_current_function_param(NOISE, context)
        c_sq = c ** 2
        E = np.exp(-2 * Z * A / c_sq)
        D_iti = 0
        D_pen = 0
        D = D_iti + D_pen
        # RR =  1/(D_iti + Z/A + (E*D))

        dRR_dZ = 1 / A + E / A + (2 * A / c_sq) * E * D
        dRR_dA = -Z / A ** 2 + (Z / A ** 2) * E - (2 * Z / c_sq) * E * D

        return [dRR_dZ, dRR_dA]


import subprocess
import sys

import portage
from portage import os
from portage import _unicode_decode
from portage.const import (BASH_BINARY, PORTAGE_BASE_PATH,
	PORTAGE_BIN_PATH, PORTAGE_PYM_PATH, USER_CONFIG_PATH)
from portage.process import find_binary
from portage.tests import TestCase
from portage.tests.resolver.ResolverPlayground import ResolverPlayground
from portage.util import ensure_dirs

class SimpleEmergeTestCase(TestCase):

	def _have_python_xml(self):
		try:
			__import__("xml.etree.ElementTree")
			__import__("xml.parsers.expat").parsers.expat.ExpatError
		except (AttributeError, ImportError):
			return False
		return True

	def testSimple(self):

		debug = False

		install_something = """
S="${WORKDIR}"

pkg_pretend() {
	einfo "called pkg_pretend for $CATEGORY/$PF"
}

src_install() {
	einfo "installing something..."
	insinto /usr/lib/${P}
	echo "blah blah blah" > "${T}"/regular-file
	doins "${T}"/regular-file
	dosym regular-file /usr/lib/${P}/symlink || die

	# Test code for bug #381629, using a copyright symbol encoded with latin-1.
	# We use $(printf "\\xa9") rather than $'\\xa9', since printf apparently
	# works in any case, while $'\\xa9' transforms to \\xef\\xbf\\xbd under
	# some conditions. TODO: Find out why it transforms to \\xef\\xbf\\xbd when
	# running tests for Python 3.2 (even though it's bash that is ultimately
	# responsible for performing the transformation).
	local latin_1_dir=/usr/lib/${P}/latin-1-$(printf "\\xa9")-directory
	insinto "${latin_1_dir}"
	echo "blah blah blah" > "${T}"/latin-1-$(printf "\\xa9")-regular-file || die
	doins "${T}"/latin-1-$(printf "\\xa9")-regular-file
	dosym latin-1-$(printf "\\xa9")-regular-file ${latin_1_dir}/latin-1-$(printf "\\xa9")-symlink || die
}

pkg_config() {
	einfo "called pkg_config for $CATEGORY/$PF"
}

pkg_info() {
	einfo "called pkg_info for $CATEGORY/$PF"
}

pkg_preinst() {
	einfo "called pkg_preinst for $CATEGORY/$PF"

	# Test that has_version and best_version work correctly with
	# prefix (involves internal ROOT -> EROOT calculation in order
	# to support ROOT override via the environment with EAPIs 3
	# and later which support prefix).
	if has_version $CATEGORY/$PN:$SLOT ; then
		einfo "has_version detects an installed instance of $CATEGORY/$PN:$SLOT"
		einfo "best_version reports that the installed instance is $(best_version $CATEGORY/$PN:$SLOT)"
	else
		einfo "has_version does not detect an installed instance of $CATEGORY/$PN:$SLOT"
	fi
}

"""

		ebuilds = {
			"dev-libs/A-1": {
				"EAPI" : "4",
				"IUSE" : "+flag",
				"KEYWORDS": "x86",
				"LICENSE": "GPL-2",
				"MISC_CONTENT": install_something,
				"RDEPEND": "flag? ( dev-libs/B[flag] )",
			},
			"dev-libs/B-1": {
				"EAPI" : "4",
				"IUSE" : "+flag",
				"KEYWORDS": "x86",
				"LICENSE": "GPL-2",
				"MISC_CONTENT": install_something,
			},
			"virtual/foo-0": {
				"EAPI" : "4",
				"KEYWORDS": "x86",
				"LICENSE": "GPL-2",
			},
		}

		installed = {
			"dev-libs/A-1": {
				"EAPI" : "4",
				"IUSE" : "+flag",
				"KEYWORDS": "x86",
				"LICENSE": "GPL-2",
				"RDEPEND": "flag? ( dev-libs/B[flag] )",
				"USE": "flag",
			},
			"dev-libs/B-1": {
				"EAPI" : "4",
				"IUSE" : "+flag",
				"KEYWORDS": "x86",
				"LICENSE": "GPL-2",
				"USE": "flag",
			},
			"dev-libs/depclean-me-1": {
				"EAPI" : "4",
				"IUSE" : "",
				"KEYWORDS": "x86",
				"LICENSE": "GPL-2",
				"USE": "",
			},
			"app-misc/depclean-me-1": {
				"EAPI" : "4",
				"IUSE" : "",
				"KEYWORDS": "x86",
				"LICENSE": "GPL-2",
				"RDEPEND": "dev-libs/depclean-me",
				"USE": "",
			},
		}

		metadata_xml_files = (
			(
				"dev-libs/A",
				{
					"herd" : "base-system",
					"flags" : "<flag name='flag'>Description of how USE='flag' affects this package</flag>",
				},
			),
			(
				"dev-libs/B",
				{
					"herd" : "no-herd",
					"flags" : "<flag name='flag'>Description of how USE='flag' affects this package</flag>",
				},
			),
		)

		playground = ResolverPlayground(
			ebuilds=ebuilds, installed=installed, debug=debug)
		settings = playground.settings
		eprefix = settings["EPREFIX"]
		eroot = settings["EROOT"]
		trees = playground.trees
		portdb = trees[eroot]["porttree"].dbapi
		portdir = settings["PORTDIR"]
		var_cache_edb = os.path.join(eprefix, "var", "cache", "edb")
		cachedir = os.path.join(var_cache_edb, "dep")
		cachedir_pregen = os.path.join(portdir, "metadata", "md5-cache")

		portage_python = portage._python_interpreter
		dispatch_conf_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "dispatch-conf"))
		ebuild_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "ebuild"))
		egencache_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "egencache"))
		emerge_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "emerge"))
		emaint_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "emaint"))
		env_update_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "env-update"))
		etc_update_cmd = (BASH_BINARY,
			os.path.join(PORTAGE_BIN_PATH, "etc-update"))
		fixpackages_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "fixpackages"))
		portageq_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "portageq"))
		quickpkg_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "quickpkg"))
		regenworld_cmd = (portage_python, "-Wd",
			os.path.join(PORTAGE_BIN_PATH, "regenworld"))

		rm_binary = find_binary("rm")
		self.assertEqual(rm_binary is None, False,
			"rm command not found")
		rm_cmd = (rm_binary,)

		egencache_extra_args = []
		if self._have_python_xml():
			egencache_extra_args.append("--update-use-local-desc")

		test_ebuild = portdb.findname("dev-libs/A-1")
		self.assertFalse(test_ebuild is None)

		test_commands = (
			env_update_cmd,
			portageq_cmd + ("envvar", "-v", "CONFIG_PROTECT", "EROOT",
				"PORTAGE_CONFIGROOT", "PORTAGE_TMPDIR", "USERLAND"),
			etc_update_cmd,
			dispatch_conf_cmd,
			emerge_cmd + ("--version",),
			emerge_cmd + ("--info",),
			emerge_cmd + ("--info", "--verbose"),
			emerge_cmd + ("--list-sets",),
			emerge_cmd + ("--check-news",),
			rm_cmd + ("-rf", cachedir),
			rm_cmd + ("-rf", cachedir_pregen),
			emerge_cmd + ("--regen",),
			rm_cmd + ("-rf", cachedir),
			({"FEATURES" : "metadata-transfer"},) + \
				emerge_cmd + ("--regen",),
			rm_cmd + ("-rf", cachedir),
			({"FEATURES" : "metadata-transfer"},) + \
				emerge_cmd + ("--regen",),
			rm_cmd + ("-rf", cachedir),
			egencache_cmd + ("--update",) + tuple(egencache_extra_args),
			({"FEATURES" : "metadata-transfer"},) + \
				emerge_cmd + ("--metadata",),
			rm_cmd + ("-rf", cachedir),
			({"FEATURES" : "metadata-transfer"},) + \
				emerge_cmd + ("--metadata",),
			emerge_cmd + ("--metadata",),
			rm_cmd + ("-rf", cachedir),
			emerge_cmd + ("--oneshot", "virtual/foo"),
			emerge_cmd + ("--pretend", "dev-libs/A"),
			ebuild_cmd + (test_ebuild, "manifest", "clean", "package", "merge"),
			emerge_cmd + ("--pretend", "--tree", "--complete-graph", "dev-libs/A"),
			emerge_cmd + ("-p", "dev-libs/B"),
			emerge_cmd + ("-B", "dev-libs/B",),
			emerge_cmd + ("--oneshot", "--usepkg", "dev-libs/B",),

			# trigger clean prior to pkg_pretend as in bug #390711
			ebuild_cmd + (test_ebuild, "unpack"),
			emerge_cmd + ("--oneshot", "dev-libs/A",),

			emerge_cmd + ("--noreplace", "dev-libs/A",),
			emerge_cmd + ("--config", "dev-libs/A",),
			emerge_cmd + ("--info", "dev-libs/A", "dev-libs/B"),
			emerge_cmd + ("--pretend", "--depclean", "--verbose", "dev-libs/B"),
			emerge_cmd + ("--pretend", "--depclean",),
			emerge_cmd + ("--depclean",),
			quickpkg_cmd + ("dev-libs/A",),
			emerge_cmd + ("--usepkgonly", "dev-libs/A"),
			emaint_cmd + ("--check", "all"),
			emaint_cmd + ("--fix", "all"),
			fixpackages_cmd,
			regenworld_cmd,
			portageq_cmd + ("match", eroot, "dev-libs/A"),
			portageq_cmd + ("best_visible", eroot, "dev-libs/A"),
			portageq_cmd + ("best_visible", eroot, "binary", "dev-libs/A"),
			portageq_cmd + ("contents", eroot, "dev-libs/A-1"),
			portageq_cmd + ("metadata", eroot, "ebuild", "dev-libs/A-1", "EAPI", "IUSE", "RDEPEND"),
			portageq_cmd + ("metadata", eroot, "binary", "dev-libs/A-1", "EAPI", "USE", "RDEPEND"),
			portageq_cmd + ("metadata", eroot, "installed", "dev-libs/A-1", "EAPI", "USE", "RDEPEND"),
			portageq_cmd + ("owners", eroot, eroot + "usr"),
			emerge_cmd + ("-p", eroot + "usr"),
			emerge_cmd + ("-p", "--unmerge", "-q", eroot + "usr"),
			emerge_cmd + ("--unmerge", "--quiet", "dev-libs/A"),
			emerge_cmd + ("-C", "--quiet", "dev-libs/B"),
		)

		distdir = playground.distdir
		pkgdir = playground.pkgdir
		fake_bin = os.path.join(eprefix, "bin")
		portage_tmpdir = os.path.join(eprefix, "var", "tmp", "portage")
		profile_path = settings.profile_path
		user_config_dir = os.path.join(os.sep, eprefix, USER_CONFIG_PATH)

		path =  os.environ.get("PATH")
		if path is not None and not path.strip():
			path = None
		if path is None:
			path = ""
		else:
			path = ":" + path
		path = fake_bin + path

		pythonpath =  os.environ.get("PYTHONPATH")
		if pythonpath is not None and not pythonpath.strip():
			pythonpath = None
		if pythonpath is not None and \
			pythonpath.split(":")[0] == PORTAGE_PYM_PATH:
			pass
		else:
			if pythonpath is None:
				pythonpath = ""
			else:
				pythonpath = ":" + pythonpath
			pythonpath = PORTAGE_PYM_PATH + pythonpath

		env = {
			"PORTAGE_OVERRIDE_EPREFIX" : eprefix,
			"CLEAN_DELAY" : "0",
			"DISTDIR" : distdir,
			"EMERGE_WARNING_DELAY" : "0",
			"INFODIR" : "",
			"INFOPATH" : "",
			"PATH" : path,
			"PKGDIR" : pkgdir,
			"PORTAGE_INST_GID" : str(portage.data.portage_gid),
			"PORTAGE_INST_UID" : str(portage.data.portage_uid),
			"PORTAGE_PYTHON" : portage_python,
			"PORTAGE_TMPDIR" : portage_tmpdir,
			"PYTHONPATH" : pythonpath,
			"__PORTAGE_TEST_PATH_OVERRIDE" : fake_bin,
		}

		if "__PORTAGE_TEST_HARDLINK_LOCKS" in os.environ:
			env["__PORTAGE_TEST_HARDLINK_LOCKS"] = \
				os.environ["__PORTAGE_TEST_HARDLINK_LOCKS"]

		updates_dir = os.path.join(portdir, "profiles", "updates")
		dirs = [cachedir, cachedir_pregen, distdir, fake_bin,
			portage_tmpdir, updates_dir,
			user_config_dir, var_cache_edb]
		etc_symlinks = ("dispatch-conf.conf", "etc-update.conf")
		# Override things that may be unavailable, or may have portability
		# issues when running tests in exotic environments.
		#   prepstrip - bug #447810 (bash read builtin EINTR problem)
		true_symlinks = ["find", "prepstrip", "sed", "scanelf"]
		true_binary = find_binary("true")
		self.assertEqual(true_binary is None, False,
			"true command not found")
		try:
			for d in dirs:
				ensure_dirs(d)
			for x in true_symlinks:
				os.symlink(true_binary, os.path.join(fake_bin, x))
			for x in etc_symlinks:
				os.symlink(os.path.join(PORTAGE_BASE_PATH, "cnf", x),
					os.path.join(eprefix, "etc", x))
			with open(os.path.join(var_cache_edb, "counter"), 'wb') as f:
				f.write(b"100")
			# non-empty system set keeps --depclean quiet
			with open(os.path.join(profile_path, "packages"), 'w') as f:
				f.write("*dev-libs/token-system-pkg")
			for cp, xml_data in metadata_xml_files:
				with open(os.path.join(portdir, cp, "metadata.xml"), 'w') as f:
					f.write(playground.metadata_xml_template % xml_data)
			with open(os.path.join(updates_dir, "1Q-2010"), 'w') as f:
				f.write("""
slotmove =app-doc/pms-3 2 3
move dev-util/git dev-vcs/git
""")

			if debug:
				# The subprocess inherits both stdout and stderr, for
				# debugging purposes.
				stdout = None
			else:
				# The subprocess inherits stderr so that any warnings
				# triggered by python -Wd will be visible.
				stdout = subprocess.PIPE

			for args in test_commands:

				if isinstance(args[0], dict):
					local_env = env.copy()
					local_env.update(args[0])
					args = args[1:]
				else:
					local_env = env

				proc = subprocess.Popen(args,
					env=local_env, stdout=stdout)

				if debug:
					proc.wait()
				else:
					output = proc.stdout.readlines()
					proc.wait()
					proc.stdout.close()
					if proc.returncode != os.EX_OK:
						for line in output:
							sys.stderr.write(_unicode_decode(line))

				self.assertEqual(os.EX_OK, proc.returncode,
					"emerge failed with args %s" % (args,))
		finally:
			playground.cleanup()


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = [
        ('eatery_profile', '0003_specials_normal_price'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='reviews',
            name='stars_given',
        ),
    ]


import torch
import pandas as pd
from torch.utils.data import Dataset
from transformers import BertTokenizerFast

class ToxicCommentsDataset(Dataset):
    """Custom Dataset

    :returns: dictionary
    :rtype: Dict
    """

    def __init__(
            self,
            data: pd.DataFrame,
            target_columns: list,
            tokenizer: BertTokenizerFast,
            max_token_len: int = 128
    ):
        self.data = data
        self.target_columns = target_columns
        self.tokenizer = tokenizer
        self.max_token_len = max_token_len

    def __len__(self):
        return len(self.data)

    def __getitem__(self, index: int):
        data_row = self.data.iloc[index]
        _id = data_row['id']
        comment_text = data_row.comment_text
        labels = data_row[self.target_columns]

        encoding = self.tokenizer.encode_plus(
            comment_text,
            max_length=self.max_token_len,
            padding='max_length',
            truncation=True,
            add_special_tokens=True,  # [CLS] & [SEP]
            return_token_type_ids=False,
            return_attention_mask=True,  # attention_mask
            return_tensors='pt',
        )

        return dict(
            _id=_id,
            comment_text=comment_text,
            input_ids=encoding['input_ids'].flatten(),
            attention_mask=encoding['attention_mask'].flatten(),
            labels=torch.FloatTensor(labels)
        )


import os
import cv2
import numpy as np
from imgaug import augmenters as iaa

_rotation_plus = iaa.Affine(rotate=3)
_rotation_minus = iaa.Affine(rotate=-3)
_translate_plus = iaa.Affine(translate_percent={"x": 0, "y": 0.2})
_translate_minus = iaa.Affine(translate_percent={"x": 0, "y": -0.2})
_scale_up = iaa.Affine(scale=1.02, order=[0, 1])
_scale_down = iaa.Affine(scale=0.8, order=[0, 1])

_brightness = iaa.Multiply(0.6)
_dropout = iaa.Dropout(p=0.09, per_channel=True)
_gaussian_noise = iaa.AdditiveGaussianNoise(scale=30, per_channel=True)
_gaussian_blur = iaa.GaussianBlur(sigma=(1.5, 1.6))
_pepper = iaa.Pepper(0.05)
_hue_and_saturation = iaa.AddToHueAndSaturation((-25, 15))
_contrast = iaa.ContrastNormalization((0.4, 0.7))

def _apply_perspective(img):
    rows, cols, ch = img.shape

    param = 0.10 * rows

    pts1 = np.float32([[0, 0], [cols, 0], [cols, rows], [0, rows]])
    pts2 = np.float32([[param, param], [cols - param, param], [cols, rows - param], [0, rows - param]])

    M = cv2.getPerspectiveTransform(pts1, pts2)
    dst = cv2.warpPerspective(img, M, (cols, rows))

    return dst

_transformations = [
    iaa.Noop(),

    iaa.OneOf([
        _rotation_plus,
        _rotation_minus,
        _gaussian_blur,
        _brightness

    ]),

    #_translate_plus,

    #iaa.Sequential([
    #    _gaussian_noise,
    #
    #]),

    #_brightness,

    #iaa.Sequential([
    #    _hue_and_saturation,
    #    iaa.Sometimes(0.8, _gaussian_noise)
    #]),
    #iaa.Sequential([
    #    iaa.Sometimes(0.6, _scale_down),
    #    _translate_minus
    #], random_order=False),

    #iaa.Sequential([
    #    iaa.Sometimes(0.6, _scale_up),
    #    _translate_minus
    #], random_order=False),

    #iaa.Sequential([
    #    _contrast,
    #    iaa.Sometimes(0.5, iaa.GaussianBlur(sigma=1.2))
    #])
]

def _augment_image(img, results):
    for t in _transformations:
        if isinstance(t, iaa.Augmenter):
            results.append(t.augment_image(img))
        else:
            results.append(t(img))

def resize_with_padding(img, size):
    width, height = img.shape[1], img.shape[0]

    target_h, target_w = size

    if width > height:
        ratio = float(target_w) / float(width)

        if height * ratio <= target_h:
            padding = int((target_h - ratio * height) / 2) + 1
            top = padding
            bottom = padding
            left = 0
            right = 0
            shape = (target_w, int(ratio * height))
            offset = (0, padding)
        else:
            ratio = float(target_h) / float(height)
            padding = int((target_w - ratio * width) / 2) + 1
            top = 0
            bottom = 0
            left = padding
            right = padding
            shape = (int(width * ratio), target_h)
            offset = (padding, 0)

    else:
        ratio = float(target_h) / float(height)

        if width * ratio <= target_w:
            padding = int((target_w - ratio * width) / 2) + 1
            top = 0
            bottom = 0
            left = padding
            right = padding
            shape = (int(ratio * width), target_h,)
            offset = (padding, 0)
        else:
            ratio = float(target_w) / float(width)
            padding = int((target_h - ratio * height) / 2) + 1
            top = padding
            bottom = padding
            left = 0
            right = 0
            shape = (target_w, int(ratio * height))
            offset = (padding, 0)

    resized_img = cv2.resize(img, dsize=shape, interpolation=cv2.INTER_AREA)
    padded = cv2.copyMakeBorder(resized_img, top, bottom, left, right, cv2.BORDER_CONSTANT)

    padded = padded[0:target_h, 0:target_w, :]

    return padded, ratio, offset

def document_imgaug( x , y ):

    x_generator = []
    y_generator = []
    # print('Aug generator')
    for i, img_array in enumerate(x):
        #img_array = np.asarray(img_array)
        #img_array = img_array.astype('float32')

        if(img_array.shape[2] == 3):
            img = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
        else:
            img = img_array

        _augment_image(img, x_generator)

        for c in range(0, len(_transformations)):
            y_generator.append(y[i])
        # print(i)
        # print(i,'  --  ', y[i],'  --  ',len(x_generator))
    # print('Aug generated')

    return np.array(x_generator), np.array(y_generator)


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import re

api_dir = './api'

# add automodule options for distributions and StochasticTensor
options = [':inherited-members:']
modules = ['zhusuan.distributions.univariate',
           'zhusuan.distributions.multivariate',
           'zhusuan.framework.stochastic',
           'zhusuan.variational.exclusive_kl',
           'zhusuan.variational.monte_carlo',
           'zhusuan.variational.inclusive_kl']

for module in modules:
    module_path = os.path.join(api_dir, module + '.rst')
    with open(module_path, 'r') as f:
        module_string = f.read()
    target = r'\:members\:(\n|.)*\:undoc-members\:(\n|.)*\:show-inheritance\:'
    indent = '    '
    rep = ':members:\n' + indent + ':undoc-members:\n' + indent + \
        ':show-inheritance:'
    for option in options:
        rep += '\n' + indent + option
    post_module_string = re.sub(target, rep, module_string)
    with open(module_path, 'w') as f:
        f.write(post_module_string)


from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController

oyna = Ursina()
osmon_texture = load_texture('assets/skybox.png')

class Voxel(Button):
    def init(self, position=(0, 0, 0)):
        super().init__(
            position=position,
            model='cube',
            origin_y=0.5,
            texture='white_cube',
            color=color.color(0, 0, random.uniform(0.9, 1)),
            scale=0.5,
        )

    def input(self, key):
        if self.hovered:
            if key == 'left mouse down':
                voxel = Voxel(position=self.position + mouse.normal)
        elif key == 'right mouse down':
            destroy(self)

        def init(self):
            super().init(
                parent=scene)

class osmon(Entity):
        model = 'sphere'
        texture = osmon_texture
        double_sided = True

for x in range(20):
    for z in range(20):
        voxel = Voxel(position=(x, 0, z))

scale = 150
oyinchi = FirstPersonController()
osmon = osmon()
oyna.run()


# def square (number):
#     a={}
#     i=1
#     while i<=number:
#         a[i]=i**2
#         i+=1
#     return a
# n=square(15)
# print(n)

def add_numbers(n1, n2):
    sum=n1+n2
    return sum
result=add_numbers(5.4)
print(result)


num = cont = soma = maior = menor = 0
resp = 's'
while resp == 's':
    num = int(input('Digite um número: '))
    cont += 1
    soma += num
    if cont == 1:
        maior = menor = num
    if maior < num:
        maior = num
    if menor > num:
        menor = num
    resp = str(input('Quere continuar [S/N]: ')).lower()
media = soma / cont
print(f'Você digitou {cont} números e a media entres eles foi {media} ')
print(f'O maior valor lido foi {maior} e o menor valor lido foi {menor}')

Português/meu aprendisado/python/exercicios de phyton/Desafio#65.py


import os
import sys
import cv2
import time
import pickle
import numpy as np
from scipy import io
import matplotlib.pyplot as plt

from config import cfg
from facial_shape import recon_facial_shape
from landmark import detect_lmk_2d, detect_lmk_3d
from helpers import relative, head_pose, eye_mask, pupil_center, eyeball_center, average_gaze, lines_intersection, W_2_I, W_2_C, I_2_W
from visualization import visualize_3d

sys.path.append('./TDDFA_V2/')

if __name__ == '__main__':
    subj_idx = cfg.EasyCali.subj_idx

    image_save_dir = os.path.join('./calibration/', cfg.EasyCali.image_save_dir, 'subject_{}'.format(subj_idx))
    image_name_lst = os.listdir(image_save_dir)
    if 'intrinsic_params.mat' in image_name_lst:
        image_name_lst.remove('intrinsic_params.mat')
    image_name_lst.sort()
    print('Now:', len(image_name_lst))

    intrinsic_params = io.loadmat(os.path.join(image_save_dir, 'intrinsic_params.mat'))

    with open(cfg.lmk_idx_path, 'rb') as lmk_idx_file:
        lmk_idx_dict = pickle.load(lmk_idx_file)

    result_save_path = os.path.join('./calibration/', cfg.EasyCali.result_save_dir, 'subject_{}'.format(subj_idx))
    if not os.path.exists(result_save_path):
        os.makedirs(result_save_path)

    # Facial shape recovery
    fs_3d_path = os.path.join(result_save_path, 'facial_shape_3d.npy')
    lmk_3d_path = os.path.join(result_save_path, 'landmark_3d.npy')

    if not os.path.exists(fs_3d_path) or not os.path.exists(lmk_3d_path):
        assert cfg.face_3d_model in ['3DDFA_V2']
        all_fs_3d = np.zeros((len(image_name_lst), cfg.face_3d_model_shape[0], 3))

        for image_idx in range(len(image_name_lst)):
            image_name = image_name_lst[image_idx]
            image_path = os.path.join(image_save_dir, image_name)

            image = cv2.imread(image_path)
            fs_3d, fs_frontal_3d, exist_fs = recon_facial_shape(image, cfg)

            all_fs_3d[image_idx, :, :] = fs_3d
            print(image_name, '-- facial shape done.')

        avg_fs_3d = np.mean(all_fs_3d, axis=0)
        print(avg_fs_3d.shape)
        np.save(fs_3d_path, avg_fs_3d)

        lmk_3d = detect_lmk_3d(avg_fs_3d, lmk_idx_dict)
        np.save(lmk_3d_path, lmk_3d)

    else:
        avg_fs_3d = np.load(fs_3d_path)
        lmk_3d = np.load(lmk_3d_path)

    fs_3d, fs_frontal_3d = avg_fs_3d, avg_fs_3d[: cfg.face_3d_model_frontal_idx, :]

    # Eyeball center calibration
    EyeballCenter_path = os.path.join(result_save_path, 'eyeball_center.npy')
    gaze_lines_path = os.path.join(result_save_path, 'gaze_lines.mat')

    if not os.path.exists(EyeballCenter_path) or not os.path.exists(gaze_lines_path):

        left_lines_strt = np.zeros((len(image_name_lst), 3))
        left_lines_vec = np.zeros((len(image_name_lst), 3))
        right_lines_strt = np.zeros((len(image_name_lst), 3))
        right_lines_vec = np.zeros((len(image_name_lst), 3))

        no_lmk_idx = []

        for image_idx in range(len(image_name_lst)):
            image_name = image_name_lst[image_idx]
            image_path = os.path.join(image_save_dir, image_name)

            image = cv2.imread(image_path)

            # 2d landmark detection
            assert cfg.face_2d_model in ['MediaPipe']
            lmk_2d, Pupil_2d, Eye_contour_2d, exist_lmk = detect_lmk_2d(image, lmk_idx_dict, cfg)

            if not exist_lmk:
                no_lmk_idx.append(image_idx)
                continue

            # Head pose
            rot_v, rot_mat, trans_v = head_pose(lmk_2d, lmk_3d, intrinsic_params)

            # W ---> I
            fs_W, fs_frontal_W = fs_3d, fs_frontal_3d
            fs_frontal_I = W_2_I(fs_frontal_W, rot_v, trans_v, intrinsic_params)

            # Two points for one gaze line
            Pupil_W_1 = I_2_W(Pupil_2d, rot_mat, trans_v, intrinsic_params, scalar=3e5)
            Pupil_W_2 = I_2_W(Pupil_2d, rot_mat, trans_v, intrinsic_params, scalar=6e5)

            left_lines_strt[image_idx, :] = Pupil_W_1[0]
            left_lines_vec[image_idx, :] = Pupil_W_2[0] - Pupil_W_1[0]
            right_lines_strt[image_idx, :] = Pupil_W_1[1]
            right_lines_vec[image_idx, :] = Pupil_W_2[1] - Pupil_W_1[1]

            print(image_name, '-- gaze line done.')

        left_lines_strt = np.delete(left_lines_strt, no_lmk_idx, axis=0)
        left_lines_vec = np.delete(left_lines_vec, no_lmk_idx, axis=0)
        right_lines_strt = np.delete(right_lines_strt, no_lmk_idx, axis=0)
        right_lines_vec = np.delete(right_lines_vec, no_lmk_idx, axis=0)

        LeftEyeballCenter_W = lines_intersection(left_lines_strt, left_lines_vec)
        RightEyeballCenter_W = lines_intersection(right_lines_strt, right_lines_vec)

        EyeballCenter_W = np.array([LeftEyeballCenter_W, RightEyeballCenter_W])
        np.save(EyeballCenter_path, EyeballCenter_W)

        gaze_lines_dict = {
            'left_lines_strt': left_lines_strt,
            'left_lines_vec': left_lines_vec,
            'right_lines_strt': right_lines_strt,
            'right_lines_vec': right_lines_vec,
        }
        io.savemat(gaze_lines_path, mdict=gaze_lines_dict)

    else:
        EyeballCenter_W = np.load(EyeballCenter_path)
        LeftEyeballCenter_W, RightEyeballCenter_W = EyeballCenter_W[0], EyeballCenter_W[1]

        gaze_lines_dict = io.loadmat(gaze_lines_path)

    # Visualize
    if cfg.EasyCali.visualize:
        vis_3d_dict_W = {  # WCS default
            'CS': 'W',
            'view': (90, -90),

            'facial_shape_3d': {'obj': fs_3d, 's': 20, 'alpha': 0.05},
            'LeftEyeballCenter': {'obj': LeftEyeballCenter_W, 'color': 'g', 's': 40, 'alpha': 0.8},
            'RightEyeballCenter': {'obj': RightEyeballCenter_W, 'color': 'g', 's': 40, 'alpha': 0.8},
            'landmark': {'obj': lmk_3d, 'color': 'r', 's': 40, 'alpha': 0.8},
            'gaze_lines_dict': {'obj': gaze_lines_dict, 'sampling_rate': 1/5, 'start_pos': 0.3, 'end_pos': 0.7,
                                'left_color': 'r', 'right_color': 'b', 'linewidth': 2.0, 'alpha': 0.4},

            'show_tick': True,  # [True, False]
        }

        visualize_3d(vis_3d_dict_W)


import os
from dotenv import load_dotenv
import time
import random
import wandb

from UNet import RecursiveUNet
from engine import Segmentor
from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts,CosineAnnealingLR, ReduceLROnPlateau
from dataloader import prep_dataloader

import numpy as np
from torch import nn
from torch.cuda import amp
import torch
import SimpleITK as sitk
sitk.ProcessObject_SetGlobalWarningDisplay(False)

import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)

def seed_everything(seed=42):
    random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = True

class CFG:

    def __init__(self):
        self.data_path = os.getenv('VIDA_PATH')
        self.name = "UNet_baseline"
        self.model = 'RecursiveUNet' # ['UNet', 'RecursiveUNet']
        self.optimizer = 'adam' # ['adam', 'adamp']
        self.scheduler = 'CosineAnnealingWarmRestarts'
        # ['CosineAnnealingWarmRestarts', 'CosineAnnealingLR', 'ReduceLROnPlateau']
        self.epochs = 10
        self.T_0 = self.epochs

        self.in_file = 'ENV18PM_ProjSubjList_cleaned_IN.in'
        self.lr = 0.0001
        self.min_lr = 1e-6
        self.train_bs = 16
        self.valid_bs = 32
        self.test_results_dir = "RESULTS"
        self.mask = 'airway'
        self.save = False
        self.debug = False

def tune_hyper():

    with wandb.init() as run:
        print('config:', dict(run.config))
        # Model
        if run.config.activation == 'elu':
            activation = nn.ELU(inplace=True)
        else:
            activation = nn.ReLU(inplace=True)

        model = RecursiveUNet(activation=activation)
        # model = RecursiveUNet(activation=nn.ELU(inplace=True))
        DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        model.to(DEVICE)
        loss_function = nn.CrossEntropyLoss()
        optimizer = torch.optim.Adam(model.parameters(),lr=0.0001)
        scheduler = CosineAnnealingWarmRestarts(optimizer,
                                                    T_0=c.T_0,
                                                    T_mult=1,
                                                    eta_min=1e-6,
                                                    last_epoch=-1)

        scaler = amp.GradScaler()
        eng = Segmentor(model=model,
                    optimizer=optimizer,
                    scheduler=scheduler,
                    loss_fn=loss_function,
                    device=DEVICE,
                    scaler=scaler)

        best_loss = np.inf
        best_dice = 0
        # Train
        wandb.watch(eng.model,log='all',log_freq=10)
        for epoch in range(c.epochs):
            trn_loss, trn_dice = eng.train(train_loader)
            val_loss, val_dice = eng.evaluate(valid_loader)
            wandb.log({'epoch': epoch, 'trn_loss': trn_loss, 'val_loss': val_loss,
                        'trn_dice': trn_dice, 'val_dice': val_dice})
            if c.scheduler == 'ReduceLROnPlateau':
                scheduler.step(val_loss)
            eng.epoch += 1
            print(f'Epoch: {epoch}, train loss: {trn_loss:5f}, valid loss: {val_loss:5f}')
            print(f'Epoch: {epoch}, train dice: {trn_dice:5f}, valid dice: {val_dice:5f}')
        #  if val_dice > best_dice:
        #      best_dice = val_dice
        #      print('Best Dice:', best_dice)
        #      if c.save:
        #          torch.save(model.state_dict(), path)
            if val_loss < best_loss:
                best_loss = val_loss
                best_dice = val_dice
                print(f'Best loss: {best_loss} with {best_dice}')
        torch.cuda.empty_cache()

if __name__ == "__main__":
    load_dotenv()
    seed_everything()
    # wandb.init(project='airway')

    sweep_config = {
        'name':'UNet',
        'method':'bayes',
        'metric': {
            'name': 'val_loss',
            'goal': 'minimize'
        },
        'parameters': {
            'activation': {
                'values' : ['elu','relu']
            }
        }
        # 'parameters': {
        #     'epochs': {
        #         'values' : [10,20]
        #     },
        #     'learning_rate' : {
        #         'min' : 0.0001,
        #         'max' : 0.01
        #     }
        # }

    }
    sweep_id = wandb.sweep(sweep_config,project='airway')

    c = CFG()
    n_case = 64
    print('***********************************************************')
    print('Configuration: ')
    print(c.__dict__)
    print('***********************************************************')

    # Data
    if c.debug: # only use 10 cases, 1 epoch
        train_loader, valid_loader = prep_dataloader(c,n_case=5)
        c.epochs = 2
    else:
        train_loader, valid_loader = prep_dataloader(c,n_case=n_case)

    count = 2
    wandb.agent(sweep_id, function=tune_hyper, count=count)


import torch
import torch.nn as nn

from .NR import NoneRegularizer
from .SVMO import SVMORegularizer
from .SVDO import SVDORegularizer
from .SO import SORegularizer

mapping = {
    'none': NoneRegularizer,
    'svdo': SVDORegularizer,
    'svmo': SVMORegularizer,
    'so': SORegularizer
}

class ConvRegularizer(nn.Module):

    def __init__(self, klass):
        super().__init__()
        self.reg_instance = klass()

    def get_all_conv_layers(self, module):

        if isinstance(module, (nn.Sequential, list)):
            for m in module:
                yield from self.get_all_conv_layers(m)

        if isinstance(module, nn.Conv2d):
            yield module

    def forward(self, net, ignore=False):

        accumulator = torch.tensor(0.0).cuda()

        if ignore:
            return accumulator

        for conv in self.get_all_conv_layers(net.module.pretrained):
            accumulator += self.reg_instance(conv.weight)

        # print(accumulator.data)
        return accumulator

def get_regularizer(name):

    return ConvRegularizer(mapping[name])


import logging

logger = logging.getLogger()

logging.basicConfig(
    filename='application-log.log',
    filemode='w',
    format='%(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)

CITIES = {
    "MOSCOW": "https://code.s3.yandex.net/async-module/moscow-response.json",
    "PARIS": "https://code.s3.yandex.net/async-module/paris-response.json",
    "LONDON": "https://code.s3.yandex.net/async-module/london-response.json",
    "BERLIN": "https://code.s3.yandex.net/async-module/berlin-response.json",
    "BEIJING": "https://code.s3.yandex.net/async-module/beijing-response.json",
    "KAZAN": "https://code.s3.yandex.net/async-module/kazan-response.json",
    "SPETERSBURG": "https://code.s3.yandex.net/async-module/spetersburg-response.json",
    "VOLGOGRAD": "https://code.s3.yandex.net/async-module/volgograd-response.json",
    "NOVOSIBIRSK": "https://code.s3.yandex.net/async-module/novosibirsk-response.json",
    "KALININGRAD": "https://code.s3.yandex.net/async-module/kaliningrad-response.json",
    "ABUDHABI": "https://code.s3.yandex.net/async-module/abudhabi-response.json",
    "WARSZAWA": "https://code.s3.yandex.net/async-module/warszawa-response.json",
    "BUCHAREST": "https://code.s3.yandex.net/async-module/bucharest-response.json",
    "ROMA": "https://code.s3.yandex.net/async-module/roma-response.json",
    "CAIRO": "https://code.s3.yandex.net/async-module/cairo-response.json",
}
ERR_MESSAGE_TEMPLATE = "Something wrong. Please contact with mentor."

MIN_MAJOR_PYTHON_VER = 3
MIN_MINOR_PYTHON_VER = 9

MIN_TIME_IN_EACH_DATE = 10
MAX_TIME_IN_EACH_DATE = 19

GOOD_WEATHER = [
    'clear', 'partly-cloudy', 'cloudy', 'overcast', 'drizzle'
]

DF_TEMPLATE = [
    {
        'Погода': 'Температура, среднее'
    },
    {
        'Погода': 'Без осадков, часов'
    }
]

MSG_NOT_ENOUGH_DATA = 'Недостаточно данных по часам для рассчета в городе "{city}". Дата: "{date}" исключена.'
MSG_RECOMMENDED_CITIES = 'Вам рекомендовано отправиться в такие города, как "{cities}"'
MSG_QUEUT_PUT = 'В очередь добавлен "{item}"'
MSG_CLEAR_QUEUE = 'Очередь пуста'
MSG_GET_ITEM_QUEUE = 'Запись "{item}" получена из очереди'
MSG_CSV_SAVED = 'Файл csv успешно сохранен'
MSG_FINAL = 'Метод analyze отработал, результат: "{cities}"'

def check_python_version():
    import sys

    if (
            sys.version_info.major < MIN_MAJOR_PYTHON_VER
            or sys.version_info.minor < MIN_MINOR_PYTHON_VER
    ):
        raise Exception(
            'Please use python version >= {}.{}'.format(
                MIN_MAJOR_PYTHON_VER, MIN_MINOR_PYTHON_VER
            )
        )


import numpy as np
import pandas as pd
import collections
from math import floor
from scipy.sparse import issparse

def balanced_newick_tree(num_taxa):
    if num_taxa%2 != 0: raise ValueError("There is no balanced tree on {num_taxa} taxa. Please specify an even number.")
    if num_taxa == 2: return "(0,1);"
    def _balanced_newick_subtree(nt, left=False):
        if nt == 2:
            return "(_,_)"
        elif nt==3:
            return "((_,_),_)" if left else "(_,(_,_))"
        else:
            if nt%2==0:
                return f"({_balanced_newick_subtree(nt/2, True)},{_balanced_newick_subtree(nt/2)})"
            else:
                return f"({_balanced_newick_subtree(floor(nt/2) + int(left), True)},{_balanced_newick_subtree(floor(nt/2) + int(not left))})"
    newick_string = f"({_balanced_newick_subtree(num_taxa/2, True)},{_balanced_newick_subtree(num_taxa/2)});"
    for i in range(0, num_taxa):
        leaf_name = str(np.base_repr(i, base=max(i+1,2))) if num_taxa <= 36 else f't{str(i)}'
        newick_string = newick_string.replace('_', leaf_name, 1)
    return newick_string

def get_balance(s, asTuple=False):
    """Returns a string formatted 'X|X' which describes the balance of a given split string"""
    s = s.split("|")
    if not asTuple:
        return str(len(s[0])) + '|' + str(len(s[1]))
    else:
        return (len(s[0]), len(s[1]))

def is_sparse(matrix):
    return issparse(matrix)

def frob_norm(matrix, data_table=None):
    """Calculates the Frobenius Norm for a given matrix"""
    if data_table is not None:
        return sum(val**2 for _, val in data_table.itertuples(index=False))**(1/2)
    if is_sparse(matrix):
        return sum(matrix[i,j]**2 for i, j in zip(*matrix.nonzero()))**(1/2)
    else:
        return np.sqrt(sum(val**2 for val in np.nditer(matrix)))

def make_substitution_matrix(subs_prob, k):
    matrix = []
    for i in range(k):
        matrix.append([1-subs_prob if j==i else subs_prob/(k-1) for j in range(k)])
    return matrix

def all_splits(num_taxa, trivial=False, only_balance=None, randomise=False):
    """Generates all splits as string-representations

    Args:
        num_taxa: The number of taxa on the tree.
        trivial: Whether or not to calculate trivial splits, default True.
        only_trivial: Whether to ONLY create trivial splits, default False.

    Returns:
        A list of string-representations of splits (using '|'-notation)
    """
    if num_taxa > 35:
        raise ValueError("Cannot generate all splits for more than 35 taxa. Please create an NXTree and use the NXTree all_splits function instead.")
    k = only_balance
    n = num_taxa
    taxa_string = "".join(np.base_repr(i, base=n) for i in range(n))
    r = 0 if trivial else 1
    loop_over = range(r, 2**(n-1) - r)
    if randomise:
        import random
        loop_over = [i for i in loop_over]
        random.shuffle(loop_over)
    for i in loop_over:
        template = format(i, f'0{n}b')
        if not only_balance or sum(int(b) for b in template) in [only_balance, n-only_balance]:
            if r < sum(int(b) for b in template) < n-r:
                left  = ""
                right = ""
                for t, b in zip(taxa_string, template):
                    if b == '0':
                        left += t
                    else:
                        right += t
                if '0' in right:
                    left, right = right, left
                yield f'{left}|{right}'
###
# Functions for reading in a sequence alignment

def read_alignment_from_file(pathToFile, check=True):
    file = open(pathToFile, 'r')
    alignment = collections.OrderedDict()  # Keeps insertion order
    currentSeq = ""
    for line in file:
        if '>' in line:
            currentSeq = line.replace('>', '').replace('\n', '')
        else:
            if currentSeq in alignment:
                alignment[currentSeq] += line.replace('\n', '')
            else:
                alignment[currentSeq] = line.replace('\n', '')
    if check:
        return alignment if __valid_alignment(alignment) else None
    else:
        return alignment

def __valid_alignment(alignmentDict):
    ordered = isinstance(alignmentDict, collections.OrderedDict)  # Must be ordered to avoid incorrect patterns
    sameLength = len(set(len(value) for key, value in alignmentDict.items())) == 1  # Must be same length
    return sameLength and ordered

def get_pattern_counts(alignment, asNumpyArray=False):
    patterns = {}
    sequences = list(alignment.values())
    sequenceLength = len(sequences[0])
    usableSequenceLength = 0
    for i in range(sequenceLength):
        patternAtSitei = ''.join(s[i] for s in sequences).upper()
        if all(c in ('A', 'C', 'G', 'T') for c in patternAtSitei):  # Need to handle U (let U = T)
            usableSequenceLength += 1
            if patternAtSitei in patterns:
                patterns[patternAtSitei] += 1
            else:
                patterns[patternAtSitei] = 1

    if asNumpyArray:
        patterns = np.array([[key, val] for key, val in patterns.items()])

    return patterns, usableSequenceLength

def pattern_counts_to_probs(patterns, seqLen):
    newCounts = [float(float(count) / seqLen) for count in patterns[:, 1]]
    patternList = patterns[:, 0]
    return np.array([patternList, newCounts]).transpose()

def pattern_probs_from_alignment(pathToFile, check=True):
    alignment = read_alignment_from_file(pathToFile, check)
    counts, sequenceLength = get_pattern_counts(alignment, True)
    probs = pattern_counts_to_probs(counts, sequenceLength)
    probs = pd.DataFrame(probs, index=probs[:, 0])
    probs[[1]] = probs[[1]].astype("float")
    return probs, len(alignment), sequenceLength

def scores_to_weights(scores):
    scores = [s if s != 0 else 10 ** (-100) for s in scores]
    inverses = [1 / s for s in scores]
    weights = [i / sum(inverses) for i in inverses]
    return weights

def split_equivalence_classes(splits, group):
    """Breaks up list of splits into equivalence classes under the canonical action of the specified group."""
    def __action(perm, split):
        index_of_sep = split.index('|')
        split = split.replace('|', '')
        split = split = ''.join(str(perm(int(s))) for s in split)
        split = sorted(["".join(sorted(split[:index_of_sep])), "".join(sorted(split[index_of_sep:]))])
        split = f'{split[0]}|{split[1]}'
        return split
    def __orbit(split, group):
        orbit = set()
        for g in group.elements:
            orbit.add(__action(g, split))
        return frozenset(orbit)
    def __orbits(splits, group):
        orbits = set()
        for split in splits:
            orbits.add(__orbit(split, group))
        return sorted(list(set(orbit) for orbit in orbits), key=len)
    return __orbits(splits, group)

def normalised(scores):
    scores = scores.copy()
    if type(scores) is dict:
        minimum = min(scores.values())
        maximum = max(scores.values())
        for key, value in scores.items():
            scores[key] = (value - minimum)/(maximum - minimum)
    elif type(scores) is list:
        minimum = min(scores)
        maximum = max(scores)
        scores = [(score-minimum)/(maximum-minimum) for score in scores]
    return scores

def check_splits_representatives(split_reps, orbits):
    represented_orbits = set()
    for orbit in orbits:
        for split in split_reps:
            if split in orbit:
                represented_orbits.add(frozenset(orbit))
    return len(represented_orbits) == len(orbits)

# Function for scaling the S part of the hard-coded H matrix
def scaled_h_matrix(_lambda):
    if _lambda == None:
        return None, None
    if _lambda == "identity":
        return np.matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [1, 1, 1, 1]]), "identity"
    H = np.matrix([[1, -1], [1, 1]])
    H = np.kron(H, H)
    S = np.empty((0, 4))
    for row in H:
        if len(set(np.asarray(row)[0])) != 1:
            S = np.append(S, row, axis=0)
    S = _lambda * S
    H = np.append(S, [[1, 1, 1, 1]], axis=0)
    return (H, _lambda)


from django.urls import path, include, re_path
from rest_framework.routers import DefaultRouter

from post.views import ProfileFeed
from . import views
from question.views import ProfilePageViewSet, QuestionViewSet, CommentViewSet

router = DefaultRouter()

router.register(
    r"profile/(?P<accounts_pk>\d+)/pages", ProfilePageViewSet, basename="profile-pages",
)
router.register(
    r"profile/(?P<accounts_pk>\d+)/pages/(?P<page_pk>\d+)/questions",
    QuestionViewSet,
    basename="profile-questions",
)
router.register(
    r"profile/(?P<accounts_pk>\d+)/pages/(?P<page_pk>\d+)/questions/(?P<question_pk>\d+)/comments",
    CommentViewSet,
    basename="profile-comment",
)
router.register(
    r"profile/(?P<accounts_pk>\d+)/feed", ProfileFeed, basename="profile-cards",
)

urlpatterns = [
    path("profile/<int:pk>/", views.ProfilePage.as_view()),
    re_path(
        r"profile/(?P<accounts_pk>\d+)/mento/", views.mento_user, name="user-mento"
    ),
    re_path(
        r"profile/(?P<accounts_pk>\d+)/mentiee/",
        views.mentiee_user,
        name="user-mentiee",
    ),
    path("", include(router.urls)),
    path("accounts/signup/", views.signup_view, name="signup"),
    path("accounts/login/", views.login_view, name="login"),
    path("accounts/tendency/", views.tendency_view, name="tendency"),
    path("accounts/tmp-password/", views.tmp_password, name="tmp-password"),
    path("accounts/delete/", views.DeleteUserView.as_view(), name="delete-user"),
    path(
        "accounts/forgot-username/", views.forgot_username_view, name="forgot-username"
    ),
    path(
        "accounts/comment/",
        views.UpdateDecsription.as_view(),
        name="update-description",
    ),
    path("accounts/password/", views.ChangePassword.as_view(), name="update-password",),
    path("accounts/username/", views.ChangeUsername.as_view(), name="update-username",),
    path("accounts/image/", views.UpdateUserImage.as_view(), name="update-image",),
]


# scrape data into CSV
# grab all links from rithm school blog
# store url, anchor tag text and date

import requests
from bs4 import BeautifulSoup
from csv import writer

def web_crawl(url):
    response = requests.get(url)

    soup = BeautifulSoup(response.text, "html.parser")
    articles = soup.find_all('article')

    with open('web_scraping/blog_data.csv', 'a') as file:
        csv_writer = writer(file)
        csv_writer.writerow(["title","link","date"])

        for article in articles:
            a_tag = article.find('a')
            title = a_tag.get_text()
            a_url = a_tag['href']
            date = article.find('time')['datetime']
            csv_writer.writerow([title, a_url, date])

    try:
        next_page = soup.find(class_ = 'current').find_next_sibling().find('a')['href']
    except:
        exit()

    next_page = next_page[5:]

    print(next_page)

    if url[-1].isdigit():
        next_url = url[:-1] + next_page[-1]
    else:
        next_url = url + next_page

    web_crawl(next_url)

    return

web_crawl('https://www.rithmschool.com/blog')