"""
Code for reweighting MSMs and obtaining reweighted estimates of steady-state.
"""
import deeptime.markov.tools.analysis
import numpy as np
from scipy.special import rel_entr
import pandas as pd
import mr_toolkit.trajectory_analysis.traj_analysis as ta
import logging
import pyemma
from .splicing import get_receiving_distribution, splice_trajectories, iterative_trajectory_splicing

log = logging.getLogger()

try:
    from msm_we.fpt import MarkovFPT
except ImportError:
    log.warning("msm_we not found, fpt_distribution calculations will be unavailable")

def compute_reweighted_stationary(
        discrete_trajectories,
        N,
        lag,
        n_clusters,
        last_frac=1.0,
        min_weight=1e-12,
        n_reweighting_iters=100,
):
    """
    Estimates a stationary distribution from a discrete trajectory using reweighted MSMs.

    Parameters
    ----------
    discrete_trajectories: array-like
        2-D array or list of lists with discretized trajectories
    N: int
        Fragment length for reweighting
    lag: int
        Lagtime used in reweighting MSMs
    n_clusters: int
        Number of total states in the reweighted models (or in the discretization)
    last_frac: float
        Fraction of the trajectories to use. I.e., last_frac=0.25 only uses the last 1/4 of the trajectories.
    min_weight: float
        Minimum bound on weights during reweighting iteration
    n_reweighting_iters: int
        Maximum number of reweighting iterations

    Returns
    -------
    (Set of state indices, Stationary distributions at each reweighting iteration, Total number of iterations before
        convergence, estimated transition matrices at each reweighting iteration)
    """

    reweighted_stationaries = np.empty(shape=(n_reweighting_iters, n_clusters))
    reweighted_matrices = np.empty(
        shape=(
            n_reweighting_iters,
            n_clusters,
            n_clusters,
        )
    )

    try:
        (
            reweighted_distributions,
            _reweighted_matrices,
            weighted_count_matrices,
            last_iter,
        ) = ta.optimized_resliced_reweighted(
            discrete_trajectories,
            n_reweighting_iters,
            N,
            lagtime=lag,
            n_states=n_clusters,
            last_frac=last_frac,
            return_matrices=True,
            min_weight=min_weight,
            convergence=1e-10,  # Convergence threshold in units of kT
        )

    except (AssertionError, np.linalg.LinAlgError) as e:
        # This may trip if something goes awry in solving the matrices

        log.error(e)
        for i in range(n_reweighting_iters):
            reweighted_stationaries[i, :] = np.nan
            reweighted_matrices[i, :, :] = np.nan

        last_iter = 0

    else:
        for i, _distribution in enumerate(reweighted_distributions):
            reweighted_stationaries[i, :] = _distribution
            reweighted_matrices[i] = _reweighted_matrices[i]

    states = np.arange(n_clusters)
    return states, reweighted_stationaries, last_iter, reweighted_matrices

def get_set_kls(distributions):
    """
    Get KL divergences between multiple sets of distributions.

    I.e., a (4x10) input corresponds to 4x 10-element distributions.
    This would return an upper triangular 4x4 matrix with the unique pairwise KL-divergences.

    Parameters
    ----------
    distributions

    Returns
    -------

    """

    n_sets = len(distributions)

    kls = np.full(
        shape=(n_sets, n_sets),
        fill_value=np.nan,
    )
    for x, y in np.array(np.triu_indices(n_sets, 1)).T:
        setA_distribution = distributions[x]
        setB_distribution = distributions[y]

        kl_sum = get_kl(setA_distribution, setB_distribution, return_nan=True)
        kls[x, y] = kl_sum

    mean = np.nanmean(kls)

    return mean

def get_kl(test_dist, ref_dist, return_nan=False):
    """
    Obtain the KL divergence between two distributions.

    :param test_dist: The distribution to test
    :param ref_dist: The reference distribution
    :param return_nan: If the KL divergence is invalid for some reason, return NaN if true or -1 otherwise.
    :return: The KL divergence of the two distributions. If invalid, -1 or NaN depending on the value of return_nan.
    """
    elem_kl = rel_entr(test_dist, ref_dist, )
    kl_sum = np.nansum(elem_kl[elem_kl < np.inf])

    if kl_sum == 0:
        return [-1, np.nan][return_nan]

    return kl_sum

