#!/usr/bin/env python
"""Provides scikit interface."""

import numpy as np
import scipy as sp
import random

from ego.optimization.part_importance_estimator import PartImportanceEstimator

from ego.optimization.neighborhood_graph_grammar import NeighborhoodAdaptiveGraphGrammar
from ego.optimization.neighborhood_graph_grammar import NeighborhoodPartImportanceGraphGrammar

from ego.optimization.neighborhood_edge_swap import NeighborhoodEdgeSwap
from ego.optimization.neighborhood_edge_label_swap import NeighborhoodEdgeLabelSwap
from ego.optimization.neighborhood_edge_label_mutation import NeighborhoodEdgeLabelMutation
from ego.optimization.neighborhood_edge_move import NeighborhoodEdgeMove
from ego.optimization.neighborhood_edge_remove import NeighborhoodEdgeRemove
from ego.optimization.neighborhood_edge_add import NeighborhoodEdgeAdd
from ego.optimization.neighborhood_edge_expand import NeighborhoodEdgeExpand
from ego.optimization.neighborhood_edge_contract import NeighborhoodEdgeContract

from ego.optimization.neighborhood_node_label_swap import NeighborhoodNodeLabelSwap
from ego.optimization.neighborhood_node_label_mutation import NeighborhoodNodeLabelMutation
from ego.optimization.neighborhood_node_remove import NeighborhoodNodeRemove
from ego.optimization.neighborhood_node_add import NeighborhoodNodeAdd
from ego.optimization.neighborhood_node_smooth import NeighborhoodNodeSmooth

from ego.optimization.neighborhood_edge_crossover import NeighborhoodEdgeCrossover
from ego.optimization.neighborhood_cycle_crossover import NeighborhoodCycleCrossover
from ego.optimization.neighborhood_double_edge_crossover import NeighborhoodDoubleEdgeCrossover
from ego.optimization.neighborhood_break_crossover import NeighborhoodBreakCrossover

from ego.optimization.score_estimator import GraphUpperConfidenceBoundEstimator
from ego.optimization.score_estimator import GraphRandomForestScoreEstimator
from ego.optimization.score_estimator import GraphLinearScoreEstimator
from ego.optimization.score_estimator import GraphExpectedImprovementEstimator
from ego.optimization.score_estimator import GraphNeuralNetworkScoreEstimator
from ego.optimization.score_estimator import GraphNearestNeighborScoreEstimator
from ego.optimization.score_estimator import EnsembleScoreEstimator

from ego.optimization.feasibility_estimator import FeasibilityEstimator
from ego.decomposition.paired_neighborhoods import decompose_neighborhood
from ego.vectorize import hash_graph
from ego.utils.parallel_utils import simple_parallel_map
# from ego.optimization.score_estimator import GraphLinearClassifier
import time
from toolz import curry
import logging

logger = logging.getLogger()

def remove_duplicates(graphs):
    """remove_duplicates."""
    df = decompose_neighborhood(radius=2)
    selected_graphs_dict = {hash_graph(
        g, decomposition_funcs=df): g for g in graphs}
    return list(selected_graphs_dict.values())

def remove_duplicates_in_set(graphs_to_filter, graph_archive):
    """remove_duplicates_in_set."""
    df = decompose_neighborhood(radius=2)
    val_set = set([hash_graph(g, decomposition_funcs=df)
                   for g in graph_archive])
    selected_graphs = [g for g in graphs_to_filter if hash_graph(
        g, decomposition_funcs=df) not in val_set]
    return selected_graphs

def biased_sample(graphs, scores, sample_size):
    """biased_sample."""
    p = np.array(scores)
    p = np.nan_to_num(p)
    p[p < 0] = 0
    # if there are non zero probabilities
    # replace negative or zero probabilities with smallest positive prob
    if len(p[p > 0]) > 1:
        min_p = np.min(p[p > 0])
        p[p == 0] = min_p
        p = p / p.sum()
        sample_ids = np.random.choice(
            len(graphs), size=sample_size, replace=False, p=p)
    # otherwise sample uniformly at random
    else:
        sample_ids = np.random.choice(
            len(graphs), size=sample_size, replace=False)
    sample_graphs = [graphs[sample_id] for sample_id in sample_ids]
    sample_scores = [scores[sample_id] for sample_id in sample_ids]
    return sample_graphs, sample_scores

def tournament_sample(graphs, scores, sample_size):
    n = len(graphs)
    ids = list(range(n))
    random.shuffle(ids)
    policy_selected_graphs = []
    policy_selected_scores = []
    for i in range(0, n - 1, 2):
        if scores[ids[i]] > scores[ids[i + 1]]:
            policy_selected_graphs.append(graphs[ids[i]])
            policy_selected_scores.append(scores[ids[i]])
        else:
            policy_selected_graphs.append(graphs[ids[i + 1]])
            policy_selected_scores.append(scores[ids[i + 1]])
    policy_selected_graphs, policy_selected_scores = policy_selected_graphs[:sample_size], policy_selected_scores[:sample_size]
    return policy_selected_graphs, policy_selected_scores

def sample(graphs, scores, sample_size, greedy_frac=0.5, policy='tournament'):
    """sample."""
    assert len(graphs) > 0, 'Something went wrong: no graphs left to sample'
    if sample_size > len(scores):
        sample_size = len(scores)

    # select with greedy strategy
    greedy_selected_graphs = []
    greedy_selected_scores = []
    n_greedy_selection = int(sample_size * greedy_frac)
    sorted_ids = np.argsort(-np.array(scores))
    greedy_selected_ids = sorted_ids[:n_greedy_selection]
    if len(greedy_selected_ids) > 0:
        greedy_selected_graphs = [graphs[greedy_selected_id]
                                  for greedy_selected_id in greedy_selected_ids]
        greedy_selected_scores = [scores[greedy_selected_id]
                                  for greedy_selected_id in greedy_selected_ids]
    # select the rest with a specific policy: either biased or random
    policy_selected_graphs = []
    policy_selected_scores = []
    unselected_ids = sorted_ids[n_greedy_selection:]
    if len(unselected_ids) > 0:
        unselected_graphs = [graphs[unselected_id]
                             for unselected_id in unselected_ids]
        unselected_scores = [scores[unselected_id]
                             for unselected_id in unselected_ids]
        size = sample_size - len(greedy_selected_graphs)
        if policy == 'tournament':
            policy_selected_graphs, policy_selected_scores = tournament_sample(
                unselected_graphs, unselected_scores, size)
        elif policy == 'biased':
            policy_selected_graphs, policy_selected_scores = biased_sample(
                unselected_graphs, unselected_scores, size)

    selected_graphs = greedy_selected_graphs + policy_selected_graphs
    selected_scores = greedy_selected_scores + policy_selected_scores
    return selected_graphs, selected_scores

@curry
def _perturb(g, neighborhood_estimator=None, part_importance_estimator=None, feasibility_estimator=None):
    neighbor_graphs = []
    neighborhood_estimator.part_importance_estimator = part_importance_estimator
    neighbors = neighborhood_estimator.neighbors(g)
    if feasibility_estimator is None:
        feasibility_scores = [True] * len(neighbors)
    else:
        feasibility_scores = feasibility_estimator.predict(neighbors)
    for neighbor, is_feasible in zip(neighbors, feasibility_scores):
        if is_feasible:
            neighbor.graph['parent'] = g.copy()
            # TODO if the score is the same, then evaluate if they are the same, if so then use 'identity' as type
            neighbor.graph['type'] = type(neighborhood_estimator).__name__
            neighbor_graphs.append(neighbor)
    return neighbor_graphs

def perturb(graphs, neighborhood_estimator, part_importance_estimator=None, feasibility_estimator=None, execute_concurrently=False):
    """perturb."""
    # generate a fixed num of neighbors for each graph in input
    _perturb_ = _perturb(neighborhood_estimator=neighborhood_estimator, part_importance_estimator=part_importance_estimator, feasibility_estimator=feasibility_estimator)
    if execute_concurrently:
        neighbors_list = simple_parallel_map(_perturb_, graphs)
    else:
        neighbors_list = [_perturb_(g) for g in graphs]
    neighbor_graphs = []
    for neighbors in neighbors_list:
        neighbor_graphs.extend(neighbors)
    return neighbor_graphs

def mutate(proposed_graphs, graphs, part_importance_estimator=None, feasibility_estimator=None):
    """mutate."""
    mutated_proposed_graphs = []
    mutated_proposed_graphs += perturb(
        proposed_graphs,
        NeighborhoodNodeLabelMutation(n_nodes=None).fit(graphs),
        part_importance_estimator,
        feasibility_estimator)
    mutated_proposed_graphs += perturb(
        proposed_graphs,
        NeighborhoodEdgeLabelMutation(n_edges=None).fit(graphs),
        part_importance_estimator,
        feasibility_estimator)
    mutated_proposed_graphs += perturb(
        proposed_graphs,
        NeighborhoodEdgeSwap(n_edges=None).fit(graphs),
        part_importance_estimator,
        feasibility_estimator)
    return mutated_proposed_graphs

def elitism(proposed_graphs, oracle_func, frac_instances_to_remove_per_iter):
    """elitism."""
    scores = [oracle_func(g) for g in proposed_graphs]
    ids = np.argsort(scores)
    n = int(frac_instances_to_remove_per_iter * len(scores))
    ids = ids[n:]
    surviving_graphs = [proposed_graphs[id] for id in ids]
    surviving_scores = [scores[id] for id in ids]
    for graph, score in zip(surviving_graphs, surviving_scores):
        graph.graph['oracle_score'] = score
    return surviving_graphs, surviving_scores

@curry
def materialize_iterated_neighborhood(
        id_estimator,
        neighborhood_estimators=None,
        next_proposed_graphs=None,
        graphs=None,
        part_importance_estimator=None,
        feasibility_estimator=None,
        score_estimator=None,
        step=None,
        n_steps_driven_by_estimator=None,
        sample_size_to_perturb=None,
        n_queries_to_oracle_per_iter=None,
        parallelization_strategy=None,
        greedy_frac=0.5):
    """materialize_iterated_neighborhood."""
    if parallelization_strategy == 'graph_wise':
        execute_concurrently = True
    else:
        execute_concurrently = False
    neighborhood_estimator = neighborhood_estimators[id_estimator]
    start_time = time.process_time()
    all_neighbor_graphs = perturb(
        next_proposed_graphs,
        neighborhood_estimator,
        part_importance_estimator,
        feasibility_estimator,
        execute_concurrently)
    next_proposed_graphs, num_estimator_queries = [], 0
    neighbor_graphs = remove_duplicates(all_neighbor_graphs)
    if len(neighbor_graphs) == 0:
        logger.debug('Warning: removing duplicates among results resulted in no graphs')
        return next_proposed_graphs, num_estimator_queries
    neighbor_graphs = remove_duplicates_in_set(neighbor_graphs, graphs)
    if len(neighbor_graphs) == 0:
        logger.debug('Warning: removing duplicates w.r.t. archived graphs resulted in no graphs')
        return next_proposed_graphs, num_estimator_queries
    predicted_scores = score_estimator.acquisition_score(neighbor_graphs)
    num_estimator_queries = len(predicted_scores)
    # sample neighborhood according to surrogate score
    if step < n_steps_driven_by_estimator - 1:
        sample_size = int(sample_size_to_perturb / len(neighborhood_estimators))
    else:
        sample_size = int(n_queries_to_oracle_per_iter / len(neighborhood_estimators))
    sample_size = max(2, sample_size)
    next_proposed_graphs, next_proposed_scores = sample(
        neighbor_graphs,
        predicted_scores,
        sample_size,
        greedy_frac=greedy_frac)
    if len(next_proposed_graphs) == 0:
        logger.debug('Warning: sampling graphs resulted in no graphs')
        return next_proposed_graphs, num_estimator_queries

    end_time = time.process_time()
    elapsed_time = (end_time - start_time) / 60.0
    if n_steps_driven_by_estimator == 1:
        step_str = ''
    else:
        step_str = '%d:' % (step + 1)
    logger.info('%s%2d/%d) %30s:%4d novel graphs out of %4d generated  %3d selected graphs   best predicted score:%.3f   time:%.1f min' % (
        step_str, id_estimator + 1, len(neighborhood_estimators),
        type(neighborhood_estimator).__name__.replace('Neighborhood', ''),
        len(neighbor_graphs), len(all_neighbor_graphs),
        len(next_proposed_graphs), max(next_proposed_scores),
        elapsed_time))
    return next_proposed_graphs, num_estimator_queries

