import logging
from six import iteritems, string_types
from spynnaker.pyNN.exceptions import InvalidParameterType
from spynnaker.pyNN.models.pynn_population_common import PyNNPopulationCommon
from spynnaker.pyNN.utilities.constants import SPIKES
from spinn_front_end_common.utilities import globals_variables
from spinn_front_end_common.utilities.exceptions import ConfigurationException

from spynnaker8.models.recorder import Recorder
from spynnaker8.models.populations import IDMixin, PopulationBase
from spynnaker8.models.populations.population_view import PopulationView
from spynnaker8.utilities import DataHolder

from pyNN import descriptions

logger = logging.getLogger(__name__)

class Population(PyNNPopulationCommon, Recorder, PopulationBase):
    """ PyNN 0.8/0.9 population object
    """

    def __init__(self, size, cellclass, cellparams=None, structure=None,
                 initial_values=None, label=None):
        # pylint: disable=too-many-arguments
        size = self._roundsize(size, label)

        # hard code initial values as required
        if initial_values is None:
            initial_values = {}

        if isinstance(cellclass, DataHolder):
            self._vertex_holder = cellclass
            self._vertex_holder.add_item(
                'label', self.create_label(
                    self._vertex_holder.data_items['label'], label))
            assert cellparams is None
        # cellparams being retained for backwards compatibility, but use
        # is deprecated
        elif issubclass(cellclass, DataHolder):
            if cellparams is None:
                internal_params = dict()
            else:
                internal_params = dict(cellparams)
            cell_label = None
            if 'label' in internal_params:
                cell_label = internal_params['label']
            internal_params['label'] = self.create_label(cell_label, label)
            self._vertex_holder = cellclass(**internal_params)
            # emit deprecation warning
        else:
            raise TypeError(
                "cellclass must be an instance or subclass of BaseCellType,"
                " not a %s" % type(cellclass))

        if 'n_neurons' in self._vertex_holder.data_items:
            if size is None:
                size = self._vertex_holder.data_items['n_neurons']
            elif size != self._vertex_holder.data_items['n_neurons']:
                raise ConfigurationException(
                    "Size parameter is {} but the {} expects a size of {}"
                    "".format(size, cellclass,
                              self._vertex_holder.data_items['n_neurons']))
        else:
            if size is None:
                raise ConfigurationException(
                    "Size parameter can not be None for {}".format(cellclass))
            self._vertex_holder.add_item('n_neurons', size)

        # convert between data holder and model (uses ** so that its taken
        # the dictionary to be the parameters themselves)
        vertex = self._vertex_holder.build_model()(
            **self._vertex_holder.data_items)

        # build our initial objects
        super(Population, self).__init__(
            spinnaker_control=globals_variables.get_simulator(),
            size=size, vertex=vertex,
            structure=structure, initial_values=initial_values)
        Recorder.__init__(self, population=self)

        # annotations used by neo objects
        self._annotations = dict()

    def __iter__(self):
        """ Iterate over local cells
        """
        for _id in range(self._size):
            yield IDMixin(self, _id)

    def __getitem__(self, index_or_slice):
        if isinstance(index_or_slice, int):
            return IDMixin(self, index_or_slice)
        else:
            return PopulationView(
                self, index_or_slice, label="view over {}".format(self.label))

    def all(self):
        """ Iterator over cell IDs on all MPI nodes."""
        for _id in range(self._size):
            yield IDMixin(self, _id)

    @property
    def annotations(self):
        """ The annotations given by the end user
        """
        return self._annotations

    @property
    def celltype(self):
        """ Implements the PyNN expected celltype property

        :return: The celltype this property has been set to
        """
        return self._vertex_holder

    def can_record(self, variable):
        """ Determine whether `variable` can be recorded from this population.
        """
        return variable in self._get_all_possible_recordable_variables()

    def record(self, variables, to_file=None, sampling_interval=None,
               indexes=None):
        """ Record the specified variable or variables for all cells in the\
            Population or view.

        :param variables: either a single variable name or a list of variable\
            names. For a given celltype class, `celltype.recordable` contains\
            a list of variables that can be recorded for that celltype.
        :type variables: str or list(str)
        :param to_file: a file to automatically record to (optional).\
            `write_data()` will be automatically called when `end()` is called.
        :type to_file: a Neo IO instance
        :param sampling_interval: a value in milliseconds, and an integer\
            multiple of the simulation timestep.
        """
        if indexes is not None:
            logger.warn(
                "record indexes parameter is non-standard PyNN, so may not "
                "be portable to other simulators. "
                "It is now deprecated and replaced with views")
        self._record_with_indexes(
            variables, to_file, sampling_interval, indexes)

    def _record_with_indexes(
            self, variables, to_file, sampling_interval, indexes):
        """ Same as record but without non-standard PyNN warning

        This method is non-standard PyNN and is intended only to be called by\
        record in a Population, View or Assembly
        """
        if variables is None:  # reset the list of things to record
            if sampling_interval is not None:
                raise ConfigurationException(
                    "Clash between parameters in record."
                    "variables=None turns off recording,"
                    "while sampling_interval!=None implies turn on recording")
            if indexes is not None:
                logger.warning(
                    "View.record with variable None is non-standard PyNN. "
                    "Only the neurons in the view have their record turned "
                    "off. Other neurons already set to record will remain "
                    "set to record")

            # note that if record(None) is called, its a reset
            Recorder._turn_off_all_recording(self, indexes)
            # handle one element vs many elements
        elif isinstance(variables, string_types):
            # handle special case of 'all'
            if variables == "all":
                logger.warning(
                    "This is non-standard PyNN, and therefore may not be "
                    "portable to other simulators.")

                # get all possible recordings for this vertex
                variables = self._get_all_possible_recordable_variables()

                # iterate though them
                for variable in variables:
                    self._record(variable, sampling_interval, to_file, indexes)
            else:
                # record variable
                self._record(variables, sampling_interval, to_file, indexes)

        else:  # list of variables, so just iterate though them
            for variable in variables:
                self._record(variable, sampling_interval, to_file, indexes)

    def write_data(self, io, variables='all', gather=True, clear=False,
                   annotations=None):
        """ Write recorded data to file, using one of the file formats\
            supported by Neo.

        :param io: \
            a Neo IO instance, or a string for where to put a neo instance
        :type io: neo instance or str
        :param variables: \
            either a single variable name or a list of variable names.\
            Variables must have been previously recorded, otherwise an\
            Exception will be raised.
        :type variables: str or list(str)
        :param gather: pointless on sPyNNaker
        :param clear: \
            clears the storage data if set to true after reading it back
        :param annotations: annotations to put on the neo block
        """
        # pylint: disable=too-many-arguments
        if not gather:
            logger.warning("sPyNNaker only supports gather=True. We will run "
                           "as if gather was set to True.")

        if isinstance(io, string_types):
            io = self._get_io(io)

        data = self._extract_neo_block(variables, None, clear, annotations)
        # write the neo block to the file
        io.write(data)

    def describe(self, template='population_default.txt', engine='default'):
        """ Returns a human-readable description of the population.

        The output may be customized by specifying a different template\
        together with an associated template engine (see\
        :mod:`pyNN.descriptions`).

        If `template` is None, then a dictionary containing the template\
        context will be returned.
        """
        vertex_context = self._vertex.describe()

        context = {
            "label": self.label,
            "celltype": vertex_context,
            "structure": None,
            "size": self.size,
            "size_local": self.size,
            "first_id": self._first_id,
            "last_id": self._last_id,
        }
        context.update(self._annotations)
        if self.size > 0:
            context.update({
                "local_first_id": self._first_id,
                "cell_parameters": {}})
        if self._structure:
            context["structure"] = self._structure.describe(template=None)
        return descriptions.render(engine, template, context)

    def _end(self):
        """ Do final steps at the end of the simulation
        """
        for variable in self._write_to_files_indicators:
            if self._write_to_files_indicators[variable] is not None:
                self.write_data(
                    io=self._write_to_files_indicators[variable],
                    variables=[variable])

    def get_data(
            self, variables='all', gather=True, clear=False, annotations=None):
        """ Return a Neo `Block` containing the data\
            (spikes, state variables) recorded from the Assembly.

        :param variables: either a single variable name or a list of variable\
            names. Variables must have been previously recorded, otherwise an
            Exception will be raised.
        :type variables: str or list
        :param: Whether to collect data from all MPI nodes or just the current\
            node (irrelevant on sPyNNaker, which always behaves as if True)
        :type gather: bool
        :param: Whether recorded data will be deleted from the `Assembly`.
        :type clear: bool
        :param annotations: annotations to put on the neo block
        :type annotations: dict
        :rtype: neo.Block
        """
        if not gather:
            logger.warning("sPyNNaker only supports gather=True. We will run "
                           "as if gather was set to True.")

        return self._extract_neo_block(variables, None, clear, annotations)

    def get_data_by_indexes(
            self, variables, indexes, clear=False, annotations=None):
        """ Return a Neo `Block` containing the data\
            (spikes, state variables) recorded from the Assembly.

        :param variables: either a single variable name or a list of variable\
            names. Variables must have been previously recorded, otherwise an
            Exception will be raised.
        :type variables: str or list
        :param indexes: List of neuron indexes to include in the data.
            Clearly only neurons recording will actually have any data
            If None will be taken as all recording as get_data
        :type indexes: list (int)
        :param clear: Whether recorded data will be deleted.
        :type clear: bool
        :param annotations: annotations to put on the neo block
        :type annotations: dict
        :rtype: neo.Block
        """
        return self._extract_neo_block(variables, indexes, clear, annotations)

    def spinnaker_get_data(self, variable):
        """ Public accessor for getting data as a numpy array, instead of\
            the neo based object

        :param variable: \
            either a single variable name or a list of variable names.\
            Variables must have been previously recorded, otherwise an\
            Exception will be raised.
        :return: numpy array of the data
        """
        logger.warning(
            "This call is non-standard PyNN and therefore may not be "
            "portable to other simulators. Nor do we guarantee that this "
            "function will exist in future releases.")
        if isinstance(variable, list):
            if len(variable) == 1:
                variable = variable[0]
            else:
                msg = "Only one type of data at a time is supported"
                raise ConfigurationException(msg)
        if variable == SPIKES:
            return self._get_spikes()
        return self._get_recorded_pynn7(variable)

    def get_spike_counts(self, gather=True):
        """ Return the number of spikes for each neuron.
        """
        spikes = self._get_spikes()
        return PyNNPopulationCommon.get_spike_counts(self, spikes, gather)

    def find_units(self, variable):
        """ Get the units of a variable

        :param variable: The name of the variable
        :return: The units of the variable
        """
        return self._get_variable_unit(variable)

    def set(self, **kwargs):
        for parameter, value in iteritems(kwargs):
            try:
                super(Population, self).set(parameter, value)
            except InvalidParameterType:
                super(Population, self).initialize(parameter, value)

    def initialize(self, **kwargs):
        for parameter, value in iteritems(kwargs):
            super(Population, self).initialize(parameter, value)

    @property
    def initial_values(self):
        if not self._vertex_population_initializable:
            raise KeyError(
                "Population does not support the initialisation")
        return self._vertex.initial_values

    # NON-PYNN API CALL
    def get_initial_value(self, variable, selector=None):
        """ See AbstractPopulationInitializable.get_initial_value
        """
        if not self._vertex_population_initializable:
            raise KeyError(
                "Population does not support the initialisation of {}".format(
                    variable))
        return self._vertex.get_initial_value(variable, selector)

    # NON-PYNN API CALL
    def set_initial_value(self, variable, value, selector=None):
        """ See AbstractPopulationInitializable.set_initial_value
        """
        if not self._vertex_population_initializable:
            raise KeyError(
                "Population does not support the initialisation of {}".format(
                    variable))
        if globals_variables.get_not_running_simulator().has_ran \
                and not self._vertex_changeable_after_run:
            raise Exception("Population does not support changes after run")
        self._vertex.set_initial_value(variable, value, selector)

    # NON-PYNN API CALL
    def get_initial_values(self, selector=None):
        """ See AbstractPopulationInitializable.get_initial_values
        """
        if not self._vertex_population_initializable:
            raise KeyError("Population does not support the initialisation")
        return self._vertex.get_initial_values(selector)

    def get(self, parameter_names, gather=False, simplify=True):
        if simplify is not True:
            logger.warning("The simplify value is ignored if not set to true")
        return super(Population, self).get(parameter_names, gather)

    @property
    def all_cells(self):
        cells = []
        for _id in range(self._size):
            cells.append(IDMixin(self, _id))
        return cells

    @property
    def position_generator(self):
        def gen(i):
            return self.positions[:, i]
        return gen

    @staticmethod
    def create(cellclass, cellparams=None, n=1):
        """ Pass through method to the constructor defined by PyNN.\
        Create n cells all of the same type.\
        Returns a Population object.

        :param cellclass: see Population.__init__
        :param cellparams: see Population.__init__
        :param n: see Population.__init__(size...)
        :return: A New Population
        """
        return Population(size=n, cellclass=cellclass, cellparams=cellparams)


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