class AnalysisRun:
    """
    WARNING: You should almost certainly not use this!

    Convenience class to handle computing various observables from a trajectory set and storing relevant hyperparameters.
    """

    def __init__(
            self,
            _run,
            _reference,
            _trajectory_sets,
            dt=1,
            lag=None,
            # mfpt_method=metaparameters['mfpt_method'],
            metaparameters={},
    ):

        self.run = _run
        self.reference = _reference
        self.trajectory_sets = _trajectory_sets
        self.dt = dt
        self.lag = lag
        self.current_direction = None

        methods = [
            "histogram",
            "pyemma_rev",
            "pyemma_irrev",
            "naive",
            "reweighted",
            "resliced",
        ]
        multi_index = pd.MultiIndex.from_product(
            [range(metaparameters["n_trajectory_sets"]), methods],
            names=["trajectory set", "method"],
        )

        self.n_stratified_clusters = len(_reference)
        self.equil_df = (
            pd.DataFrame(
                index=multi_index,
                columns=range(self.n_stratified_clusters),
                dtype=np.float64,
            )
            .apply(pd.to_numeric)
            .sort_index()
        )

        # TODO: Generalize to more than 2 directions
        self.directions = ["unfolding", "folding"]
        multi_index = pd.MultiIndex.from_product(
            [range(metaparameters["n_trajectory_sets"]), self.directions, methods],
            names=["trajectory set", "direction", "method"],
        )
        self.ness_df = (
            pd.DataFrame(
                index=multi_index,
                columns=range(self.n_stratified_clusters),
                dtype=np.float64,
            )
            .apply(pd.to_numeric)
            .sort_index()
        )

        self.current_traj_set = -1
        self.active_df = None
        self.spliced_trajs = None
        self.current_direction = None

        self.metaparameters = metaparameters
        self.mfpt_method = self.metaparameters["mfpt_method"]

    def compute_avg_kl(self, method):

        # Compute average set-set KL
        kls = np.full(
            shape=(
                self.metaparameters["n_trajectory_sets"],
                self.metaparameters["n_trajectory_sets"],
            ),
            fill_value=np.nan,
        )

        for x, y in np.array(
                np.triu_indices(self.metaparameters["n_trajectory_sets"], 1)
        ).T:
            setA_converged_iter = self.equil_df.loc[(x, method)].values.shape[0]
            setB_converged_iter = self.equil_df.loc[(y, method)].values.shape[0]

            setA_reweighted_last = self.equil_df.loc[(x, method)].values.astype(float)
            setB_reweighted_last = self.equil_df.loc[(y, method)].values.astype(float)
            kl_sum = get_kl(setA_reweighted_last, setB_reweighted_last, return_nan=True)
            kls[x, y] = kl_sum

        mean_kl = np.nanmean(kls)
        std_kl = 2 * np.nanstd(kls) / np.sqrt(self.metaparameters["n_trajectory_sets"])

        return mean_kl, std_kl

    def compute_stationary(self, method, **kwargs):

        # TODO: Optionally disable logging / data storage here, so I can reuse this in compute_mfpt

        set_idx = self.current_traj_set
        assert self.active_df is not None, "No dataframe is active for storing results"

        stationary = np.zeros(shape=self.n_stratified_clusters)

        if method == "reweighted":
            (
                states,
                stationaries,
                last_iter,
                reweighted_matrices,
            ) = self.compute_reweighted_stationary(
                self.trajectory_sets[set_idx], **kwargs
            )

            assert np.isclose(
                np.sum(stationaries[:last_iter], axis=1), 1.0
            ).all(), "Stationary distributions not normalized!"

            # Compute KL to reference
            resliced_kl = get_kl(stationaries[0], self.reference)
            reweighted_kl = get_kl(stationaries[last_iter], self.reference)

            self.run.log_metric(f"set{set_idx}_resliced_kl", resliced_kl)
            self.run.log_metric(f"set{set_idx}_reweighted_kl", reweighted_kl)

            self.equil_df.loc[(set_idx, "resliced"), states] = stationaries[0]
            self.equil_df.loc[(set_idx, "reweighted"), states] = stationaries[last_iter]

            return stationaries[last_iter], reweighted_kl

        if method == "naive":
            _states, _stationary, tmatrix = self.compute_stationary_naive(
                self.trajectory_sets[set_idx]
            )
        elif method == "pyemma_rev":
            kwargs.pop('N')
            _states, _stationary, tmatrix = self.compute_pyemma_stationary(
                self.trajectory_sets[set_idx], reversible=True, **kwargs
            )
        elif method == "pyemma_irrev":
            kwargs.pop('N')
            _states, _stationary, tmatrix = self.compute_pyemma_stationary(
                self.trajectory_sets[set_idx], reversible=False, **kwargs
            )
        elif method == "histogram":
            _states, counts = np.unique(
                self.trajectory_sets[set_idx], return_counts=True
            )
            _stationary = counts / sum(counts)
        else:
            raise NotImplementedError("Invalid method specified")

        stationary[_states] = _stationary
        assert np.isclose(
            np.sum(stationary), 1.0
        ), "Stationary distribution not normalized!"

        # Compute KL to reference
        kl = get_kl(stationary, self.reference)

        self.run.log_metric(f"set{set_idx}_{method}_kl", kl)

        self.equil_df.loc[(set_idx, method), :] = stationary

        return stationary, kl

    @staticmethod
    def compute_stationary_naive(discrete_trajectories):

        tmatrix, state_map, cmatrix, weights = ta.build_msm(
            discrete_trajectories, reslicing=False, normalize_initial=False
        )
        evals, evecs = np.linalg.eig(tmatrix.T)
        max_eig_index = np.argmin(1 - evals)
        stationary = np.real(evecs[:, max_eig_index]) / np.real(
            sum(evecs[:, max_eig_index])
        )

        states = list(state_map.keys())

        return states, stationary, tmatrix

    @staticmethod
    def compute_pyemma_stationary(discrete_trajectories, lag, reversible=False):
        # TODO: Support different lagtimes

        pyemma_msm = pyemma.msm.estimate_markov_model(
            [x for x in discrete_trajectories], lag=lag, reversible=reversible
        )
        pyemma_stationary = pyemma_msm.stationary_distribution
        pyemma_states = pyemma_msm.active_set

        return pyemma_states, pyemma_stationary, pyemma_msm.transition_matrix

    def compute_reweighted_stationary(
            self,
            discrete_trajectories,
            N,
            lag,
            last_frac=None,
            min_weight=None,
            n_reweighting_iters=None,
            store_matrices=False,
    ):

        # TODO: This is obsolete now, remove this and just wrap a call to the static version

        if last_frac is None:
            last_frac = self.metaparameters.get("last_frac")
        if min_weight is None:
            min_weight = self.metaparameters.get("min_weight")
        if n_reweighting_iters is None:
            n_reweighting_iters = self.metaparameters.get("n_reweighting_iters")

        reweighted_stationaries = np.empty(shape=(n_reweighting_iters, self.n_stratified_clusters))
        reweighted_matrices = np.empty(
            shape=(
                n_reweighting_iters,
                self.n_stratified_clusters,
                self.n_stratified_clusters,
            )
        )

        try:
            (
                reweighted_distributions,
                _reweighted_matrices,
                weighted_count_matrices,
                last_iter,
            ) = ta.optimized_resliced_reweighted(
                discrete_trajectories,
                n_reweighting_iters,
                N,
                lagtime=lag,
                n_states=self.n_stratified_clusters,
                last_frac=last_frac,
                return_matrices=True,
                min_weight=min_weight,
                convergence=1e-10,  # Convergence threshold in units of kT
            )

        except (AssertionError, np.linalg.LinAlgError) as e:
            # This may trip if something goes awry in solving the matrices

            log.error(e)
            for i in range(n_reweighting_iters):
                reweighted_stationaries[i, :] = np.nan
                reweighted_matrices[i, :, :] = np.nan

            last_iter = 0

        else:
            for i, _distribution in enumerate(reweighted_distributions):
                reweighted_stationaries[i, :] = _distribution
                reweighted_matrices[i] = _reweighted_matrices[i]

        if store_matrices:
            index = pd.MultiIndex.from_product(
                [
                    range(last_iter + 1),
                    range(self.n_stratified_clusters),
                    range(self.n_stratified_clusters),
                ],
                names=["Iteration", "From", "To"],
            )
            reweighted_matrix_df = pd.DataFrame(
                np.array(reweighted_matrices).flatten(), index=index
            )
            reweighted_matrix_df.to_pickle(
                f"../results/{self.run.id}_set{self.current_traj_set}_reweighted_matrix_df.pkl"
            )

            index = pd.MultiIndex.from_product(
                [
                    range(self.n_stratified_clusters),
                    range(self.n_stratified_clusters),
                    range(self.n_stratified_clusters),
                ],
                names=["FragStart", "From", "To"],
            )
            count_matrix_df = pd.DataFrame(
                np.array(weighted_count_matrices).flatten(), index=index
            )
            count_matrix_df.to_pickle(
                f"../results/{self.run.id}_set{self.current_traj_set}_weighted_count_matrix_df.pkl"
            )

        states = np.arange(self.n_stratified_clusters)
        return states, reweighted_stationaries, last_iter, reweighted_matrices

    def iterative_trajectory_splicing(self,
                                      source_states,
                                      sink_states,
                                      splice_msm_lag=None,
                                      msm_reversible=False,
                                      target_steps_to_keep=1,
                                      convergence=1e-9,
                                      max_iterations=100):

        # This just wraps the external call for backwards-compatibility with some existing analysis scripts.

        if splice_msm_lag is None:
            print(f"No lag provided for splice MSM -- using set value of {self.lag}")
            splice_msm_lag = self.lag

        spliced_trajectories = iterative_trajectory_splicing(
            source_states=source_states,
            sink_states=sink_states,
            splice_msm_lag=splice_msm_lag,
            msm_reversible=msm_reversible,
            target_steps_to_keep=target_steps_to_keep,
            convergence=convergence,
            max_iterations=max_iterations,
            n_clusters=self.n_stratified_clusters
        )

        self.spliced_trajs = spliced_trajectories

    def splice_trajectories(
            self,
            source_states,
            sink_states,
            msm_lag=1,
            msm_reversible=False,
            target_steps_to_keep=1,
            trajs_to_splice=None,
            pbar_visible=True
    ):
        # This just wraps the external call for backwards-compatibility with some existing analysis scripts.

        set_idx = self.current_traj_set

        # Build an MSM to approximate the equilibrium distribution over the boundary states
        # TODO: Do we want to just use the PyEmma MSM? Or is there a better choice?
        if trajs_to_splice is None:
            trajs_to_splice = self.trajectory_sets[set_idx]

        spliced_trajs = splice_trajectories(
            trajs_to_splice=trajs_to_splice,
            source_states=source_states,
            sink_states=sink_states,
            msm_lag=msm_lag,
            msm_reversible=msm_reversible,
            target_steps_to_keep=target_steps_to_keep,
            pbar_visible=pbar_visible
        )

        self.spliced_trajs = spliced_trajs

    def compute_mfpt(self, method, source_states, target_states, **kwargs):
        set_idx = self.current_traj_set
        assert self.active_df is not None, "No dataframe is active for storing results"

        assert (
                self.current_direction is not None
        ), "No direction is specified for NESS/MFPTs"
        direction_index = self.directions.index(self.current_direction)

        if 'lag' in kwargs.keys():
            kwargs.pop('lag')
            print("Warning: Lag specification in arguments to compute_mfpt is unnecessary and unused")

        # Get the stationary distribution and transition matrix
        if method == "reweighted":

            (
                states,
                stationaries,
                last_iter,
                tmatrices,
            ) = self.compute_reweighted_stationary(self.spliced_trajs, lag=self.lag, **kwargs)

            if last_iter == 0:
                log.warning("Bad stationary distribution -- not attempting an MFPT")
                return None, None, None

            assert np.isclose(
                np.sum(stationaries[:last_iter], axis=1), 1.0
            ).all(), "Stationary distributions not normalized!"

            resliced_stationary = np.zeros(shape=self.n_stratified_clusters)
            reweighted_stationary = np.zeros(shape=self.n_stratified_clusters)
            resliced_tmatrix = np.zeros(
                shape=(self.n_stratified_clusters, self.n_stratified_clusters)
            )
            reweighted_tmatrix = np.zeros(
                shape=(self.n_stratified_clusters, self.n_stratified_clusters)
            )

            _resliced_stationary, _resliced_tmatrix = stationaries[0], tmatrices[0]
            resliced_stationary[states] = _resliced_stationary
            resliced_tmatrix[np.ix_(states, states)] = _resliced_tmatrix

            # resliced_mfpt = cg.get_hill_mfpt(resliced_stationary, resliced_tmatrix, target_states) * self.dt
            # resliced_mfpt = pyemma.msm.markov_model(resliced_tmatrix).mfpt(A=source_states, B=target_states)
            resliced_mfpt = self.mfpt(
                self.mfpt_method,
                resliced_tmatrix,
                source_states,
                target_states,
                lag=self.lag,
                dt=self.dt,
                stationary=resliced_stationary,
            )

            self.run.log_metric(
                f"set{set_idx}_resliced_mfpt_{self.current_direction}", resliced_mfpt
            )
            self.ness_df.loc[
                (set_idx, self.current_direction, "resliced"), states
            ] = resliced_stationary

            _reweighted_stationary, _reweighted_tmatrix = (
                stationaries[last_iter],
                tmatrices[last_iter],
            )
            reweighted_stationary[states] = _reweighted_stationary
            reweighted_tmatrix[np.ix_(states, states)] = _reweighted_tmatrix

            # reweighted_mfpt = cg.get_hill_mfpt(reweighted_stationary, reweighted_tmatrix, target_states) * self.dt
            # reweighted_mfpt = pyemma.msm.markov_model(reweighted_tmatrix).mfpt(A=source_states, B=target_states)
            reweighted_mfpt = self.mfpt(
                self.mfpt_method,
                reweighted_tmatrix,
                source_states,
                target_states,
                lag=self.lag,
                dt=self.dt,
                stationary=reweighted_stationary,
            )

            if not np.isnan(reweighted_mfpt):
                self.run.log_metric(
                    f"set{set_idx}_reweighted_mfpt_{self.current_direction}",
                    reweighted_mfpt,
                )

            self.ness_df.loc[
                (set_idx, self.current_direction, "reweighted")
            ] = reweighted_stationary
            return (
                (resliced_mfpt, reweighted_mfpt),
                (resliced_tmatrix, reweighted_tmatrix),
                (resliced_stationary, reweighted_stationary),
            )

        if method == "naive":
            print("Warning: Naive estimator assumes a lag of 1!")
            _states, _stationary, _tmatrix = self.compute_stationary_naive(
                self.spliced_trajs
            )
        elif method == "pyemma_rev":
            if 'N' in kwargs.keys(): kwargs.pop('N')
            _states, _stationary, _tmatrix = self.compute_pyemma_stationary(
                self.spliced_trajs, reversible=True, lag=self.lag  # TODO: Manage lag correctly
            )
        elif method == "pyemma_irrev":
            if 'N' in kwargs.keys(): kwargs.pop('N')
            _states, _stationary, _tmatrix = self.compute_pyemma_stationary(
                self.spliced_trajs, reversible=False, lag=self.lag  # TODO: Manage lag correctly
            )
        else:
            raise NotImplementedError("Invalid method specified")

        stationary = np.zeros(shape=self.n_stratified_clusters)
        tmatrix = np.zeros(
            shape=(self.n_stratified_clusters, self.n_stratified_clusters)
        )

        stationary[_states] = _stationary
        assert np.isclose(
            np.sum(stationary), 1.0
        ), "Stationary distribution not normalized!"
        tmatrix[np.ix_(_states, _states)] = _tmatrix

        mfpt = self.mfpt(
            self.mfpt_method,
            tmatrix,
            source_states,
            target_states,
            lag=self.lag,
            dt=self.dt,
            stationary=stationary,
        )

        if not np.isnan(mfpt):
            self.run.log_metric(
                f"set{set_idx}_{method}_mfpt_{self.current_direction}", mfpt
            )

        self.ness_df.loc[(set_idx, self.current_direction, method), :] = stationary

        return mfpt, tmatrix, stationary

    @staticmethod
    def mfpt(
            method,
            tmatrix,
            source_states,
            sink_states,
            dt,
            lag,
            stationary=None,
            clean_stationary=True,
    ):

        valid_methods = ["hill", "pyemma", "first_step", "fpt_distribution"]
        assert (
                method in valid_methods
        ), f"Invalid method -- choose one of {valid_methods}"

        _stationary = stationary.copy()
        _tmatrix = tmatrix.copy()

        # * Get MFPT via Hill relation
        # We shouldn't need to do this as a distribution, unless we don't actually have the stationary distribution
        #   -- the target flux should be constant in steady state.
        if method == "hill":
            # mfpt = cg.get_hill_mfpt(stationary, tmatrix, sink_states)
            if clean_stationary:
                _stationary[sink_states] = 0
                _stationary = _stationary / sum(_stationary)

            flux = 0
            for state in sink_states:
                flux += np.dot(_stationary, _tmatrix[:, state]).sum()

            mfpt = 1.0 / flux

        # * Get MFPT via PyEmma
        # To do this, we need a "proper" transition matrix -- i.e., not including any fully zeroed out rows
        elif method == "pyemma" or method == "first_step":
            # state_map = {}
            # _j = 0
            # for _i, _sum in enumerate(_tmatrix.sum(axis=1)):
            #     if np.isclose(_sum, 1):
            #         state_map[int(_i)] = int(_j)
            #         _j += 1
            #
            # try:
            #     remapped_source = np.array([
            #         state_map[x] for x in source_states if x in state_map.keys()
            #     ]).astype(int)
            #     remapped_target = np.array([
            #         state_map[x] for x in sink_states if x in state_map.keys()
            #     ]).astype(int)
            # except KeyError as e:
            #     raise e
            #
            # valid_states = list(state_map.keys())
            # clean_tmatrix = _tmatrix[valid_states][:, valid_states]

            # These methods fail by default for transition matrices with zero rows, because that'll fail deeptime's
            #   is_transition_matrix check.
            # However, by specifying a stationary distribution, it won't try to recalculate the stationary distribution,
            #   which is where that check is.
            # So even an "invalid" (by their definition) transition matrix will work.

            receiving_distribution = np.zeros_like(_stationary)
            receiving_distribution[source_states] = get_receiving_distribution(_tmatrix,
                                                                               _stationary, source_states)

            mfpt = deeptime.markov.tools.analysis.mfpt(_tmatrix, origin=source_states, target=sink_states,
                                                       mu=receiving_distribution)

        elif method == "fpt_distribution":

            assert _stationary is not None

            receiving_distribution = get_receiving_distribution(_tmatrix, _stationary, source_states)
            initial_probs = receiving_distribution
            initial_states = source_states
            # initial_probs = _stationary[source_states] / sum(_stationary[source_states])

            fpt_probs, _, _, times = MarkovFPT.adaptive_fpt_distribution(
                Tmatrix=_tmatrix,
                initial_states=initial_states,
                initial_state_probs=initial_probs,
                target_states=sink_states,
                verbose=False,
                fine_increment=1.1,
                increment=1.2,
            )

            mfpt = np.average(times, weights=fpt_probs)

        # Do NOT need to adjust for lag time here!
        mfpt = mfpt * dt  # \* lag

        return mfpt