def select_iterated_neighborhoods(
        proposed_graphs,
        neighborhood_fitting_graphs,
        neighborhood_fitting_scores,
        graphs,
        neighborhood_estimators,
        part_importance_estimator,
        feasibility_estimator,
        score_estimator,
        n_steps_driven_by_estimator,
        sample_size_to_perturb,
        n_queries_to_oracle_per_iter,
        parallelization_strategy,
        greedy_frac):
    """select_iterated_neighborhoods."""
    all_n_estimator_queries = 0
    if parallelization_strategy == 'neighborhood_wise':
        execute_concurrently = True
    else:
        execute_concurrently = False
    for n_estimator, neighborhood_estimator in enumerate(neighborhood_estimators):
        neighborhood_estimator.fit(neighborhood_fitting_graphs, neighborhood_fitting_scores)
    for step in range(n_steps_driven_by_estimator):
        all_proposed_graphs = []
        _materialize_iterated_neighborhood_ = materialize_iterated_neighborhood(
            neighborhood_estimators=neighborhood_estimators,
            next_proposed_graphs=proposed_graphs[:],
            graphs=graphs,
            part_importance_estimator=part_importance_estimator,
            feasibility_estimator=feasibility_estimator,
            score_estimator=score_estimator,
            step=step,
            n_steps_driven_by_estimator=n_steps_driven_by_estimator,
            sample_size_to_perturb=sample_size_to_perturb,
            n_queries_to_oracle_per_iter=n_queries_to_oracle_per_iter)
        if execute_concurrently:
            list_of_graphs = simple_parallel_map(_materialize_iterated_neighborhood_, range(len(neighborhood_estimators)))
        else:
            list_of_graphs = [_materialize_iterated_neighborhood_(i) for i in range(len(neighborhood_estimators))]
        for gs, n_estimator_queries in list_of_graphs:
            all_proposed_graphs += gs
            all_n_estimator_queries += n_estimator_queries
        # for n_estimator, neighborhood_estimator in enumerate(neighborhood_estimators):
        #    next_proposed_graphs = _materialize_iterated_neighborhood_(n_estimator)
        #    all_proposed_graphs += next_proposed_graphs
        proposed_graphs = remove_duplicates(all_proposed_graphs)
        proposed_graphs = remove_duplicates_in_set(proposed_graphs, graphs)
        proposed_predicted_scores = score_estimator.acquisition_score(proposed_graphs)
        if step < n_steps_driven_by_estimator - 1:
            sample_size = sample_size_to_perturb
        else:
            sample_size = n_queries_to_oracle_per_iter
        proposed_graphs, proposed_predicted_scores = sample(
            proposed_graphs,
            proposed_predicted_scores,
            sample_size,
            greedy_frac)

    if n_queries_to_oracle_per_iter < len(proposed_graphs):
        logger.info('sampling %d out of %d non redundant graphs out of %d graphs generated for oracle evaluation' % (
            n_queries_to_oracle_per_iter, len(proposed_graphs), len(all_proposed_graphs)))
        proposed_graphs, proposed_predicted_scores = sample(
            proposed_graphs,
            proposed_predicted_scores,
            n_queries_to_oracle_per_iter,
            greedy_frac)
    # at this point we have proposed_graphs, proposed_scores:
    # for each graph we have the 'parent' and the 'type' and the score
    # we can formulate a learning task where starting from the parent graph
    # we have to predict in multiclass the type that has max score in any of the offsprings
    # so we have to collect all predictions relative to the same (hashed) parent graph
    # and select the argmax as the class to predict
    # the prediction can be used to allocate resources: expand only the k types that are
    # predicted to be yielding the best future improvements with k depending on the budget
    # the type predictor will be passed to the 'perturb' function and will mute the generation
    # for types predicted to under perform
    # NOTE: code should allow empty neighbor_graphs
    # Note: allow for no prediction at all when all types need to be generated

    return proposed_graphs, proposed_predicted_scores, all_n_estimator_queries

def compute_prediction_correlation(predicted_scores, true_scores):
    val, p = sp.stats.spearmanr(predicted_scores, true_scores)
    if np.isnan(val):
        val = 0
    return val

def optimize(graphs,
             oracle_func=None,
             n_iter=100,
             n_queries_to_oracle_per_iter=100,
             frac_instances_to_remove_per_iter=.1,
             sample_size_to_perturb=8,
             neighborhood_estimators=None,
             score_estimator=None,
             part_importance_estimator=None,
             feasibility_estimator=None,
             threshold_score_to_terminate=.99,
             monitor=None,
             draw_graphs=None,
             parallelization_strategy='graph_wise',
             greedy_sample_vs_tournament_frac=0.5,
             patience=3,
             n_steps_driven_by_estimator_base=3,
             add_mutations_when_asking_oracle=True):
    """optimize."""
    assert oracle_func is not None, 'An oracle function must be made available'
    n_steps_driven_by_estimator = n_steps_driven_by_estimator_base
    original_n_queries_to_oracle_per_iter = n_queries_to_oracle_per_iter
    exploration_vs_exploitation = score_estimator.exploration_vs_exploitation
    oracle_start_time = time.process_time()
    true_scores = [oracle_func(graph)
                   if graph.graph.get('oracle_score', None) is None
                   else graph.graph['oracle_score']
                   for graph in graphs]
    for graph, score in zip(graphs, true_scores):
        graph.graph['oracle_score'] = score
    oracle_end_time = time.process_time()
    oracle_elapsed_time = (oracle_end_time - oracle_start_time) / 60.0
    logger.info('Oracle evaluated on %d graphs in %.1f min' % (len(graphs), oracle_elapsed_time))

    proposed_graphs = []
    proposed_true_scores = []
    proposed_predicted_scores = []
    corr_true_vs_pred_scores_history = []
    max_score_history = []
    corr_true_vs_pred_scores_delta = 0
    corr_true_vs_pred_scores = 0
    max_score_delta = 0
    curr_patience = patience
    for i in range(n_iter):
        logger.info('\n\n- iteration: %d/%d' % (i + 1, n_iter))

        # update with oracle
        num_proposed_graphs = len(proposed_graphs)
        if num_proposed_graphs:
            oracle_start_time = time.process_time()
            proposed_graphs, proposed_true_scores = elitism(
                proposed_graphs, oracle_func, frac_instances_to_remove_per_iter)
            graphs += proposed_graphs[:]
            true_scores += proposed_true_scores[:]
            weights, errors = score_estimator.estimate_weights(proposed_graphs, proposed_true_scores)
            estimator_names = [type(estimator).__name__.replace('Graph', '').replace('Score', '').replace('Estimator', '') for estimator in score_estimator.estimators]
            for es, er, we in zip(estimator_names, errors, weights):
                print('est: %30s:   err: %.1e   w:%.3f' % (es, er, we))
            selected_proposed_predicted_scores = score_estimator.predict(proposed_graphs)
            if len(proposed_true_scores) == len(selected_proposed_predicted_scores):
                corr_true_vs_pred_scores = compute_prediction_correlation(proposed_true_scores, selected_proposed_predicted_scores)
            else:
                # start case
                corr_true_vs_pred_scores = 0
            corr_true_vs_pred_scores_history.append(corr_true_vs_pred_scores)
            if len(corr_true_vs_pred_scores_history) > 1:
                corr_true_vs_pred_scores_delta = (corr_true_vs_pred_scores_history[-1] - corr_true_vs_pred_scores_history[-2])
            else:
                corr_true_vs_pred_scores_delta = 0
            oracle_end_time = time.process_time()
            oracle_elapsed_time = (oracle_end_time - oracle_start_time) / 60.0
            logger.info('Oracle evaluated on %d graphs in %.1f min' % (num_proposed_graphs, oracle_elapsed_time))
            logger.info('Correlation between %d predicted and true scores: %.3f (increased of %.3f from previous iteration)' %
                        (len(proposed_true_scores), corr_true_vs_pred_scores, corr_true_vs_pred_scores_delta))
        # policy for n_steps_driven_by_estimator
        if corr_true_vs_pred_scores_delta > 0:
            n_steps_driven_by_estimator_factor = 1
        else:
            n_steps_driven_by_estimator_factor = -1
        n_steps_driven_by_estimator += n_steps_driven_by_estimator_factor
        n_steps_driven_by_estimator = int(n_steps_driven_by_estimator)
        if corr_true_vs_pred_scores < 0.5:
            n_steps_driven_by_estimator = n_steps_driven_by_estimator_base
        n_steps_driven_by_estimator = max(n_steps_driven_by_estimator_base, n_steps_driven_by_estimator)
        n_steps_driven_by_estimator = min(10, n_steps_driven_by_estimator)
        logger.info('n_steps_driven_by_estimator: %d' % (n_steps_driven_by_estimator))
        # termination condition
        if proposed_true_scores:
            max_iteration_score = max(proposed_true_scores)
        else:
            max_iteration_score = 0
        max_score = max(true_scores)
        max_score_history.append(max_score)
        if max_score >= threshold_score_to_terminate:
            logger.info('Termination! score:%.3f is above user defined threshold:%.3f' % (max_score, threshold_score_to_terminate))
            break
        else:
            logger.info('Max score in last iteration: %.3f    Global max score: %.3f' % (max_iteration_score, max_score))
            if draw_graphs is not None:
                if proposed_graphs:
                    logger.info('Current iteration')
                    draw_graphs(proposed_graphs)
                logger.info('Current status')
                draw_graphs(graphs)
        # policy for n_queries_to_oracle_per_iter and exploration_vs_exploitation
        if len(max_score_history) > 1:
            max_score_delta = max_score_history[-1] - max_score_history[-2]
            if max_score_delta <= 0 and curr_patience > 0:
                curr_patience -= 1
                logger.info('Lack of improvement detected  [patience:%d]'%curr_patience)
            elif max_score_delta <= 0 and curr_patience <= 0:
                curr_patience = patience
                logger.info('Lack of improvement detected  [patience:%d]  INCREASING exploration_vs_exploitation_factor AND INCREASING n_queries_to_oracle_per_iter'%curr_patience)
                n_queries_to_oracle_per_iter_increment_factor = 1.2
                exploration_vs_exploitation_factor = 10
            else:
                curr_patience = patience
                n_queries_to_oracle_per_iter_increment_factor = 0.08
                exploration_vs_exploitation_factor = 0.1
        else:
            max_score_delta = 0
            n_queries_to_oracle_per_iter_increment_factor = 1
            exploration_vs_exploitation_factor = 1
        exploration_vs_exploitation *= exploration_vs_exploitation_factor
        exploration_vs_exploitation = min(1, exploration_vs_exploitation)
        exploration_vs_exploitation = max(1e-4, exploration_vs_exploitation)
        score_estimator.set_exploration_vs_exploitation(exploration_vs_exploitation)
        logger.info('exploration_vs_exploitation:%.2e' % exploration_vs_exploitation)
        n_queries_to_oracle_per_iter *= n_queries_to_oracle_per_iter_increment_factor
        n_queries_to_oracle_per_iter = int(n_queries_to_oracle_per_iter)
        n_queries_to_oracle_per_iter = max(original_n_queries_to_oracle_per_iter, n_queries_to_oracle_per_iter)
        n_queries_to_oracle_per_iter = min(original_n_queries_to_oracle_per_iter * 20, n_queries_to_oracle_per_iter)
        logger.info('n_queries_to_oracle_per_iter:%d' % n_queries_to_oracle_per_iter)

        # update score_estimator
        score_estimator_start_time = time.process_time()
        score_estimator.fit(graphs, true_scores)
        score_estimator_end_time = time.process_time()
        score_estimator_elapsed_time = (score_estimator_end_time - score_estimator_start_time) / 60.0
        logger.info('Score estimator fitted on %d graphs in %.1f min' % (len(graphs), score_estimator_elapsed_time))

        # update part_importance_estimator
        part_importance_estimator_start_time = time.process_time()
        part_importance_estimator.fit(graphs, true_scores)
        part_importance_estimator_end_time = time.process_time()
        part_importance_estimator_elapsed_time = (part_importance_estimator_end_time - part_importance_estimator_start_time) / 60.0
        logger.info('Part importance  estimator fitted on %d graphs in %.1f min' % (len(graphs), part_importance_estimator_elapsed_time))

        # select small number (sample_size_to_perturb) of promising graphs for neighborhood expansion
        proposed_graphs, proposed_true_scores = sample(
            graphs,
            true_scores,
            sample_size_to_perturb,
            greedy_sample_vs_tournament_frac)

        # materialize neighborhood and select best candidates using score estimator
        iter_start_time = time.process_time()
        logger.info('From a biased draw of %d samples from %d graphs...' % (sample_size_to_perturb, len(graphs)))
        proposed_graphs, proposed_predicted_scores, all_n_estimator_queries = select_iterated_neighborhoods(
            proposed_graphs,
            graphs,
            true_scores,
            graphs,
            neighborhood_estimators,
            part_importance_estimator,
            feasibility_estimator,
            score_estimator,
            n_steps_driven_by_estimator,
            sample_size_to_perturb,
            n_queries_to_oracle_per_iter,
            parallelization_strategy,
            greedy_sample_vs_tournament_frac)

        if add_mutations_when_asking_oracle is True:
            mutated_proposed_graphs = mutate(proposed_graphs, graphs, part_importance_estimator, feasibility_estimator)
            logger.info('Added %d mutations to original %d proposed graphs'%(len(mutated_proposed_graphs),len(proposed_graphs)))
            proposed_graphs += mutated_proposed_graphs

        if monitor:
            monitor(i, proposed_graphs, graphs, score_estimator, all_n_estimator_queries, part_importance_estimator=part_importance_estimator)
        if len(proposed_graphs) == 0:
            break
        iter_end_time = time.process_time()
        iter_elapsed_time_m = (iter_end_time - iter_start_time) / 60.0
        iter_elapsed_time_h = iter_elapsed_time_m / 60.0
        logger.info('overall iteration time: %.1f min (%.1f h)' % (iter_elapsed_time_m, iter_elapsed_time_h))

    # final update with oracle
    num_proposed_graphs = len(proposed_graphs)
    if num_proposed_graphs:
        oracle_start_time = time.process_time()
        proposed_graphs, proposed_true_scores = elitism(
            proposed_graphs, oracle_func, frac_instances_to_remove_per_iter)
        max_iteration_score = max(proposed_true_scores)
        graphs += proposed_graphs[:]
        true_scores += proposed_true_scores[:]
        max_score = max(true_scores)
        oracle_end_time = time.process_time()
        oracle_elapsed_time = (oracle_end_time - oracle_start_time) / 60.0
        logger.info('Oracle evaluated on %d graphs in %.1f min' % (num_proposed_graphs, oracle_elapsed_time))
        logger.info('Max score in last iteration: %.3f' % (max_iteration_score))
        logger.info('Max score globally: %.3f' % (max_score))
        if draw_graphs is not None:
            if proposed_graphs:
                logger.info('Current iteration')
                draw_graphs(proposed_graphs)
            logger.info('Current status')
            draw_graphs(graphs)
    return graphs