"""
Apply desica to a single pixel of GSWP3 forcing

That's all folks.
"""
__author__ = "Martin De Kauwe"
__version__ = "1.0 (06.03.2018)"
__email__ = "mdekauwe@gmail.com"

import pandas as pd
import sys
import numpy as np
import matplotlib.pyplot as plt
import os
from generate_met_data import generate_met_data
from canopy import Canopy, FarquharC3
from math import isclose
from calc_pet import calc_net_radiation, calc_pet_energy
import constants as c
from desica import Desica
from desica import plot_time_to_mortality
from desica import plot_transpiration
from desica import plot_cwd
from desica import plot_sw
from desica import plot_transpiration_and_pet

if __name__ == "__main__":

    time_step = 30
    keep_dry = False
    fname = "gswp3_met/GSWP3_met_10_17.csv"
    df = pd.read_csv(fname)

    years = np.unique(df.year)
    for year in years:
        dfx = df[df.year == year]

        met = generate_met_data(year=year, Tmin=dfx.tmin.iloc[0], Tmax=dfx.tmax.iloc[0],
                                RH=dfx.rh.iloc[0]*100., PPFDmax=df.par.iloc[0],
                                precip=dfx.rain.iloc[0], lat=dfx.lat.iloc[0],
                                lon=dfx.lon.iloc[0], ndays=1, time_step=time_step,
                                keep_dry=keep_dry)

        for i in range(1, len(dfx)):

            met_df = generate_met_data(year=year, Tmin=dfx.tmin.iloc[i],
                                       Tmax=dfx.tmax.iloc[i], RH=dfx.rh.iloc[i]*100.,
                                       PPFDmax=dfx.par.iloc[i], precip=dfx.rain.iloc[i],
                                       lat=dfx.lat.iloc[0], lon=dfx.lon.iloc[0], ndays=1,
                                       time_step=time_step, keep_dry=keep_dry)

            met_df.day = i+1
            met = met.append(met_df)
        met = met.reset_index(drop=True)

        # first year, initialise
        if year == years[0]:
            psi_leaf0 = -1.
            psi_stem0 = -0.5
            sw0 = 0.4
        # else use the last point from previous year
        else:
            psi_leaf0 = out.psi_leaf.iloc[-1]
            psi_stem0 = out.psi_stem.iloc[-1]
            sw0 = out.sw.iloc[-1]

        print(year, round(psi_leaf0, 2), round(psi_stem0, 2), round(sw0, 2))

        AL = 3.        # plant leaf area, m2
        p50 = -4.      # xylem pressure inducing 50% loss of hydraulic
                       # conductivity due to embolism, MPa
        psi_f = -3.    # reference potential for Tuzet model, MPa
        gmin = 10.     # minimum stomatal conductance, mmol m-2 s-1
        Cl = 10000.    # leaf capacitance, mmol MPa-1 (total plant)
        Cs = 120000.   # stem capacitance, mmol MPa-1
        g1 = 4.0       # sensitivity of stomatal conductance to the assimilation
                       # rate, kPa
        g0 = 0.0
        theta_J = 0.85
        Rd25 = 0.92
        Q10 = 1.92
        Vcmax25 = 50.0
        Jmax25 = 100.
        Eav = 58550.0
        deltaSv = 629.26
        Eaj = 29680.
        deltaSj = 631.88
        soil_depth = 2.0
        FAO = False
        F = Canopy(g1=g1, g0=g0, theta_J=theta_J, Rd25=Rd25, Q10=Q10,
                   Vcmax25=Vcmax25, Jmax25=Jmax25, Eav=Eav, deltaSv=deltaSv,
                   Eaj=Eaj, deltaSj=deltaSj)
        D = Desica(psi_stem0=psi_stem0, AL=AL, p50=p50, psi_f=psi_f, gmin=gmin,
                   Cl=Cl, Cs=Cs, F=F, g1=g1, run_twice=True,
                   soil_depth=soil_depth, sw0=sw0, stop_dead=True, FAO=FAO)
        out, day_of_death = D.run_simulation(met)

        odir = "plots"
        if not os.path.exists(odir):
            os.makedirs(odir)

        plot_time_to_mortality(odir, out, time_step, year)
        plot_transpiration(odir, out, year)
        plot_transpiration_and_pet(odir, out, year)
        plot_cwd(odir, out, time_step, year)
        plot_sw(odir, out, time_step, year)

        odir = "outputs"
        if not os.path.exists(odir):
            os.makedirs(odir)

        ofname = os.path.join(odir, "desica_out_%d.csv" % (year))
        out.to_csv(ofname, index=False)