import sys
from io import StringIO
import unittest

class TestClass(unittest.TestCase):
    def assertIO(self, input, output):
        stdout, stdin = sys.stdout, sys.stdin
        sys.stdout, sys.stdin = StringIO(), StringIO(input)
        resolve()
        sys.stdout.seek(0)
        out = sys.stdout.read()[:-1]
        sys.stdout, sys.stdin = stdout, stdin
        self.assertEqual(out, output)

    def test_Sample_Input_1(self):
        input = """dIfFiCuLt"""
        output = """Yes"""
        self.assertIO(input, output)

    def test_Sample_Input_2(self):
        input = """eASY"""
        output = """No"""
        self.assertIO(input, output)

    def test_Sample_Input_3(self):
        input = """a"""
        output = """Yes"""
        self.assertIO(input, output)

alpha2num = lambda c: ord(c) - ord('A')

def resolve():
  S = list(input())

  for i in range(len(S)):
    if i%2 and alpha2num(S[i]) >= 32:
      print("No")
      return
    if i%2==0 and alpha2num(S[i]) <= 25:
      print("No")
      return

  print("Yes")

resolve()

if __name__ == "__main__":
    unittest.main()


import tensorflow as tf
import numpy as np
import sys
import os
config = tf.ConfigProto()
# config.gpu_options.per_process_gpu_memory_fraction = 0.33
config.gpu_options.allow_growth = True

def load_mnist():
    (train_x, train_y), (_, _) = tf.keras.datasets.mnist.load_data()
    return train_x.reshape((-1, 28, 28, 1))/255.0, train_y

def create_model(image_in):
    conv_1 = tf.layers.conv2d(image_in, filters=32, kernel_size=[5, 5], activation=tf.nn.relu, padding='same', name='conv_1')
    print ('conv_1', conv_1.shape)
    pool_1 = tf.layers.max_pooling2d(conv_1, 2, 2, name='pool_1')
    print ('pool_1', pool_1.shape)
    conv_2 = tf.layers.conv2d(pool_1,   filters=64, kernel_size=[3, 3], activation=tf.nn.relu, padding='same', name='conv_2')
    print ('conv_2', conv_2.shape)
    pool_2 = tf.layers.max_pooling2d(conv_2, 2, 2, name='pool_2')
    print ('pool_2', pool_2.shape)
    conv_3 = tf.layers.conv2d(pool_2,   filters=128, kernel_size=[3, 3], activation=tf.nn.relu, padding='same', name='conv_3')
    print ('conv_3', conv_3.shape)
    pool_3 = tf.layers.max_pooling2d(conv_3, 2, 2, name='pool_3')
    print ('pool_3', pool_3.shape)
    flat   = tf.layers.flatten(pool_3)
    print ('flat', flat.shape)
    dense_1 = tf.layers.dense(flat,    units=64, activation=tf.nn.relu, name='dense_1')
    print ('dense_1', dense_1.shape)
    dense_2 = tf.layers.dense(dense_1, units=64, activation=tf.nn.relu, name='dense_2')
    print ('dense_2', dense_2.shape)
    dense_3 = tf.layers.dense(dense_2, units=10, activation=tf.nn.relu, name='dense_3')
    print ('dense_3', dense_3.shape)
    return dense_3

def shuffle_data(train_x, train_y):
    print ('Shuffling data...', end='')
    p = np.random.permutation(train_x.shape[0])
    print ('Done')
    return train_x[p], train_y[p]

def data_generator(train_x, train_y, batch_size):
    batch_cout = train_x.shape[0] // batch_size
    while True:
        train_x, train_y = shuffle_data(train_x, train_y)
        for batch in range(batch_cout):
            data_x = list()
            data_y = list()
            for i in range(batch_size):
                data_x.append(train_x[batch*batch_size+i])
                data_y.append(train_y[batch*batch_size+i])
            yield np.array(data_x), np.array(data_y)

def main():
    if len(sys.argv) != 4:
        print ("usage: python3 mnist.py <batch_size> <learning rate> <gpu ID>")
        sys.exit()
    os.environ["CUDA_VISIBLE_DEVICES"] = str(sys.argv[3])

    # load data
    train_x, train_y = load_mnist()

    # placeholders
    mnist_in  = tf.placeholder(tf.float32, shape=[None, 28, 28, 1], name='images')
    mnist_out = tf.placeholder(tf.int64,   shape=[None],            name='labels')

    # creat model
    with tf.variable_scope('model'): probs = create_model(mnist_in)

    # outpt of the computational graph
    classes  = tf.argmax(probs, axis=1, name='output')
    equality = tf.equal(classes, mnist_out)
    accuracy = tf.reduce_mean(tf.cast(equality, tf.float32))

    # loss, optimizer
    loss = tf.reduce_mean(
            tf.nn.softmax_cross_entropy_with_logits(
                labels=tf.one_hot(indices=mnist_out, depth=10),
                logits=probs
                )
            )

    opt = tf.train.AdamOptimizer(learning_rate=float(sys.argv[2])).minimize(loss)

    batch_size = int(sys.argv[1])
    steps_per_epoch = train_x.shape[0] // batch_size
    gen = data_generator(train_x, train_y, batch_size)

    saver = tf.train.Saver()

    with tf.Session(config=config) as sess:

        sess.run(tf.global_variables_initializer())

        for step in range(steps_per_epoch*40):
            curEpoch = 1+step//steps_per_epoch
            batch_x, batch_y = gen.__next__()
            feed_dict = {mnist_in: batch_x, mnist_out: batch_y}
            target = [opt, loss, accuracy]
            _, curLoss, curAcc = sess.run(target, feed_dict=feed_dict)
            pattern = 'Epoch {}, Step {}, Loss {:.6f}, Acc {:.6f}'
            print (pattern.format(curEpoch, step+1, curLoss, curAcc))

        model_path = 'batchsize_{}_lr_{}'.format(batch_size, float(sys.argv[2]))
        if not os.path.exists(model_path): os.mkdir(model_path)
        saver.save(sess, os.path.join(model_path, "model.ckpt"))

if __name__ == '__main__':
    main()


from __future__ import annotations
import time
import logging
from typing import TYPE_CHECKING, List, Dict, Any, Tuple

import hvac
import mongoengine as me

from mist.api import config
from mist.api.exceptions import BadRequestError
from mist.api.exceptions import ServiceUnavailableError
from mist.api.secrets.models import Secret, VaultSecret

if TYPE_CHECKING:
    from mist.api.users.models import Organization

log = logging.getLogger(__name__)

def create_secret_name(path: str) -> str:
    if path == ".":
        return ""
    elif not path.endswith("/"):
        return path + "/"
    else:
        return path

class MistVaultError(Exception):
    ...

class VaultPortalClient:
    """The portal's Vault client. This client should only be used to mount
    secrets engines and create policies for Organization clients.

    Either approle credentials(VAULT_ROLE_ID,VAULT_SECRET_ID) or
    token(VAULT_TOKEN) must be available in config.py with the required
    policies attached.
    """

    _client = hvac.Client(url=config.VAULT_ADDR)

    def authenticate(self) -> None:
        if config.VAULT_SECRET_ID and config.VAULT_ROLE_ID:
            try:
                self._client.auth.approle.login(
                    role_id=config.VAULT_ROLE_ID,
                    secret_id=config.VAULT_SECRET_ID,
                )
            except hvac.exceptions.InvalidRequest:
                raise MistVaultError("Vault approle authentication failed.")
        elif config.VAULT_TOKEN:
            self._client.token = config.VAULT_TOKEN
        else:
            raise MistVaultError("Vault credentials missing")

        try:
            is_authenticated = self._client.is_authenticated()
        except hvac.exceptions.VaultDown:
            raise MistVaultError("Vault is sealed")

        if is_authenticated is False:
            raise MistVaultError(
                "Failed to authenticate with portal Vault client"
            )

    def get_approle_credentials(
        self,
        secrets_engine_path: str,
    ) -> Tuple[str, str]:
        """
        Generate scoped approle credentials for the provided secrets engine.

        Returns a tuple of the created role_id, secret_id
        """

        policy = config.VAULT_ORGANIZATION_POLICY.format(
            secret_engine_path=secrets_engine_path
        )
        policy_name = config.VAULT_ORGANIZATION_POLICY_NAME.format(
            secret_engine_path=secrets_engine_path
        )
        role_name = config.VAULT_ORGANIZATION_ROLE_NAME.format(
            secret_engine_path=secrets_engine_path
        )

        self._client.sys.create_or_update_policy(
            name=policy_name,
            policy=policy,
        )

        try:
            self._client.auth.approle.create_or_update_approle(
                role_name=role_name,
                token_policies=[policy_name],
                token_type="service",
            )
        except hvac.exceptions.InvalidPath:
            raise MistVaultError("Unsupported Vault path")

        role_id = self._client.auth.approle.read_role_id(role_name=role_name)[
            "data"
        ]["role_id"]

        secret_id = self._client.auth.approle.generate_secret_id(
            role_name=role_name,
        )["data"]["secret_id"]

        return role_id, secret_id

class BaseSecretController:
    def __init__(self, org: Organization) -> None:
        """
        Initialize a secrets controller given an organization.

        It is expected to access a controller from inside the organization.

        For example:

        org = Organization.objects.get(id=org_id)
        org.secrets_ctl.list_secrets()
        """
        self.org = org

    def list_secrets(
        self, path: str = ".", recursive: bool = False
    ) -> List[Secret]:
        raise NotImplementedError()

    def create_or_update_secret(
        self, name: str, attributes: Dict[str, Any]
    ) -> None:
        raise NotImplementedError()

    def read_secret(self, name: str) -> Dict[str, Any]:
        raise NotImplementedError()

    def delete_secret(self, name: str) -> None:
        raise NotImplementedError()