def optimizer_setup(use_RandomForest_estimator=False,
                    use_Linear_estimator=False,
                    use_EI_estimator=False,
                    use_UCB_estimator=False,
                    use_KNN_stimator=False,
                    use_ANN_estimator=False,
                    ANN_estimator_hidden_layer_sizes=[100, 50],
                    n_estimators_ANN=10,
                    n_neighbors_KNN=5,
                    n_estimators=100,
                    exploration_vs_exploitation=0,
                    decomposition_score_estimator=None,
                    execute_estimator_concurrently=False,

                    use_feasibility_estimator=False,
                    decomposition_feasibility_estimator=None,
                    domain_graphs_feasibility_estimator=None,

                    decomposition_part_importance_estimator=None,

                    use_part_importance_graph_grammar=False,
                    n_neighbors_part_importance_graph_grammar=None,
                    conservativeness_part_importance_graph_grammar=1,
                    max_num_substitutions_part_importance_graph_grammar=None,
                    context_size_part_importance_graph_grammar=1,
                    decomposition_part_importance_graph_grammar=None,
                    domain_graphs_part_importance_graph_grammar=None,
                    fit_at_each_iteration_part_importance_graph_grammar=False,

                    use_adaptive_graph_grammar=False,
                    n_neighbors_adaptive_graph_grammar=None,
                    conservativeness_adaptive_graph_grammar=1,
                    max_num_substitutions_adaptive_graph_grammar=None,
                    context_size_adaptive_graph_grammar=1,
                    part_size_adaptive_graph_grammar=4,
                    decomposition_adaptive_graph_grammar_base=None,
                    decomposition_adaptive_graph_grammar_approx=None,

                    use_edge_crossover=False,
                    n_neighbors_edge_crossover=10,
                    n_edge_edge_crossover=1,
                    tournament_size_factor_edge_crossover=10,
                    size_n_std_to_accept_edge_crossover=2,

                    use_break_crossover=False,
                    n_neighbors_break_crossover=1,
                    n_edge_break_crossover=1,
                    min_size_break_crossover=5,
                    max_size_break_crossover=.5,
                    tournament_size_factor_break_crossover=3,
                    size_n_std_to_accept_break_crossover=1,

                    use_cycle_crossover=False,
                    n_neighbors_cycle_crossover=10,
                    n_cycle_cycle_crossover=1,
                    n_edges_per_cycle_cycle_crossover=1,
                    tournament_size_factor_cycle_crossover=10,
                    size_n_std_to_accept_cycle_crossover=2,

                    use_double_edge_crossover=False,
                    n_neighbors_double_edge_crossover=10,
                    n_double_edges_double_edge_crossover=10,
                    tournament_size_factor_double_edge_crossover=10,
                    size_n_std_to_accept_double_edge_crossover=2,

                    use_edge_swapping=False,
                    n_neighbors_edge_swapping=None, n_edge_swapping=1,

                    use_edge_label_swapping=False,
                    n_neighbors_edge_label_swapping=None,
                    n_edge_label_swapping=1,

                    use_edge_label_mutation=False,
                    n_neighbors_edge_mutation=None, n_edge_mutation=1,

                    use_edge_move=False,
                    n_neighbors_edge_move=None, n_edge_move=1,

                    use_edge_removal=False,
                    n_neighbors_edge_removal=None, n_edge_removal=1,

                    use_edge_addition=False,
                    n_neighbors_edge_addition=None, n_edge_addition=1,

                    use_edge_expand=False,
                    n_neighbors_edge_expand=None, n_edge_expand=1,

                    use_edge_contract=False,
                    n_neighbors_edge_contract=None, n_edge_contract=1,

                    use_node_label_swapping=False,
                    n_neighbors_node_label_swapping=None,
                    n_node_label_swapping=1,

                    use_node_label_mutation=False,
                    n_neighbors_node_mutation=None, n_node_mutation=1,

                    use_node_removal=False,
                    n_neighbors_node_removal=None, n_node_removal=1,

                    use_node_addition=False,
                    n_neighbors_node_addition=None, n_node_addition=1,

                    use_node_smooth=False,
                    n_neighbors_node_smooth=None, n_node_smooth=1):
    """optimizer_setup."""
    part_importance_estimator = PartImportanceEstimator(
        decompose_func=decomposition_part_importance_estimator)

    neighborhood_estimators = []

    if use_edge_crossover:
        nec = NeighborhoodEdgeCrossover(
            n_edges=n_edge_edge_crossover,
            n_neighbors=n_neighbors_edge_crossover,
            tournament_size_factor=tournament_size_factor_edge_crossover,
            size_n_std_to_accept=size_n_std_to_accept_edge_crossover)
        neighborhood_estimators.append(nec)

    if use_break_crossover:
        nbc = NeighborhoodBreakCrossover(
            n_edges=n_edge_break_crossover,
            min_size = min_size_break_crossover,
            max_size = max_size_break_crossover,
            n_neighbors=n_neighbors_break_crossover,
            tournament_size_factor=tournament_size_factor_break_crossover,
            size_n_std_to_accept=size_n_std_to_accept_break_crossover)
        neighborhood_estimators.append(nbc)

    if use_cycle_crossover:
        ncc = NeighborhoodCycleCrossover(
            n_cycles=n_cycle_cycle_crossover,
            n_edges_per_cycle=n_edges_per_cycle_cycle_crossover,
            n_neighbors=n_neighbors_cycle_crossover,
            tournament_size_factor=tournament_size_factor_cycle_crossover,
            size_n_std_to_accept=size_n_std_to_accept_cycle_crossover)
        neighborhood_estimators.append(ncc)

    if use_double_edge_crossover:
        ndec = NeighborhoodDoubleEdgeCrossover(
            n_double_edges=n_double_edges_double_edge_crossover,
            n_neighbors=n_neighbors_double_edge_crossover,
            tournament_size_factor=tournament_size_factor_double_edge_crossover,
            size_n_std_to_accept=size_n_std_to_accept_double_edge_crossover)
        neighborhood_estimators.append(ndec)

    if use_edge_swapping:
        nes = NeighborhoodEdgeSwap(
            n_edges=n_edge_swapping, n_neighbors=n_neighbors_edge_swapping)
        neighborhood_estimators.append(nes)

    if use_edge_label_swapping:
        nels = NeighborhoodEdgeLabelSwap(
            n_edges=n_edge_label_swapping, n_neighbors=n_neighbors_edge_label_swapping)
        neighborhood_estimators.append(nels)

    if use_edge_label_mutation:
        nelm = NeighborhoodEdgeLabelMutation(
            n_edges=n_edge_mutation, n_neighbors=n_neighbors_edge_mutation)
        neighborhood_estimators.append(nelm)

    if use_edge_move:
        nem = NeighborhoodEdgeMove(
            n_edges=n_edge_move, n_neighbors=n_neighbors_edge_move)
        neighborhood_estimators.append(nem)

    if use_edge_removal:
        ner = NeighborhoodEdgeRemove(
            n_edges=n_edge_removal, n_neighbors=n_neighbors_edge_removal)
        neighborhood_estimators.append(ner)

    if use_edge_addition:
        nea = NeighborhoodEdgeAdd(
            n_edges=n_edge_addition, n_neighbors=n_neighbors_edge_addition)
        neighborhood_estimators.append(nea)

    if use_edge_expand:
        nee = NeighborhoodEdgeExpand(
            n_edges=n_edge_expand, n_neighbors=n_neighbors_edge_expand)
        neighborhood_estimators.append(nee)

    if use_edge_contract:
        nec = NeighborhoodEdgeContract(
            n_edges=n_edge_contract, n_neighbors=n_neighbors_edge_contract)
        neighborhood_estimators.append(nec)

    if use_node_label_swapping:
        nnls = NeighborhoodNodeLabelSwap(
            n_nodes=n_node_label_swapping,
            n_neighbors=n_neighbors_node_label_swapping)
        neighborhood_estimators.append(nnls)

    if use_node_label_mutation:
        nnlm = NeighborhoodNodeLabelMutation(
            n_nodes=n_node_mutation, n_neighbors=n_neighbors_node_mutation)
        neighborhood_estimators.append(nnlm)

    if use_node_removal:
        nnr = NeighborhoodNodeRemove(
            n_nodes=n_node_removal, n_neighbors=n_neighbors_node_removal)
        neighborhood_estimators.append(nnr)

    if use_node_addition:
        nna = NeighborhoodNodeAdd(
            n_nodes=n_node_addition, n_neighbors=n_neighbors_node_addition)
        neighborhood_estimators.append(nna)

    if use_node_smooth:
        nns = NeighborhoodNodeSmooth(
            n_nodes=n_node_smooth, n_neighbors=n_neighbors_node_smooth)
        neighborhood_estimators.append(nns)

    if use_part_importance_graph_grammar:
        npigge = NeighborhoodPartImportanceGraphGrammar(
            decomposition_function=decomposition_part_importance_graph_grammar,
            context=context_size_part_importance_graph_grammar,
            count=conservativeness_part_importance_graph_grammar,
            filter_max_num_substitutions=max_num_substitutions_part_importance_graph_grammar,
            frac_nodes_to_select=.5,
            n_neighbors=n_neighbors_part_importance_graph_grammar,
            fit_at_each_iteration=fit_at_each_iteration_part_importance_graph_grammar,
            domain_graphs=domain_graphs_part_importance_graph_grammar)
        # if fitting is not done iteratively then fit once on the domain_graphs here
        if fit_at_each_iteration_part_importance_graph_grammar is False:
            # fitting the grammar always adds the input graphs to the domain_graphs
            # so here we will add no novel graphs to the domain_graphs
            npigge.fit_grammar([])
        neighborhood_estimators.append(npigge)

    if use_adaptive_graph_grammar:
        ane = NeighborhoodAdaptiveGraphGrammar(
            base_decomposition_function=decomposition_adaptive_graph_grammar_base,
            approximate_decomposition_function=decomposition_adaptive_graph_grammar_approx,
            context=context_size_adaptive_graph_grammar,
            count=conservativeness_adaptive_graph_grammar,
            filter_max_num_substitutions=max_num_substitutions_adaptive_graph_grammar,
            n_neighbors=n_neighbors_adaptive_graph_grammar,
            ktop=part_size_adaptive_graph_grammar,
            enforce_connected=True)
        neighborhood_estimators.append(ane)

    score_estimators = []
    if use_UCB_estimator:
        score_estimator = GraphUpperConfidenceBoundEstimator(
            decomposition_funcs=decomposition_score_estimator,
            exploration_vs_exploitation=exploration_vs_exploitation)
        score_estimators.append(score_estimator)

    if use_RandomForest_estimator:
        score_estimator = GraphRandomForestScoreEstimator(
            decomposition_funcs=decomposition_score_estimator,
            n_estimators=n_estimators,
            exploration_vs_exploitation=exploration_vs_exploitation)
        score_estimators.append(score_estimator)

    if use_Linear_estimator:
        score_estimator = GraphLinearScoreEstimator(
            decomposition_funcs=decomposition_score_estimator,
            n_estimators=n_estimators,
            exploration_vs_exploitation=exploration_vs_exploitation)
        score_estimators.append(score_estimator)

    if use_EI_estimator:
        score_estimator = GraphExpectedImprovementEstimator(
            decomposition_funcs=decomposition_score_estimator,
            exploration_vs_exploitation=exploration_vs_exploitation)
        score_estimators.append(score_estimator)

    if use_ANN_estimator:
        score_estimator = GraphNeuralNetworkScoreEstimator(
            hidden_layer_sizes=ANN_estimator_hidden_layer_sizes,
            decomposition_funcs=decomposition_score_estimator,
            n_estimators=n_estimators_ANN,
            exploration_vs_exploitation=exploration_vs_exploitation)
        score_estimators.append(score_estimator)

    if use_KNN_stimator:
        score_estimator = GraphNearestNeighborScoreEstimator(
            n_neighbors=n_neighbors_KNN,
            decomposition_funcs=decomposition_score_estimator,
            n_estimators=n_estimators,
            exploration_vs_exploitation=exploration_vs_exploitation)
        score_estimators.append(score_estimator)

    score_estimator = EnsembleScoreEstimator(score_estimators, execute_concurrently=execute_estimator_concurrently)
    score_estimator.set_exploration_vs_exploitation(exploration_vs_exploitation)

    if use_feasibility_estimator:
        feasibility_estimator = FeasibilityEstimator(
            decomposition_funcs=decomposition_feasibility_estimator)
        logger.info('Fitting feasibility estimator on %d graphs' % len(domain_graphs_feasibility_estimator))
        feasibility_estimator.fit(domain_graphs_feasibility_estimator)
    else:
        feasibility_estimator = None
    return neighborhood_estimators, score_estimator, part_importance_estimator, feasibility_estimator