from Products.CMFCore.utils import getToolByName

class ProviderMixin(object):

    def convertAliasBrains(self, brains):
        converted = []
        catalog = getToolByName(self.context, 'portal_catalog')
        for brain in brains:
            if brain.meta_type == 'Alias':
                ref = brain.getObject().getReference()
                if ref is None:
                    continue
                results = catalog(UID=ref.UID())
                if not len(results):
                    continue
                refbrain = results[0]
                if not 'alias' in refbrain:
                    refbrain.__record_schema__['alias'] = max(refbrain.__record_schema__.values()) + 1
                refbrain.alias = brain
                converted.append(refbrain)
            else:
                converted.append(brain)
        return converted


from __future__ import annotations
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from abc import ABC, abstractmethod
import gc

from warnings import warn

warn('The module GaussFit is deprecated.', DeprecationWarning, stacklevel=2)

class BaseGaussFuncs:
    """Abstract class listing methods that a base class for GaussFit must provide.
    """
    @staticmethod
    @abstractmethod
    def full_field(x: np.ndarray, *params) -> np.ndarray:
        """Calculate full fit result (background + gaussians)

        Args:
            x (np.ndarray): X data.
            params (list): Background and Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        pass

    @staticmethod
    @abstractmethod
    def background(x: np.ndarray, *params) -> np.ndarray:
        """Calculate background only.

        Args:
            x (np.ndarray): X data.
            params (list): Background and Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        pass

    @staticmethod
    @abstractmethod
    def gaussian_w_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Calculate fit result (background + single gaussian)

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian [[0, N), where N is the total number of Gaussians.]
            params (list): Background and Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        pass

    @staticmethod
    @abstractmethod
    def gaussian_wo_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Calculate single Gaussian (without background)

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian [[0, N), where N is the total number of Gaussians.]
            params (list): Background and Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        pass

    @staticmethod
    def gaussian(x: np.ndarray, H: float, A: float, x0: float, sigma: float) -> np.ndarray:
        """Evaluate a Gaussian.

        Args:
            x (np.ndarray): X data.
            H (float): Y Offset (constant).
            A (float): Amplitude.
            x0 (float): Mean.
            sigma (float): Width (2*sigma^2)

        Returns:
            np.ndarray: Evaluated Gaussian.
        """
        return H + A * np.exp(-((x - x0)/sigma)**2)

    @staticmethod
    @abstractmethod
    def n_gaussians(*params) -> int:
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        pass

    @staticmethod
    @abstractmethod
    def n_background_params() -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        pass