class VaultSecretController(BaseSecretController):
    def __init__(self, org: Organization) -> None:
        super().__init__(org)
        url = org.vault_address or config.VAULT_ADDR
        token = None
        if org.vault_token:
            token = org.vault_token
        elif (
            not org.vault_address and
            not org.vault_role_id and
            config.VAULT_TOKEN
        ):
            token = config.VAULT_TOKEN
        is_authenticated = False
        if token:
            self.client = hvac.Client(url=url, token=token)
            try:
                is_authenticated = self.client.is_authenticated()
            except hvac.exceptions.VaultDown:
                raise ServiceUnavailableError("Vault is sealed.")

        if not token or not is_authenticated:
            if org.vault_role_id and org.vault_secret_id:
                self.client = hvac.Client(url=url)
                try:
                    result = self.client.auth.approle.login(
                        role_id=org.vault_role_id,
                        secret_id=org.vault_secret_id,
                    )
                except hvac.exceptions.InvalidRequest:
                    raise BadRequestError(
                        "Vault approle authentication failed."
                    )
                client_token = result.get("auth", {}).get("client_token")
                if client_token:
                    org.vault_token = client_token
                    org.save()
                    return
            raise BadRequestError("Vault authentication failed.")

    def ensure_secrets_engine(self) -> None:
        """
        Make sure that a secrets engine exists for the organization.
        """
        try:
            self.client.sys.enable_secrets_engine(
                backend_type="kv",
                path=self.org.vault_secret_engine_path,
                options={
                    "version": config.VAULT_KV_VERSION,
                },
            )
        except hvac.exceptions.InvalidRequest:
            log.info("Secrets engine already exists for org %s", self.org.id)
        else:
            log.info("Created secrets engine for org %s", self.org.id)

class KV1VaultSecretController(VaultSecretController):
    def list_secrets(
        self, path: str = ".", recursive: bool = False
    ) -> List[VaultSecret]:
        """
        List Vault secrets in the specified path.

        Parameters:
          path(str): Specifies the path of the secrets to list.
          recursive(bool): List secrets following all sub-paths available.
                           This is meant to be True only when polling secrets.
        """
        self.ensure_secrets_engine()

        try:
            response = self.client.secrets.kv.v1.list_secrets(
                mount_point=self.org.vault_secret_engine_path, path=path
            )
            keys = response["data"]["keys"]
        except hvac.exceptions.InvalidPath:
            if path == ".":  # there aren't any secrets stored
                keys = []
            else:
                raise BadRequestError(
                    "The path specified does not exist in Vault."
                )
        except hvac.exceptions.Forbidden:
            raise BadRequestError(
                "Make sure your Vault token has the "
                "permissions to list secrets"
            )

        current_path = create_secret_name(path)
        secrets = []
        for key in keys:
            try:
                secret = VaultSecret.objects.get(
                    name=current_path + key, owner=self.org
                )
            except me.DoesNotExist:
                secret = VaultSecret(name=current_path + key, owner=self.org)

            if key.endswith("/") and recursive:
                secrets += self.list_secrets(
                    current_path + key, recursive=True
                )
            secret.save()
            secrets.append(secret)

        if path == "." and recursive:  # this is meant for poller only
            # delete secret objects that have been removed
            # from Vault, from mongoDB
            VaultSecret.objects(
                owner=self.org, id__nin=[s.id for s in secrets]
            ).delete()

        return list(set(secrets))

    def create_or_update_secret(
        self, name: str, attributes: Dict[str, Any]
    ) -> None:
        """
        Create a new version of a secret at the specified path.

        Parameters:
          name(str): The name/path of the secret to create or update.
          attributes(dict): The contents of the secret.
        """
        self.ensure_secrets_engine()
        try:  # existing secret
            existing_secret = self.org.ctl.read_secret(name)
        except BadRequestError:  # new secret
            existing_secret = {}

        try:
            self.client.secrets.kv.v1.create_or_update_secret(
                mount_point=self.org.vault_secret_engine_path,
                path=name,
                secret={**existing_secret, **attributes},
            )
        except hvac.exceptions.Forbidden:
            raise BadRequestError(
                "Make sure your Vault token has the "
                "permissions to create secret"
            )
        # self.list_secrets(recursive=True)

    def read_secret(self, name: str) -> Dict[str, Any]:
        """
        Retrieve the secret's contents at the specified path.

        Parameters:
          name(str): The name/path of the secret to retrieve.
        """
        try:
            api_response = self.client.secrets.kv.v1.read_secret(
                mount_point=self.org.vault_secret_engine_path,
                path=name,
            )
        except hvac.exceptions.Forbidden:
            raise BadRequestError(
                "Make sure your Vault token has the "
                "permissions to read secret"
            )
        except hvac.exceptions.InvalidPath:
            raise BadRequestError("Secret does not exist")

        return api_response["data"]

    def delete_secret(self, name: str) -> None:
        """
        Permanently delete the secret at the specified path.

        Parameters:
          name(str): The name/path of the secret to delete.
        """
        try:
            self.client.secrets.kv.v1.delete_secret(
                mount_point=self.org.vault_secret_engine_path,
                path=name,
            )
        except hvac.exceptions.Forbidden:
            raise BadRequestError(
                "Make sure your Vault token has the "
                "permissions to delete secret"
            )

        # list all secrets
        self.list_secrets(path=".", recursive=True)

class KV2VaultSecretController(VaultSecretController):
    def list_secrets(
        self, path: str = ".", recursive: bool = False
    ) -> List[VaultSecret]:
        """
        List Vault secrets in the specified path.

        Parameters:
          path(str): Specifies the path of the secrets to list.
          recursive(bool): List secrets following all sub-paths available.
                           This is meant to be True only when polling secrets.
        """
        self.ensure_secrets_engine()

        try:
            response = self.client.secrets.kv.list_secrets(
                mount_point=self.org.vault_secret_engine_path, path=path
            )
            keys = response["data"]["keys"]
        except hvac.exceptions.InvalidPath:
            if path == ".":  # there aren't any secrets stored
                keys = []
            else:
                raise BadRequestError(
                    "The path specified does not exist in Vault."
                )

        except hvac.exceptions.Forbidden:
            raise BadRequestError(
                "Make sure your Vault token has the "
                "permissions to list secrets"
            )

        current_path = create_secret_name(path)
        secrets = []
        for key in keys:
            try:
                secret = VaultSecret.objects.get(
                    name=current_path + key, owner=self.org
                )
            except me.DoesNotExist:
                secret = VaultSecret(name=current_path + key, owner=self.org)

            if key.endswith("/") and recursive:
                secrets += self.list_secrets(
                    current_path + key, recursive=True
                )
            secret.save()
            secrets.append(secret)

        if path == "." and recursive:  # this is meant for poller only
            # delete secret objects that have been removed
            # from Vault, from mongoDB
            VaultSecret.objects(
                owner=self.org, id__nin=[s.id for s in secrets]
            ).delete()
        return list(set(secrets))

    def create_or_update_secret(
        self, name: str, attributes: Dict[str, Any]
    ) -> None:
        """
        Create a new version of a secret at the specified path.

        Parameters:
          name(str): The name/path of the secret to create or update.
          attributes(dict): The contents of the secret.
        """
        self.ensure_secrets_engine()
        for _ in range(5):
            try:
                self.client.secrets.kv.v2.patch(
                    mount_point=self.org.vault_secret_engine_path,
                    path=name,
                    secret=attributes,
                )
            except (hvac.exceptions.InvalidPath, KeyError):
                # no existing data in this path
                self.client.secrets.kv.v2.create_or_update_secret(
                    mount_point=self.org.vault_secret_engine_path,
                    path=name,
                    secret=attributes,
                )

                return
            except hvac.exceptions.Forbidden:
                raise BadRequestError(
                    "Make sure your Vault token has the "
                    "permissions to create a secret")
            except hvac.exceptions.InvalidRequest as exc:
                # When a KV2 secrets engine is mounted, it starts as KV1 and
                # immediately upgrades itself. During this time, requests to
                # create or read secrets will result in 400, containing a
                # message like the one below.
                # See the following issue for more info:
                # https://github.com/hashicorp/terraform-provider-vault/issues/677  # noqa: E501
                if "Upgrading from non-versioned to versioned data" in str(exc):  # noqa: E501
                    time.sleep(1)
                    continue

                raise
            else:
                return

    def read_secret(self, name: str) -> Dict[str, Any]:
        """
        Retrieve the secret's contents at the specified path.

        Parameters:
          name(str): The name/path of the secret to retrieve.
        """
        try:
            api_response = self.client.secrets.kv.v2.read_secret_version(
                mount_point=self.org.vault_secret_engine_path,
                path=name,
            )
        except hvac.exceptions.Forbidden:
            raise BadRequestError(
                "Make sure your Vault token has the "
                "permissions to read secret"
            )
        except hvac.exceptions.InvalidPath:
            raise BadRequestError("Secret does not exist")

        return api_response["data"]["data"]

    def delete_secret(self, name: str) -> None:
        """
        Permanently delete the secret at the specified path.

        Parameters:
          name(str): The name/path of the secret to delete.
        """
        try:
            self.client.secrets.kv.v2.delete_metadata_and_all_versions(
                mount_point=self.org.vault_secret_engine_path,
                path=name,
            )
        except hvac.exceptions.Forbidden:
            raise BadRequestError(
                "Make sure your Vault token has the "
                "permissions to delete secret"
            )

        # list all secrets
        self.list_secrets(path=".", recursive=True)


# # import numpy
#
# print('hello world!')
#
# # a = [15, 5.78, True, 'hello']
# a = (15, 5.78, True, 'hello')
# b = 19
#
# # a[1] = 'world'
#
# print(a)
# print(a[1])
#
# print(type(a))
# # print(type(b))
#
# print(dir(a))
# # print(dir(b))
#

a = 15
b = 189
c = 4

print(a, b, c)

# a, b, c = b, c, a
(a, b, c) = (b, c, a)

# (a,)

print(a, b, c)


from django.urls import path
from django.views import View
from . import views
from .views import (
    addcomment,
    Cart,
    add_to_cart,
    CartDeleteView,
    all_orders,
    createres,
    payment,
    recommended,
    get_cultural_foods,
    get_fasting_foods,
    get_best_selling_foods,
    get_spicy_foods,
    suggest_address,
    get_new_foods,
    dashbord,
    AddressView,
    PostListView,
    PostDetailView,
    PostCreateView,
    PostUpdateView,
    PostDeleteView,
    UserPostListView,
    RestaurantListView
)