import pygame
from pygame.locals import *

import const

class LineC():
    def lf(self):
        Line.pos += const.LINEHEIGHT

    def menu_lf(self):
        Line.pos += const.MENUHEIGHT

    def output(self, text, color, font, lf):
        label = font.render(text, True, color)
        text_rect = label.get_rect(midtop=const.SCREENRECT.midtop)
        text_rect.move_ip(0,Line.pos)
        self.screen.blit(label,text_rect)
        lf()

    def big(self, text, color = (0,0,0,255)):
        self.output(text, color, const.font.other, self.menu_lf)

    def small(self, text, color = (0,0,0,255)):
        self.output(text, color, const.font.small, self.lf)

Line = LineC()

class MenuEntry(pygame.sprite.Sprite):
    def __init__(self,label,result,width):
        pygame.sprite.Sprite.__init__(self,self.containers)

        self.image = pygame.Surface((width,const.MENUHEIGHT),flags=SRCALPHA).convert_alpha()
        self.image.fill((255, 255, 255, 0))

        self.rect = self.image.get_rect(midtop=const.SCREENRECT.midtop)

        self.label = const.font.menu.render(label, True, (0,0,0,255))
        self.label_rect = self.label.get_rect(center=self.rect.center)

        self.rect.move_ip(0,Line.pos)

        Line.menu_lf()

        self.result   = result

        self.prev = MenuEntry.prev
        MenuEntry.prev = self
        if self.prev:
            self.prev.next = self
        self.next = None

    def update(self,selected):
        if selected:
            alpha = 125
        else:
            alpha = 0
        self.image.fill((0, 0, 0, alpha))
        self.image.blit(self.label, self.label_rect)

def menu(screen):
    background = pygame.Surface(screen.get_size()).convert()
    # imgbg = load_image('background-menu.png')
    background.fill((250, 250, 250))
    #background.blit(imgbg,(0,0))
    screen.blit(background, (0,0))
    pygame.display.flip()

    # Groups of sprite
    entries = pygame.sprite.Group()
    selected_entry = pygame.sprite.GroupSingle()
    visible = pygame.sprite.RenderUpdates()

    MenuEntry.containers = visible, entries
    MenuEntry.prev = None
    Line.pos = 0
    Line.screen = screen

    Line.lf()
    Line.big("Move Them",color = (22,22,90))
    Line.small("You are alone")
    Line.small("They want you")
    Line.small("And this is not a cannon you have")
    Line.lf()
    Line.lf()
    Line.lf()
    Line.small("Use arrow key to control, space to lock")
    Line.small("or a pad (A to lock)")
    Line.lf()

    pygame.display.flip()

    selected_entry.add(MenuEntry("Play",'play',screen.get_width()))
    MenuEntry("Highscore",'score',screen.get_width())
    MenuEntry("Quit",'quit',screen.get_width())

    # a clock
    clock = pygame.time.Clock()

    def noop(): pass
    def next():
        if selected_entry.sprite.next:
            selected_entry.add(selected_entry.sprite.next)
    def prev():
        if selected_entry.sprite.prev:
            selected_entry.add(selected_entry.sprite.prev)

    while True:
        nextaction = noop
        for event in pygame.event.get():
            if event.type == QUIT or \
                (event.type == KEYDOWN and event.key == K_ESCAPE):
                return 'quit'
            elif event.type == KEYDOWN and event.key == K_RETURN:
                return selected_entry.sprite.result
            elif event.type == KEYDOWN and event.key == K_UP:
                nextaction = prev
            elif event.type == KEYDOWN and event.key == K_DOWN:
                nextaction = next
            elif event.type == JOYAXISMOTION and event.joy == 0 and event.axis == 1:
                if event.value > 0.5:
                    nextaction = next
                elif event.value < -0.5:
                    nextaction = prev
            elif event.type == JOYBUTTONDOWN and event.joy == 0 and event.button == 0:
                return selected_entry.sprite.result
            elif event.type == JOYBUTTONDOWN and event.joy == 0 and (event.button == 2 or event.button == 6):
                return 'quit'

        nextaction()

        visible.clear(screen, background)

        visible.update(False)

        selected_entry.update(True)

        dirty = visible.draw(screen)
        pygame.display.update(dirty)

        #cap the framerate
        clock.tick(5)


import pygame
import os
from constants import WIDTH, HEIGHT

# Sprite dimensions
SPRITE_HEIGHT = 128
SPRITE_WIDTH = 128

SPRITE_SPAWN_X = WIDTH // 2 - SPRITE_WIDTH // 2
SPRITE_SPAWN_Y = HEIGHT // 2 - SPRITE_HEIGHT // 2

# Players
# Character sprite sheet
character_sprite_sheet = pygame.image.load(os.path.join("Assets", "Characters", "character_spritesheet.png"))

# Player
def get_player_surface(direction):
    if direction == "up":
        return pygame.transform.scale(character_sprite_sheet.subsurface(0, 48, 32, 32), (SPRITE_HEIGHT, SPRITE_WIDTH))
    elif direction == "down":
        return pygame.transform.scale(character_sprite_sheet.subsurface(0, 0, 32, 32), (SPRITE_HEIGHT, SPRITE_WIDTH))
    elif direction == "left":
        return pygame.transform.scale(character_sprite_sheet.subsurface(0, 96, 32, 32), (SPRITE_HEIGHT, SPRITE_WIDTH))
    elif direction == "right":
        return pygame.transform.scale(character_sprite_sheet.subsurface(0, 144, 32, 32), (SPRITE_HEIGHT, SPRITE_WIDTH))

# Player rect
player1_rect = pygame.Rect(SPRITE_SPAWN_X, SPRITE_SPAWN_Y, SPRITE_WIDTH, SPRITE_HEIGHT)


import pytest
import numpy as np

from dpipe.torch import to_np
from dpipe.train import ConsoleLogger
from dpipe.torch import inference_step
from dpipe.train.policy import Schedule
from dpipe.prototypes import GradientsAccumulator, LossAccumulator, train_multiple_strategies

@pytest.mark.parametrize("use_hf", [False, True])
def test_single_forward_loss(forward_strategy_all_ds_batch, task_data, use_hf):
    task_inputs, task_gt = task_data
    s, model = forward_strategy_all_ds_batch(use_hf, n_targets=1)
    s.start_epoch(0)
    pred = inference_step(task_inputs[..., None], architecture=model)[..., 0]
    # loss weight == 2
    true_loss = 2 * np.mean((pred - task_gt) ** 2)
    first_batch_loss = to_np(s.process_batch(0, 0))
    assert np.isclose(true_loss, first_batch_loss)
    second_batch_loss = to_np(s.process_batch(0, 1))
    assert np.isclose(true_loss, second_batch_loss)

@pytest.mark.parametrize("use_hf", [False, True])
@pytest.mark.parametrize("accum", [GradientsAccumulator, LossAccumulator])
def test_single_optimization(accum, use_hf, single_strategy, task_data):
    lr = 0.1
    task_inputs, task_gt = task_data
    s, model = single_strategy(accum, use_hf, lr=lr, n_targets=1)
    x_data, y_data = task_data
    # test several iterations
    s.start_epoch(0)
    for batch_index in range(4):
        init_bias = to_np(model.bias).copy()
        init_weight = to_np(model.weight).copy()
        pred = inference_step(x_data[..., None], architecture=model)[..., 0]
        s.process_batch(0, batch_index)
        # correct gradients
        w_grad = 4 / len(x_data) * np.dot(x_data, pred - y_data)
        b_grad = 4 * np.mean((pred - y_data))
        # check gradients
        atol = 1e-3
        assert np.isclose(to_np(model.bias.grad), b_grad, atol=atol)
        assert np.isclose(to_np(model.weight.grad), w_grad, atol=atol)
        # check updates
        assert np.isclose(to_np(model.weight), init_weight - lr * w_grad, atol=atol)
        assert np.isclose(to_np(model.bias), init_bias - lr * b_grad, atol=atol)