class GaussFuncsBasic(BaseGaussFuncs):
    """This class accepts parameters of length 5 + 3 * N, where
    N is the number of Gaussians to be fit. first 5 parameters are
    a0 to a4. The background is evaluated as:
    background = a0 + a1*x + a2*x^2 + a3*x^3 + a4*x^4.
    For each Gaussian, the parameters to be supplied are [C, A, W].
    The Gaussians are evaluated as A*exp(-((x-C)/W)^2).

    WARNING: Very basic fit function. With large (or small) values of x,
    the polynomial evaluated can be very large. Use if x is around 0.
    GaussFuncs is the preferred class.
    """

    def __init__(self):
        warn('GaussFuncsBasic class is deprecated. Please use GenericFitFunc and GenericFitManager.', category=DeprecationWarning, stacklevel=2)
        pass

    @staticmethod
    def full_field(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background + gaussians from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncsBasic.background(x, params)
        for i in range(5, len(params), 3):
            ctr = params[i]
            amp = params[i + 1]
            wid = params[i + 2]
            dy = (amp * np.exp(-((x - ctr)/wid)**2))
            y += dy
        return y

    @staticmethod
    def background(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        for i in range(5):
            y += float(params[i]) * (x ** i)
        return y

    @staticmethod
    def gaussian_w_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, with the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncsBasic.background(x, params)
        base = 5 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def gaussian_wo_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, without the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        base = 5 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def n_gaussians(*params):
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        if len(params) == 1:
            params = params[0]
        return (len(params) - 5) // 3

    @staticmethod
    def n_background_params() -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        return 5

class GaussFuncs(BaseGaussFuncs):
    """This class accepts parameters of length 6 + 3 * N, where
    N is the number of Gaussians to be fit. The first parameter
    is x0, and the next 5 parameters are a0 to a4.
    The background is evaluated as:
    background = a0 + a1(x-x0) + a2(x-x0)^2 + a3(x-x0)^3 + a4(x-x0)^4.
    For each Gaussian, the parameters to be supplied are [C, A, W].
    The Gaussians are evaluated as A*exp(-((x-C)/W)^2).

    This is the preferred function suite used to fit Gaussians.
    """
    @staticmethod
    def full_field(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background + gaussians from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncs.background(x, params)
        for i in range(6, len(params), 3):
            ctr = params[i]
            amp = params[i + 1]
            wid = params[i + 2]
            dy = (amp * np.exp(-((x - ctr)/wid)**2))
            y += dy
        return y

    @staticmethod
    def background(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        x0 = params[0]
        for i in range(5):
            y += float(params[i+1])*((x-x0)**i)
        return y

    @staticmethod
    def gaussian_w_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, with the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncs.background(x, params)
        base = 6 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def gaussian_wo_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, without the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        base = 6 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def n_gaussians(*params):
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        if len(params) == 1:
            params = params[0]
        return (len(params) - 6) // 3

    @staticmethod
    def n_background_params() -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        return 6

class GaussFuncsExtS(BaseGaussFuncs):
    """This class accepts parameters of length 9 + 3 * N, where
    N is the number of Gaussians to be fit. The first 4 parameters
    are the x1 to x4, and the next 5 parameters are a0 to a4.
    The background is evaluated as:
    background = a0 + a1(x-x1) + a2(x-x2)^2 + a3(x-x3)^3 + a4(x-x4)^4.
    For each Gaussian, the parameters to be supplied are [C, A, W].
    The Gaussians are evaluated as A*exp(-((x-C)/W)^2).

    WARNING: Use this class only in case of very stubborn fits.
    """
    @staticmethod
    def full_field(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background + gaussians from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncsExtS.background(x, params)
        for i in range(9, len(params), 3):
            ctr = params[i]
            amp = params[i + 1]
            wid = params[i + 2]
            dy = (amp * np.exp(-((x - ctr)/wid)**2))
            y += dy
        return y

    @staticmethod
    def background(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        x1 = params[0]
        x2 = params[1]
        x3 = params[2]
        x4 = params[3]
        y += float(params[4])
        y += float(params[5])*(x-x1)
        y += float(params[6])*(x-x2)**2
        y += float(params[7])*(x-x3)**3
        y += float(params[8])*(x-x4)**4
        return y

    @staticmethod
    def gaussian_w_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, with the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncsExtS.background(x, params)
        base = 9 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def gaussian_wo_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, without the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        base = 9 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def n_gaussians(*params):
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        if len(params) == 1:
            params = params[0]
        return (len(params) - 9) // 3

    @staticmethod
    def n_background_params() -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        return 9

class GaussFit:
    """Fit N Gaussians with a polynomial background using the GaussFuncs* class.
    """

    def __init__(self, x: np.ndarray, y: np.ndarray, p0: tuple | list | np.ndarray, *, baseclass: BaseGaussFuncs = GaussFuncs(), plot: bool = True, figure_title: str = None, window_title: str = None, **kwargs):
        """Initialize a Gauss fit object.

        Args:
            x (np.ndarray): X data.
            y (np.ndarray): Y data.
            p0 (tuple | list | np.ndarray): Fit parameters initial guess. p0 must have the length
            compatible with the GaussFuncs* class.
            baseclass (GaussFuncs*(BaseGaussFuncs), optional): Function set to evaluate background and gaussians. Defaults to GaussFuncs().
            plot (bool, optional): Plot progress of the fit. Defaults to True.
            figure_title (str, optional): Progress figure title. Defaults to None.
            window_title (str, optional): Progress window title. Defaults to None.
            kwargs: Additional arguments passed to the internal plt.subplots() call.

        Raises:
            TypeError: Base function class is not of GaussFuncs* type.
        """
        warn('GaussFit class is deprecated. Please use GenericFitFunc and GenericFitManager.', category=DeprecationWarning, stacklevel=2)
        if not isinstance(baseclass, BaseGaussFuncs):
            raise TypeError('Base class must be of GaussFuncs* type.')
        self._baseclass = baseclass
        self._n_gaussians = self._baseclass.n_gaussians(p0)
        if (plot):
            fig, ax = plt.subplots(1, 1, **kwargs)
            self._fig = fig
            if figure_title is not None and len(figure_title) > 0:
                self._fig.suptitle(figure_title)
            if window_title is not None and len(window_title) > 0:
                self._fig.canvas.manager.set_window_title(window_title)
            self._ax = ax
            self._orig, = self._ax.plot(x, y, color='r')
            self._bck, = self._ax.plot(
                x, self._baseclass.background(x, p0), color='k')
            self._sig = []
            for i in range(self._n_gaussians):
                sig, = self._ax.plot(
                    x, self._baseclass.gaussian_w_bg(x, i, p0))
                self._sig.append(sig)
            self._ax.set_xlim(x.min(), x.max())
        self._plot = plot
        self._iteration = 0
        self._param = p0
        self._x = x
        self._y = y
        self._last_res = None
        self._plot_every = 0
        if plot:
            plt.ion()
            plt.show()

    @property
    def param(self) -> tuple:
        """Get the latest fit parameters.

        Returns:
            tuple: Fit parameters.
        """
        return tuple(self._param)

    def _fit_func(self, x, *params):
        y = self._baseclass.full_field(x, params)
        self._update(x, *params)
        self._param = params
        return y

    def _update(self, x, *params):
        if self._plot:
            if self._plot_every > 0 and (self._iteration % self._plot_every):
                pass
            else:
                bck = self._baseclass.background(x, params)
                self._bck.set_ydata(bck)
                for idx, sig in enumerate(self._sig):
                    gaus = self._baseclass.gaussian_wo_bg(x, idx, params)
                    gaus += bck
                    sig.set_ydata(gaus)
                data = self._baseclass.full_field(x, params)
                self._ax.set_ylim(np.min([data.min(), self._y.min()]), np.max(
                    [data.max(), self._y.max()]))
                res = np.sqrt(np.sum((self._y - data)**2))
                if self._last_res is None:
                    self._last_res = res
                dres = res - self._last_res
                self._last_res = res
                self._ax.set_title('Gaussians: %d, Iteration: %d, Residual: %.3e, Delta: %.3e' % (
                    self._n_gaussians, self._iteration, res, dres))
                self._fig.canvas.draw()
                self._fig.canvas.flush_events()
        self._iteration += 1

        if self._interval > 0 and self._plot:
            plt.pause(self._interval)

    def run(self, *, plot_every: int = 0, interval: float = 1/24, ioff: bool = True, close_after: bool = False, **kwargs) -> tuple:
        """Run the fit routine. Read documentation for scipy.optimize.curve_fit to see what additional named parameters can be passed through kwargs (x, y, p0 are internally passed).

        Args:
            plot_every (int, optional): Plot every Nth frame (0 to plot every frame, default.) Setting this
            to a positive value causes run() to ignore the value of interval.
            interval (float, optional): Interval between plot frame updates in seconds. Defaults to 1/24.
            Interval is used only when plot_every is non-positive.
            ioff (bool, optional): Execute plt.ioff() after run() to revert the system to non-interactive state. Defaults to True.
            close_after (bool, optional): Execute plt.close() after run() to close the active window. Defaults to False.
            kwargs: Named arguments passed to scipy.optimize.curve_fit. DO NOT pass f, xdata, ydata, p0.

        Returns:
            tuple: popt, pcov (and infodict, mesg, ier if full_output=True) from scipy.optimize.curve_fit.
        """
        if plot_every < 0:
            plot_every = 0
        self._plot_every = plot_every
        if plot_every > 0:
            self._interval = 0
        else:
            self._interval = interval
        output = curve_fit(self._fit_func, self._x, self._y,
                           p0=self._param, **kwargs)
        if self._plot:
            data = self._baseclass.full_field(self._x, output[0])
            res = np.sqrt(np.sum((self._y - data)**2))
            self._ax.set_title('Gaussians: %d, Iteration: %d, Residual: %.3e\nOptimization complete.' % (
                self._n_gaussians, self._iteration, res))
            self._fig.canvas.draw()
            self._fig.canvas.flush_events()
            if ioff:
                plt.ioff()
            if close_after:
                plt.cla()
                plt.close(self._fig)
                self._fig = None
                gc.collect()
        return output

    def plot(self, *, x: list | np.ndarray = None, y : list | np.ndarray = None, ax: plt.Axes = None, figure_title: str = '', window_title: str = '', show: bool = True, **kwargs) -> plt.Figure:
        """Plot the data and the fit.

        Args:
            x (list | np.ndarray, optional): X data. Defaults to None for internal X data.
            y (list | np.ndarray, optional): Y data. Defaults to None for internal Y data.
            ax (plt.Axes, optional): External axis object. Defaults to None, creates new figure and axis.
            figure_title (str, optional): Figure title, used if ax is not supplied. Defaults to ''.
            window_title (str, optional): Plot window title, used if ax is not supplied. Defaults to ''.
            show (bool, optional): Execute plt.show() at the end. Defaults to True.

        Raises:
            ValueError: _description_

        Returns:
            plt.Figure: _description_
        """
        ax_is_internal = False
        if ax is None:
            # create fig, ax
            ax_is_internal = True
            fig, ax = plt.subplots(1, 1, **kwargs)
            if figure_title is not None and len(figure_title) > 0:
                fig.suptitle(figure_title)
            if window_title is not None and len(window_title) > 0:
                fig.canvas.manager.set_window_title(window_title)
        if x is None:
            x = self._x
        if y is None:
            y = self._y
        if len(x) != len(y):
            raise ValueError('X and Y arrays must have the same length.')
        x = np.asarray(x, dtype=float)
        y = np.asarray(y, dtype=float)
        # plot everything here
        ax.plot(x, y, label='Data')
        ax.plot(x, self.background(x), label='Background')
        for i in range(self.n_gaussians()):
            ax.plot(x, self.gaussian_w_bg(id=i), label='Gaussian %d'%(i))
        if ax_is_internal:
            ax.set_xlim(x.min(), x.max())
            ax.legend()
        if ax_is_internal and show:
            plt.show()
        return ax.get_figure()

    def full_field(self, x: np.ndarray = None, params: tuple | list | np.ndarray = None) -> np.ndarray:
        """Calculate full fit result (background + gaussians)

        Args:
            x (np.ndarray, optional): X data. Leave empty or None to use the X coordinates used to initialize the GaussFit() instance.
            params (tuple | list | np.ndarray, optional): Background and Gaussian parameters. Set to None to use the inferred parameters.

        Returns:
            np.ndarray: Y data.
        """
        if x is None:
            x = self._x
        if params is None:
            params = self._param
        return self._baseclass.full_field(x, params)

    def background(self, x: np.ndarray = None, params: tuple | list | np.ndarray = None) -> np.ndarray:
        """Calculate background only.

        Args:
            x (np.ndarray, optional): X data. Leave empty or None to use the X coordinates used to initialize the GaussFit() instance.
            params (tuple | list | np.ndarray, optional): Background and Gaussian parameters. Set to None to use the inferred parameters.

        Returns:
            np.ndarray: Y data.
        """
        if x is None:
            x = self._x
        if params is None:
            params = self._param
        return self._baseclass.background(x, params)

    def gaussian_w_bg(self, x: np.ndarray = None, id: int = 0, params: tuple | list | np.ndarray = None) -> np.ndarray:
        """Calculate fit result (background + single gaussian)

        Args:
            x (np.ndarray, optional): X data. Leave empty or None to use the X coordinates used to initialize the GaussFit() instance.
            id (int, optional): ID of the Gaussian [[0, N), where N is the total number of Gaussians.] Defaults to 0.
            params (tuple | list | np.ndarray, optional): Background and Gaussian parameters. Set to None to use the inferred parameters.

        Returns:
            np.ndarray: Y data.
        """
        if x is None:
            x = self._x
        if params is None:
            params = self._param
        return self._baseclass.gaussian_w_bg(x, id, params)

    def gaussian_wo_bg(self, x: np.ndarray = None, id: int = 0, params: tuple | list | np.ndarray = None) -> np.ndarray:
        """Calculate single Gaussian (without background)

        Args:
            x (np.ndarray, optional): X data. Leave empty or None to use the X coordinates used to initialize the GaussFit() instance.
            id (int, optional): ID of the Gaussian [[0, N), where N is the total number of Gaussians.] Defaults to 0.
            params (tuple | list | np.ndarray, optional): Background and Gaussian parameters. Set to None to use the inferred parameters.

        Returns:
            np.ndarray: Y data.
        """
        if x is None:
            x = self._x
        if params is None:
            params = self._param
        return self._baseclass.gaussian_wo_bg(x, id, params)

    @staticmethod
    def gaussian(x: np.ndarray, H: float, A: float, x0: float, sigma: float) -> np.ndarray:
        """Evaluate a Gaussian.

        Args:
            x (np.ndarray): X data.
            H (float): Y Offset (constant).
            A (float): Amplitude.
            x0 (float): Mean.
            sigma (float): Width (2*sigma^2)

        Returns:
            np.ndarray: Evaluated Gaussian.
        """
        return H + A * np.exp(-((x - x0)/sigma)**2)

    def n_gaussians(self, params: tuple | list | np.ndarray = None) -> int:
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        if params is None:
            params = self._param
        return self._baseclass.n_gaussians(params)

    def n_background_params(self) -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        return self._baseclass.n_background_params()

    def gaussfunc_name(self) -> str:
        """Get name of the underlying Gaussian function class.

        Returns:
            str: ame of the underlying Gaussian function class
        """
        return type(self._baseclass).__name__

if __name__ == '__main__':
    x = np.linspace(-2, 2, 100)
    p_def = (0, 10, 0.2, -0.5, 0, 0, 0.2, 10, 0.1)
    y = 10 + 0.2 * x - 0.5 * x**2 + 10 * np.exp(-((x - 0.2) / 0.1)**2)
    p0 = (0, 10, 0.1, -0.1, 0, 0, 0, 8, 0.5)
    p_low = (-2, 0, -np.inf, -np.inf, -1e-8, -1e-8, -2, 0, 0)
    p_high = (2, np.inf, np.inf, 0, 1e-8, 1e-8, 2, np.inf, np.inf)
    gfit = GaussFit(x, y, p0)
    gfit.run(ioff=True, close_after=False, bounds=(p_low, p_high))
    fparam = list(gfit.param)
    fparam[-1] = abs(fparam[-1])
    fparam = tuple(fparam)
    print('Original:', p_def)
    print('Derived:', fparam)
    print('Error:', ((y - gfit.full_field())**2).sum())
    gfit.plot()


'''
파일 입출력
프로그램에서 생성된 데이터를 파일 형태로 출력(저장)을 하거나
파일이 저장되어 있는 데이터를 읽어서 프로그램에서 사용하기 위한 데이터로 쓰기 위해 사용한다

open() 내장함수를 사용한다
open(path, mode, encoding)
    path : 파일을 읽거나 쓰기위한 경로.
            절대 경로 : 드라이브명 부터 시작하는 모든 경로
                    예) C:\\Program Files\\Python3\\python.exe
            상대 경로 : 프로그램이 실행되는 위치의 상대적인 경로
                    예) python.exe
                        .\\python.exe
                        ..\\Python3\\python.exe         #..은 현재 위치보다 상위폴더임을 알려준다

    mode : 파일을 읽기 또는 쓰기 모드를 지정하기 위해 사용하는 인자
            r (read) 읽기 모드 // w (write) 쓰기 모드(덮어쓰기) // a (append) 쓰기모드 (이어쓰기)

    encoding : 작성된 문자열을 자장할때 또는 읽을 때 사용할 변환 코드명 지정
                cp949 : MS 한글 기본 인코딩 방식
                utf-8 : 표준 인코딩 방식
'''
fo = open('D:\\File_Test\\DirA\\sampleA.txt', mode='r', encoding='cp949')
#print(fo.read)
print(fo.readlines())
fo.close()      #make sure you write close statement at the end

fo = open('..\\..\\File_Test\\DirA\\sampleA.txt', mode='r', encoding='cp949')
print(fo.read())
fo.close()

fo = open('D:\\201807_python 기초_김도은\\workspace\\ch02\\03_input_ex_bmi.py', mode='r', encoding='utf-8')
print(fo.read())
fo.close()

fo = open('D:\\File_Test\\DirB\\sampleA.txt', mode='w', encoding='utf-8')
fo.write('hello world \n')
fo.write('world hello \n')
fo.close()

fo = open('D:\\File_Test\\DirB\\sampleA.txt', mode='a', encoding='utf-8')
fo.write('append \n')
fo.close()


import numpy as np
from mesa.model import Model
from mesa.time import SimultaneousActivation
from mesa.datacollection import DataCollector
from agents import Node
import igraph as ig

# This file contains the model class of the model
# In this model firms are heterogeneous in production and customers are heterogeneous in demand
# this model focuses on different strategies applied by firms and customers
# customers can choose to 1) rewiring (build new links) or 2) decrease their environmental sensitivities (consciousness)
# firms can choose to 1) let a new customer connect even if the firm can sell out its products, 2) only accept new customers when it is over-producing

class SignedNetwork(Model):
    def __init__(self, network, alpha = 1, uthr = 0, c_adapt = 0.2, f_adapt = 0.2, c_rate = 0.31, f_rate = 0.6,
                 unilateral = True, bilateral = False, smart = False, max_steps = 5, verbose = True):
        '''
        The model that simulates the signed network dynamics of firms and customers.
        At every time step, every customer is activated to make a decision and it can either form a link or adapt its environmental sensitivity.
        network: the network in which firms and customers are embedded
        alpha: parameter of utility function when determining the signs of links
        uthr: threshold of utility function when determining the signs of links
        c_adapt: the possibility that a customer chooses to decrease its sensitivity rather than rewiring
        f_adapt: the possibility that a firm chooses to increase its sensitivity
        c_rate: customers' rate to decrease environmental sensitivity
        f_rate: firms' rate to increase environmental sensitivity
        unilateral: firms accept new customers without any conditions
        bilateral: firms only accept new customers when it is over-producing
        smart: customer will only make a new link with a firm only if the new link is positive
        max_steps: the maximum number of steps to simulate
        verbose: whether to print the information of the model
        '''

        # initialize the parameters of the model as the inputs above
        # the meanings of the inputs are described above
        # the parameters have the same names and the meanings as the corresponding inputs
        super().__init__()
        self.network = network
        self.max_steps = max_steps
        self.schedule = SimultaneousActivation(self)
        self.node_num = network.vcount() # number of nodes in the network
        self.customer_num = network["customer_count"] # the number of customers
        self.alpha = alpha
        self.uthr = uthr
        self.c_adapt = c_adapt
        self.f_adapt = f_adapt
        self.c_rate = c_rate
        self.f_rate = f_rate
        self.unilateral = unilateral
        self.bilateral = bilateral
        self.smart = smart
        self.verbose = verbose

        # initialize the parameters of each node(agent)
        # the meaning of the parameters below are illustrated in "agents.py"
        for i in range(self.node_num):
            node_type = self.network.vs[i]["type"]
            state = self.network.vs[i]["state"]
            cf_num = self.network.vs[i]["cf_num"]
            demand = self.network.vs[i]["demand"]
            demand_rec = self.network.vs[i]["demand_rec"]
            production = self.network.vs[i]["production"]
            local_supply = self.network.vs[i]["local_supply"]
            local_demand = self.network.vs[i]["local_demand"]
            potential_firm = self.network.vs[i]["potential_firm"]
            sensitivity = self.network.vs[i]["sensitivity"]
            local_pollution = self.network.vs[i]["local_pollution"]
            pollution = self.network.vs[i]["pollution"]

            node = Node(i, self, node_type, state, cf_num, demand, demand_rec, production, local_supply, local_demand, potential_firm,
                              sensitivity, local_pollution, pollution)

            self.schedule.add(node)

        self.datacollector = DataCollector(
            model_reporters={"SCS": lambda m: m.scs,
                            "Demand satisfied": lambda m: m.demand_satisfied,
                            "Network": lambda m: m.network_storage,
                            "Firm eta": lambda m: m.firm_eta,
                            "Customer eta": lambda m: m.customer_eta,
                            "Gini": lambda m: m.gini_calculation}

                           )
        """
        model_reporters: a dictionary of functions for collecting data from the model.
        "SCS": share of customers suffering from shortage
        "Demand satisfied": share of demand be satisfied
        "Network": store the network in every step
        "Firm eta": store firms' environmental sensitivity (consciousness) in every step
        "Customer eta": store customers' environmental sensitivity (consciousness) in every step
        "Gini": Gini coefficient of customers' satisfied demand in the network
        """

        self.datacollector.collect(self)

    @property
    # share of customers suffering from shortage
    def scs(self):
        scs = self.network["scs"]
        # if scs is too small, set it to 0
        if scs < 0.001:
            scs = 0
        return scs

    @property
    # share of demand be satisfied
    def demand_satisfied(self):
        customer_drec = 0   # products received by customers
        customer_d = 0     # customers' demands

        # calculate the total demand and the total products received by customers
        for i in self.network["customer_list"]:
            customer_drec += self.network.vs[i]["demand_rec"]
            customer_d += self.network.vs[i]["demand"]
        # calculate the share of demand be satisfied on the network level
        ds = customer_drec / customer_d

        # if ds is too large, set it to 1
        if ds > 0.999:
            ds =1
        return ds

    @property
    # store the network in every step
    def network_storage(self):
        return self.network.copy()

    @property
    # store customers' environmental sensitivity in every step
    def customer_eta(self):
        ce = []
        for i in self.network["customer_list"]:

            ce.append(self.network.vs[i]["sensitivity"])
        return ce

    @property
    # store firms' environmental sensitivity in every step
    def firm_eta(self):
        fe = []
        for i in self.network["firm_list"]:

            fe.append(self.network.vs[i]["sensitivity"])
        return fe

    @property
    # calculate the gini coefficient of of customers
    def gini_calculation(self):
        wealth = []
        for i in self.network["customer_list"]:

            wealth.append(self.network.vs[i]["demand_rec"])

        cum_wealths = np.cumsum(sorted(np.append(wealth,0)))
        sum_wealths = cum_wealths[-1]

        xarray = np.array(range(0, len(cum_wealths))) / np.float(len(cum_wealths) - 1)
        yarray = cum_wealths / sum_wealths

        B = np.trapz(yarray, x = xarray)
        A = 0.5 - B

        gini = A / (A + B)

        if gini <= 0.001:
            gini = 0
        elif gini >=0.999:
            gini = 1

        return gini

    ##############################################
    ##############################################
    def local_pollution_calculation(self):
        """
        calculate the local pollution of customers in the network
        """
        for i in range(len(self.network.vs)):

            if self.network.vs[i]["type"] == "customer":
                # calculate the total pollution from customer i's neighboring firms
                self.network.vs[i]["local_pollution"] = 0
                for j in self.network.neighbors(i):
                    if self.network.vs[j]["type"] == "firm":
                        self.network.vs[i]["local_pollution"] += self.network.vs[j]["pollution"]

            # firms do not have local pollution, set as 0
            elif self.network.vs[i]["type"] == "firm":
                self.network.vs[i]["local_pollution"] = 0  # firms do not have local pollution

    #######################################################################################################################
    #######################################################################################################################
    def sign_assign_utility(self):
        """
        calculate the utility between a customer and one of its neighboring firm
        determining the sign of the link between this customer and this firm
        """
        for i in range(len(self.network.vs)):

            if self.network.vs[i]["type"] == "customer":

                f_neighbor = 0            # the number of neighboring firms
                pollution_i = 0           # the amount of pollution customer expects to receive from a neighboring firm

                for j in self.network.neighbors(i):

                    if self.network.vs[j]["type"] == "firm":

                        f_neighbor += 1

                pollution_i = self.network.vs[i]["demand"] / f_neighbor
                if self.network.vs[i]["sensitivity"]!=0:
                    pollution_i /= self.network.vs[i]["sensitivity"]
                # if the customer is not sensitive to pollution, set the pollution_i to a very large number
                else:
                    pollution_i = 1000000
                    if self.verbose == True:
                        print(self.c_adapt, self.c_rate, self.f_adapt, self.f_rate)
                        self.verbose = False
                # the amount of pollution customer expects to receive from a neighboring firm
                self.network.vs[i]["pollution_exp"] = pollution_i

            if self.network.vs[i]["type"] == "firm":

                c_neighbor = 0      # the number of neighboring customers
                pollution_k = 0     # the amount of pollution firm K expects to give to a neighboring customer

                for j in self.network.neighbors(i):

                    if self.network.vs[j]["type"] == "customer":

                        c_neighbor += 1

                if c_neighbor > 0:

                    pollution_k = self.network.vs[i]["production"] / c_neighbor

                # very large number when there's no custoemrs link to the firm
                else:
                    pollution_k = self.network.vs[i]["production"]
                pollution_k /= self.network.vs[i]["sensitivity"]

                # the amount of pollution firm K expects to give to a neighboring customer
                self.network.vs[i]["pollution_exp"] = pollution_k

        # assign the sign of links between customers and firms
        # utility = customer i's expected pollution - firm K's expected pollution
        # if utility > 0, this link is positive, otherwise negative

        # "alpha" is constant here
        alpha = self.alpha
        uthr = self.uthr
        for i in range(len(self.network.es)):
            if (self.network.vs[self.network.es[i].tuple[0]]["type"] == "customer")\
               & (self.network.vs[self.network.es[i].tuple[1]]["type"] == "firm"):

                if (alpha * self.network.vs[self.network.es[i].tuple[0]]["pollution_exp"] - \
                  self.network.vs[self.network.es[i].tuple[1]]["pollution_exp"])> uthr:

                    self.network.es[i]["sign"] = 1
                else:
                    self.network.es[i]["sign"] = -1

            if (self.network.vs[self.network.es[i].tuple[0]]["type"] == "firm") & \
               (self.network.vs[self.network.es[i].tuple[1]]["type"] == "customer"):
                if (alpha * self.network.vs[self.network.es[i].tuple[1]]["pollution_exp"] -\
                  self.network.vs[self.network.es[i].tuple[0]]["pollution_exp"]) > uthr:
                    self.network.es[i]["sign"] = 1
                else:
                    self.network.es[i]["sign"] = -1

    ##############################################
    ##############################################

    def shortage_signed(self):
        """
        Update the state of costumers and firms based on the demand and supply mismatch.
        """
        sc_count = 0   # count the number of customers who suffer from shortage
        sf_count = 0   # count the number of firms wo suffer from shortage

        for i in range(len(self.network.vs)):
            sd = 0.0   # count the actual supply of firm or actual demand supplied of customer

            # figure out in which condition firm will suffer from shortage
            if self.network.vs[i]["type"] == "firm":

                for j in self.network.neighbors(i):

                    if self.network.vs[j]["type"] == "customer":

                        sk_t = 0  # calculate the denominator part of distribution factor
                        for k in self.network.neighbors(j):
                            if self.network.vs[k]["type"] == "firm":

                                sk_t += np.exp(- self.network.vs[j]["sensitivity"] * \
                                           (self.network.vs[k]["pollution"]/\
                                            self.network.vs[j]["local_pollution"]))

                        # calculate the numerator part of distribution factor
                        sk = np.exp(- self.network.vs[j]["sensitivity"] *\
                                    (self.network.vs[i]["pollution"]/self.network.vs[j]["local_pollution"]))

                        # to get the sign of the link between node i and node j
                        link_ij = self.network.get_eid(i,j)
                        sign_ij = self.network.es[link_ij]["sign"]

                        # calculate the demand of customer j from firm i, add it to total demand requirement of firm i
                        sd += self.network.vs[j]["demand"] *  sk / sk_t * ((1 + sign_ij) / 2)

                self.network.vs[i]["local_demand"] = sd   # local demand of the firm
                self.network.vs[i]["local_supply"] = 0    # firms do not have local supply
                self.network.vs[i]["demand_rec"] = 0      # firms do not have demand received
                self.network.vs[i]["pollution_rec"] = 0   # firms do not have pollution received
                # the amount of products the firm has sold
                self.network.vs[i]["production_sold"] = min(self.network.vs[i]["local_demand"], \
                                                                  self.network.vs[i]["production"])

                # firm state is determined by local_demand and average production
                if self.network.vs[i]["local_demand"] < 0.999 * self.network.vs[i]["production"]:
                    self.network.vs[i]["state"] = 1     # firms with overproduction have a state of 1

                    # the amount of products the firm has not sold
                    self.network.vs[i]["production_unsold"] = self.network.vs[i]["production"] -\
                                                     self.network.vs[i]["production_sold"]

                    sf_count += 1
                else:
                    self.network.vs[i]["state"] = 0    # firms without overproduction have a state of 0
                    self.network.vs[i]["production_unsold"] = 0   # all products have been sold

            # figure out in which condition customer will suffer from shortage
            if self.network.vs[i]["type"] == "customer":
                ls = 0    # calculate local supply
                sk_t = 0  # calculate the denominator part of distribution factor
                P_rec = 0    # calculate the customer's pollution received
                for k in self.network.neighbors(i):
                    if self.network.vs[k]["type"] == "firm":

                        sk_t += np.exp(- self.network.vs[i]["sensitivity"] *\
                                       (self.network.vs[k]["pollution"]/self.network.vs[i]["local_pollution"]))

                for j in self.network.neighbors(i):

                    if self.network.vs[j]["type"] == "firm":

                        # to get the sign of the link between node i and node j
                        link_ij = self.network.get_eid(i,j)
                        sign_ij = self.network.es[link_ij]["sign"]

                        # the max. amount production firm j can offer customer i
                        max_supply = self.network.vs[j]["production"] / self.network.vs[j]["cf_num"]* ((1 + sign_ij) / 2)

                        # the max. amount production customer i want to gain from firm j
                        sk = np.exp(- self.network.vs[i]["sensitivity"] *\
                                    (self.network.vs[j]["pollution"]/self.network.vs[i]["local_pollution"]))

                        max_want = self.network.vs[i]["demand"] *  sk / sk_t * ((1 + sign_ij) / 2)

                        # production customer i can really gain from firm j is the min. value of max_supply and max_want
                        sd += min(max_supply, max_want)
                        ls += max_supply
                        P_rec += min(max_supply, max_want) * self.network.vs[j]["unit_pollution"]

                self.network.vs[i]["local_demand"] = 0         # customers do not have local demand
                self.network.vs[i]["local_supply"] = ls        # customer's local supply
                self.network.vs[i]["demand_rec"] = min(sd, self.network.vs[i]["demand"])         # customer's demand received
                self.network.vs[i]["pollution_rec"] = P_rec    # customer's pollution received
                self.network.vs[i]["production_sold"] = 0      # customers do not have this attribute
                self.network.vs[i]["production_unsold"] = 0    # customers do not have this attribute

                # customer state is determined by demand_rec and the threshold of satisfaction
                if self.network.vs[i]["demand_rec"] < 0.999 * self.network.vs[i]["demand"]:
                    self.network.vs[i]["state"] = 0   # customer suffer from shortage has a state of 0
                    sc_count += 1
                else:
                    self.network.vs[i]["state"] = 1   # custoemr do not suffer from shortage has a state of 1

        # assign new attributes to network
        self.network["sc_count"] = sc_count   # number of customers who suffer from shortage
        self.network["sf_count"] = sf_count   # number of firms who suffer from shortage
        self.network["scs"] = sc_count / self.network["customer_count"]   # share of customers who suffer from shortage
        self.network["sfs"] = sf_count / self.network["firm_count"]   # share of firms who suffer from shortage

    #############################################################################################################
    #############################################################################################################

    # do the simulation for one step
    def step(self):
        self.datacollector.collect(self)
        self.schedule.step()
        self.local_pollution_calculation()
        self.sign_assign_utility()
        self.shortage_signed()

        if self.schedule.steps > self.max_steps:
            self.running = False


"""
897. 递增顺序查找树
给你一个树，请你 按中序遍历 重新排列树，使树中最左边的结点现在是树的根，并且每个结点没有左子结点，只有一个右子结点。
示例 ：
输入：[5,3,6,2,4,null,8,1,null,null,null,7,9]
       5
      / \
    3    6
   / \    \
  2   4    8
 /        / \
1        7   9
输出：[1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9]
 1
  \
   2
    \
     3
      \
       4
        \
         5
          \
           6
            \
             7
              \
               8
                \
                 9
提示：
给定树中的结点数介于 1 和 100 之间。
每个结点都有一个从 0 到 1000 范围内的唯一整数值。
"""
# 简单题的话，暴力法就能解决问题，直接中序遍历完，然后一个一个节点的生成
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    def increasingBST(self, root: TreeNode) -> TreeNode:
        if not root:
            return None
        ans = []

        def inorder(root):
            # 中序遍历二叉树，将遍历顺序存入列表
            if not root:
                return None
            if root.left:
                inorder(root.left)
            ans.append(root.val)
            if root.right:
                inorder(root.right)
        inorder(root)
        ans_node = TreeNode(None)
        cur_node = ans_node
        for value in ans:
            cur_node.right = TreeNode(value)
            cur_node = cur_node.right
        return ans_node.right

    def increasingBST_2(self, root: TreeNode) -> TreeNode:
        # 那能不能一边遍历的时候就一边把这棵树给构建出来呢,递归和迭代的方法应该都可以，因为遍历到这个节点的时候，我直接将它作为当前叶节点的
        # 右儿子节点
        if not root:
            return None
        ans = TreeNode(None)
        cur = ans

        def inorder(root, cur):
            if not root:
                return None
            if root.left:
                cur = inorder(root.left,cur)
            cur.right = TreeNode(root.val)
            cur = cur.right
            if root.right:
                cur = inorder(root.right,cur)
            return cur
        inorder(root, cur)
        return ans.right
    # 这里用递归方法貌似有点问题，如果指针在调用前更新了好像调用时不能更新了。错了错了，递归也是可以的，只不过你要知道递归的时候如何更新
    # 这个cur指针，返回就可以了啊。

    def increasingBST_3(self, root: TreeNode) -> TreeNode:
        # 上面用了递归方法，那么采用栈结构非递归中序遍历，在遍历过程中构建这棵二叉树当然也是可以的
        # 这就要求熟悉二叉树中序遍历的非递归方法,貌似我又忘了怎么写了，依稀记得要一直向左，直到最左边，然后在出栈，直到第一个有右子节点的，
        # 再继续寻找这个右子节点的最左边子节点
        if not root:
            return None

        stack = []
        ans = TreeNode(None)
        cur = ans
        while stack or root:
            while root:
                stack.append(root)
                root = root.left
            if stack:
                node = stack.pop()
                root = node.right
                cur.right = TreeNode(node.val)
                cur = cur.right
        return ans.right
        # 方法三是最好的方法。

solu = Solution()
root = TreeNode(5)
root.left = TreeNode(3)
root.right = TreeNode(6)
root.left.left = TreeNode(2)
root.left.right = TreeNode(4)
root.right.right = TreeNode(8)
root.left.left.left = TreeNode(1)
root.right.right.left = TreeNode(7)
root.right.right.right = TreeNode(9)
ans = solu.increasingBST_2(root)


from Config import api_config as api
import requests
import json
import csv
from datetime import datetime

class getData:
    def __init__(self):
        pass

    def getApiResponseData(self):
        try:
            # Configure api url from api.config.py file.
            url = api.api_config["url"] + "?" + "resource_id=" +  api.api_config["resource_id"] + "&limit=" + str(api.api_config["limit"])
            print("Loading api url.")
            # Getting response from api using GET method.
            res = requests.get(url)
            print("Getting response from api.")
            # Checking response code if returning 200 then will go for further opertions.
            if res.status_code == 200:
                print("Got ok response from api.")
                # Loading json response data.
                jsondata = json.loads(res.content.decode('UTF-8'))
                print("Loading response data in json.")
                # We will open a file for writing.
                datafile = open("Files/Output_file.csv", 'w', newline='')
                print("Opening a file for writing.")
                # Create csv writer object.
                csv_wrt = csv.writer(datafile)
                print("Create csv writer object.")
                count = 0
                # Filtering out the json response data through loop.
                for data in jsondata['result']['records']:
                    if count == 0:
                        # Loading header for CSV file.
                        header = data.keys()
                        # Writing header of CSV file.
                        csv_wrt.writerow(header)
                        count +=1
                    # Writing data of CSV file
                    csv_wrt.writerow(data.values())
                # Colsing the opened file.
                datafile.close()
                print('completed')
        except Exception as e:
            raise e


import logging
import os
import random
from typing import List
from datetime import datetime, timedelta

import numpy as np
import pandas as pd
from akademy.common import project_paths
from akademy.models import OHLCV

def format_float(value: float, do_rounding: bool = True) -> str:
    """
    Convenience function to adjust report checkpoint_save_dir formatting.
    Turns floating point numbers into comma-separated strings
    rounded to the nearest 2 decimal places.
    """
    if do_rounding:
        value = round(value, 2)
    return f"{value:,}"

def get_file_extension(filepath: str) -> str:
    """
    Given the path to a file, return only the extension such that the following
    cases are handled:
        1. file.ext -> ext
        2. file.tar.gz -> tar.gz
        3. file -> ''
    """
    filename = os.path.basename(filepath)
    if "." not in filename:
        return filename
    return ".".join(filename.split('.')[1:])

def remove_old_file_versions(
        filepath: str,
        remove_key: str,
        keep_key: str = None,
        keep_count: int = 0
) -> List[str]:
    """
    Given an absolute filepath to a file, removes all files within that directory
    that contain the <key> value within them, keeping <keep> many files,
    where the deleted files are those oldest via last_mod check.
    Args:
        filepath: path to the file from which to remove old versions.
        keep_count: the number of files to keep. Default 0 means delete them all.
        remove_key: character sequence to identify similar files.
            e.g. if <key> in <file>
        keep_key: files matching <keep_key> in <file> are not considered.
    Returns:
        List of deleted filepaths.
    """
    file_dir = os.path.abspath(os.path.dirname(filepath))
    file_name = os.path.basename(file_dir).split('.')[0]
    file_ext = ".".join(os.path.basename(filepath).split('.')[1:])

    # get list of filenames intended for removal.
    files = [f for f in os.listdir(file_dir) if remove_key in f]

    # remove files matching keep mask
    if keep_key:
        files = [f for f in files if keep_key not in f]

    # sort by filemod time
    files = sorted(files,
                   key=lambda x: os.path.getmtime(
                       os.path.abspath(os.path.join(file_dir, x))
                   ),
                   reverse=True
                   )

    # keep max if specified
    if keep_count > 0:
        if keep_count > len(files):
            pass
        else:
            files = files[keep_count:]

    # delete the older files
    deleted = []
    for file in files:

        fullpath = os.path.abspath(os.path.join(
            file_dir,
            file
        ))
        deleted.append(fullpath)
        logging.info(f'removing file: {fullpath}')
        os.remove(fullpath)

    return deleted

def load_csv_ohlcv_data(
        filepath: str,
        count: int = 0,
        date_ms_unit: str = "ns") -> pd.DataFrame:
    """
    Loads financial data presumed to have the following columns present:
        Open, High, Low, Close, Volume, Date
    Args:
        filepath: the path to the CSV file to open.
        count: total number of records to return; earlier rows dropped; 0 == all
        date_ms_unit: the unit measure of the date timestamp. Default Nanoseconds.
    Return:
        data as a DataFrame with open, high, low, close, volume
        headers and indexed by ascending date
    """
    data = pd.read_csv(filepath)

    # lowercase all column names for standardization
    data.columns = [x.lower() for x in data.columns]

    # drop all but essential pricing + date + volume
    data = data[['date', 'open', 'high', 'low', 'close', 'volume']]

    # convert types
    data = data.astype(
        {
            'date': f'datetime64[{date_ms_unit}]',
            'open': np.float32,
            'high': np.float32,
            'low': np.float32,
            'close': np.float32,
            'volume': np.float32
        }
    )

    # reindex
    data.set_index('date', inplace=True)

    # 0 == all the data
    if count == 0:
        return data

    # check more data not asked for than available
    if count > len(data):
        raise Exception(
            f"The request number of records <{count}> "
            f"is more than total available: <{len(data)}>"
        )

    # otherwise, return the <count> most recent data items
    return data[len(data) - count:]

def load_spy_daily(count: int = 0):
    """
    Loads the locally-saved SPY pricing data with daily OHLCV prices
    ranging from 1/29/1993 to 9/6/2022 in ascending order.
    7455 total entries in daily increments.
    Arguments:
        count: number of days to return from the max period. End is most recent.
    Notes:
        'close' is replaced by 'Adj Close'; but renamed as 'close'
    Returns:
        pd.DataFrame object
    """
    return load_csv_ohlcv_data(project_paths.SPY_DATA, count=count)

def flatten_price_obs(obs: pd.DataFrame) -> np.ndarray:
    """
    Given a DataFrame (or slice) of
    """
    if not all(c in obs.columns for c in
               ['open', 'high', 'low', 'close', 'volume']):
        raise Exception("Price observations must contain OHLCV columns.")
    # converts to NumPy array, flattens the data, and clips all but the opening
    # price of the last column (current known price)
    return obs.to_numpy().flatten()[:-4]

def minmax_normalize(data: np.ndarray) -> np.ndarray:
    """
    Converts all values within an ndarray to values within the range of 0-1
    where
    Args:
        data: numpy array of data.
    Returns:
        a new numpy array containing values in range 0-1
    """
    _min = np.min(data)
    _ptp = np.ptp(data)
    return (data-_min)/_ptp

def make_ohlcv_from_df_row(row: tuple) -> OHLCV:
    """
    Makes an OHLCV object from a pandas row
    """
    # get pricing data from state for the report
    _open       = row[1]['open']
    _high       = row[1]['high']
    _low        = row[1]['low']
    _close      = row[1]['close']
    _volume     = row[1]['volume']
    # _date       = pandas_timestamp_to_unix(row[0])

    # might need re-evaluating based on formats
    try:
        dt = datetime.strptime(str(row[0]), "%Y-%m-%d %H:%M:%S%z")
    except ValueError as e:

        # without tz info
        dt = datetime.strptime(str(row[0]), "%Y-%m-%d %H:%M:%S")

    _date = int(dt.timestamp())

    return OHLCV(
        open=_open, high=_high, low=_low,
        close=_close, volume=_volume, date=_date
        )

def sample_ohlcv_data(count: int) -> List[OHLCV]:
    """
    Generates a collection of OHLCV objects with random values.
    Args:
        count: Number of randomized OHLCV objects to return.
    """
    prices = []
    date = datetime.now()
    for i in range(count):
        prices.append(
            OHLCV(
                open=random.uniform(10., 100.),
                high=random.uniform(10., 100.),
                low=random.uniform(10., 100.),
                close=random.uniform(10., 100.),
                volume=random.randint(1000, 5000),
                date=int((date - timedelta(days=i)).timestamp())
            )
        )
    return prices


class Config:
    def __init__(self, env):

        if env.lower() not in ['dev', 'qa', 'staging']:
            raise Exception(f'{env} is not a supported environment')

        self.base_url = {
            'dev': 'https://mydevenv.com',
            'qa': 'https://myqaenv.com',
            'staging': 'https://mystagingenv.com'
        }[env]

        self.app_port = {
            'dev': 8080,
            'qa': 80,
            'staging': 666
        }[env]


#!/usr/bin/python

#######################################
# module: infl.py
# YOUR NAME
# YOUR A#
#######################################

from const import const
from deriv import deriv
from poly12 import find_poly_1_zeros
from tof import tof
from point2d import point2d
from plus import plus
from prod import prod
from pwr import pwr
from maker import make_point2d

def get_highest_power(expr):
	el1 = expr.get_elt1()
	while isinstance(el1, plus):
		el1 = el1.get_elt1()
	if isinstance(el1, prod):
		return el1.get_mult2().get_deg()
	elif isinstance(el1, pwr):
		return el1.get_deg()

def test_zeros(zeros, expr, second_der):
	f_expr = tof(expr)
	f_der = tof(second_der)
	sol = []
	for z in zeros:
		val = z.get_val()
		if f_der(val - .1) < 0 and f_der(val + .1) > 0:
			sol.append(make_point2d(val, f_expr(val)))
		elif f_der(val - .1) > 0 and f_der(val + .1) < 0:
			sol.append(make_point2d(val, f_expr(val)))
	if not sol:
		return None
	else:
		return sol

def find_infl_pnts(expr):
	p = get_highest_power(expr).get_val()
	der = deriv(expr)
	second_der = deriv(der)
	zeros = []
	if p == 3:
		zeros = [find_poly_1_zeros(second_der)]
	elif p < 3:
		raise Exception("needed a function of at least deg 3")
	return test_zeros(zeros, expr, second_der)