urlpatterns = [
    path('', PostListView.as_view(), name='blog-home'),
    path('user/<str:username>', UserPostListView.as_view(), name='user-posts'),
    path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
    path('order_details/', views.all_orders, name='order_details'),
    path('post/new/', PostCreateView.as_view(), name='post-create'),
    path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
    path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),
    path('about/', views.about, name='blog-about'),
    path('Dashboard/', views.dashbord, name='dashboard'),
    path('restaurantreg/',views.createres,name='res-reg'),
    path('Fasting Food/',views.get_fasting_foods,name='fasting'),
    path('New Food/',views.get_new_foods,name='new'),
    path('Cultural Food/',views.get_cultural_foods,name='cultural'),
    path('Best Selling Foods/',views.get_best_selling_foods,name='best'),
    path('recommended/',views.recommended,name='recommended'),
    path('Spicy Foods/',views.get_spicy_foods,name='spicy'),
    path('cart/',views.Cart,name='cart'),
    path('restaurants/',RestaurantListView.as_view() ,name='restaurants'),
    path('post/<int:pk>/comment/', views.addcomment, name='addcomment'),
    path('post/<int:pk>/cart',views.add_to_cart,name='add_to_cart'),
    path('remove_from_cart/<int:pk>/',views.CartDeleteView.as_view(),name='remove_from_cart'),
    path('remove_from_order/<int:pk>/',views.OrderDeleteView.as_view(),name='remove_from_order'),
    path('payment/',views.payment,name='payment'),
    path('Nearby_Services/',views.AddressView.as_view(),name='location'),
    path('Address/',views.suggest_address,name='address')

]


import abc
from collections import Counter

import numpy as np

import pandas as pd

from ..core import SKCMethodABC
from ..utils import Bunch, deprecated, doc_inherit

# =============================================================================
# DM BASE
# =============================================================================

class SKCDecisionMakerABC(SKCMethodABC):
    """Abstract class for all decisor based methods in scikit-criteria."""

    _skcriteria_abstract_class = True
    _skcriteria_dm_type = "decision_maker"

    @abc.abstractmethod
    def _evaluate_data(self, **kwargs):
        raise NotImplementedError()

    @abc.abstractmethod
    def _make_result(self, alternatives, values, extra):
        raise NotImplementedError()

    def evaluate(self, dm):
        """Validate the dm and calculate and evaluate the alternatives.

        Parameters
        ----------
        dm: :py:class:`skcriteria.data.DecisionMatrix`
            Decision matrix on which the ranking will be calculated.

        Returns
        -------
        :py:class:`skcriteria.data.RankResult`
            Ranking.

        """
        data = dm.to_dict()

        result_data, extra = self._evaluate_data(**data)

        alternatives = data["alternatives"]
        result = self._make_result(
            alternatives=alternatives, values=result_data, extra=extra
        )

        return result

# =============================================================================
# RESULTS
# =============================================================================

class ResultABC(metaclass=abc.ABCMeta):
    """Base class to implement different types of results.

    Any evaluation of the DecisionMatrix is expected to result in an object
    that extends the functionalities of this class.

    Parameters
    ----------
    method: str
        Name of the method that generated the result.
    alternatives: array-like
        Names of the alternatives evaluated.
    values: array-like
        Values assigned to each alternative by the method, where the i-th
        value refers to the valuation of the i-th. alternative.
    extra: dict-like
        Extra information provided by the method regarding the evaluation of
        the alternatives.

    """

    _skcriteria_result_series = None

    def __init_subclass__(cls):
        """Validate if the subclass are well formed."""
        result_column = cls._skcriteria_result_series
        if result_column is None:
            raise TypeError(f"{cls} must redefine '_skcriteria_result_series'")

    def __init__(self, method, alternatives, values, extra):
        self._validate_result(values)
        self._method = str(method)
        self._extra = Bunch("extra", extra)
        self._result_series = pd.Series(
            values,
            index=pd.Index(alternatives, name="Alternatives", copy=True),
            name=self._skcriteria_result_series,
            copy=True,
        )

    @abc.abstractmethod
    def _validate_result(self, values):
        """Validate that the values are the expected by the result type."""
        raise NotImplementedError()

    @property
    def values(self):
        """Values assigned to each alternative by the method.

        The i-th value refers to the valuation of the i-th. alternative.

        """
        return self._result_series.to_numpy(copy=True)

    @property
    def method(self):
        """Name of the method that generated the result."""
        return self._method

    @property
    def alternatives(self):
        """Names of the alternatives evaluated."""
        return self._result_series.index.to_numpy(copy=True)

    @property
    def extra_(self):
        """Additional information about the result.

        Note
        ----
        ``e_`` is an alias for this property

        """
        return self._extra

    e_ = extra_

    # UTILS ===================================================================

    def to_series(self):
        """The result as `pandas.Series`."""
        series = self._result_series.copy(deep=True)
        series.index = self._result_series.index.copy(deep=True)
        return series

    # CMP =====================================================================

    @property
    def shape(self):
        """Tuple with (number_of_alternatives, ).

        rank.shape <==> np.shape(rank)

        """
        return np.shape(self._result_series)

    def __len__(self):
        """Return the number ot alternatives.

        rank.__len__() <==> len(rank).

        """
        return len(self._result_series)

    def values_equals(self, other):
        """Check if the alternatives and ranking are the same.

        The method doesn't check the method or the extra parameters.

        """
        return (self is other) or (
            isinstance(other, type(self))
            and self._result_series.equals(other._result_series)
        )

    def aequals(self, other, rtol=1e-05, atol=1e-08, equal_nan=False):
        """Return True if the result are equal within a tolerance.

        The tolerance values are positive, typically very small numbers.  The
        relative difference (`rtol` * abs(`b`)) and the absolute difference
        `atol` are added together to compare against the absolute difference
        between `a` and `b`.

        NaNs are treated as equal if they are in the same place and if
        ``equal_nan=True``.  Infs are treated as equal if they are in the same
        place and of the same sign in both arrays.

        The proceeds as follows:

        - If ``other`` is the same object return ``True``.
        - If ``other`` is not instance of 'DecisionMatrix', has different shape
          'criteria', 'alternatives' or 'objectives' returns ``False``.
        - Next check the 'weights' and the matrix itself using the provided
          tolerance.

        Parameters
        ----------
        other : Result
            Other result to compare.
        rtol : float
            The relative tolerance parameter
            (see Notes in :py:func:`numpy.allclose`).
        atol : float
            The absolute tolerance parameter
            (see Notes in :py:func:`numpy.allclose`).
        equal_nan : bool
            Whether to compare NaN's as equal.  If True, NaN's in dm will be
            considered equal to NaN's in `other` in the output array.

        Returns
        -------
        aequals : :py:class:`bool:py:class:`
            Returns True if the two result are equal within the given
            tolerance; False otherwise.

        See Also
        --------
        equals, :py:func:`numpy.isclose`, :py:func:`numpy.all`,
        :py:func:`numpy.any`, :py:func:`numpy.equal`,
        :py:func:`numpy.allclose`.

        """
        if self is other:
            return True
        is_veq = self.values_equals(other) and set(self._extra) == set(
            other._extra
        )
        keys = set(self._extra)
        while is_veq and keys:
            k = keys.pop()
            sv = self._extra[k]
            ov = other._extra[k]
            if isinstance(ov, np.ndarray):
                is_veq = is_veq and np.allclose(
                    sv,
                    ov,
                    rtol=rtol,
                    atol=atol,
                    equal_nan=equal_nan,
                )
            else:
                is_veq = is_veq and sv == ov
        return is_veq

    def equals(self, other):
        """Return True if the results are equal.

        This method calls `aquals` without tolerance.

        Parameters
        ----------
        other : :py:class:`skcriteria.DecisionMatrix`
            Other instance to compare.

        Returns
        -------
        equals : :py:class:`bool:py:class:`
            Returns True if the two results are equals.

        See Also
        --------
        aequals, :py:func:`numpy.isclose`, :py:func:`numpy.all`,
        :py:func:`numpy.any`, :py:func:`numpy.equal`,
        :py:func:`numpy.allclose`.

        """
        return self.aequals(other, 0, 0, False)

    def __eq__(self, other):
        """x.__eq__(y) <==> x == y."""
        return self.equals(other)

    def __ne__(self, other):
        """x.__eq__(y) <==> x == y."""
        return not self == other

    # REPR ====================================================================

    def __repr__(self):
        """result.__repr__() <==> repr(result)."""
        kwargs = {"show_dimensions": False}

        # retrieve the original string
        df = self._result_series.to_frame().T
        original_string = df.to_string(**kwargs)

        # add dimension
        string = f"{original_string}\n[Method: {self.method}]"

        return string

    def _repr_html_(self):
        """Return a html representation for a particular result.

        Mainly for IPython notebook.

        """
        df = self._result_series.to_frame().T
        original_html = df.style._repr_html_()
        rtype = self._skcriteria_result_series.lower()

        # add metadata
        html = (
            f"
\n"
            f"{original_html}"
            f"Method: {self.method}\n"
            "
"
        )

        return html

@doc_inherit(ResultABC, warn_class=False)
class RankResult(ResultABC):
    """Ranking of alternatives.

    This type of results is used by methods that generate a ranking of
    alternatives.

    """

    _skcriteria_result_series = "Rank"

    @doc_inherit(ResultABC._validate_result)
    def _validate_result(self, values):
        cleaned_values = np.unique(values)

        length = len(cleaned_values)
        expected = np.arange(length) + 1
        if not np.array_equal(np.sort(cleaned_values), expected):
            raise ValueError(f"The data {values} doesn't look like a ranking")

    @property
    def has_ties_(self):
        """Return True if two alternatives shares the same ranking."""
        values = self.values
        return len(np.unique(values)) != len(values)

    @property
    def ties_(self):
        """Counter object that counts how many times each value appears."""
        return Counter(self.values)

    @property
    def rank_(self):
        """Alias for ``values``."""
        return self.values

    @property
    def untied_rank_(self):
        """Ranking whitout ties.

        if the ranking has ties this property assigns unique and consecutive
        values in the ranking. This method only assigns the values using the
        command ``numpy.argsort(rank_) + 1``.

        """
        if self.has_ties_:
            return np.argsort(self.rank_) + 1
        return self.rank_

    def to_series(self, *, untied=False):
        """The result as `pandas.Series`."""
        if untied:
            return pd.Series(
                self.untied_rank_,
                index=self._result_series.index.copy(deep=True),
                copy=True,
                name="Untied rank",
            )
        return super().to_series()

@doc_inherit(ResultABC, warn_class=False)
class KernelResult(ResultABC):
    """Separates the alternatives between good (kernel) and bad.

    This type of results is used by methods that select which alternatives
    are good and bad. The good alternatives are called "kernel"

    """

    _skcriteria_result_series = "Kernel"

    @doc_inherit(ResultABC._validate_result)
    def _validate_result(self, values):
        if np.asarray(values).dtype != bool:
            raise ValueError(f"The data {values} doesn't look like a kernel")

    @property
    def kernel_(self):
        """Alias for ``values``."""
        return self.values

    @property
    def kernel_size_(self):
        """How many alternatives has the kernel."""
        return np.sum(self.kernel_)

    @property
    def kernel_where_(self):
        """Indexes of the alternatives that are part of the kernel."""
        return np.where(self.kernel_)[0]

    @property
    @deprecated(
        reason=("Use ``kernel_where_`` instead"),
        version="0.7",
    )
    def kernelwhere_(self):
        """Indexes of the alternatives that are part of the kernel."""
        return self.kernel_where_

    @property
    def kernel_alternatives_(self):
        """Return the names of alternatives in the kernel."""
        return self._result_series.index[self._result_series].to_numpy(
            copy=True
        )