@pytest.mark.parametrize("use_hf", [True, False])
@pytest.mark.parametrize("accum", [GradientsAccumulator, LossAccumulator])
def test_single_strategy_lifecycle(accum, use_hf, single_strategy):
    weights = []

    class Logger(ConsoleLogger):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)

        def policies(self, policies: dict, step: int):
            nonlocal weights
            weights.append(policies['weight'])
            return super().policies(policies, step)

    # check policy update
    lr = Schedule(initial=1, epoch2value_multiplier={1: 2, 2: 3, 3: 4, 4: 5})
    weight = Schedule(initial=1, epoch2value_multiplier={1: 2, 2: 3, 3: 4, 4: 5})
    s, _ = single_strategy(accum, use_hf, lr=lr, weight=weight, logger=Logger(), n_targets=1)
    counter = 0

    def validate():
        nonlocal counter
        counter += 1
        return {'counter': counter}

    s.strategies[0].validate_step = validate
    train_multiple_strategies(s, n_epochs=5)
    assert counter == 5
    assert weights == [1, 2, 6, 24, 120]

    for param_group in s.optimization_policy.optimizer.param_groups:
        assert param_group['lr'] == 120

@pytest.mark.parametrize("use_hf", [True, False])
@pytest.mark.parametrize("accum", [GradientsAccumulator, LossAccumulator])
def test_double_grad_propagation(accum, use_hf, single_strategy, task_data):
    lr = 1
    s, model = single_strategy(accum, use_hf, lr=lr, n_targets=0)
    x_data, y_data = task_data
    s.start_epoch(0)

    # test several iterations
    for batch_index in range(4):
        init_bias = to_np(model.bias).copy()
        init_weight = to_np(model.weight).copy()

        s.process_batch(0, batch_index)
        # correct gradients
        atol = 1e-4
        b_grad = 0.
        w_grad = 4 * init_weight * np.mean((x_data - y_data) ** 2)
        # check gradients
        assert np.isclose(to_np(model.bias.grad), b_grad, atol=atol)
        assert np.isclose(to_np(model.weight.grad), w_grad, atol=atol)
        # check updates
        assert np.isclose(to_np(model.weight), init_weight - lr * w_grad, atol=atol)
        assert np.isclose(to_np(model.bias), init_bias - lr * b_grad, atol=atol)


from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
# Standard imports
from future import standard_library
standard_library.install_aliases()
from builtins import *
import unittest

# Our imports
from emission.core import common
from emission.core.get_database import get_test_db

class TestGeoJSON(unittest.TestCase):
  def setUp(self):
    self.Sections=get_test_db()

  def tearDown(self):
    get_test_db().delete_many({})

  def testGeoWithinPostsData(self):
    post1 = {"author": "Mike",
           "text": "My first blog post!",
           "tags": ["mongodb", "python", "pymongo"],
           'loc':{'type':'Point', 'coordinates':[100,32]}}
    self.Sections.insert_one(post1)
    post2 = {"author": "hh",
           "text": "My 2 blog post!",
           "tags": ["mongodb", "python", "pymongo"],
           "loc":{'type':'Point', 'coordinates':[200,30]}}
    self.Sections.insert_one(post2)

    retVal = []
    for a in self.Sections.find({ "loc" : { "$geoWithin" : { "$polygon" :[ [ 90,31 ],[90,40] ,[ 110,40 ],[110,31]] } } }):
        retVal.append(a)

    self.assertEqual(len(retVal), 1)
    self.assertEqual(retVal[0]['text'], "My first blog post!")
    self.assertEqual(retVal[0]['loc']['coordinates'], [100,32])

  def testGeoWithinCounts(self):
    post1 = {"author": "Mike",
           "text": "My first blog post!",
           "tags": ["mongodb", "python", "pymongo"],
           'loc':{'type':'Point', 'coordinates':[100,32]}}
    self.Sections.insert_one(post1)
    post2 = {"author": "hh",
           "text": "My 2 blog post!",
           "tags": ["mongodb", "python", "pymongo"],
           "loc":{'type':'Point', 'coordinates':[200,30]}}
    self.Sections.insert_one(post2)

    retVal = []
    matching_counts = self.Sections.count_documents({ "loc" : { "$geoWithin" : { "$polygon" :[ [ 90,31 ],[90,40] ,[ 110,40 ],[110,31]] } } })

    self.assertEqual(matching_counts, 1)

  def getTestPolygon(self):
    return [ [ 90.234,-31.0323 ],[95.0343,-45.03453] ,[ 110.02322,-43.3435 ],[100.343423,-33.33423]]

  def testGeoWithOurStructureAndTestPolyFunction(self):
    sec1 = {'track_location':{'type':'Point', 'coordinates':[100,-40]}}
    self.Sections.insert_one(sec1)

    retVal = []
    for a in self.Sections.find({ "track_location" : { "$geoWithin" : { "$polygon" : self.getTestPolygon() } } }):
        retVal.append(a)
    self.assertEqual(len(retVal), 1)

  def getTestPolygon2(self):
    # return [ [30,+50],[30,+100], [60,+100],[60,+50]] # works1
    # return [ [30,+100],[30,+150], [60,+150],[60,+100]] # fails
    # return [ [35,+110],[35,+120], [36,+120],[36,+110]] # fails
    # return [ [30,+50],[30,+100], [60,+100],[60,+50]] # fails
    # return [ [35,+50],[35,+100], [36,+100],[36,+50]] # works2
    # return [ [35,+75],[35,+76], [36,+75],[36,+76]] # works3
    # return [ [35,+85],[35,+86], [36,+85],[36,+86]] # works4
    # return [ [35,+95],[35,+96], [36,+95],[36,+96]] # fails
    # return [ [35,+90],[35,+91], [36,+90],[36,+91]] # FAILS
    # return [ [35,+89],[35,+90], [36,+89],[36,+90]] # WORKS!
    # return [ [89,35],[89,36], [90,35],[90,36]] # works
    return [ [90,35],[90,36], [91,35],[91,36]] # works

  def testGeoWithOurStructureAndTestPolyFunction2(self):
    # pnt = {'type':'Point', 'coordinates': [40, +70]} # works1
    # pnt = {'type':'Point', 'coordinates': [40, +120]} # fails
    # pnt = {'type':'Point', 'coordinates': [35.5, +115]} # fails
    # pnt = {'type':'Point', 'coordinates': [35, +95]} # fails
    # pnt = {'type':'Point', 'coordinates': [35.5, +75]} # works2
    # pnt = {'type':'Point', 'coordinates': [35.5, +75.5]} # works2
    # pnt = {'type':'Point', 'coordinates': [35.5, +85.5]} # works3
    # pnt = {'type':'Point', 'coordinates': [35.5, +85.5]} # works4
    # pnt = {'type':'Point', 'coordinates': [35.5, +95.5]} # fails
    # pnt = {'type':'Point', 'coordinates': [35.5, +90.5]} # fails
    # pnt = {'type':'Point', 'coordinates': [35.5, +89.5]} # WORKS!
    # pnt = {'type':'Point', 'coordinates': [89.5, 35.5]} # works
    pnt = {'type':'Point', 'coordinates': [90.5, 35.5]} # works
    sec1 = {'track_location': pnt}
    self.Sections.insert_one(sec1)

    retVal = []
    for a in self.Sections.find({ "track_location" : { "$geoWithin" : { "$polygon" : self.getTestPolygon2() } } }):
        retVal.append(a)

    self.assertEqual(len(retVal), 1)

  def getTestNegPolygon(self):
    return [ [-90,35],[-90,36],
             [-91,35],[-91,36]] # works
    # return [[-123,36],[-123,38],
    #         [-122,36],[-122,38]]

  def testGeoWithNegativeValues(self):
    test = {'track_location': {'type':'Point', 'coordinates': [-90.5,35.5]} }
    self.Sections.insert_one(test)

    retVal = []
    for a in self.Sections.find({ "track_location" : { "$geoWithin" : { "$polygon" : self.getTestNegPolygon() } } }):
        retVal.append(a)
        print("Found match for %s" % a)

    self.assertEqual(len(retVal), 1)

  def getTestNegPolygon(self):
#     return [[-122,36],[-122,38], # FAILS
#             [-123,36],[-123,38]]
    return [[-122,37],[-122,38],   # WORKS
            [-123,38],[-123,37]]

  def testGeoWithNegativeValues(self):
    test = {'track_location': {'type':'Point', 'coordinates': [-122.5,37.5]} }
    self.Sections.insert_one(test)

    retVal = []
    for a in self.Sections.find({ "track_location" : { "$geoWithin" : { "$polygon" : self.getTestNegPolygon() } } }):
        retVal.append(a)
        print("Found match for %s" % a)

    self.assertEqual(len(retVal), 1)

  def getRealBerkeleyPolygon(self):
    return [[-122,37],[-122,38],
            [-123,38],[-123,37]]

  def testGeoWithOurStructureAndRealPolyFunction(self):
    test = {'track_location': {'type':'Point', 'coordinates': [-122.5, 37.5]} }
    self.Sections.insert_one(test)

    library = {'track_location': {'type':'Point', 'coordinates': [-122.259475, 37.872370]} }
    self.Sections.insert_one(library)

    mclaughlin = {'track_location': {'type':'Point', 'coordinates': [-122.259169, 37.873873]} }
    self.Sections.insert_one(mclaughlin)

    soda = {'track_location': {'type':'Point', 'coordinates': [-122.258740, 37.875711]} }
    self.Sections.insert_one(soda)

    wurster = {'track_location': {'type':'Point', 'coordinates': [-122.254577, 37.870352]} }
    self.Sections.insert_one(wurster)

    retVal = []
    for a in self.Sections.find({ "track_location" : { "$geoWithin" : { "$polygon" : self.getTestNegPolygon() } } }):
        retVal.append(a)
        print("Found match for %s" % a)

    self.assertEqual(len(retVal), 5)

    retVal = []
    for a in self.Sections.find({ "track_location" : { "$geoWithin" : { "$polygon" : self.getRealBerkeleyPolygon() } } }):
        retVal.append(a)
        print("Found match for %s" % a)

    self.assertEqual(len(retVal), 5)

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


import pdfplumber, pyttsx3

## works with English text only

with pdfplumber.PDF(open(file=f'task_a.pdf', mode='rb')) as pdf:
    pages = [page.extract_text() for page in pdf.pages]

text = ''.join(pages)
text = text.replace('\n', '')

speaker = pyttsx3.init()
speaker.save_to_file(text, 'task_a.mp3')
speaker.runAndWait()
speaker.stop()


"""
Database manipulation commands
"""

import click
from brewblox_ctl import click_helpers
from brewblox_ctl import migration, utils

@click.group(cls=click_helpers.OrderedGroup)
def cli():
    """Command collector"""

@cli.group()
def database():
    """Database migration commands."""

@database.command()
def from_couchdb():
    """Migrate configuration data from CouchDB to Redis.

    In the 2020/09/22 release (config version 0.6.0)
    Redis replaced CouchDB as configuration database.

    This command copies the configuration data from CouchDB to Redis.

    \b
    Steps:
        - Create CouchdDB container.
        - Fetch data from CouchDB.
        - Write data to Redis.
    """
    utils.check_config()
    utils.confirm_mode()
    migration.migrate_couchdb()

@database.command()
@click.option('--target',
              default='victoria',
              help='Where to store exported data',
              type=click.Choice(['victoria', 'file']))
@click.option('--duration',
              default='',
              prompt='From how far back do you want to migrate data? (eg. 1d, 30d, 1y). '
              'Leave empty to migrate everything.',
              help='Period of exported data. Example: 30d')
@click.option('--offset',
              multiple=True, nargs=2, type=click.Tuple([str, int]),
              default=[],
              help='Start given service(s) with an offset. Useful for resuming exports. '
              'Example: [--offset spark-one 10000 --offset spark-two 5000]')
@click.argument('services', nargs=-1)
def from_influxdb(target, duration, offset, services):
    """Migrate history data from InfluxDB to Victoria Metrics or file.

    In config version 0.7.0 Victoria Metrics replaced InfluxDB as history database.

    This command exports the history data from InfluxDB,
    and then either immediately imports it to Victoria Metrics, or saves it to file.

    By default, all services are migrated.
    You can override this by listing the services you want to migrate.

    When writing data to file, files are stored in the ./influxdb-export/ directory.

    \b
    Steps:
        - Create InfluxDB container.
        - Get list of services from InfluxDB. (Optional)
        - Read data from InfluxDB.
        - Write data to Victoria Metrics.     (Optional)
        - OR: write data to file.             (Optional)
    """
    utils.check_config()
    utils.confirm_mode()
    migration.migrate_influxdb(target, duration, list(services), list(offset))


import abc
import mypy
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from dualGPy import Utils as ut
class Graph(abc.ABC):
    """Base class for the Graph2D and Graph3D representation.
     WARNING! 2D and 3D are considered from a geometric point of
     view:

     mesh : dualGPy mesh representation
    """
    def __init__(self, mesh):
     self.mesh = mesh
     self.graph={}
     self.nx_graph = nx.Graph(self.mesh.connectivity)
     self.edges = []
     self.vertex = [0]
     self.weight = []

    @abc.abstractmethod
    def get_CSR(self):
     raise NotImplementedError

    def adj_to_csr(self):
     """Parsing the adjacency matrix in numpy in a CSR (Compressed Sparse Row) representation
     Parameters:

     adj : Adjacency matrix

     """
     # the CSR representation is built starting from the definition, hence
     # we cycle over the adjacency matrix, we identify the non zero entry
     # and finally we fill the vertex vector with the position where we find the
     # non zero elements in the edges vector. We hence fill the edges vector at the same
     # way
     # numpy adjacency matrix
     adj = nx.to_numpy_array(self.nx_graph, nodelist=range(len(self.mesh.cells)))
     non_zero = np.count_nonzero(adj,axis=1)
     for i,e in enumerate(adj[:,1]):
       print(i)
       self.vertex.append(np.sum(non_zero[:i]))
       for j,f in enumerate(adj[i,:]):
           if adj[i,j]!=0:
            self.edges.append(j)
     self.vertex.append(np.sum(np.count_nonzero(adj)))

class Graph2D(Graph):
    def __init__(self, mesh):
        super().__init__(mesh)

    def get_CSR(self):
     edges = []
     vertex = []
     somma = 0
     # Initialize the keys of the graph dictionary
     # cycle on the points
     for key in self.mesh.connectivity:
        self.edges.extend(self.mesh.connectivity[key])
        somma += len(self.mesh.connectivity[key])
        self.vertex.append(somma)

    def draw_graph(self, string, mesh = None):
     """Draw the mesh and the graph.
Parameters:
* string: str
  Name of the file in which the graph will be printed
* mesh: dualGPy mesh, default: None
  mesh whose graph will be drawn. If None, the mesh used to set up the graph is used
"""
     m = self.mesh if mesh is None else mesh
     plt.figure()
     ### cycle on the elements
     for elemento in m.cells:
      ### cycle on the point of the elements
         for i in range(len(elemento)):
       ## # plot the grid
          x_value = [m.mesh.points[elemento[i-1],0],m.mesh.points[elemento[i],0]]
          y_value = [m.mesh.points[elemento[i-1],1],m.mesh.points[elemento[i],1]]
          plt.plot(x_value,y_value,c='r')
     ##draw the adjacency graph
     nx.draw(self.nx_graph,pos=m.centers,with_labels = True)
     plt.savefig(string)


import discord
import logging
_LOG = logging.getLogger(__name__)

from gtts import gTTS
from doosbot.const import BUFFER_TTS

class DoosBotClient(discord.Client):

	_volume_level = 0.5
	_active_media = None

	async def on_ready(self):
		_LOG.info(f"Connected to Discord with identity { self.user }")

	async def on_message(self, message: discord.message.Message):
		_LOG.info(f"MESSAGE { message.author.display_name }→{self.user.display_name}: { message.content }")

	def get_voice_client(self, channel) -> discord.VoiceClient:
		voice_client: discord.VoiceClient = None
		for voice_client in self.voice_clients:
			if voice_client.channel == channel:
				break

		return voice_client

	async def play_file(self, file_name, channel: discord.VoiceChannel, loop: bool = False):
		voice_client = self.get_voice_client(channel)
		if voice_client == None:
			voice_client = await channel.connect()
		voice_client.stop()

		if loop:
			media = discord.FFmpegPCMAudio(file_name, before_options="-stream_loop -1")
		else:
			media = discord.FFmpegPCMAudio(file_name)
		media_volume = discord.PCMVolumeTransformer(media, self._volume_level)
		self._active_media = media_volume
		voice_client.play(media_volume)

	async def play_tts(self, text, channel: discord.VoiceChannel):
		gTTS(text, lang="nl").save(BUFFER_TTS)
		await self.play_file(BUFFER_TTS, channel)

	async def set_volume(self, volume):
		self._volume_level = volume
		if self._active_media != None:
			self._active_media.volume = self._volume_level


from dotenv import load_dotenv
load_dotenv()
import openai
import os

def chat_with_gpt(prompt):
    openai.organization = os.getenv('OPENAI_ORG_ID')
    openai.api_key = os.getenv('OPENAI_API_KEY')

    messages = [
        {"role": "system", "content": "You are a super helpful AI assistant. You will always find a way to answer the questions "
         "you are asked. If you do not know the answer you will answer truthfully that you do not know"},
        {"role": "user", "content": prompt}
        ]

    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=messages,
        temperature=1
    )

    return response.choices[0].message['content'].strip()

def main():
    while True:
        prompt = input('What is your question? > ')
        response = chat_with_gpt(prompt)
        print(response)
        again = input('Quit or ask again? > ').strip().lower()
        if again.startswith('q'):
            break

if __name__ == "__main__":
    main()


import json
from flask import Flask, render_template

app = Flask(__name__, static_folder='static', static_url_path='')

@app.route("/")
def start_page():
    decisions = [decision_name for decision_name,
                 _ in load_decisions().items()]

    return render_template('start.html', decisions=decisions)

@app.route("/article/<string:id>")
def decision_page(id: str):
    print(id)
    decisions = load_decisions()
    decision = decisions[id]

    # load matching paragraphs:
    paragraphs = decision['paragraphs']
    with open('../comparison_values.json', 'r', encoding='utf-8') as file:
        comparison_data = json.load(file)
    for paragraph in paragraphs:
        hash = __correct_hash(paragraph['hash'])

        if hash in comparison_data:
            matches: dict = comparison_data[hash]
            paragraph['matches'] = []
            paragraph['matches'] = matches
            for match, match_rate in matches.items():
                for decision_name, dec in decisions.items():
                    for par in dec['paragraphs']:
                        if __correct_hash(par['hash']) == match:
                            matches[match] = {
                                'decision': decision_name,
                                'id': decision_name.replace(' ', '-'),
                                'original': par['original'],
                                'confidence': match_rate,
                                'index': par['index']}
                pass
            print('gugus!!!!!!!!!')

    return render_template('decision.html', decision=id, decision_object=decision)

def load_decisions() -> dict:
    with open('static/json_data.json', 'r', encoding='utf-8') as file:
        decision = json.load(file)
    return decision

def __correct_hash(hash: str) -> str:
    """Just in case the binary arrays haven't been saved
    correctly as a string"""

    if hash[:2] == "b'" and hash[-1] == "'":
        hash = hash[2:-1]
    return hash

app.run(debug=True)


# 使用curve_fit
import re
from typing import Tuple

import numpy as np
from scipy.optimize import curve_fit
from numpy import sin, cos, tan, arcsin, arccos, arctan
from numpy import cosh, tanh, sinh
from numpy import sinc
from numpy import exp, exp2, floor, ceil, power, mod
predefined_functions = {
    'linear': {
        'vars': 'a,b',
        'function': 'a*x+b'
        },
    'sqrt': {
        'vars': 'a,b,c',
        'function': 'a*x**2+b*x+c'
        },
    'cubic': {
        'vars': 'a,b,c,d',
        'function': 'a*x**3+b*x**2+c*x**1+d'
        },
    'customized': {
        'vars': '',
        'function': ''
        }
    }

def check_identifiers(argsStr, funcStr) -> Tuple[bool, str]:
    identifiers_in_def = re.split(r'[;,:\./ \\!&\|\*\+\s\(\)\{\}\[\]0~9]', argsStr)
    func_identifiers = re.split(r'[;,:\./ \\!&\|\*\+\s\(\)\{\}\[\]0~9]', funcStr)
    print(identifiers_in_def)
    print(func_identifiers)
    identifiers_in_def = list(filter(lambda s: (not s == '') and (s.isidentifier()), identifiers_in_def))
    func_identifiers = list(filter(lambda s: (not s == '') and (s.isidentifier()), func_identifiers))

    identifiers_in_func_set = set(func_identifiers)
    identifiers_in_def_set = set(identifiers_in_def)

    for identifier in func_identifiers:
        if (not identifier
            in identifiers_in_def_set) and (globals().get(identifier) is None) and (identifier not in ['x', 'y', 'z']):
            print('Identifier \'%s\' is not defined!' % identifier)
            return False, 'Identifier \'%s\' is not defined!' % identifier

    for identifier in identifiers_in_def:
        if not identifier in identifiers_in_func_set:
            print('Unused Identifier \'%s\'' % identifier)
            return False, 'Unused Identifier \'%s\'' % identifier

    print('checking alright!')
    return True, ''

def fit(x, y, argsStr='a,b,c,d', funcstr='a * x ** 3 + b * x ** 2 + c * x + d'):
    # 非线性最小二乘法拟合

    st = """
def func(x, %s):
    return %s
    """ % (argsStr, funcstr)
    exec(st, globals())
    popt, pcov = curve_fit(func, x, y)
    # 获取popt里面是拟合系数
    xvals = np.linspace(np.min(x), np.max(x), 100)
    yvals = func(xvals, *popt)
    print(yvals)
    # 拟合，将数组作为函数的参数进行传入。
    return popt, pcov, xvals, yvals

def loadVariables():
    return {}
    import novalmber
    path = novalmber.getUserDataPath(debug=False)  # 通过网络直接远程获取。
    a = __file__.split('apps')[0]

    import os
    import pickle
    path = os.path.join(path, 'pluginfiles/scientificshell')
    print(path)
    varDic = {}

    dirList = os.listdir(path)
    print(dirList)
    for file in dirList:
        if file.endswith('.pkl'):
            sl = file.split('.')

            try:
                f = open(os.path.join(path, file), 'rb')
                name = sl[0]
                varDic[name] = pickle.load(f)
                f.close()
            except:
                import traceback
                traceback.print_exc()
    print(varDic)
    return varDic

if __name__ == '__main__':
    check_identifiers('a,b,c,d,e', 'a+b+c/(c**8)')
    check_identifiers('a,b,c', 'a+b+c/(c**8)')
    check_identifiers('a,b,c', 'sin(a)+b+c/(c**8)')
    check_identifiers('a,b,c', 'a+b+c1/(c**8)')


from django import forms
from .models import  CapacitacionCabecera, CapacitacionDetalle

from django.forms.models import inlineformset_factory