import argparse
import time
import math
import numpy as np
import pickle
import random

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torch.autograd as autograd
from torch.autograd import Variable

from utils import to_gpu, LoadEmbeddingsFromText, Corpus, batchify, truncate
from models import LanguageModel, RNNDiscriminator

parser = argparse.ArgumentParser(description='PyTorch Sequence GAN for Text')
parser.add_argument('--data', type=str, default='./snli_lm',
                    help='location of the data corpus')
parser.add_argument('--rnn_type', type=str, default='LSTM',
                    help='type of recurrent net (LSTM, GRU)')
parser.add_argument('--embedding_file', type=str, default='',
                    help='path to pretrained embedding file')
parser.add_argument('--emsize', type=int, default=50,
                    help='size of word embeddings')
parser.add_argument('--nhidden', type=int, default=50,
                    help='number of hidden units per layer')
parser.add_argument('--nlayers', type=int, default=1,
                    help='number of layers')
parser.add_argument('--lowercase', type=bool,  default=True,
                    help='whether to lowercase')
parser.add_argument('--k', type=int, default=1,
                    help='number of layers')
parser.add_argument('--pretrain-lr', type=float, default=20,
                    help='initial learning rate')
parser.add_argument('--lr', type=float, default=0.01,
                    help='initial learning rate')
parser.add_argument('--beta1', type=float, default=0.5,
                    help='beta1 for adam. default=0.5')
parser.add_argument('--clip', type=float, default=0.25,
                    help='gradient clipping')
parser.add_argument('--pretrain-epochs', type=int, default=3,
                    help='pretraining epochs limit')
parser.add_argument('--epochs', type=int, default=40,
                    help='upper epoch limit')
parser.add_argument('--pretrain-batch_size', type=int, default=32, metavar='N',
                    help='batch size')
parser.add_argument('--batch_size', type=int, default=50, metavar='N',
                    help='batch size')
parser.add_argument('--bidirectional', action='store_true',
                    help='bidirectional discriminator')
parser.add_argument('--dropout', type=float, default=0.2,
                    help='dropout applied to layers (0 = no dropout)')
parser.add_argument('--tied', action='store_true',
                    help='tie the word embedding and softmax weights')
parser.add_argument('--seed', type=int, default=111,
                    help='random seed')
parser.add_argument('--pretrain', action='store_true',
                    help='pretrains the generator on a language model objective')
parser.add_argument('--cuda', action='store_true',
                    help='use CUDA')
parser.add_argument('--log-interval', type=int, default=200, metavar='N',
                    help='report interval')
parser.add_argument('--save', type=str,  default='netG.pt',
                    help='path to save the final model')
parser.add_argument('--outf', type=str, default='./generated_output')
parser.add_argument('--netG', default='netG_best.pth', help="path to netG (to continue training)")
parser.add_argument('--netD', default='', help="path to netD (to continue training)")
parser.add_argument('--sample', action='store_true',\
                    help='will sample for generation')
parser.add_argument('--temp', type=float, default=1.0,\
                    help='softmax temperaturen')
args = parser.parse_args()

with open(args.outf+'/args.txt', 'w') as f:
    f.write(str(args))

# Set the random seed manually for reproducibility.
torch.manual_seed(args.seed)
if torch.cuda.is_available():
    if not args.cuda:
        print("WARNING: You have a CUDA device, so you should probably run with --cuda")
    else:
        torch.cuda.manual_seed(args.seed)

###############################################################################
# Load data
###############################################################################

corpus = Corpus(args.data, lowercase=args.lowercase)

eval_batch_size = 10
val_data = batchify(corpus.valid, eval_batch_size, max_len=150)
test_data = batchify(corpus.test, eval_batch_size, max_len=150)

print("Loaded data!")

###############################################################################
# Build the models
###############################################################################

ntokens = len(corpus.dictionary.word2idx)

if args.embedding_file == "":
    vocab = None
else:
    # Load pretrained word embeddings
    vocab = LoadEmbeddingsFromText(corpus.word2idx, embedding_dim=args.emsize, path=args.embedding_file)

netG = LanguageModel(rnn_type=args.rnn_type,
                     emsize=args.emsize,
                     nhidden=args.nhidden,
                     ntokens=ntokens,
                     nlayers=args.nlayers,
                     dropout=args.dropout,
                     tied=args.tied,
                     max_unroll=50,
                     initial_vocab=vocab,
                     gpu=args.cuda)
if args.netG != "":
    print('Loading generator from '+args.netG)
    netG.load_state_dict(torch.load(args.netG))

print(netG.max_unroll)

if args.cuda:
    netG.cuda()

lm_criterion = nn.CrossEntropyLoss()

###############################################################################
# Training code
###############################################################################

netD = RNNDiscriminator(rnn_type=args.rnn_type,
                        emsize=args.emsize,
                        nhidden=args.nhidden,
                        ntokens=ntokens,
                        nlayers=args.nlayers,
                        bidirectional=args.bidirectional,
                        dropout=args.dropout,
                        initial_vocab=vocab,
                        gpu=args.cuda)
if args.netD != "":
    print('Loading discriminator from '+args.netD)
    netD.load_state_dict(torch.load(args.netD))

if args.cuda:
    netD.cuda()

# input image, noise, fixed noise for comparing learning improvement over epochs, label (real/fake)
input_ = torch.FloatTensor(args.batch_size, 9, args.emsize)
noise = torch.FloatTensor(args.nlayers, args.batch_size, args.nhidden)
fixed_noise = torch.FloatTensor(args.nlayers, args.batch_size, args.nhidden).normal_(0, 1)
label = torch.FloatTensor(args.batch_size)
ones = torch.FloatTensor(args.batch_size)
real_label = 1
fake_label = 0

input_ = to_gpu(args.cuda, Variable(input_))
label = to_gpu(args.cuda, Variable(label))
noise = to_gpu(args.cuda, Variable(noise))
fixed_noise = to_gpu(args.cuda, Variable(fixed_noise))
ones = to_gpu(args.cuda, Variable(ones))

# Binary cross entropy loss
criterion = nn.BCELoss()

if args.cuda:
    netD.cuda()
    netG.cuda()
    criterion.cuda()
    input_, label = input_.cuda(), label.cuda()
    noise, fixed_noise = noise.cuda(), fixed_noise.cuda()

# Main Training Loop
g_losses = []
d_losses = []

netD.embedding._parameters['weight'] = netG.embedding._parameters['weight']

# do checkpointing
last_D_x = 0.5
last_D_z = None
best_test_loss = None

def lm_evaluate(data_source):
    # Turn on evaluation mode which disables dropout.
    netG.eval()
    total_loss = 0
    ntokens = len(corpus.dictionary.word2idx)
    vocab = corpus.dictionary.idx2word
    for i, batch in enumerate(data_source):
        source, target, lengths = batch
        source = to_gpu(args.cuda, Variable(source, volatile=True))
        target = to_gpu(args.cuda, Variable(target, volatile=True))

        mask = target.gt(0)
        masked_target = target.masked_select(mask)
        flat_mask = mask.view(-1, 1)
        output_mask = flat_mask.view(-1, 1).expand(flat_mask.size(0), ntokens) # examples x ntokens

        netG.zero_grad()
        output = netG(teacher_forcing=True, input_=source) # output: batch x seq_len x ntokens
        flattened_output = output.view(-1, ntokens)

        masked_output = flattened_output.masked_select(output_mask).view(-1, ntokens)

        total_loss += lm_criterion(masked_output, masked_target).data

    return total_loss[0] / len(data_source)

print("Saving generated output to "+args.outf)

for i in range(5):
    batch_size = args.batch_size

    # Resample noise for generator
    noise.data.resize_(args.nlayers, batch_size, args.nhidden)
    noise.data.normal_(0, 1)

    with open("%s/%d_no_sampling_fake.txt" % (args.outf, i), "w") as f:
        fake_embeddings, list_indices = netG(teacher_forcing=False, input_=noise, sample=False, temp=1)
        indices = torch.cat(list_indices, 1)
        fake, fake_lengths = truncate(indices, args.cuda, sort=False)
        for ex in fake.data:
            chars = " ".join([corpus.dictionary.idx2word[x] for x in ex])
            f.write(chars)
            f.write("\n")

    with open("%s/%d_sampling_fake.txt" % (args.outf, i), "w") as f:
        fake_embeddings, list_indices = netG(teacher_forcing=False, input_=noise, sample=True, temp=1)
        indices = torch.cat(list_indices, 1)
        fake, fake_lengths = truncate(indices, args.cuda, sort=False)
        for ex in fake.data:
            chars = " ".join([corpus.dictionary.idx2word[x] for x in ex])
            f.write(chars)
            f.write("\n")

    with open("%s/%d_sampling_temp0.5_fake.txt" % (args.outf, i), "w") as f:
        fake_embeddings, list_indices = netG(teacher_forcing=False, input_=noise, sample=True, temp=0.5)
        indices = torch.cat(list_indices, 1)
        fake, fake_lengths = truncate(indices, args.cuda, sort=False)
        for ex in fake.data:
            chars = " ".join([corpus.dictionary.idx2word[x] for x in ex])
            f.write(chars)
            f.write("\n")

# Run on test data.
print("Evaluating on language model task...")
test_loss = lm_evaluate(test_data)
print('=' * 89)
print('| End of training | test loss {:5.2f} | test ppl {:8.2f}'.format(
    test_loss, math.exp(test_loss)))
print('=' * 89)


import math
import numpy as np
cos = math.cos
sin = math.sin

def Quaternion2Euler(e):
	e0 = e[0]
	e1 = e[1]
	e2 = e[2]
	e3 = e[3]

	phi = np.arctan2(2*(e0*e1+e2*e3),(e0**2+e3**2-e1**2-e2**2))
	theta = np.arcsin(2*(e0*e2-e1*e3))
	psi = np.arctan2(2*(e0*e3+e1*e2),(e0**2+e1**2-e2**2-e3**2))
	angles = [phi, theta, psi]
	return angles

def Euler2Quaternion(angles):

	phi = angles[0]
	theta = angles[1]
	psi = angles[2]

	e0 = cos(psi/2)*cos(theta/2)*cos(phi/2) + sin(psi/2)*sin(theta/2)*sin(phi/2)
	e1 = cos(psi/2)*cos(theta/2)*sin(phi/2) - sin(psi/2)*sin(theta/2)*cos(phi/2)
	e2 = cos(psi/2)*sin(theta/2)*cos(phi/2) + sin(psi/2)*cos(theta/2)*sin(phi/2)
	e3 = sin(psi/2)*cos(theta/2)*cos(phi/2) - cos(psi/2)*sin(theta/2)*sin(phi/2)

	e = [e0, e1, e2, e3]
	return e

def TransformationFormInertialToBody(wind_s, angles):
	wn_s = wind_s[0]
	we_s = wind_s[1]
	wd_s = wind_s[2]

	phi = angles[0]
	theta = angles[1]
	psi = angles[2]

	wn_b = cos(theta)*cos(psi)*wn_s + cos(theta)*sin(psi)*we_s + (-sin(theta))*wd_s
	we_b = (sin(phi)*sin(theta)*cos(psi)-cos(phi)*sin(psi))*wn_s + (sin(phi)*sin(theta)*sin(psi)+cos(phi)*cos(psi))*we_s + sin(phi)*cos(theta)*wd_s
	wd_b = (cos(phi)*sin(theta)*cos(psi)+sin(phi)*sin(psi))*wn_s + (cos(phi)*sin(theta)*sin(psi)-sin(phi)*cos(psi))*we_s + cos(phi)*cos(theta)*wd_s

	wind_b = [wn_b, we_b, wd_b]
	return wind_b


from transformers import AutoTokenizer, AutoModelForCausalLM
from transformers import pipeline

# https://www.philschmid.de/fine-tune-a-non-english-gpt-2-model-with-huggingface

tokenizerPath = "D:/ProgramData/torchHome/mymodel/codegen-350M-multi/";
modelPath = "./miaomiaoGpt/"
input_path = "./predictionData/P4.txt";

def load_input(inputPath):
    with open(inputPath, "r", encoding='GBK') as f:  # 打开测试文件
        text = f.read()
        return text.split("\n");

print("开始加载模型:" + modelPath)
chef = pipeline('text-generation', model=modelPath, tokenizer=tokenizerPath)

print("开始加载预测文件:" + input_path)
inputStrList = load_input(input_path);
# inputStrList = ["@MySet"]
print("pipeline开始预测:")
for inputStr in inputStrList:
    print("开始预测:\n" + inputStr)
    results = chef(inputStr)
    result = results[0]['generated_text'];
    print("预测结果:\n" + result)

# https://huggingface.co/docs/transformers/main/en/model_doc/codegen

# 加载预测模型
# Init tokenizer
tokenizer = AutoTokenizer.from_pretrained(tokenizerPath)
# Init model
model = AutoModelForCausalLM.from_pretrained(modelPath)
# 执行预测验证
print("model.generate开始预测:\n")
for inputStr in inputStrList:
    print("开始预测:\n" + inputStr)
    completion = model.generate(**tokenizer(inputStr, return_tensors="pt"))
    result = tokenizer.decode(completion[0]);
    print("预测结果:\n" + result)


from django.db import models
from django.contrib.auth.models import User
from django.core.validators import RegexValidator

class StudentProfile(models.Model):
    # Validators
    roll = RegexValidator(r'^[BMP][0-9]{2}[A-Z]{2}[0-9]{3}')
    # Models
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    roll_no = models.CharField(max_length=8, validators=[roll])
    courses = models.ManyToManyField('courses.Course', blank=True)