class CapacitacionCabeceraForm(forms.ModelForm):
      #validaciones para que se envie como mayusculas los datos
    def clean_tema(self):
        data = self.cleaned_data["tema"].upper()
        return data

    def clean_lugar(self):
        data = self.cleaned_data["lugar"].upper()
        return data

    def clean_objetivo(self):
        data = self.cleaned_data["objetivo"].upper()
        return data

    def clean_dirigido(self):
        data = self.cleaned_data["dirigido"].upper()
        return data

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['areaSolicitante'].empty_label = "------N/A--------"

    class Meta:

        model = CapacitacionCabecera
        fields = (
            'fecha',
            'hora_inicio',
            'hora_fin' ,
            'lugar' ,
            'tema',
            'tipoCapacitacion',
            'areaSolicitante' ,
            'dirigido',
            'instructor',
            'objetivo',
            )

        widgets = {
            'fecha': forms.DateInput(format=('%Y-%m-%d'),
                attrs={
                    'type':'date',
                    'class': 'form-control',
                    'autofocus':'autofocus'
                }
            ),
            'hora_inicio': forms.TextInput(
                attrs={
                    'type':'time',
                    'class': 'form-control'
                }
            ),
            'hora_fin' : forms.TextInput(
                attrs={
                    'type':'time',
                    'class': 'form-control'
                }
            ),
            'lugar' : forms.TextInput(
                attrs={
                    'class': 'form-control solo-letra'
                }
            ),
            'tema': forms.TextInput(
                attrs={
                    'class': 'form-control solo-letra'
                }
            ),
            'tipoCapacitacion': forms.Select(
                attrs={
                     'class': 'form-control select'
                }
            ),
            'areaSolicitante' : forms.Select(
                attrs={
                    'class': 'form-control select'
                }
            ),
            'dirigido': forms.TextInput(
                attrs={
                    'class': 'form-control solo-letra'
                }
            ),
            'instructor': forms.Select(
                attrs={
                     'class': 'form-control '
                }
            ),
            'objetivo': forms.TextInput(
                attrs={
                    'class': 'form-control solo-letra'
                }
            ),

        }

class CapacitacionDetalleForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['empleado'].empty_label = "------N/A--------"

    class Meta:

        model = CapacitacionDetalle
        fields = (
            'capacitacionCabecera',
            'empleado',
            'observacion',

            )

        widgets = {
            'empleado': forms.Select(
                attrs={
                    'onkeyup':"javascript:this.value=this.value.toUpperCase();",
                    'class': 'form-control select'
                }
            ),
            'observacion':forms.Textarea(
                attrs={
                    'class': 'form-control solo-letra',
                    "rows":1, "cols":10
                }
            ),
        }

CapacitacionCabDetalleForm = inlineformset_factory(CapacitacionCabecera,CapacitacionDetalle,
    form=CapacitacionDetalleForm, extra=1,can_delete= True)


from typing import Any, Union, Tuple, Optional, Mapping, Dict, cast

import numpy as np

from bag.simulation.core import TestbenchManager
from bag.simulation.cache import SimulationDB, DesignInstance, SimResults, MeasureResult
from bag.simulation.measure import MeasurementManager, MeasInfo
from bag.util.math import Calculator

from bag3_testbenches.measurement.dc.base import DCTB

class DriverPullUpDownMM(MeasurementManager):
    default_gate_bias = dict(pu='full', pd='full')

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        self._tbm_info: Optional[Tuple[DCTB, Mapping[str, Any]]] = None
        self._mos_mapping = {}

        super().__init__(*args, **kwargs)

    def initialize(self, sim_db: SimulationDB, dut: DesignInstance) -> Tuple[bool, MeasInfo]:
        specs = self.specs
        vdd = specs['vdd']
        v_offset_map = specs['v_offset_map']

        gate_bias = self.default_gate_bias.copy()
        if 'gate_bias' in specs:
            gate_bias.update(specs['gate_bias'])

        specs['stack_pu'] = dut.sch_master.params['stack_p']
        specs['stack_pd'] = dut.sch_master.params['stack_n']
        self._dut = dut

        mos_mapping = specs['mos_mapping']['lay' if specs['extract'] else 'sch']

        # Find all transistors that match the passed in transistor names
        # The passed in transistor name can match multiple "transistors" in the netlist
        # in cases like seg > 1 or stack > 1
        # TODO: allow for more complex name matching (e.g., via regex)
        for mos, term in mos_mapping.items():
            voff_list = [v for k, v in v_offset_map.items() if term in k and k.endswith('_d')]
            if len(voff_list) == 0:
                raise ValueError(f"No matching transistor found for {term}")
            self._mos_mapping[mos] = voff_list

        for k, v in gate_bias.items():
            if v == 'full':
                gate_bias[k] = vdd if k == 'pd' else 0
            else:
                gate_bias = self._eval_expr(v)

        sup_values = dict(VDD=vdd, pden=gate_bias['pd'], puenb=gate_bias['pu'], out=vdd / 2)

        tbm_specs = dict(**specs['tbm_specs'])
        tbm_specs['sweep_var'] = 'v_out'
        tbm_specs['sweep_options'] = dict(type='LINEAR')
        for k in ['pwr_domain', 'sim_params', 'pin_values']:
            if k not in tbm_specs:
                tbm_specs[k] = {}
        tbm_specs['dut_pins'] = list(dut.sch_master.pins.keys())
        tbm_specs['load_list'] = []
        tbm_specs['sup_values'] = sup_values

        # Set all internal DC sources to 0 (since these are just used for current measurements)
        tbm_specs['sim_params'].update({k: 0 for k in v_offset_map.values()})

        tbm = cast(DCTB, sim_db.make_tbm(DCTB, tbm_specs))
        self._tbm_info = tbm, {}

        return False, MeasInfo('pd', {})

    def _eval_expr(self, expr) -> float:
        if isinstance(expr, str):
            return Calculator.evaluate(expr, dict(vdd=self.specs['vdd']))
        else:
            return expr

    def get_sim_info(self, sim_db: SimulationDB, dut: DesignInstance, cur_info: MeasInfo
                     ) -> Tuple[Union[Tuple[TestbenchManager, Mapping[str, Any]],
                                      MeasurementManager], bool]:
        tbm = self._tbm_info[0]
        drain_bias = self.specs['drain_bias'][cur_info.state]
        drain_bias = list(map(self._eval_expr, drain_bias))

        swp_options = dict(num=len(drain_bias))
        if len(drain_bias) == 1:
            swp_options['start'] = swp_options['stop'] = drain_bias[0]
        elif len(drain_bias) == 2:
            swp_options['start'], swp_options['stop'] = drain_bias
        else:
            raise ValueError("Either 1 or 2 values must be given for drain_bias")
        tbm.specs['sweep_options'].update(**swp_options)
        return self._tbm_info, True

    def process_output(self, cur_info: MeasInfo, sim_results: Union[SimResults, MeasureResult]
                       ) -> Tuple[bool, MeasInfo]:
        specs = self.specs
        state = cur_info.state
        data = cast(SimResults, sim_results).data
        mos_mapping = self._mos_mapping[state]
        stack = specs[f'stack_{state}']

        # Compute the total current by the following:
        # Find the drain current of all the unit transistors (the number of these transistors is seg * stack)
        # Add all these currents together, then divide by the number of stacks (to approximate
        # the sum of currents for each "finger"/parallel path of stacked transistors).
        # This calculation assumes that the junction leakage is negligible compared to the measured current.
        total_current = np.zeros(data.data_shape)
        for sig in data.signals:
            if any(filter(lambda x: x in sig, mos_mapping)):
                total_current += data._cur_ana[sig]
        total_current /= stack

        # Calculate resistance
        swp_options = self._tbm_info[0].specs['sweep_options']
        if swp_options['num'] == 1:
            res = swp_options['start'] / total_current[:, 0]
        else:
            res = (swp_options['stop'] - swp_options['start']) / (total_current[:, 1] - total_current[:, 0])
        res = np.abs(res)

        result = cur_info.prev_results.copy()
        if state == 'pd':
            result['sim_envs'] = data.sim_envs
        result[f'res_{state}'] = res

        next_state = 'done' if state == 'pu' else 'pu'
        return next_state == 'done', MeasInfo(next_state, result)


import codecs
import html
import logging
import os
import re

import nltk.tokenize as tok
import scrapy

import infstruments.classify as cla

class BroadbandSpider(scrapy.Spider):
    """ Experimental generic spider to extract sentences of natural language.

    USAGE:
    $ scrapy crawl sentences_broadband -a url=https://en.wikipedia.org/wiki/Radio -o radio.json --logfile crawl.log
    """
    name = "sentences_broadband"
    TAKE = "_TAKE_"
    DROP = "_DROP_"
    OPEN = "_OPEN_"
    SPIDER_SPECIFIC_DEFAULTS = {
                'url': "https://en.wikipedia.org/wiki/Band-pass_filter",
                'trainpath': "training",
                'code': "code_aa.txt",
                'nlang': "nlang-de_aa.txt",
                'unclear': "json_aa.txt"
            }
    def __init__(self, *args, **kwargs):
        def _preproc(s):
            """ Preprocessing string content for character frequency analysis.

            Map string content so that the character frequency in the resulting
            string makes it easy to distinguish program code from natural
            language.
            """
            s = re.sub('[#@^]', '@', s) # Special chars in natural language
            s = re.sub(r'\d', '#', s)   # Digits
            s = re.sub(r'\w', 'L', s)   # Characters (digits already replaced)
            ### program language related specials
            s = re.sub(r'===|!==|\(\);', 'ccc', s)  # 3 char operators
            ### Typical elements in code: () && || ... =" !=
            s = re.sub(r'\(\)|&&|\|\||\+\+|--|[-+!=<>]=|!!|=[\'"]', 'cc', s)
            s = re.sub(r'[<>|@/\\{}\[\]()]', ']', s)  # braces
            return s
        logging.info("sentences_broadband: __init__")
        logging.info("args:%s kwargs:%s" % (args, kwargs))
        self.spsp_settings = BroadbandSpider.SPIDER_SPECIFIC_DEFAULTS
        self.spsp_settings.update(kwargs)
        BroadbandSpider.start_urls = [self.spsp_settings['url']]
        # load training examples for programming code
        fname = os.path.join(self.spsp_settings['trainpath'],
                             self.spsp_settings['code'])
        f = codecs.open(fname, "r", encoding="utf-8")
        code = [ re.sub(r"[\n\r]", "", c) for c in f.readlines() ]
        f.close()
        # load training examples for natural language
        fname = os.path.join(self.spsp_settings['trainpath'],
                             self.spsp_settings['nlang'])
        f = codecs.open(fname, "r", encoding="utf-8")
        nlang = [ re.sub(r"[\n\r]", "", c) for c in f.readlines() ]
        f.close()
        # load training examples for cases to keep open/undecided
        fname = os.path.join(self.spsp_settings['trainpath'],
                             self.spsp_settings['unclear'])
        f = codecs.open(fname, "r", encoding="utf-8")
        unclear = [ re.sub(r"[\n\r]", "", c) for c in f.readlines() ]
        f.close()
        training = code + nlang + unclear
        target = ( [BroadbandSpider.DROP]*len(code)
                 + [BroadbandSpider.TAKE]*len(nlang)
                 + [BroadbandSpider.OPEN]*len(unclear))
        self.clfier = cla.CharStatsClassifier(
                    training, target, preproc=_preproc, debuglog=True)
        super().__init__(*args, **kwargs)

    def parse(self, response):
        logging.info("parse: %s" % response.url)
        for h1 in response.css('h1::text').getall():
            yield { 'h1': h1 }
        all_texts = []
        for text in response.css('body *::text').getall():
            text = html.unescape(text.strip())
            text = re.sub("\s+", " ", text)
            if text == "": continue
            if len(text) < 10:
                logging.debug("P: _SMALL_ %s" % text)
                all_texts.append(text)
            else:
                cla = self.clfier.classify_s(text)
                if cla in [BroadbandSpider.DROP, BroadbandSpider.OPEN]:
                    all_texts.append("-X-")
                else:
                    all_texts.append(text)
        sentences = tok.sent_tokenize(" ".join(all_texts))
        for sent in sentences:
            yield {'sentence': sent}


# *****************************************
# A simple neural net using
# Keras and TensorFlow for
# training a neural netowrk to learn
# the Feigenbaum Map
#
# June 1, 2017
# Miles R. Porter, Painted Harmony Group
# This code is free to use and distribute
# *****************************************
import tensorflow as tf
import numpy as np
np.random.seed(42)

from tensorflow import set_random_seed
set_random_seed(2)

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

import pandas as pd
from matplotlib import pyplot as plt
from datetime import datetime

# load dataset
df = pd.read_csv("logistics_10k.csv", header=1, names=['n','n+1'])

# split into input (X) and output (Y) variables

X = df['n']
Y = df['n+1']

# Set up the network

def neural_network_model():

    the_model = Sequential()
    the_model.add(Dense(5, activation='relu', input_dim=1))
    the_model.add(Dense(500, activation='relu', input_dim=1))
    the_model.add(Dense(1200, activation='relu', input_dim=1))
    the_model.add(Dense(500, activation='relu', input_dim=1))
    the_model.add(Dense(5, activation='relu', input_dim=1))
    the_model.add(Dense(1))
    the_model.summary()
    the_model.compile(optimizer='rmsprop',
                  loss='mean_squared_error')
    return the_model

model = neural_network_model()
st = datetime.now()
results = model.fit(X, Y, epochs=25, batch_size=16, verbose=1)
et = datetime.now()
print("Training is complete.\n")
print("\n\nTraining time: {}\n\n".format(et-st))

plt.subplot(2, 1, 1)
plt.title("Error")
plt.plot(results.history['loss'])

# Make predictions

inputs = np.random.rand(1, 100)[0]

prediction = model.predict(inputs, batch_size=1, verbose=0)

# Plot the predicted and expected results
expected = inputs * 4.0 * (1.0 - inputs)

plt.subplot(2, 1, 2)
plt.title("Results")
for i in range(0, len(inputs)):
    p = prediction[i]
    e = expected[i]
    plt.scatter(i, p, s=1, color="red")
    plt.scatter(i, e, s=1, color="blue")

plt.show()


import time
import os
from datetime import datetime
opentime = datetime.now().time()
print('Initalizing setup...')
time.sleep(2)
s1 = open('config.config', "w")
s1.write('localhost = 1\nscripts = scripts\nstart.config\nonlocal=0\nlocalToken="33434An3ikNm3am"\na4a4a4a4a4a4a4a4a4a4a4a4a\ns--------------------------\nbreak()')
s1.close()
print('Config file successfully written.')
time.sleep(1)
print('creating EZlog file...')
s2 = open('EZlog', "w")
s2.write('# log setup\nlogSetup = true\nnil\n')
s2.close()
time.sleep(2)
s3 = open('EZsetvar', 'w')
s3.write('var=1\nvar2\nvar3\nYou shouldnt be reading this file xd')
s3.close()
print('Setting variables..')
time.sleep(2)
print('Creating Directories..')
os.mkdir('EZpack1')
os.mkdir('EZpack2')
os.mkdir('EZpack3')
os.mkdir('EZpack4')
os.mkdir('EZpack5') #Creates Different Packages.
os.mkdir('logKeeper') # The directory that keeps log files
time.sleep(5)
print('Writing config...')
con = open('config.config', "w")
con.write('config.rewrite = true\nEZpack1\nEZpack2\nEZpack3\nEZpack4\nEZpack5\nEZlog\n')
con.close()
time.sleep(0.90)
print('config file rewritten.')
time.sleep(0.50)
print('Initalizing Final Setup..')
coc = open('config.config', "a")
coc.write('config.files.start = 1')
coc.close()

time.sleep(3)
print('Setup Complete! Say ezsetup log for what files were made/deleted')
setchance = input('>_')
if setchance.strip() == 'ezsetup log':
    print(opentime)
    print('created config.config')
    print(opentime)
    print('created EZLog File.')
    print(opentime)
    print('Created EZsetvar')
    print(opentime)
    print('Set 6 Packages, EZpack1 EZpack2 EZpack3 EZpack4 EZpack5 LogKeeper')

elif setchance.strip() == '':
    print('exiting...')
time.sleep(0.60)

print('Exit slip')
exit = input('>')


import pandas as pd
import numpy as np
from matplotlib.colors import ListedColormap
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_style("darkgrid", {'font.family':'serif'})
plt.style.use("seaborn-darkgrid")
color = ['#fccbcb','#F99797', '#F76C6C', '#ABD0E6', '#23305E']
cmap = ListedColormap(color)
color2 = ['#90113f', '#afb5c0', '#e39292', '#61749f', '#b4d5ea', '#c26f6d']

#====================================== Data Airbnb Singapore ============================================
df = pd.read_csv('data-airbnbsg.csv')

host_features = ['total_price','host_is_superhost','scores_rating', 'host_response_rate','scores_communication', 'scores_cleanliness','scores_checkin' , 'scores_communication','scores_location', 'number_of_reviews', 'reviews_per_month']
listings_features = ['total_price', 'number_of_reviews', 'bathrooms', 'bedrooms', 'beds', 'accommodates', 'area','region','property_type', 'room_type', 'scores_location', 'scores_cleanliness', 'scores_rating']

# Property type data  --------------------------------------------------------------------------------
a = df['property_type'].value_counts().reset_index()
fig = plt.figure(figsize=(10,10))
ax =sns.barplot(y='index', x='property_type', data=a, palette=color)
plt.xlabel('Property Type')
plt.ylabel('Count')
plt.title('Airbnb Listings by Property Type', fontsize=15, weight='bold')
for p in ax.patches:
    ax.text(p.get_width()+50, p.get_height()/2 + p.get_y(), int(p.get_width()) ,ha="center")
plt.show()
plt.close()

a['persen'] = a['property_type'] / len(df) *100
# print(a)
a = a[a['persen'] > 4]['index']
a = a.tolist()
df = df[df['property_type'].isin(a)]

#================================================= Exploring the Data =================================================

# Figure : Listings by Region
a = df['region'].value_counts().reset_index().sort_values(by='index').reset_index(drop=True)
f, ax = plt.subplots(ncols=2, nrows=1, figsize=(12, 8))
sns.barplot(y='index', x='region', data=a, palette=color, ax=ax[0])
ax[0].set(xlabel='Count of Property Type', ylabel='')
ax[0].set_title('Airbnb Listings by Region', fontsize=15, weight='bold')
numListing =[]
for p in ax[0].patches:
    ax[0].text(p.get_width()+170, p.get_height()/2 + p.get_y(), int(p.get_width()), ha="center")
    numListing.append(int(p.get_width()))
# Figure : Bookings by Region
a = df.groupby(by=['region'], as_index=False).number_of_reviews.sum()
sns.barplot(y='region', x='number_of_reviews', data=a, palette=color, ax=ax[1])
ax[1].set(xlabel='Numbers of Booking', ylabel='')
ax[1].set_title('Airbnb Bookings in Singapore', fontsize=15, weight='bold')
for p in ax[1].patches:
    ax[1].text(p.get_width()+2600, p.get_height()/2 + p.get_y(), int(p.get_width()) ,ha="center")
plt.tight_layout()
plt.show()
a['numListing'] = numListing
a['supply-demand'] = numListing/a['number_of_reviews'] *100
print(a)

# Figure : Number of Listings by sub-district in Singapore ----------------------------------------
f, ax = plt.subplots(ncols=2, nrows=1, figsize=(10, 6))
a = df.groupby(['area', 'property_type']).size().unstack()
# print(a)
a.plot(kind='barh', stacked=True, ax=ax[0], color=color)
ax[0].set_title('Number of Listings by Subdistrict', fontsize=12)
ax[0].set(xlabel='Count of Property', ylabel='')
ax[0].yaxis.grid(True)
ax[0].legend()
# Figure : Number of Property_type ----------------------------------------------------------------------
a = df['property_type'].value_counts().reset_index().sort_values(by='index').reset_index(drop=True)
sns.barplot(
    x='property_type', y='index', data=a,
    label="Property type", palette=color, ax= ax[1])
ax[1].set(xlabel='Count of Property Type', ylabel='')
ax[1].set_title('Number of Listings by Property', fontsize=12)
ax[1].yaxis.grid(True)
plt.suptitle('Airbnb in Central Singapore Area', fontsize=18, weight='bold')
plt.subplots_adjust(top=.88, wspace=0.3)
plt.show()
plt.close()

# Figure : Property_type vs price ---------------------------------------------------------------------
f, ax = plt.subplots(figsize=(10, 8), ncols=2, nrows=1)
ax[0].set_xscale("log")

sns.boxplot(x="total_price", y="property_type", data=df,
            whis="range", palette=color, ax=ax[0])

ax[0].set_title("Price by Property Type", fontsize=15, weight='bold')
ax[0].xaxis.grid(True)
ax[0].set(xlabel="Price",ylabel="")
# Figure : Room_type vs price -----------------------------------------------------------------------------
ax[1].set_xscale("log")

sns.boxplot(x="total_price", y="room_type", data=df,
            whis="range", palette=color, ax=ax[1])

ax[1].set_title("Price by Room Type", fontsize=15, weight='bold')
ax[1].xaxis.grid(True)
ax[1].set(xlabel="Price",ylabel="")
plt.tight_layout()
plt.show()
plt.close()

# Figure : Price per guest ------------------------------------------------------------------------------
f, ax = plt.subplots(figsize=(14, 10))

sns.heatmap(df.groupby([
        'area', 'accommodates']).total_price.median().unstack(),
            annot=True,
            fmt=".0f",
           cmap=cmap)
ax.set(xlabel="Maximum Number of Guest",ylabel="")
ax.set_title('Price (USD) per Guest', fontsize=15, weight='bold')
plt.show()
plt.close()
# Figure : Area vs price -----------------------------------------------------------------------------
df.plot(kind="scatter", x="longitude", y="latitude", alpha=0.4,c="total_price", cmap=plt.get_cmap("gnuplot2_r"), colorbar=True, figsize=(10,8))
plt.title('Airbnb Singapore Price', fontsize=12)
# plt.show()
plt.close()
# Figure : Area vs price -----------------------------------------------------------------------------
fig, ax = plt.subplots(figsize = (10,8))
sns.boxplot(x="area", y="total_price", data=df,
            whis="range", palette=color)
ax.set_yscale("log")
ax.set_title("Price by Area", fontsize=15, weight='bold')
ax.xaxis.grid(True)
ax.set(xlabel="",ylabel="Price")
ax.set_xticklabels(ax.get_xticklabels(), rotation=90)
plt.tight_layout()
plt.subplots_adjust(bottom= .28)
plt.show()
plt.close()

# Figure : Number of Super Host Listings  ----------------------------------------------------------------
f, ax = plt.subplots(figsize=(10, 8))
a = df['area'].value_counts().reset_index()
sns.barplot(
    x='area', y='index', data=a,
    label="Total Listings", color=color[0])

a = df['area'][df['host_is_superhost'] == 1].value_counts().reset_index()

sns.barplot(x="area", y="index", data=a,
            label="Superhost Listings", color=color[-1])

ax.legend(ncol=2, loc="best", frameon=True)
ax.set(ylabel="", xlabel="Airbnb Listings")
sns.despine(left=True, bottom=True)
ax.set_title("Number of Superhost Listings in Central Singapore", fontsize=15, weight='bold')
for p in ax.patches:
    ax.text(p.get_width()+23, p.get_height()/2 + p.get_y(), int(p.get_width()) ,ha="center")
plt.show()
plt.close()

# Correlation Price - Host Features ----------------------------------------------------------------------------

f, ax = plt.subplots(figsize=(12, 10))
corr = df[host_features].corr(method= 'spearman')
sns.heatmap(corr, cmap= cmap, annot=True, fmt=".2f")
ax.set_xticklabels(ax.get_xticklabels())
plt.tight_layout()
plt.subplots_adjust(bottom= .25)
plt.show()
plt.close()

# Correlation Price - Listings Features ------------------------------------------------------------------------
f, ax = plt.subplots(figsize=(12, 10))
corr = df[listings_features].corr(method= 'spearman')
sns.heatmap(corr, cmap= cmap, annot=True, fmt=".2f")
ax.set_xticklabels(ax.get_xticklabels())
plt.tight_layout()
plt.subplots_adjust(bottom= .21)
plt.show()
plt.close()

# save to csv
df.to_csv('data-airbnbsg_cleaned.csv', header=True, index=False)