class FacultyProfile(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()
    office_address = models.TextField(null=True, blank=True)


import flask
from app.forms import LoginForm, RegisterForm, CommunityForm, PostForm, searchForm
from . import comms as co
from flask import Flask, request, make_response, redirect, render_template, session, url_for, current_app, abort, flash
from flask.helpers import send_from_directory
from models import User, Community, Post, Comment, get_user, user_by_id
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import secure_filename
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from db_service import db
from app.file_handling import allowed_file, validate_image
from uuid import uuid4
import os
from nudenet import NudeClassifierLite

###
### functions for files, community and posts management
###

def uploaded_file(filename):
    return send_from_directory(current_app.config['UPLOAD_FOLDER'],
                               filename)

def get_communities():
    community_list = Community.query.all()
    return community_list

###
### routes for community blueprint, list of communities, searching and add communities
###
@co.route('/explore')
def comms():
    community_list = get_communities()
    user = current_user
    search_form = searchForm()
    return render_template('communities.html', current_user=user, community_list=community_list, search_form=search_form)

@co.route('/add', methods=['GET', 'POST'])
@login_required
def addCommunity():
    community_form = CommunityForm()
    user = current_user
    if community_form.validate_on_submit():
        name = community_form.community_name.data
        description = community_form.description.data
        fb = community_form.facebook.data
        discord = community_form.discord.data
        pic = community_form.picture.data
        filename = secure_filename(pic.filename)

        if pic and allowed_file(filename):
            file_ext = os.path.splitext(filename)[1]
            if file_ext != validate_image(pic.stream):
                flash('Please upload an image file', 'info')
                return redirect(url_for('communities.addCommunity'))

            pic_name = uuid4().hex
            path = os.path.join(current_app.config['UPLOAD_FOLDER'], pic_name)
            pic.save(path)
            pic_classifier = NudeClassifierLite()                  # this is a NSFW filter implementation to avoid users from posting NSFW images as community pictures.
            if pic_classifier.classify(path)[path]['safe'] > 0.50:     # check the image and the 'safe' parameter returned, it has to be over 50% safe to accept the image
                new_community = Community(name=name, description=description, facebook=fb, discord=discord,
                                        picture=pic_name, id_creator=current_user.id)

                db.session.add(new_community)
                db.session.commit()
                flash('The community has ben added, gg!')
                return redirect(url_for('communities.comms'))
            else:
                os.remove(path)
                flash('Try to upload a different image')
                return redirect(url_for('communities.addCommunity'))
        flash('Make sure you upload an image file', 'info')
    return render_template('newCommunity.html', community_form=community_form, current_user=user)

@co.route('/search')
def searchCommunity():
    query = request.args.get('query')
    user = current_user
    search_form = searchForm()
    community_list = Community.query.filter(Community.name.ilike(f'%{query}%')).all()
    return render_template('communities.html', current_user=user, community_list=community_list, search_form=search_form)


import inspect
from ..pyqt import pyqtProperty, pyqtSignal, QVariant, QMetaObject, Qt, Q_ARG, Q_RETURN_ARG, QDate, QDateTime
from .. import objects

CLASS_PROPERTIES = { }

class QObjectHelper:
    """ Add QObject properties via python dict. """

    PRINT_EMITS = False

    def registerQtProperties(attrEntries=None, itemType=None, globalContext={}):
        """ Dynamically add Qt properties, signals, and slots to match objects.Property.
            'convertTo' sets the type on the property for the pyqtProperty while leaving 'type' in place. This allows converting types between the model and the items.
        """
        classAttrs = inspect.currentframe().f_back.f_locals
        classAttrs['qsignals'] = qsignals = classAttrs.get('qsignals', [])
        classAttrs['qproperties'] = qproperties = classAttrs.get('qproperties', [])
        # classAttrs['qslots'] = qslots = classAttrs.get('qslots', [])
        classAttrs['qpropertyNames'] = qpropertyNames = classAttrs.get('qpropertyNames', [])
        classAttrs['qsignalNames'] = qsignalNames = classAttrs.get('qsignalNames', [])
        if attrEntries is None:
            attrEntries = objects.Item.classProperties(itemType)
        for kwargs in attrEntries:
            kwargs['globalContext'] = globalContext
            def closure(kwargs, globalContext):
                attr = kwargs['attr']

                if not 'type' in kwargs:
                    kwargs['type'] = str

                if not 'convertTo' in kwargs:
                    kind = kwargs['type']
                else:
                    kind = kwargs.get('convertTo')

                signalName = '%sChanged' % attr
                getterName = attr
                setterName = 'set' + attr[0].upper() + attr[1:]
                resetterName = 'reset' + attr[0].upper() + attr[1:]
                #
                qsignal = pyqtSignal(QVariant)

                # These are separated out into instance methods to provide
                # a way for the instance to access them directly.
                # If they are just closures then there is no way to call them.
                def propGetter(self):
                    return self._cachedPropGetter(kwargs)

                def propSetter(self, value):
                    self._cachedPropSetter(kwargs, value)

                def propResetter(self):
                    self._cachedPropResetter(kwargs)

                #
                if not isinstance(type(kind), type):
                    raise TypeError("'kind' attribute must be a type, instead got: %" % kind)
                if kwargs.get('constant'):
                    qproperty = pyqtProperty(kind,
                                             fget=propGetter,
                                             freset=propResetter,
                                             notify=qsignal,
                                             constant=True)
                else:
                    qproperty = pyqtProperty(kind,
                                             fget=propGetter,
                                             fset=propSetter,
                                             freset=propResetter,
                                             notify=qsignal)
                #
                ret = {}
                ret[signalName] = qsignal
                ret[getterName] = qproperty
                ret[resetterName] = propResetter
                if not kwargs.get('constant'):
                    ret[setterName] = propSetter
                    # qslots.append(propSetter)
                qsignals.append(qsignal)
                qproperties.append(qproperty)
                qpropertyNames.append(getterName)
                qsignalNames.append(signalName)
                return ret
            propAttrs = closure(kwargs, globalContext)
            classAttrs.update(propAttrs)

        # inheritance
        global CLASS_PROPERTIES
        __qualname__ = classAttrs['__qualname__']
        CLASS_PROPERTIES[__qualname__] = attrEntries

    @staticmethod
    def classProperties(kind):
        ret = []
        for ctor in reversed(kind.mro()):
            classAttrs = CLASS_PROPERTIES.get(ctor.__qualname__, [])
            for kwargs in classAttrs:
                ret.append(kwargs)
        return ret

    def registerQmlMethods(entries):
        """ Forwards calls to QObject class methods to their qml-javascript correlates. """
        classAttrs = inspect.currentframe().f_back.f_locals
        for entry in entries:
            def make_meth(entry):
                def meth(self, *args):
                    self.checkInitQml()
                    qobject = self.qml.rootObject()
                    name = entry['name']
                    qargs = (Q_ARG(QVariant, arg) for arg in args)
                    try:
                        if entry.get('return'):
                            if qargs:
                                return QMetaObject.invokeMethod(qobject, name, Qt.DirectConnection,
                                                                Q_RETURN_ARG(QVariant),
                                                                *qargs)
                            else:
                                return QMetaObject.invokeMethod(qobject, name, Qt.DirectConnection,
                                                                Q_RETURN_ARG(QVariant))
                        else:
                            if qargs:
                                return QMetaObject.invokeMethod(qobject, name, Qt.DirectConnection,
                                                                *qargs)
                            else:
                                return QMetaObject.invokeMethod(qobject, name, Qt.DirectConnection)
                    except RuntimeError as e:
                        pass
                    # raise RuntimeError('QMetaObject.invokeMethod failed on %s.%s' % (self.__class__.__name__, name))
                return meth
            name = entry['name']
            if not name in classAttrs:
                classAttrs[name] = make_meth(entry)

    def initQObjectHelper(self, storage=False):
        if hasattr(self, '_propCache'):
            return
        self._propCache = {} # should be the converted (i.e. qml-exposed) value
        self._refreshingAllProperties = False
        self._refreshingAttr = None
        self._blockRefresh = False
        self._defaultStorage = {}
        if storage:
            # Set all to defaults. Let's see if this shoudl be reusable.
            attrs = self.classProperties(self.__class__)
            for kwargs in attrs:
                attr = kwargs['attr']
                x = self.defaultFor(attr)
                self._defaultStorage[attr] = x
        self.refreshAllProperties()

    def _emitAttrChanged(self, attr, x):
        """ Provides a way to capture signal emissions in a specific subclass
        when debugging, and also provide a way to force emission for certain
        strange property instances."""
        if self.PRINT_EMITS:
            print(f"{attr}Changed[{x}]")
        getattr(self, attr + 'Changed').emit(x)

    def refreshingAttr(self):
        """ Don't emit any changed signals for this prop name. """
        return self._refreshingAttr

    def refreshAllProperties(self):
        if self._refreshingAllProperties:
            return
        if self._blockRefresh:
            return
        self._refreshingAllProperties = True
        for kwargs in self.classProperties(self.__class__):
            attr = kwargs['attr']
            self.refreshProperty(attr)
        self._refreshingAllProperties = False

    def refreshProperty(self, attr):
        """
        The only place the changed signal is emitted for locally stored variables.
        Should not be called from &.set() or it won't have access to prop values set in &.set().
        """
        if self._blockRefresh:
            return
        kwargs = self.propAttrsFor(attr)
        if kwargs:
            x = self.get(attr)
            if not attr in self._propCache or x != self._propCache[attr]:
                if self.refreshingAttr() != attr:
                    # refreshProperty will be called again from _cachedPropSetter() immediately following this call.
                    self._propCache[attr] = x
                    self._emitAttrChanged(attr, x)

    def propAttrsFor(self, attr):
        """ Return the most recent property attributes for property,
        potentially updated using registerModelProperties."""
        attrs = self.classProperties(self.__class__)
        for entry in attrs:
            if entry['attr'] == attr:
                return entry

    def defaultFor(self, attr):
        """ Calculate a prop's default value based on either ['default'] or ['type']. """
        attrs = self.propAttrsFor(attr)
        if attrs:
            if 'convertTo' in attrs:
                return attrs.get('convertTo')()
            elif 'default' in attrs:
                return attrs.get('default')
            else:
                return attrs.get('type')()
        else:
            raise AttributeError('There is no property named %s' % attr)

    ## Value conversions

    def getterConvertTo(self, attr, x):
        value = x
        attrs = self.propAttrsFor(attr)
        if not attrs:
            return value
        convertTo = attrs.get('convertTo')
        if convertTo == Qt.CheckState:
            if x is True:
                value = Qt.Checked
            elif x is False:
                value = Qt.Unchecked
            elif x is None:
                value = Qt.Unchecked
            else:
                value = Qt.PartiallyChecked
        elif convertTo == QDateTime:
            if isinstance(x, QDate):
                value = QDateTime(x)
        return value

    def setterConvertTo(self, attr, value):
        """ Must be called explicitly in set(). """
        convertTo = self.propAttrsFor(attr).get('convertTo')
        x = value
        if convertTo == Qt.CheckState:
            if value == Qt.PartiallyChecked:
                x = True
            elif value == Qt.Checked:
                x = True
            elif value == Qt.Unchecked:
                x = False
        elif convertTo == QDateTime:
            x = value.dateTime()
        return x

    # Property behavior

    def _cachedPropGetter(self, kwargs):
        """ The first call directly from the Qt property.
        Should only access the cache; Cache should be explicitly updated with refreshProperty().
        """
        attr = kwargs['attr']
        ret = self._propCache[attr] # should be appropriately updated elsewhere
        return ret

    def _cachedPropSetter(self, kwargs, value):
        """ The first call directly from the Qt property. """
        attr = kwargs['attr']
        if kwargs.get('global'):
            if value != kwargs['globalContext'][attr]:
                kwargs['globalContext'][attr] = value
                self._emitAttrChanged(attr, value)
        else:
            if value != self._propCache.get(attr):
                was = self._refreshingAttr
                self._refreshingAttr = attr
                # may trigger onItemProperty, which emits changed signal from refreshProperty
                self.set(attr, value)
                self._refreshingAttr = was
                self.refreshProperty(attr)

    def _cachedPropResetter(self, kwargs):
        """ The first call directly from the Qt property. """
        attr = kwargs['attr']
        x = self.defaultFor(attr)
        if x != self._propCache[attr]:
            was = self._refreshingAttr
            self._refreshingAttr = attr
            self.reset(attr)
            self._refreshingAttr = was
            self.refreshProperty(attr)

    ## Virtuals

    def get(self, attr):
        """ Virtual. Should never return None. """
        kwargs = self.propAttrsFor(attr)
        if kwargs and kwargs.get('global'):
            x = kwargs['globalContext'][attr]
            return x
        elif self._defaultStorage:
            if kwargs:
                x = self._defaultStorage.get(attr)
                x = self.getterConvertTo(attr, x)
                return x
            else:
                raise RuntimeError('The property `%s` does not exist on %s' % (attr, self))

    def set(self, attr, x):
        """ Virtual """
        if self._defaultStorage:
            if self.propAttrsFor(attr):
                x = self.setterConvertTo(attr, x)
                self._defaultStorage[attr] = x
                self.refreshProperty(attr)
            else:
                raise RuntimeError('The property `%s` does not exist on %s' % (attr, self))

    def reset(self, attr):
        """ Virtual. Reimplement for new properties defined in model. """
        if self._defaultStorage:
            if self.propAttrsFor(attr):
                # self._defaultStorage[attr] = self.defaultFor(attr)
                default = self.defaultFor(attr)
                if default != self._defaultStorage[attr]:
                    self._defaultStorage[attr] = default
                    self.refreshProperty(attr)
            else:
                raise RuntimeError('The property `%s` does not exist on %s' % (attr, self))


#Plot radiative forcing  in the CICERO SCM
#and compare to IPCC AR6 forcing timeseries.
#https://github.com/chrisroadmap/ar6/blob/main/data_output/AR6_ERF_1750-2019.csv
#https://raw.githubusercontent.com/chrisroadmap/ar6/main/data_output/AR6_ERF_1750-2019.csv
#Can add 90% CI as well.

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

plt.rcParams['font.size'] = 4

#Read results:
outdir = '/div/no-backup/users/ragnhibs/ciceroscm/scripts/output_test/'
scen = 'test'
df_rf=pd.read_csv(outdir+'/output_forc.txt', sep='\t', index_col=0)
#print(df_rf)

#Read ipcc AR6 forcing timeseries
df_rf_ar6 = pd.read_csv('data_compare/AR6_ERF_1750-2019.csv',sep=',',header=0,index_col=0)

#print(df_rf_ar6.columns)
#Components given in IPCC
#'co2', 'ch4', 'n2o', 'other_wmghg', 'o3', 'h2o_stratospheric',
#       'contrails', 'aerosol-radiation_interactions',
#       'aerosol-cloud_interactions', 'bc_on_snow', 'land_use', 'volcanic',
#       'solar', 'nonco2_wmghg', 'aerosol', 'chapter2_other_anthro',
#       'total_anthropogenic', 'total_natural', 'total'

complist_ar6 = ['co2', 'ch4', 'n2o', 'other_wmghg',
                'o3','h2o_stratospheric','land_use','aerosol',
                'total_anthropogenic']

fig, axs = plt.subplots(nrows=3, ncols=3,sharex=True,figsize=(12,20))
axs=axs.flatten()
fig.suptitle('CICERO SCM simulation, Radiative Forcing')
for i,comp in enumerate(complist_ar6):
    print(i)
    print(comp)
    df_rf_ar6[comp].plot(ylabel='RF [Wm$^{-2}$ ]',
                         ax=axs[i],color='black',label='IPCC AR6')
    if comp == 'co2':
        df_rf['CO2'].plot(ax=axs[i],label=scen)
    elif comp == 'ch4':
        df_rf['CH4'].plot(ax=axs[i],label=scen)
    elif comp == 'n2o':
        df_rf['N2O'].plot(ax=axs[i],label=scen)
    elif comp == 'other_wmghg':
        complist_other=['CFC-11', 'CFC-12', 'CFC-113', 'CFC-114',
                        'CFC-115', 'CH3Br', 'CCl4', 'CH3CCl3', 'HCFC-22', 'HCFC-141b',
                        'HCFC-142b',
                        'C2F6', 'C6F14', 'CF4', 'SF6','HCFC-123',
                        'H-1211', 'H-1301', 'H-2402','HFC125','HFC134a',
                        'HFC143a','HFC227ea', 'HFC23', 'HFC245fa','HFC32',
                        'HFC4310mee']
        df_rf['other_wmghg'] = df_rf[complist_other].sum(axis=1, skipna=True)
        #print(df_rf['other_wmghg'])
        df_rf['other_wmghg'].plot(ax=axs[i],label=scen)
    elif comp == 'o3':
        print(df_rf.columns)
        df_rf['o3'] = df_rf['TROP_O3']+df_rf['STRAT_O3']
        df_rf['o3'].plot(ax=axs[i],label=scen)
    elif comp == 'h2o_stratospheric':
        df_rf['STRAT_H2O'].plot(ax=axs[i],label=scen)
    elif comp == 'land_use':
        df_rf['LANDUSE'].plot(ax=axs[i],label=scen)
    elif comp == 'aerosol':
        complist_aerosols = ['SO2','SO4_IND','BMB_AEROS_BC',
                             'BMB_AEROS_OC','BMB_AEROS', 'BC', 'OC']
        df_rf['aerosol'] = df_rf[complist_aerosols].sum(axis=1, skipna=True)
        df_rf['aerosol'].plot(ax=axs[i],label=scen)
    elif comp == 'total_anthropogenic':
        df_rf['Total_forcing'].plot(ax=axs[i],label=scen)
    axs[i].set_title(comp)
    axs[i].legend()
    axs[i].axhline(y=0,color='k',linestyle=':',linewidth=0.5)

fig, axs = plt.subplots(nrows=1, ncols=1,sharex=True,figsize=(6,6))
fig.suptitle('CICERO SCM simulation, Aerosol Radiative Forcing')

comp = 'aerosol-radiation_interactions'
df_rf_ar6[comp].plot(ylabel='RF [Wm$^{-2}$ ]',
                     ax=axs,color='green',label='IPCC AR6 '+comp)

comp = 'aerosol-cloud_interactions'
df_rf_ar6[comp].plot(ylabel='RF [Wm$^{-2}$ ]',
                     ax=axs,color='blue',label='IPCC AR6 '+comp)

complist_aerosols_direct = ['SO2','BMB_AEROS_BC',
                            'BMB_AEROS_OC','BMB_AEROS', 'BC', 'OC']
df_rf['aerosol_direct'] = df_rf[complist_aerosols_direct].sum(axis=1, skipna=True)
df_rf['aerosol_direct'].plot(ax=axs,color='purple',label='SCM ' + scen + ' sum aerosols direct')
df_rf['SO4_IND'].plot(ax=axs,color='orange',label='SCM ' + scen + ' SO4 indirect')

axs.legend()

plt.show()
exit()