import os
import sys
import json
import time
import numpy as np
from datetime import datetime
import argparse
import tensorflow as tf
from warnings import warn

from typing import Dict, Tuple, List, Any, Optional, Sequence, Union

import tf_agents
from tf_agents.agents.ppo.ppo_agent import PPOAgent
from tf_agents.environments.tf_py_environment import TFPyEnvironment
from tf_agents.agents.reinforce.reinforce_agent import ReinforceAgent
from tf_agents.drivers.dynamic_episode_driver import DynamicEpisodeDriver
from tf_agents.replay_buffers.tf_uniform_replay_buffer import TFUniformReplayBuffer
from tf_agents.metrics import tf_metrics, tf_metric

from experiment.rl import custom_tf_metrics
from snc.environments import scenarios
from snc.agents.hedgehog.workload.workload import compute_load_workload_matrix
from snc.agents.hedgehog.minimal_draining_time import compute_minimal_draining_time_from_env_cvxpy
from experiment.experiment_utils import get_args_string
from snc.environments.rl_environment_wrapper import rl_env_from_snc_env
from snc.agents.rl.agents import create_reinforce_agent, create_ppo_agent

from snc.simulation.store_data.json_logging import set_up_json_logging

set_up_json_logging()

def get_environment(env_name: str,
                    agent_name: str,
                    episode_len_to_min_drain_time_ratio: float,
                    terminal_discount_factor: float = 0.7,
                    action_repetitions: int = 1,
                    parallel_environments: int = 8,
                    env_overload_params: Optional[Dict] = None,
                    agent_params: Optional[Dict] = None,
                    seed: Optional[int] = None) \
        -> Tuple[TFPyEnvironment, float, float, int, Tuple[int, ...]]:
    """
    Builds and initialises a TensorFlow environment implementation of the Single Server Queue.

    :param env_name: The name of the scenario to load. Must be in the list of implemented scenarios.
    :param agent_name: The name of the RL agent the environment is to be set up for.
    :param episode_len_to_min_drain_time_ratio: Maximum number of time steps per episode as a
        proportion of the minimal draining time.
    :param terminal_discount_factor: The discount applied to the final time step from which a
        per-step discount factor is calculated.
    :param action_repetitions: Number of time steps each selected action is repeated for.
    :param parallel_environments: Number of environments to run in parallel.
    :param env_overload_params: Dictionary of parameters to override the scenario defaults.
    :param agent_params: Optional dictionary of agent parameters the environment can be adapted for.
    :param seed: Random seed used to initialise the environment.
    :return: The environment wrapped and ready for TensorFlow Agents.
    """
    # Handle some default argument clean up.
    if env_overload_params is None:
        env_overload_params = {}

    env = scenarios.load_scenario(env_name, seed, env_overload_params).env

    if np.all(env.state_initialiser.initial_state == 0):
        env.max_episode_length = 450
    else:
        if env.state_initialiser.initial_state.ndim == 1:
            initial_state = env.state_initialiser.initial_state.reshape((-1, 1))
        else:
            initial_state = env.state_initialiser.initial_state
        minimal_draining_time = compute_minimal_draining_time_from_env_cvxpy(initial_state, env)
        env.max_episode_length = int(episode_len_to_min_drain_time_ratio * minimal_draining_time)
    discount_factor = np.exp(np.log(terminal_discount_factor) / env.max_episode_length)
    load = np.max(compute_load_workload_matrix(env).load)
    max_ep_len = env.max_episode_length

    # Allow toggling of observation normalisation in the environment.
    # The typical behaviour for PPO is that PPO normalises observations internally as necessary so
    # normalisation in the environment is not necessary.
    if agent_name == 'ppo' and agent_params.get('normalize_observations', True):
        normalise_obs_in_env = False
    else:
        normalise_obs_in_env = True

    # Wrap and parallelise environment for tf agents.
    tf_env, action_dims = rl_env_from_snc_env(env,
                                              discount_factor,
                                              action_repetitions,
                                              parallel_environments,
                                              normalise_observations=normalise_obs_in_env)
    return tf_env, discount_factor, load, max_ep_len, action_dims

def get_reinforce_agent(
        env: TFPyEnvironment,
        discount_factor: float,
        debug: bool = False,
        agent_params: Optional[Dict[str, Any]] = None
    ) -> ReinforceAgent:
    """
    Builds and initialises a REINFORCE learning agent for the environment.

    :param env: The TensorFlow environment used to set up the agent with correct action spaces etc.
    :param discount_factor: The discount applied to future rewards.
    :param debug: Flag which determines whether to include extra TensorBoard logs for debugging.
    :param agent_params: A dictionary of possible overrides for the default TF-Agents agent set up.
    :return: An initialised REINFORCE agent.
    """
    # Set up a training step counter.
    global_step = tf.compat.v1.train.get_or_create_global_step()
    agent = create_reinforce_agent(
        env,
        gamma=discount_factor,
        debug=debug,
        training_step_counter=global_step,
        agent_params=agent_params
    )
    agent.initialize()
    agent.train = tf.function(agent.train)
    return agent

def get_ppo_agent(
        env: TFPyEnvironment,
        num_epochs: int,
        discount_factor: float,
        debug: bool = False,
        agent_params: Optional[Dict[str, Any]] = None
    ) -> Union[ReinforceAgent, PPOAgent]:
    """
    Builds and initialises a reinforcement learning agent for the environment.

    :param env: The TensorFlow environment used to set up the agent with correct action spaces etc.
    :param num_epochs: The (maximal) number of internal PPO epochs to run.
    :param discount_factor: The discount applied to future rewards.
    :param debug: Flag which determines whether to include extra TensorBoard logs for debugging.
    :param agent_params: A dictionary of possible overrides for the default TF-Agents agent set up.
    :return: An initialised RL agent.
    """
    # Set up a training step counter.
    global_step = tf.compat.v1.train.get_or_create_global_step()

    agent = create_ppo_agent(
        env,
        num_epochs,
        gamma=discount_factor,
        debug=debug,
        training_step_counter=global_step,
        agent_params=agent_params
    )
    agent.initialize()
    agent.train = tf.function(agent.train)
    return agent

def get_replay_buffer(
        env: TFPyEnvironment, agent: Union[ReinforceAgent, PPOAgent], max_length: int = 100000
    ) -> TFUniformReplayBuffer:
    """
    Sets up a replay buffer object for use in training the agent.

    :param env: TensorFlow environment which provides specifications for use in setting up a replay
        buffer.
    :param agent: The agent which provides specifications for use in setting up a replay buffer.
    :param max_length: The maximum length/capacity of the replay buffer.
    :return: A replay buffer (TFUniformReplayBuffer)
    """
    replay_buffer = TFUniformReplayBuffer(
        data_spec=agent.collect_data_spec,
        batch_size=env.batch_size,
        max_length=max_length
    )
    return replay_buffer

def set_up_summary_writers(log_dir: str,
                           action_space_dims: Tuple[int, ...],
                           flush_milliseconds: int = 500) \
        -> Tuple[tf.summary.SummaryWriter,
                 Sequence[tf.summary.SummaryWriter],
                 tf.summary.SummaryWriter]:
    """
    Sets up log directories and summary file writers to write log files which can be read by
    TensorBoard for in-play logging.

    :param log_dir: The base directory in which to store logs. If this directory does not exist it
        will be made along side 'train' and 'eval' subdirectories.
    :param action_space_dims: Action space dimensions, each entry is number of actions for a
        resource set.
    :param flush_milliseconds: The largest interval between flushes of the summary writers (in
        milliseconds)
    :return: A summary writer for training logging, a series of summary writers for action
        probability logging and another summary writer for logging at test/evaluation time.
    """
    # Build the two summary writers using TensorFlow's helper function.
    train_summary_writer = tf.summary.create_file_writer(
        os.path.join(log_dir, "train"), flush_millis=flush_milliseconds, max_queue=3)
    eval_summary_writer = tf.summary.create_file_writer(
        os.path.join(log_dir, "eval"), flush_millis=flush_milliseconds, max_queue=3)
    # Build a series of summary writers, one per action, to allow plotting on shared axes.

    action_probability_summary_writers = [
        tf.summary.create_file_writer(
            os.path.join(log_dir, f"train/action_{i}-{j}"),
            flush_millis=flush_milliseconds,
            max_queue=3)
        for i in range(len(action_space_dims))
        for j in range(action_space_dims[i])
    ]

    return train_summary_writer, action_probability_summary_writers, eval_summary_writer

def get_metrics(agent: Union[ReinforceAgent, PPOAgent],
                batch_size: int = 1,
                for_single_server_queue: bool = False,
                log_file_path: Optional[str] = None) -> \
        Tuple[List[tf_metric.TFStepMetric],
              List[tf_metric.TFStepMetric],
              List[tf_metric.TFStepMetric],
              List[tf_metric.TFStepMetric]]:
    """
    Sets up all of the metrics to track and log during training and evaluation.

    :param agent: The agent being trained and evaluated.
    :param batch_size: The batch size of the environment (i.e. how many environments are running in
        parallel).
    :param for_single_server_queue: Flag denoting whether the single server queue metrics can be
        utilised.
    :param log_file_path: Path to a file in which to save evaluation episode logs. If None defaults
        to sys.stdout.
    :return: Four lists of metrics, one for those to be tracked during training, one for action
        probability tracking and two for those to be tracked during periodic policy evaluation (the
        first at an episodic resolution and the second at a per time step level).
    """

    # We have a conditional tracker which requires the set up of conditions which evaluate which
    # time steps to include and which to discard when calculating the metric. The metric we wish to
    # calculate is the proportion of the number of time steps in which the buffer is non-empty that
    # the policy is not idling. This requires us to set up two conditions as below.
    # The first flags time steps where the buffer is not empty.
    def activity_tracker_filter_condition(trajectory):
        return tf.reduce_any(trajectory.observation > 0, axis=-1)

    # The second condition tests that the policy is not idling (True means the policy is acting to
    # drain the buffer).
    def activity_tracker_activity_condition(trajectory):
        return trajectory.action[..., 1] == 1

    # The metrics tracked during training are as follows:
    #   - Number of Episodes and Overall Number of Time Steps to allow for more informative
    #     TensorBoard plots.
    #   - The average return (over episodes) in the logging period.
    #   - [For SSQ] Our custom metric which tracks how the policy acts when the buffer is not empty.
    metrics = [
        tf_metrics.NumberOfEpisodes(),
        tf_metrics.EnvironmentSteps(),
        tf_metrics.AverageReturnMetric(batch_size=batch_size)
    ]
    # Track the policy evolution through training by logging an (average) probability of each
    # action. This ignores the state but in many cases where the buffers are not empty (the usual
    # case for SNC) this logging is more valid. This is particularly useful where a steadystate is
    # reached.
    # We treat the case where action spec is a tuple (so that actions are a tuple of distributions)
    # separately. This occurs when there are multiple resource sets and hence there is a
    # distribution per resource set. See RLControlledRandomWalk for explicit treatment of resource
    # sets.
    if isinstance(agent.action_spec, tuple):
        policy_metrics = [custom_tf_metrics.ActionProbabilityMetric(agent.collect_policy, (i, j),
                                                                    batch_size=batch_size)
                          for i in range(len(agent.action_spec))
                          for j in range(agent.action_spec[i].shape[-1])]
    else:
        policy_metrics = [
            custom_tf_metrics.ActionProbabilityMetric(agent.collect_policy, (i,),
                                                      batch_size=batch_size)
            for i in range(agent.action_spec.shape[-1])
        ]
    if for_single_server_queue:
        metrics += [custom_tf_metrics.EpisodicConditionalActivityTracker1D(
            filter_condition=activity_tracker_filter_condition,
            activity_condition=activity_tracker_activity_condition,
            name="active_when_non_empty", dtype=tf.float32,
            batch_size=batch_size)]
    # The metrics tracked for policy evaluation at episode-level resolution are as follows:
    #   - The average return (over episodes) over the evaluation episodes.
    #   - [For SSQ] Our custom metric which tracks how the policy acts when the buffer is not empty.
    eval_metrics = [tf_metrics.AverageReturnMetric()]
    if for_single_server_queue:
        eval_metrics += [custom_tf_metrics.EpisodicConditionalActivityTracker1D(
            filter_condition=activity_tracker_filter_condition,
            activity_condition=activity_tracker_activity_condition,
            name="active_when_non_empty", dtype=tf.float32, batch_size=batch_size)]
    # At every time step of each policy evaluation episode we log the state and the action. This is
    # performed by a metric which writes the related values to a log file or standard out.
    per_step_eval_metrics = [
        custom_tf_metrics.EvalPerStepLogger(output_stream=f'file://{log_file_path}')
    ]
    return metrics, policy_metrics, eval_metrics, per_step_eval_metrics

def get_collection_driver(
        env: TFPyEnvironment, agent: Union[ReinforceAgent, PPOAgent], observers: List[Any],
        policy_observers: Optional[List[tf_metric.TFStepMetric]], num_episodes: int
    ) -> DynamicEpisodeDriver:
    """
    Sets up a driver which will run data collection and in-training metric tracking.
    The driver is defined in tf_agents and handles agent play and monitoring as well as data
    storage for a fixed number of episodes at a time. This driver will be run to collect data once
    per training iteration.

    :param env: The TensorFlow environment object which will be run.
    :param agent: The agent to play in the environment.
    :param observers: A list of operations (including metrics to track) which will be executed in
        play to collect data and perform logging.
    :param policy_observers: A list of metrics to track which are executed in play throughout
        training.
    :param num_episodes: The number of episodes to play out in each driver run.
    :return: A driver to use for data collection (and in-play performance tracking)
    """
    collection_driver = DynamicEpisodeDriver(
        env,
        agent.collect_policy,
        observers=observers + policy_observers,
        num_episodes=num_episodes
    )
    # Wrap the run function for faster execution.
    collection_driver.run = tf.function(collection_driver.run)
    return collection_driver

def evaluate_policy(metrics: List[Any],
                    environment: TFPyEnvironment,
                    policy: tf_agents.policies.tf_policy.Base,
                    per_step_metrics: Optional[List[tf.Module]] = None,
                    num_episodes: int = 1,
                    train_step: Optional[Any] = None,
                    summary_writer: Optional[tf.summary.SummaryWriter] = None,
                    summary_prefix: str = "Eval",
                    logging: bool = False,
                    tf_log_stream_path: Optional[str] = None) -> None:
    """
    Track performance (via metrics) using policy in the environment provided.
    Prints a dictionary of results {metric_name: metric_value}.

    *NOTE*: Because placeholders are not compatible with Eager mode this is not compatible with
    python policies.

    This function is adapted from tf_agents.eval.metric_utils.eager_compute to allow for per time
    step logging.

    :param metrics: List of metrics to compute.
    :param environment: tf_environment instance.
    :param policy: tf_policy instance used to step the environment.
    :param per_step_metrics: List of metrics to be passed as observers to run every time step during
        evaluation.
    :param num_episodes: Number of episodes to compute the metrics over.
    :param train_step: An optional step to write summaries against.
    :param summary_writer: An optional writer for generating metric summaries.
    :param summary_prefix: An optional prefix scope for metric summaries.
    :param logging: Option to enable logging to the console of standard metrics.
    :param tf_log_stream_path: Path to a file which tf.print calls are set to write to. If none
        tf.print statements print to sys.stdout.
    """
    # Reset the state of all metrics (e.g. running totals for averages).
    for metric in metrics + per_step_metrics:
        metric.reset()

    # Attain the initial state of the environment and policy.
    time_step = environment.reset()
    policy_state = policy.get_initial_state(environment.batch_size)

    # Set up a driver to run the evaluation episodes while logging the desired metrics.
    driver = DynamicEpisodeDriver(
        environment,
        policy,
        observers=metrics,
        transition_observers=per_step_metrics,
        num_episodes=num_episodes)

    # Run the driver which adds experience to the replay buffer.
    driver.run(time_step, policy_state)

    # If we have the required prerequisites then perform the TensorBoard logging as well as logging
    # results to the console.
    if train_step and summary_writer:
        # Utilise a (possibly) different summary writer to put the evaluation metrics to
        # TensorBoard.
        with summary_writer.as_default():
            for m in metrics:
                # Attain the full name of the metric to record.
                tag = "/".join([summary_prefix, m.name])
                # Simply calculating and forming the scalar summary in the current context with a
                # default summary writer does the logging to TensorBoard for us.
                tf.summary.scalar(name=tag, data=m.result(), step=train_step)
    # If requested to then log metrics to the console.
    if logging and train_step:
        for m in metrics:
            tf.print(f"Evaluation at step {train_step.numpy()}: {m.name}\t{m.result()}",
                     output_stream=f'file://{tf_log_stream_path}' if tf_log_stream_path else
                     sys.stdout)

def train_agent(
        env: TFPyEnvironment,
        agent: Union[ReinforceAgent, PPOAgent],
        data_collection_driver: DynamicEpisodeDriver,
        replay_buffer: TFUniformReplayBuffer,
        num_iters: int,
        global_step=None,
        metrics: Optional[Sequence[tf_metric.TFStepMetric]] = None,
        policy_metrics: Optional[Sequence[tf_metric.TFStepMetric]] = None,
        policy_summary_writers: Optional[Sequence[tf.summary.SummaryWriter]] = None,
        eval_env: Optional[TFPyEnvironment] = None,
        eval_summary_writer: Optional[tf.summary.SummaryWriter] = None,
        num_eval_episodes: int = 1,
        eval_metrics: Optional[List[tf_metric.TFStepMetric]] = None,
        per_step_eval_metrics: Optional[List[Any]] = None,
        eval_freq: int = 10,
        log_freq: int = 5,
        save_freq: int = 5,
        model_save_path: Optional[str] = None,
        tf_log_stream_path: Optional[str] = None) -> None:
    """
    Function for putting the pieces together to train and evaluate an agent.

    :param env: The environment for which the agent will be trained.
    :param agent: The agent to train.
    :param data_collection_driver: The driver used for data collection and metric tracking.
    :param replay_buffer: Replay buffer in which to store experience.
    :param num_iters: The number of training iterations to perform.
    :param global_step: A counter of the number of training iterations.
    :param metrics: A list of the metrics to track during training.
    :param policy_metrics: A list of metrics related to the policy distribution to track during
        training.
    :param policy_summary_writers: A list of summary writers to facilitate overlaying plots of
        policy metrics in TensorBoard.
    :param eval_env: The environment in which to play out evaluations of the policy.
    :param eval_summary_writer: The summary writer used for evaluation metrics.
    :param num_eval_episodes: The number of evaluation episodes to run at each evaluation point.
    :param eval_metrics: The metrics to track when evaluating the policy (with episodic resolution).
    :param per_step_eval_metrics: The metrics to track when evaluating the policy (with time step
        resolution).
    :param eval_freq: The number of training iterations between runs of policy evaluation logging.
    :param log_freq: The frequency with which to log values to TensorBoard.
    :param save_freq: The number of training iterations between model saves.
    :param model_save_path: Directory in which to save model checkpoints (weights etc). If None
        model will not be saved.
    :param tf_log_stream_path:
    """
    # Get the initial states of the agent and environment before training.
    time_step = env.reset()
    policy_state = agent.collect_policy.get_initial_state(env.batch_size)

    # Set up the model saving infrastructure if a path to save to is provided.
    save_model = bool(model_save_path)
    if save_model:
        # Ensure that we save all trackable values (i.e. variables) from the TensorFlow Agent.
        checkpoint = tf.train.Checkpoint(agent=agent)
        # The checkpoint manager enables us to save multiple versions of the check point at
        # different training steps. We save the 20 most recent saves to span a wide section of
        # training.
        checkpoint_manager = tf.train.CheckpointManager(checkpoint, model_save_path, max_to_keep=20)
    else:
        # Warn the user that training will continue but models will not be saved.
        warn("No save directory provided. Model will not be saved.")

    if metrics is None:
        metrics = []
    if per_step_eval_metrics is None:
        per_step_eval_metrics = []
    # Set up a minimal training loop to simply test training mechanics work.
    for i in range(num_iters):
        with tf.summary.record_if(lambda: tf.math.equal(global_step % log_freq, 0)):
            # Collect experience.
            time_step, policy_state = data_collection_driver.run(
                time_step=time_step,
                policy_state=policy_state
            )
            # Now the replay buffer should have data in it so we can collect the data and train the
            # agent.
            experience = replay_buffer.gather_all()
            agent.train(experience)
            # Clear the replay buffer and return to play.
            replay_buffer.clear()
            for metric in metrics:
                metric.tf_summaries(
                    train_step=global_step,
                    step_metrics=metrics[:2]
                )
            # Run the policy tracking metrics one at a time each on their own summary writer to
            # enable shared axes on TensorBoard.
            for metric, summary_writer in zip(policy_metrics, policy_summary_writers):
                with summary_writer.as_default():
                    tf.summary.scalar(name=metric.name, data=metric.result(), step=global_step)

        if eval_summary_writer and eval_metrics and eval_env:
            if i > 0 and global_step % eval_freq == 0:
                evaluate_policy(
                    eval_metrics,
                    eval_env,
                    agent.policy,
                    per_step_metrics=per_step_eval_metrics,
                    num_episodes=num_eval_episodes,
                    train_step=global_step,
                    summary_writer=eval_summary_writer,
                    summary_prefix="Metrics",
                    logging=True,
                    tf_log_stream_path=tf_log_stream_path
                )
        # Periodically save the model provided that we have the infrastructure in place.
        if save_model and i > 0 and (i + 1) % save_freq == 0:
            checkpoint_manager.save(i + 1)
        if i % (num_iters // 100) == 0:
            print(f"\tCompleted: {i / num_iters * 100} %")
    checkpoint_manager.save(num_iters)

def main(params):
    """Puts everything together to set up and run the experiment."""
    # Attain a step counter which will be incremented every time the agent is trained.
    global_step = tf.compat.v1.train.get_or_create_global_step()
    # Attain separate environments for training and evaluation.
    env, gamma, load, max_ep_len, action_dims = get_environment(
        params.env_name,
        params.agent_name,
        params.episode_len_to_min_drain_time_ratio,
        params.terminal_discount_factor,
        params.action_repetitions,
        params.parallel_envs,
        params.env_param_overrides,
        params.rl_agent_params
    )
    eval_params = params.env_param_overrides.copy()
    eval_params['job_gen_seed'] = (eval_params.get('job_gen_seed', 0) or 0) + 1234
    eval_env, _, _, _, _ = get_environment(
        params.env_name,
        params.agent_name,
        params.episode_len_to_min_drain_time_ratio,
        params.terminal_discount_factor,
        params.action_repetitions,
        1,
        params.env_param_overrides,
        params.rl_agent_params,
        seed=eval_params['job_gen_seed']
    )

    # Update parameters with calculated values so that they can be logged.
    params.load = load
    params.max_ep_len = max_ep_len

    # Get the summary writers.
    train_summary_writer, action_prob_summary_writers, eval_summary_writer = \
        set_up_summary_writers(params.logdir, action_dims)
    # Set the training summary writer as the default. This means that unless otherwise stated any
    # summary made on the main thread will be logged using train_summary_writer.
    train_summary_writer.set_as_default()
    # Set up the agent, replay buffer and metrics.
    if params.agent_name.lower() == 'reinforce':
        agent = get_reinforce_agent(
            env, gamma, debug=not params.no_debug, agent_params=params.rl_agent_params
        )
    elif params.agent_name.lower() == 'ppo':
        assert 'num_epochs' in params.rl_agent_params, "PPO Agents require a num_epochs parameter."
        agent = get_ppo_agent(
            env,
            params.rl_agent_params['num_epochs'],
            gamma,
            debug=not params.no_debug,
            agent_params=params.rl_agent_params
        )
    else:
        raise NotImplementedError(f"RL Agent of type {params.agent_name} not currently supported.")

    with open(os.path.join(params.logdir, 'params.txt'), 'w') as param_file:
        param_file.write(get_args_string(params))

    replay_buffer = get_replay_buffer(env, agent)
    # Write logs to a file rather than the console.
    tf_log_stream_path = os.path.join(params.logdir, 'eval_logs.txt')
    metrics, policy_metrics, eval_metrics, per_step_eval_metrics = get_metrics(
        agent=agent,
        batch_size=env.batch_size,
        for_single_server_queue=params.env_name == 'single_server_queue',
        log_file_path=tf_log_stream_path)
    # Combine the method to add experience to the replay buffer with the metrics to track during
    # training to attain the list of observers which will be run by the DynamicEpisodeDriver.
    observers = [replay_buffer.add_batch] + metrics
    # Attain a driver which will run episodes with the policy for data collection (using Boltzmann
    # exploration), save experience to the replay buffer and log metrics to TensorBoard.
    driver = get_collection_driver(env, agent, observers, policy_observers=policy_metrics,
                                   num_episodes=params.episodes_per_iter)
    # Run the full training function.
    train_agent(env, agent, driver, replay_buffer, params.num_iters, global_step,
                eval_env=eval_env, metrics=metrics, eval_metrics=eval_metrics,
                policy_metrics=policy_metrics, per_step_eval_metrics=per_step_eval_metrics,
                policy_summary_writers=action_prob_summary_writers, eval_freq=params.eval_freq,
                eval_summary_writer=eval_summary_writer, log_freq=params.log_freq,
                save_freq=params.save_freq, model_save_path=params.savedir,
                tf_log_stream_path=tf_log_stream_path)

def parse_args() -> argparse.Namespace:
    """Processes command line arguments and collects them in the named tuple returned·"""
    params = argparse.ArgumentParser(description="Experiment Arguments with RL agent.")
    params.add_argument("--env_name", type=str, default="klimov_model",
                        help="Name of environment to run on. "
                             "Must be in the list of implemented scenarios.")
    params.add_argument("--agent_name", type=str, default="ppo",
                        help="Name of the agent to train.")
    params.add_argument("--experiment_name", type=str, default=None,
                        help="A name for the experiment. Used in creating log directories."
                             "If None, the results will be saved in a time stamped folder.")
    params.add_argument("--logdir", type=str, default="../../../../tmp/{}/{}",
                        help="Directory for storing TensorBoard logs.")
    params.add_argument("--savedir", type=str, default=None,
                        help="directory for storing model weights."
                             "If None then will save in subdirectory of logdir.")
    params.add_argument("-melr", "--episode_len_to_min_drain_time_ratio", type=float, default=1.5,
                        help="Maximum number of steps per episode as a proportion of the minimal "
                             "draining time.")
    params.add_argument("--action_repetitions", type=int, default=1,
                        help="Number of time steps each selected action is repeated for.")
    params.add_argument("--log_freq", type=int, default=5,
                        help="Number of training iterations between TensorBoard logging.")
    params.add_argument("--eval_freq", type=int, default=5,
                        help="Number of training iterations between policy evaluation.")
    params.add_argument("--save_freq", type=int, default=500,
                        help="Number of training iterations between model saves. "
                             "The training process will store the 20 most recent models and hence "
                             "the range of iterations captured in saved models will be "
                             "20 * save_freq")
    params.add_argument("-N", "--num_iters", type=int, default=250,
                        help="Number of training iterations.")
    params.add_argument("-n", "--episodes_per_iter", type=int, default=25,
                        help="Number of episodes to run in each training iteration.")
    params.add_argument("-ep", "--env_param_overrides", type=str, default='{}',
                        help="JSON formatted dictionary of environment parameter overrides. "
                             "May be a string or a path to a JSON file.")
    params.add_argument("--rl_agent_params", type=str, default='{"num_epochs": 10}',
                        help="JSON formatted dictionary of agent parameters. "
                             "May be a string or a path to a JSON file.")
    params.add_argument("-df", "--terminal_discount_factor", type=float, default=0.7,
                        help="Compounded discount factor at the final episode step.")
    params.add_argument("--seed", type=int, default=int(time.time()),
                        help="Random seed for numpy and TensorFlow.")
    params.add_argument("--parallel_envs", type=int, default=8,
                        help="Number of environments to run in parallel to speed up data "
                             "collection")

    # Debug logging is on by default.
    params.add_argument("--no_debug", default=False, action="store_true")

    # Collect the parameters in a namespace.
    parsed_params = params.parse_args()

    assert parsed_params.env_name in scenarios.SCENARIO_CONSTRUCTORS, \
        "Scenario passed does not exist."

    # Set the logging folder from the load automatically.
    if "{}" in parsed_params.logdir:
        if parsed_params.experiment_name is None:
            parsed_params.experiment_name = datetime.strftime(datetime.now(),
                                                              "%Y_%m_%d__%H:%M:%S.%f")
        # Set the logging folder from the load automatically.
        parsed_params.logdir = parsed_params.logdir.format(
            parsed_params.env_name + '-' + parsed_params.agent_name,
            parsed_params.experiment_name)
    # If no specific save directory provided then build one based on the logging directory.
    if parsed_params.savedir is None:
        parsed_params.savedir = os.path.join(parsed_params.logdir.format(
            parsed_params.env_name + '-' + parsed_params.agent_name), "models")

    # Support environment parameters passed as a string of a dictionary or as a path to a JSON file.
    if os.path.exists(parsed_params.env_param_overrides):
        with open(parsed_params.env_param_overrides, 'r') as json_file:
            parsed_params.env_param_overrides = json.load(json_file)
    else:
        parsed_params.env_param_overrides = json.loads(parsed_params.env_param_overrides)

    # Load agent parameters.
    if os.path.exists(parsed_params.rl_agent_params):
        with open(parsed_params.rl_agent_params, 'r') as json_file:
            parsed_params.rl_agent_params = json.load(json_file)
    else:
        parsed_params.rl_agent_params = json.loads(parsed_params.rl_agent_params)

    # We want to make sure that the seed is not None
    if parsed_params.seed is None:
        parsed_params.seed = int(time.time())

    # If job_gen_seed has not been specified, then we are going to set it equal to param.seed
    # If the two seeds are different, we will print a warning
    if 'job_gen_seed' in parsed_params.env_param_overrides:
        assert parsed_params.env_param_overrides["job_gen_seed"] is not None
        if parsed_params.env_param_overrides["job_gen_seed"] != parsed_params.seed:
            warn("Seed for environment job generator differs from random seed "
                 "supplied to  rl_experiment_script.py.")
    else:
        parsed_params.env_param_overrides["job_gen_seed"] = parsed_params.seed

    # This means that all list type objects will be cast to numpy arrays before passing to the
    # environment. Should functionality of the ControlledRandomWalk environments change in future
    # this will need to be reviewed.
    for p in parsed_params.env_param_overrides:
        if isinstance(parsed_params.env_param_overrides[p], list):
            parsed_params.env_param_overrides[p] = np.array(parsed_params.env_param_overrides[p])

    # Write the parameters out to a log file for future reference.
    if not os.path.exists(parsed_params.logdir):
        os.makedirs(parsed_params.logdir)

    return parsed_params

if __name__ == "__main__":
    args = parse_args()
    np.random.seed(args.seed)
    # TODO: Delete line when TensorFlow Issue 37252 is remedied. Link below
    # TODO: https://github.com/tensorflow/tensorflow/issues/37252
    # monkey_patch_tf_get_seed(args.seed)
    # tf.random.set_seed(args.seed)
    main(args)
    print("Training complete!")


"""
ADVENT OF CODE: 2018
Day 14: Chocolate Charts
"""

input_val = open("inputs/14.input", "r").read().strip()

scoreboard = "37"
elfA = 0
elfB = 1

while input_val not in scoreboard[-10:]:
    # create new recipies
    scoreboard += str(int(scoreboard[elfA])+int(scoreboard[elfB]))
    # advance elves
    elfA = (elfA + 1 + int(scoreboard[elfA])) % len(scoreboard)
    elfB = (elfB + 1 + int(scoreboard[elfB])) % len(scoreboard)

answerA = scoreboard[int(input_val):int(input_val)+10]
answerB = scoreboard.index(input_val)
print("Part A: " + answerA)
print("Part B: " + str(answerB))


"""
Given a list of integers S and a target number k, write a function that returns
a subset of S that adds up to k. If such a subset cannot be made, then return
null.

Integers can appear more than once in the list. You may assume all numbers in
the list are positive.

For example, given S = [12, 1, 61, 5, 9, 2] and k = 24, return [12, 9, 2, 1]
since it sums up to 24.
"""

def sum_up_to(arr, k):

    sorted_arr = sorted(arr)
    sub_arr = list()

    for i in range(0, len(arr)):

        if sum(sub_arr) == k:
            return sub_arr
        if sum(sub_arr) < k:
            sub_arr.append(sorted_arr[i])
        if sum(sub_arr) > k:
            number_to_find = sum(sub_arr) - k
            if number_to_find in sub_arr:
                sub_arr.pop(sub_arr.index(number_to_find))

    return None

def main():

    arr = [12, 1, 61, 5, 9, 2]
    print(sum_up_to(arr, 24))

if __name__ == '__main__':
    main()


from django.urls import path, include
from .views import DashboardView, KPIDetailView, Ibyakozwe, District_chartView, Sector_chartView, CreateFamily, \
    change_status, AddKpi, load_cells, load_village

urlpatterns = [
    path('', DashboardView.as_view(), name='dashboard'),
    path('kpi/<int:pk>', KPIDetailView.as_view(), name='kpi-detail'),
    path('ibyakozwe/<int:pk>', Ibyakozwe.as_view(), name='ibyakozwe'),
    path('ibisigaye/<int:pk>', Ibyakozwe.as_view(), name='ibisigaye'),
    path('ibyakozwe_sector/<int:pk>', Ibyakozwe.as_view(), name='ibyakozwe_sector'),
    path('ibisigaye_sector/<int:pk>', Ibyakozwe.as_view(), name='ibisigaye_sector'),
    path('kpi/charts/<int:pk>', District_chartView.as_view(), name='kpi_charts'),
    path('sector_chart/charts/<int:pk>', Sector_chartView.as_view(), name='sector_charts'),
    path('add_family', CreateFamily.as_view(), name='family'),
    path('add_kpi', AddKpi.as_view(), name='add_kpi'),
    path('status/<int:fam_id>', change_status, name='status'),

    path('ajax/load-cells/', load_cells, name='ajax_load_cells'),
    path('ajax/load-villages/', load_village, name='ajax_load_villages'),
]


import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.inspection import DecisionBoundaryDisplay
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

FIG_SIZE = (9, 3)
CMAP = plt.cm.RdBu
BRIGHT_CMAP = ListedColormap(["#FF0000", "#0000FF"])
MARGIN = 0.5

TEST_SIZE = 0.4
RANDOM_STATE = 42

def plot_datasets(datasets):
    plt.figure(figsize=FIG_SIZE)

    for index, dataset in enumerate(datasets):
        X, y = dataset
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)
        x_min, x_max, y_min, y_max = X[:, 0].min() - MARGIN, X[:, 0].max() + MARGIN, X[:, 1].min() - MARGIN, X[:, 1].max() + MARGIN

        ax = plt.subplot(1, len(datasets), index + 1)

        ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap=BRIGHT_CMAP, edgecolors="k")
        ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap=BRIGHT_CMAP, alpha=0.6, edgecolors="k")

        ax.set_xlim(x_min, x_max)
        ax.set_ylim(y_min, y_max)

        ax.set_xticks(())
        ax.set_yticks(())

    plt.tight_layout()
    plt.show()

def plot_classifier(classifier, datasets):
    plt.figure(figsize=FIG_SIZE)

    for index, dataset in enumerate(datasets):
        X, y = dataset
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)
        x_min, x_max, y_min, y_max = X[:, 0].min() - MARGIN, X[:, 0].max() + MARGIN, X[:, 1].min() - MARGIN, X[:, 1].max() + MARGIN

        ax = plt.subplot(1, len(datasets), index + 1)

        classifier = make_pipeline(StandardScaler(), classifier)
        classifier.fit(X_train, y_train)
        score = classifier.score(X_test, y_test)
        DecisionBoundaryDisplay.from_estimator(classifier, X, cmap=CMAP, alpha=0.8, ax=ax, eps=0.5)

        ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap=BRIGHT_CMAP, edgecolors="k")
        ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap=BRIGHT_CMAP, edgecolors="k", alpha=0.6)

        ax.set_xlim(x_min, x_max)
        ax.set_ylim(y_min, y_max)

        ax.set_xticks(())
        ax.set_yticks(())

        ax.text(x_max - 0.3, y_min + 0.3, ("%0.2f" % score).lstrip("0"), size=20, horizontalalignment="right")

    plt.tight_layout()
    plt.show()


class Solution:
    def isValidSudoku(self, board):
        def is_valid_row(board):
            for row in board:
                if not is_valid(row):
                    return False
            return True

        def is_valid_column(board):
            for col in zip(*board):
                if not is_valid(col):
                    return False
            return True

        def is_valid_square(board):
            for i in (0, 3, 6):
                for j in (0, 3, 6):
                    square = [board[x][y] for x in range(i, i + 3)
                              for y in range(j, j + 3)]
                    if not is_valid(square):
                        return False
            return True

        def is_valid(value):
            res = [i for i in value if i != '.']
            return len(res) == len(set(res))

        return is_valid_row(board) and is_valid_column(board) and is_valid_square(board)

    def isValidSudoku0(self, board):
        import collections
        return 1 == max(collections.Counter(
            x
            for i, row in enumerate(board)
            for j, c in enumerate(row)
            if c != '.'
            for x in ((c, i), (j, c), (i / 3, j / 3, c))
        ).values() + [1])

    def isValidSudoku1(self, board):
        seen = sum(([(c, i), (j, c), (i / 3, j / 3, c)]
                    for i, row in enumerate(board)
                    for j, c in enumerate(row)
                    if c != '.'), [])
        return len(seen) == len(set(seen))

    def isValidSudoku2(self, board):
        seen = set()
        return not any(x in seen or seen.add(x)
                       for i, row in enumerate(board)
                       for j, c in enumerate(row)
                       if c != '.'
                       for x in ((c, i), (j, c), (i / 3, j / 3, c)))

    def isValidSudoku3(self, board):
        seen = sum(([(c, i), (j, c), (i / 3, j / 3, c)]
                    for i in range(9) for j in range(9)
                    for c in [board[i][j]] if c != '.'), [])
        return len(seen) == len(set(seen))


import sys
import mido
import struct
import serial
import os
import numpy as np
from PyQt5.QtWidgets import QMainWindow,QApplication,QPushButton,QWidget,QAction,QTabWidget,QLayout,QVBoxLayout,QHBoxLayout,QLabel,QComboBox,QSlider,QTextEdit,QLineEdit,QGridLayout,QDesktopWidget,QSizePolicy,QGraphicsOpacityEffect
from PyQt5.QtGui import QIcon,QColor,QPalette,QFont,QPixmap,QPainter,QPainterPath,QPen,QBrush,QLinearGradient
from PyQt5.QtCore import pyqtSlot,Qt,QSize,QRect,QTimer,QRectF
from PyQt5.QtTest import QTest

class App(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = 'Gatorscribe'
        self.setWindowIcon(QIcon('uf_logo.png'))
        self.setWindowTitle(self.title)
        self.p = self.palette()
        self.setStyleSheet('background-color: rgb(42,41,46); color: rgb(200,209,218)')
        self.central_widget = Central(self)
        self.setCentralWidget(self.central_widget)
        self.show()

class Central(QWidget):

    def __init__(self, parent):
        super().__init__(parent)
        self.layout = QVBoxLayout(self)
        self.window_width = QDesktopWidget().screenGeometry().width()
        self.window_height = QDesktopWidget().screenGeometry().height()
        self.remove_background = 'background-color: rgb(0,0,0,0); border-width: 0px; border-radius: 0px;'
        self.format_button = 'background-color: rgb(247,247,247);'
        self.p = self.palette()
        self.p.setColor(self.backgroundRole(), QColor(127,255,127))
        self.purple = QColor(93,107,68)
        self.blue = QColor(50,200,255)
        self.green = QColor(104,236,196)
        self.orange = QColor(251,165,125)
        self.magenta = QColor(157,39,89)
        self.yellow = QColor(177,176,148)
        self.init_gatorscribe()
        self.layout.setSpacing(self.window_height/40)
        self.layout.addLayout(self.horizontal_menu)
        self.layout.addLayout(self.options_layout)
        self.layout.addStretch()
        self.layout.setContentsMargins(self.window_width/25,self.window_width/50,self.window_height/15,self.window_height/50)
        self.setLayout(self.layout)
        self.ser = serial.Serial()
        self.opacity = 0.9
        self.timer = QTimer()
        self.timer.setInterval(180)
        self.timer.timeout.connect(self.read_serial_port)
        self.timer.start()
        self.connectBoard()

    def get_midi_file(self):
        if (self.ser.isOpen()):
            msg = self.ser.read().decode('ascii')
            x = np.array([], dtype='uint8')
            i = 0
            while(msg != '-'):
                if (i == 1):
                    midi_msg = midi_msg + msg
                    x = np.append(x, np.array([int(midi_msg,16)], dtype="uint8"))
                    i = 0
                else:
                    midi_msg = msg
                    i = i + 1

                msg = self.ser.read().decode('ascii')
                #print(msg)

            x.tofile('my_song.mid') # simply name of file
            print("Created MIDI File")
            self.start_btn.setText("Start")
            os.popen("open my_song.mid") # open file
        else:
            print("No board connected")
            self.connectBoard()

    def read_serial_port(self):
        if self.ser.isOpen():
            # decode serial message from board
            try:
                serial_msg = self.ser.read() #.decode('ascii')
                s = list(serial_msg)
                #print(s)
                if (len(s) > 0):
                    if (s[0] < 128):
                        msg = serial_msg.decode('ascii')
                        if (msg == '-'):
                            self.get_midi_file()

                    if (s[0] == 255): # read bpm
                        serial_msg = self.ser.read() #.decode('ascii')
                        s = list(serial_msg)
                        self.tempo_edit.setText(str(s[0]))
                        #print(s)

                    elif (s[0] == 254): # start recording
                        self.startGatorscribe()
                    elif(s[0] == 253):
                        self.reset_start_stop()
                    elif(s[0] == 252):
                        print("Getting pitch")
                        self.getPitch()
            except:
                self.start_btn.setText("Reconnect")
                self.ser.close()

    def getPitch(self):
        s = [0,0,0,0]
        for i in range(0,4):
            serial_msg = self.ser.read() #.decode('ascii')
            s[i] = list(serial_msg)[0]
        aa= bytearray(s)
        values = struct.unpack('<f', aa)
        self.input_freq = values[0]
        print(values[0])
        self.updateTuner()

    def find_nearest(self,array,value):
        idx = (np.abs(array-value)).argmin()
        return (idx)

    def updateTuner(self):
        freq_index = self.find_nearest(self.freq_array, self.input_freq)
        self.closest_freq = self.freq_array[freq_index]
        lower_index = freq_index-1
        lower_freq = self.freq_array[lower_index]
        higher_index = freq_index+1
        higher_freq = self.freq_array[higher_index]
        left_freq = (lower_freq + self.closest_freq) / 2
        right_freq = (higher_freq + self.closest_freq) / 2
        print("Lower Frequency: " + str(lower_freq))
        print("Closest Frequency: " + str(self.closest_freq))
        print("Higher Frequency: " + str(higher_freq))
        if (self.input_freq < self.closest_freq):
            self.percent_change = (self.input_freq - self.closest_freq)/(self.closest_freq - left_freq)
        else:
            self.percent_change = (self.input_freq - self.closest_freq)/(right_freq - self.closest_freq)
        print("P change = " + str(self.percent_change))
        print("Index is " + str(freq_index))
        print(self.freq_dict[self.freq_array[freq_index]])
        print(self.freq_dict[self.freq_array[lower_index]])
        print(self.freq_dict[self.freq_array[higher_index]])
        self.update()

    def init_gatorscribe(self):
        # Create label to mark gatorscribe area
        self.gatorscribe_lbl = QLabel('Transcribe')
        self.gatorscribe_lbl.setFont(QFont('Calibri Light',12))
        self.gatorscribe_lbl.setStyleSheet('background-color: rgb(128,128,128,64); color: rgb(255,255,255)')

        # Create grid layout for options
        self.options_layout = QGridLayout()
        self.options_layout.setSpacing(self.window_width/50)
        self.options_layout.setContentsMargins(int(self.window_width/50),int(self.window_width/50),int(self.window_width/50),int(self.window_width/50))

        # Create grid layout for title and start
        self.horizontal_menu = QHBoxLayout()
        # Create layout for song title
        self.title_layout = QVBoxLayout()
        self.title_lbl = QLabel('Title')
        self.title_lbl.setFont(QFont('Calibri Light',self.window_width/128))
        self.title_lbl.setStyleSheet(self.remove_background + '; color: rgb(42,41,46)')
        self.title_lbl.setMaximumSize(self.title_lbl.minimumSizeHint().width()*1.5,self.title_lbl.minimumSizeHint().height()*1.5)
        self.title_lbl.setContentsMargins(self.title_lbl.minimumSizeHint().width()/5,self.title_lbl.minimumSizeHint().width()/5,self.title_lbl.minimumSizeHint().width()/5,self.title_lbl.minimumSizeHint().width()/5)
        self.title_edit = QLineEdit('Song Title')
        self.title_edit.setStyleSheet(self.remove_background)
        self.title_edit.setFont(QFont('Calibri Light',64))
        self.title_edit.setAlignment(Qt.AlignLeft)
        self.title_edit.setMaxLength(30)
        self.title_edit.setMinimumSize(self.title_edit.minimumSizeHint().width()*1.2,self.title_edit.minimumSizeHint().height()*0.9)
        self.title_edit.editingFinished.connect(self.editTitle)

        self.editing_title = False
        self.finishing_title = False
        self.grad_position = 0.0
        self.title_layout.addWidget(self.title_edit)
        self.title_layout.addWidget(self.title_lbl)
        self.title_layout.setSpacing(0)

        # Create tuner button
        self.tuner_btn = QPushButton('Tuner')
        self.tuner_btn.setFont(QFont('Calibri',self.window_width/128))
        self.tuner_btn.setStyleSheet('background-color: rgb(127,127,127,127); border-width: 2px; border-style: outset; border-radius: 5px; padding: 2px; border-color: transparent')
        self.tuner_btn.setMinimumSize(self.tuner_btn.minimumSizeHint().width()*2,self.tuner_btn.minimumSizeHint().height())
        self.tuner_btn.pressed.connect(lambda: self.tuner_btn.setStyleSheet('background-color: rgb(192,192,192,127); border-width: 2px; border-style: outset; border-radius: 5px; padding: 2px; border-color: transparent'))
        self.tuner_btn.released.connect(lambda: self.tuner_btn.setStyleSheet('background-color: rgb(127,127,127,127); border-width: 2px; border-style: outset; border-radius: 5px; padding: 2px; border-color: transparent'))
        self.tuner_btn.clicked.connect(self.toggleTuner)

        # Create start button for gatorscribe
        self.start_btn = QPushButton('Start')
        self.start_btn.setFont(QFont('Calibri',self.window_width/128))
        self.start_btn.setStyleSheet('background-color: rgb(127,127,127,127); border-width: 2px; border-style: outset; border-radius: 5px; padding: 2px; border-color: transparent')
        self.start_btn.setMinimumSize(self.start_btn.minimumSizeHint().width()*2,self.start_btn.minimumSizeHint().height())
        self.start_btn.pressed.connect(lambda: self.start_btn.setStyleSheet('background-color: rgb(192,192,192,127); border-width: 2px; border-style: outset; border-radius: 5px; padding: 2px; border-color: transparent'))
        self.start_btn.released.connect(lambda: self.start_btn.setStyleSheet('background-color: rgb(127,127,127,127); border-width: 2px; border-style: outset; border-radius: 5px; padding: 2px; border-color: transparent'))
        self.start_btn.clicked.connect(self.sendStartStopFlag)

        # Create horizontal menu
        self.horizontal_menu.addLayout(self.title_layout)
        self.horizontal_menu.addWidget(self.tuner_btn)
        self.horizontal_menu.addWidget(self.start_btn)
        self.horizontal_menu.setAlignment(self.tuner_btn,Qt.AlignLeft)
        self.horizontal_menu.setAlignment(self.start_btn,Qt.AlignLeft)
        #self.horizontal_menu.addStretch()

        # Class variables for key signature
        self.current_key = 'C Major\nA minor'
        self.midi_keys_dict = {'C Major\nA minor':0, 'G Major\nE minor':1, 'D Major\nB minor':2, 'A Major\nF# minor':3, 'E Major\nC# minor':4,
            'B Major\nG# minor':5, 'F# Major\nD# minor':6, 'C# Major\nA# minor':7, 'F Major\nD minor':-1, 'Bb Major\nG minor':-2, 'Eb Major\nC minor':-3,
            'Ab Major\nF minor':-4, 'Db Major\nBb minor':-5, 'Gb Major\nEb minor':-6, 'Cb Major\nAb minor':-7}
       # self.minor_keys_dict = {'A Minor':0, 'E':1, 'B':2, 'F#':3, 'C#':4, 'G#':5, 'D#':6, 'A#':7, 'D':-1, 'G':-2, 'C':-3, 'F':-4, 'Bb':-5, 'Eb':-6, 'Ab':-7}

        # Class variables for instruments
        self.instrument_dict = {'guitar.png':27, 'piano.png':0, 'violin.png':40, 'trumpet.png':56, 'synth.png':80, 'sax.png':64}
        self.current_instrument = 'piano.png'

        # Array to hold all colors
        self.color_array = [self.blue, self.yellow, self.green, self.orange, self.magenta]

        # Look up table for set frequencies
        self.freq_array = np.array([8.176,8.662,9.177,9.723,10.301,10.913,11.562,12.250,12.978,13.750,14.568,15.434,
                            16.35,17.32,18.35,19.45,20.60,21.83,23.12,24.50,25.96,27.50,29.14,30.87,32.70,34.65,36.71,38.89,41.20,43.65,
                            46.25,49.00,51.91,55.00,58.27,61.74,65.41,69.30,73.42,77.78,82.41,87.31,92.50,98.00,103.83,110.00,116.54,123.47,
                            130.81,138.59,146.83,155.56,164.81,174.61,185.00,196.00,207.65,220.00,233.08,246.94,261.63,277.18,293.66,311.13,
                            329.63,349.23,369.99,392.00,415.30,440.00,466.16,493.88,523.25,554.37,587.33,622.25,659.25,698.46,739.99,783.99,
                            830.61,880.00,932.33,987.77,1046.50,1108.73,1174.66,1244.51,1318.51,1396.91,1479.98,1567.98,1661.22,1760.00,1864.66,
                            1975.53,2093.00,2217.46,2349.32,2489.02,2637.02,2793.83,2959.96,3135.96,3322.44,3520.00,3729.31,3951.07,4186.01,4434.92,
                            4698.63,4978.03,5274.04,5587.65,5919.91,6271.93,6644.88,7040.00,7458.62,7902.13])

        self.note_array = [ 'C','C#','D','D#','E','F','F#','G','G#','A','A#','B',
                            'C','C#','D','D#','E','F','F#','G','G#','A','A#','B',
                            'C','C#','D','D#','E','F','F#','G','G#','A','A#','B',
                            'C','C#','D','D#','E','F','F#','G','G#','A','A#','B',
                            'C','C#','D','D#','E','F','F#','G','G#','A','A#','B',
                            'C','C#','D','D#','E','F','F#','G','G#','A','A#','B',
                            'C','C#','D','D#','E','F','F#','G','G#','A','A#','B',
                            'C','C#','D','D#','E','F','F#','G','G#','A','A#','B',
                            'C','C#','D','D#','E','F','F#','G','G#','A','A#','B',
                            'C','C#','D','D#','E','F','F#','G','G#','A','A#','B']

        self.percent_change = 0.0
        self.input_freq = 395.0
        self.freq_dict = dict(zip(self.freq_array,self.note_array))
        self.closest_freq = self.freq_array[40]

        # Create all components of the instrument menu
        self.initInstrumentMenu()

        # Create all components of the main menu
        self.initMainMenu()

        # Create all components of the time menu
        self.initTimeMenu()

        # Create all components of the tuner menu
        self.initTunerMenu()

        # Fill in options grid layout
        self.options_layout.addLayout(self.time_layout,0,0,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addLayout(self.key_layout,0,1,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addLayout(self.tempo_layout,1,0,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addLayout(self.instrument_layout,1,1,Qt.AlignLeft | Qt.AlignVCenter)

    def resizeEvent(self, event):
        self.gatorscribe_lbl.resize(self.width(),self.gatorscribe_lbl.height())

    def paintEvent(self, event):
        paint = QPainter()
        paint.begin(self)
        self.drawShapes(event, paint, self.paint_code)

    def drawShapes(self, event, paint, paint_code):
        color = self.blue
        alpha = 240
        color.setAlpha(alpha)
        paint.setBrush(QBrush(color))
        paint.setPen(Qt.NoPen)
        rect_width = int(self.window_width/250)
        title_rect = QRect(self.title_lbl.x()+self.title_lbl.width(),self.title_lbl.y(),(self.start_btn.x()+self.start_btn.width())-(self.title_lbl.x()+self.title_lbl.width()),rect_width/2)
        if self.editing_title or self.finishing_title:
            grad = QLinearGradient(title_rect.x(),title_rect.y() + title_rect.height(),title_rect.x() + title_rect.width(),title_rect.y() + title_rect.height())
            color_1 = QColor()
            color_2 = QColor()
            if self.editing_title:
                color_1 = QColor(255,255,255)
                color_2 = QColor(self.blue)
            if self.finishing_title:
                color_1 = QColor(self.blue)
                color_2 = QColor(255,255,255)
            grad.setColorAt(self.grad_position,color_1)
            grad.setColorAt(self.grad_position+0.05,color_2)
            paint.setBrush(QBrush(grad))
        paint.drawRect(title_rect)
        paint.drawRect(QRect(self.title_lbl.x(),self.title_lbl.y(),self.title_lbl.width(),self.title_lbl.height()))

        if paint_code is 0:
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.time_lbl.x()-(rect_width*3),self.time_button.y(),rect_width,self.time_lbl.y()-self.time_button.y()+self.time_lbl.height()))
            color = self.green
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.key_lbl.x()-(rect_width*3),self.key_btn.y(),rect_width,self.key_lbl.y()-self.key_btn.y()+self.key_lbl.height()))
            color = self.orange
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.tempo_lbl.x()-(rect_width*3),self.tempo_edit.y(),rect_width,self.tempo_lbl.y()-self.tempo_edit.y()+self.tempo_lbl.height()))
            color = self.magenta
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.instrument_lbl.x()-(rect_width*3),self.instrument_pic.y(),rect_width,self.instrument_lbl.y()-self.instrument_pic.y()+self.instrument_lbl.height()))
        elif paint_code is 1:
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.button_44.x()-(rect_width*3),self.button_44.y(),rect_width,self.button_44.height()))
            color = self.green
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.button_34.x()-(rect_width*3),self.button_34.y(),rect_width,self.button_34.height()))
            color = self.orange
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.button_24.x()-(rect_width*3),self.button_24.y(),rect_width,self.button_24.height()))
            color = self.magenta
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.button_68.x()-(rect_width*3),self.button_68.y(),rect_width,self.button_68.height()))
        elif paint_code is 2:
            color = self.blue
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.guitar.x()-(rect_width*3),self.guitar.y(),rect_width,self.guitar.height()))
            color = self.green
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.piano.x()-(rect_width*3),self.piano.y(),rect_width,self.piano.height()))
            color = self.orange
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.violin.x()-(rect_width*3),self.violin.y(),rect_width,self.violin.height()))
            color = self.magenta
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.trumpet.x()-(rect_width*3),self.trumpet.y(),rect_width,self.trumpet.height()))
            color = self.purple
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.synth.x()-(rect_width*3),self.synth.y(),rect_width,self.synth.height()))
            color = self.yellow
            color.setAlpha(alpha*self.opacity)
            paint.setBrush(QBrush(color))
            paint.drawRect(QRect(self.sax.x()-(rect_width*3),self.sax.y(),rect_width,self.sax.height()))
        elif paint_code is 3:
            i = 0
            items = (self.options_layout.itemAt(i) for i in range(self.options_layout.count()))
            for widget in items:
                color = self.color_array[i]
                color.setAlpha(alpha*self.opacity)
                paint.setBrush(QBrush(color))
                current_widget = widget.widget()
                paint.drawRect(QRect(current_widget.x()-(rect_width*3),current_widget.y(),rect_width,current_widget.height()))
                if i > 3:
                    i = 0
                else:
                    i = i + 1
            top_widget = self.options_layout.itemAtPosition(0,0).widget()
            middle_widget = self.options_layout.itemAtPosition(self.options_layout.rowCount()/2,0).widget()
            right_widget = self.options_layout.itemAtPosition(2,self.options_layout.columnCount()-1).widget()
            bottom_widget = self.options_layout.itemAtPosition(self.options_layout.rowCount()-1,0).widget()
            paint.setBrush(QColor(200,209,218,20))
            path = QPainterPath()
            path.addRoundedRect(QRectF(top_widget.x()-(rect_width*5),top_widget.y()-(rect_width*2),right_widget.x()+right_widget.width()+(rect_width*10)-top_widget.x(),(middle_widget.y())-(rect_width)),10,10)
            paint.drawPath(path)
        elif paint_code is 4:
            base_line_y = self.exit_btn.y()-self.options_layout.rowMinimumHeight(0)/2.2
            base_line_x = self.exit_btn.x() - ((self.tuner_btn.x()+self.tuner_btn.width()) - (self.exit_btn.x()+self.exit_btn.width()))
            base_line_width = (2*((self.tuner_btn.x()+self.tuner_btn.width()) - (self.exit_btn.x()+self.exit_btn.width())))+self.exit_btn.width()
            paint.setBrush(QColor(50,200,255, 255))
            paint.drawRect(QRect(base_line_x,base_line_y,base_line_width,self.window_height/500))
            marker_height = self.options_layout.rowMinimumHeight(0)/2
            marker_y = base_line_y - (marker_height/2)
            paint.setBrush(QColor(50,200,255, 60))
            paint.drawRect(QRect(self.exit_btn.x()+(self.exit_btn.width()/2),marker_y,self.window_height/500,marker_height))
            marker_x = self.exit_btn.x()+(self.exit_btn.width()/2) + (self.percent_change*(base_line_width/2))
            marker_width = self.window_height/250
            if(abs(self.percent_change) < 0.1):
                paint.setBrush(QColor(104,236,196, 255))
            else:
                paint.setBrush(QColor(QColor(157,39,89, 255)))

            paint.drawRect(QRect(marker_x,marker_y,marker_width,marker_height))
            self.tuner_note.setText(self.freq_dict[self.closest_freq])

    # Traverse through input layout to change the transparency of each widget
    def fadeOptions(self, layout):
        if layout is not None:
            items = (layout.itemAt(i) for i in range(layout.count()))
            for item in items:
                widget = item.widget()
                if widget is not None:
                    op = QGraphicsOpacityEffect(widget)
                    op.setOpacity(self.opacity)
                    widget.setGraphicsEffect(op)
                    widget.setAutoFillBackground(True)
                else:
                    self.fadeOptions(item.layout())
                self.update()

    # Fade out function that calls fadeOptions for increasingly smaller alpha
    def fadeOut(self):
        self.opacity = 0.9
        while self.opacity >= 0:
            QTest.qWait(0.5)
            self.fadeOptions(self.options_layout)
            self.opacity = self.opacity - 0.1

    # Fade in function that calls fadeOptions for increasingly larger alpha
    def fadeIn(self):
        self.opacity = 0.0
        while self.opacity <= 0.9:
            QTest.qWait(0.5)
            self.fadeOptions(self.options_layout)
            self.opacity = self.opacity + 0.1
        self.hide()
        self.show()

    def clearGridLayout(self):
        if self.options_layout is not None:
            items = (self.options_layout.itemAt(i) for i in reversed(range(self.options_layout.count())))
            for item in items:
                if item is not None:
                    layout = item.layout()
                    if layout is not None:
                        self.options_layout.removeItem(layout)
                        layout.setParent(None)
                    else:
                        widget = item.widget()
                        if widget is not None:
                            self.options_layout.removeWidget(widget)
                            widget.setParent(None)
        self.options_layout.setRowMinimumHeight(0,0)
        painter = QPainter()
        painter.eraseRect(0,self.title_lbl.y(),self.window_width,self.window_height-self.title_edit.y())
        self.hide()
        self.show()

    def initMainMenu(self):
        self.paint_code = 0
        # Time signature table
        self.time_list = ['4/4','3/4','2/4','6/8']
        self.time_index = 0

        # Create time signature layout
        self.time_layout = QVBoxLayout()
        self.time_lbl = QLabel('Time Signature')
        self.time_lbl.setFont(QFont('Calibri Light',self.window_width/128))  # Edit label font
        self.time_lbl.setMaximumSize(self.time_lbl.minimumSizeHint().width(),self.time_lbl.height())

        self.time_button = QPushButton(self.time_list[self.time_index])
        self.time_button.setStyleSheet(self.remove_background)
        self.time_button.setFont(QFont('Calibri Light',self.window_width/20))
        self.time_button.setMaximumSize(self.time_button.minimumSizeHint().width()*1.2,self.time_button.minimumSizeHint().height())
        self.time_button.clicked.connect(self.constructTimeMenu)

        self.time_layout.addWidget(self.time_button)
        self.time_layout.addWidget(self.time_lbl)
        self.time_layout.setAlignment(self.time_lbl,Qt.AlignLeft)
        self.time_layout.setAlignment(self.time_button,Qt.AlignLeft)
        self.time_layout.setSpacing(0)

        # Create key signature layout
        self.key_layout = QVBoxLayout()
        self.key_lbl = QLabel('Key Signature')
        self.key_lbl.setFont(QFont('Calibri Light',self.window_width/128))  # Edit label font
        self.key_lbl.setAlignment(Qt.AlignLeft)
        self.key_btn = QPushButton(self.current_key)
        self.key_btn.setStyleSheet(self.remove_background)
        self.key_btn.setFont(QFont('Calibri Light',self.window_width/39))
        self.key_btn.setMaximumSize(self.key_btn.minimumSizeHint().width()*1.2,self.key_btn.minimumSizeHint().height())
        self.key_btn.clicked.connect(self.constructKeyMenu)
        self.key_layout.addWidget(self.key_btn)
        self.key_layout.addWidget(self.key_lbl)
        self.key_layout.setAlignment(self.key_btn,Qt.AlignLeft)
        self.key_layout.setAlignment(self.key_lbl,Qt.AlignLeft)
        self.key_layout.setSpacing(0)

         # Create tempo layout
        self.tempo_layout = QVBoxLayout()
        self.tempo_lbl = QLabel('Tempo')
        self.tempo_lbl.setFont(QFont('Calibri Light',self.window_width/128))  # Edit label font
        self.tempo_lbl.setStyleSheet('color: rgb(170,179,188)')
        self.tempo_lbl.setMaximumSize(self.tempo_lbl.minimumSizeHint().width(),self.tempo_lbl.height())
        self.tempo_edit = QLineEdit()
        self.tempo_edit.setStyleSheet(self.remove_background)
        self.tempo_edit.setFont(QFont('Calibri Light',self.window_width/20))
        self.tempo_edit.setAlignment(Qt.AlignLeft)
        self.tempo_edit.setMaxLength(3)
        self.tempo_edit.setMaximumSize(self.tempo_edit.minimumSizeHint().width()*2.4,self.tempo_edit.minimumSizeHint().height())
        self.tempo_edit.setText('100')
        #self.tempo_edit.editingFinished.connect(self.sendTempo)
        self.tempo_layout.addWidget(self.tempo_edit)
        self.tempo_layout.addWidget(self.tempo_lbl)
        self.tempo_layout.setAlignment(self.tempo_lbl,Qt.AlignLeft)
        self.tempo_layout.setAlignment(self.tempo_edit,Qt.AlignLeft)
        self.tempo_layout.setSpacing(0)

        # Create instrument layout
        self.instrument_layout = QVBoxLayout()
        self.instrument_lbl = QLabel('Playback Instrument')
        self.instrument_lbl.setFont(QFont('Calibri Light',self.window_width/128))  # Edit label font
        self.instrument_lbl.setStyleSheet('color: rgb(170,179,188)')
        self.instrument_lbl.setAlignment(Qt.AlignLeft)
        self.instrument_pic = QPushButton()
        self.instrument_pic.setStyleSheet(self.remove_background)
        self.instrument_pic.setIcon(QIcon(self.current_instrument))
        self.instrument_pic.setIconSize(QSize(self.key_btn.height(),self.key_btn.height()))
        self.instrument_pic.clicked.connect(lambda: self.constructInstrumentMenu())
        self.instrument_layout.addWidget(self.instrument_pic)
        self.instrument_layout.addWidget(self.instrument_lbl)
        self.instrument_layout.setAlignment(self.instrument_pic,Qt.AlignLeft)
        self.instrument_layout.setAlignment(self.instrument_lbl,Qt.AlignLeft)
        self.instrument_layout.setSpacing(0)

    def initTimeMenu(self):

        self.button_44 = QPushButton('4/4')
        self.button_44.setStyleSheet(self.remove_background)
        self.button_44.setFont(QFont('Calibri Light',self.window_width/20))
        self.button_44.setMaximumSize(self.button_44.minimumSizeHint().width(),self.button_44.minimumSizeHint().height())
        self.button_44.clicked.connect(lambda: self.changeTime(0))

        self.button_34 = QPushButton('3/4')
        self.button_34.setStyleSheet(self.remove_background)
        self.button_34.setFont(QFont('Calibri Light',self.window_width/20))
        self.button_34.setMaximumSize(self.time_button.width(),self.time_button.height())
        self.button_34.clicked.connect(lambda: self.changeTime(1))

        self.button_24 = QPushButton('2/4')
        self.button_24.setStyleSheet(self.remove_background)
        self.button_24.setFont(QFont('Calibri Light',self.window_width/20))
        self.button_24.setMaximumSize(self.time_button.width(),self.time_button.height())
        self.button_24.clicked.connect(lambda: self.changeTime(2))

        self.button_68 = QPushButton('6/8')
        self.button_68.setStyleSheet(self.remove_background)
        self.button_68.setFont(QFont('Calibri Light',self.window_width/20))
        self.button_68.setMaximumSize(self.time_button.width(),self.time_button.height())
        self.button_68.clicked.connect(lambda: self.changeTime(3))

    def constructKeyMenu(self):
        self.fadeOut()
        self.clearGridLayout()

        row = 0
        column = 0
        i = 0
        for key,value in self.midi_keys_dict.items():
            button = QPushButton(key)
            button.setStyleSheet(self.remove_background)
            button.setFont(QFont('Calibri',self.window_width/70))
            button.setMaximumSize(button.minimumSizeHint().width(),button.minimumSizeHint().height())
            button.clicked.connect(self.changeKey)
            button.clicked.connect(self.constructMainMenu)
            self.options_layout.addWidget(button,row,column,Qt.AlignLeft)
            if column < 4:
                column = column + 1
            else:
                column = 0
                row = row + 2
            i = i + 1

        self.paint_code = 3
        self.fadeIn()

    def initInstrumentMenu(self):
        self.pic_size = 0.8*QSize(self.window_height/6,self.window_height/6)

        self.guitar = QPushButton()
        self.guitar.setStyleSheet(self.remove_background)
        self.guitar.setIcon(QIcon('guitar.png'))
        self.guitar.setIconSize(self.pic_size)
        self.guitar.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
        self.guitar.clicked.connect(lambda: self.changeInstrument('guitar.png'))
        self.guitar.clicked.connect(self.constructMainMenu)

        self.piano = QPushButton()
        self.piano.setStyleSheet(self.remove_background)
        self.piano.setIcon(QIcon('piano.png'))
        self.piano.setIconSize(self.pic_size)
        self.piano.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
        self.piano.clicked.connect(lambda: self.changeInstrument('piano.png'))
        self.piano.clicked.connect(self.constructMainMenu)

        self.violin = QPushButton()
        self.violin.setStyleSheet(self.remove_background)
        self.violin.setIcon(QIcon('violin.png'))
        self.violin.setIconSize(self.pic_size)
        self.violin.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
        self.violin.clicked.connect(lambda: self.changeInstrument('violin.png'))
        self.violin.clicked.connect(self.constructMainMenu)

        self.trumpet = QPushButton()
        self.trumpet.setStyleSheet(self.remove_background)
        self.trumpet.setIcon(QIcon('trumpet.png'))
        self.trumpet.setIconSize(self.pic_size)
        self.trumpet.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
        self.trumpet.clicked.connect(lambda: self.changeInstrument('trumpet.png'))
        self.trumpet.clicked.connect(self.constructMainMenu)

        self.synth = QPushButton()
        self.synth.setStyleSheet(self.remove_background)
        self.synth.setIcon(QIcon('synth.png'))
        self.synth.setIconSize(self.pic_size)
        self.synth.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
        self.synth.clicked.connect(lambda: self.changeInstrument('synth.png'))
        self.synth.clicked.connect(self.constructMainMenu)

        self.sax = QPushButton()
        self.sax.setStyleSheet(self.remove_background)
        self.sax.setIcon(QIcon('sax.png'))
        self.sax.setIconSize(self.pic_size)
        self.sax.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Expanding)
        self.sax.clicked.connect(lambda: self.changeInstrument('sax.png'))
        self.sax.clicked.connect(self.constructMainMenu)

    def initTunerMenu(self):
        self.exit_btn = QPushButton('')
        self.exit_btn.setFont(QFont('Calibri',self.window_width/128))
        self.exit_btn.setStyleSheet('background-color: rgb(127,127,127,0); border-width: 2px; border-style: outset; border-radius: 5px; padding: 2px; border-color: transparent')
        self.exit_btn.setMaximumSize(self.exit_btn.minimumSizeHint().width()*3,self.exit_btn.minimumSizeHint().height())

        self.tuner_note = QLabel('E')
        self.tuner_note.setFont(QFont('Calibri Light',80))
        self.tuner_note.setStyleSheet('background-color: rgb(128,128,128,0); color: rgb(255,255,255)')
        self.tuner_note.setAlignment(Qt.AlignHCenter)

    def constructMainMenu(self):
        self.fadeOut()
        self.clearGridLayout()

        self.time_button.setText(self.time_list[self.time_index])
        self.instrument_pic.setIcon(QIcon(self.current_instrument))
        self.key_btn.setText(self.current_key)
        # Fill in options grid layout
        self.options_layout.addLayout(self.time_layout,0,0,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addLayout(self.key_layout,0,1,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addLayout(self.tempo_layout,1,0,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addLayout(self.instrument_layout,1,1,Qt.AlignLeft | Qt.AlignVCenter)

        self.paint_code = 0
        self.fadeIn()

    def constructTimeMenu(self):
        self.fadeOut()
        self.clearGridLayout()

        self.options_layout.addWidget(self.button_44,0,0,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addWidget(self.button_34,0,1,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addWidget(self.button_24,1,0,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addWidget(self.button_68,1,1,Qt.AlignLeft | Qt.AlignVCenter)

        self.paint_code = 1
        self.fadeIn()

    def constructInstrumentMenu(self):
        self.fadeOut()
        self.clearGridLayout()

        self.options_layout.addWidget(self.guitar,0,0,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addWidget(self.piano,0,1,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addWidget(self.violin,0,2,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addWidget(self.trumpet,1,0,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addWidget(self.synth,1,1,Qt.AlignLeft | Qt.AlignVCenter)
        self.options_layout.addWidget(self.sax,1,2,Qt.AlignLeft | Qt.AlignVCenter)

        self.paint_code = 2
        self.fadeIn()

    def constructTunerMenu(self):
        self.fadeOut()
        self.clearGridLayout()
        self.options_layout.setRowMinimumHeight(0,self.button_44.minimumSizeHint().height()*3)
        self.options_layout.addWidget(self.exit_btn,1,0,Qt.AlignVCenter)

        self.options_layout.addWidget(self.tuner_note, 0, 0, Qt.AlignVCenter | Qt.AlignTop)

        print(self.options_layout.rowMinimumHeight(0))
        self.paint_code = 4
        self.fadeIn()

    def editTitle(self):
        self.editing_title = True
        self.grad_position = 0.0
        while self.grad_position <= 0.95:
            QTest.qWait(1)
            self.update()
            self.grad_position = self.grad_position + 0.05
        self.editing_title = False
        self.hide()
        self.show()

    def connectBoard(self):
        if (os.path.exists("/dev/tty.usbmodem1462")):
            self.ser = serial.Serial(port='/dev/tty.usbmodem1462', baudrate=115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, timeout=0.2)
        elif (os.path.exists("/dev/tty.usbmodem1442")):
            self.ser = serial.Serial(port='/dev/tty.usbmodem1442', baudrate=115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, timeout=0.2)
        elif (os.path.exists("/dev/tty.usbserial-A904RDA3")):
            self.ser = serial.Serial(port='/dev/tty.usbserial-A904RDA3', baudrate=115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, timeout=0.1)
        if(self.ser.isOpen()):
            print("Serial port is open")
            self.reset_board()
            self.start_btn.setText('Start')
        else:
            print("No board connected")
            self.start_btn.setText('Reconnect')

    def toggleTuner(self):
        if (self.tuner_btn.text() == 'Tuner'):
            self.tuner_btn.setText('Back')
            self.sendStartTuning()
            self.constructTunerMenu();
        else:
            self.tuner_btn.setText('Tuner')
            self.sendStopTuning()
            self.constructMainMenu();

    def changeTime(self,number):
        self.time_index = number
        self.constructMainMenu()

    def changeKey(self):
        button = self.sender()
        self.current_key = button.text()

    def changeInstrument(self,instrument):
        self.current_instrument = instrument

    def reset_start_stop(self):
        self.tuner_btn.setText('Tuner')
        self.start_btn.setText("Start")
        self.constructMainMenu()

    def reset_board(self):
        if self.ser.isOpen():
            try:
                self.ser.write([252])
            except:
                return
        else:
            print('Reconnecting...')
            self.connectBoard()

    def sendStartStopFlag(self):
        if self.ser.isOpen():
            try:
                self.ser.write([255])
            except:
                return
        else:
            print('Reconnecting...')
            self.connectBoard()

    def sendStartTuning(self):
        if self.ser.isOpen():
            try:
                self.ser.write([254])
            except:
                return
        else:
            print('Reconnecting...')
            self.connectBoard()

    def sendStopTuning(self):
        if self.ser.isOpen():
            try:
                self.ser.write([253])
            except:
                return
        else:
            print('Reconnecting...')
            self.connectBoard()

    def sendTitle(self):
        if self.ser.isOpen():
            self.ser.write(self.title_edit.text().encode('ascii'))
            self.ser.write([0])
        else:
            self.connectBoard()
            print('Cannot send title ' + self.title_edit.text() + ' | Board not connected')

    def sendTime(self):
        if self.ser.isOpen():
            self.ser.write([self.time_index])
        else:
            self.connectBoard()
            print('Cannot send time ' + self.time_list[number] + ' | Board not connected')

    def sendKey(self):
        if self.ser.isOpen():
            self.ser.write([self.midi_keys_dict.get(self.current_key) + 7]) # can't send negative numbers. add 7 on board
        else:
            self.connectBoard()
            print('Cannot send key. Board not connected')

    def sendTempo(self):
        if self.ser.isOpen():
            self.ser.write([int(self.tempo_edit.text())])
        else:
            self.connectBoard()
            print('Cannot send tempo ' + self.tempo_edit.text() + ' | Board not connected')

    def sendInstrument(self):
        if self.ser.isOpen():
            self.ser.write([self.instrument_dict.get(self.current_instrument)])
        else:
            self.connectBoard()
            print('Cannot send instrument ' + self.current_instrument + ' | Board not connected')
        self.constructMainMenu()

    def startGatorscribe(self):
        if(self.ser.isOpen()):
            self.sendTitle()
            self.sendTime()
            self.sendKey()
            self.sendTempo()
            self.sendInstrument()
            self.start_btn.setText("Stop")
            print("Recording!")
        else:
            print("Cannot open serial port")

app = QApplication(sys.argv)
ex = App()
ex.setWindowOpacity(0.99)
sys.exit(app.exec_())


from datetime import datetime, timedelta
import common

client = common.getTransmissionClient('cleanup')
print('Running cleanup')
torrents = client.get_torrents()
delay = common.getConfigParser().getint('cleanup', 'delay')
for torrent in torrents:
    if torrent.isFinished and not torrent.error and datetime.fromtimestamp(torrent.doneDate) + timedelta(days=delay) < datetime.now():
        print('%s is finished and will be removed' % (torrent.name))
        client.remove(torrent.hashString, delete_data=True)


from math import factorial

class Model():
    def __init__(self, lam, mu, s):
        self.lam = lam
        self.mu = mu
        self.ratio = lam/mu
        self.s = s
        self.ro = lam/(s*mu)
        self.setP_cero()
        self.setLq()
        self.L = self.Lq + (self.lam/self.mu)
        self.Wq = self.Lq/self.lam
        self.W = self.Wq +(1/self.mu)

        msg = """
Parameters:
Lamda: {} \t Mu: {} \t server count: {}
Ro: {} \t P0: {}
Lq: {} \t L: {}
Wq: {} \t W: {}
        """.format(self.lam, self.mu, self.s, self.ro, self.P_cero, self.Lq, self.L, self.Wq, self.W)
        self.toString = msg

    def getC(self, n):
        if n < self.s:
            val = ((self.lam/self.mu)**n)/(factorial(n))
        else:
            val = ((self.lam/self.mu)**self.s)/factorial(self.s)
            val *= (self.lam/(self.s*self.mu))**(n-self.s)
        return val

    def setP_cero(self):
        left = 0
        for i in range(self.s):
            left += (self.ratio**i)/factorial(i)
        right = (self.ratio**self.s)/factorial(self.s)
        right *= 1/(1-self.ro)
        self.P_cero = 1/(left+right)

    def setLq(self):
        top = self.P_cero *self.ro*((self.lam/self.mu)**self.s)
        bot = factorial(self.s)*((1-self.ro)**2)
        self.Lq = top/bot

    def __getitem__(self, n):
        if n==0:
            val = 1
        elif 1<=n<self.s:
            val = ((self.ratio**n)/factorial(n))
        elif n>=self.s:
            val = (self.lam/self.mu)**n
            val /= factorial(self.s) * (self.s**(n-self.s))
        val *= self.P_cero
        #result = 'The probability of any server to be busy after {} elements is: {}'.format(n, val)
        return val

    def __str__(self):
        return self.toString


# coding: utf-8

import sys
import os
import matplotlib.pyplot as plt
from math import sqrt, exp
import numpy
import time
import random
from threading import *
lock = RLock()
# Specification du dossier de travail
#os.chdir("/media/mcd/MCD/M1Informatique/S2/TER/DynamiqueMoleculaire/Code_&_Resultat/Test")

class Univexp(Thread):

	fichier="univexp01"
	tecr=0.
	tsor=0.
	n=100

	def __init__(self):
		Thread.__init__(self)
		self.init()
		self.f = open(self.fichier, "w")
		self.wbande()

		self.tecr = self.tecr + self.dti
		self.tsor = self.tecr + self.dtsor

	def run(self):
		while True:
			if abs(self.tecr -self.tstop) > self.dtsor / 2.:
				if abs(self.tecr -self.tsor) > self.dti / 2.:
					self.avance()
					self.ordonne()
					self.tecr = self.tecr + self.dti
					#plt.scatter(self.x, self.v)
					#plt.pause(0.01)
				self.wbande()
				self.tsor = self.tsor + self.dtsor

			self.f.close()
			print ("fin de programme.")

    #************************************************************************************************

	def avance(self):
		r2 = -sqrt(2.)
		tier = 1./3.
		#AA=BB=E=0.

		#calcul de l'acceleration
		a = numpy.zeros((self.m), dtype='d')
		self.eav = self.epolar + 0.5 * self.n
		for i in range(self.ifirst, self.ilast):
			self.eap = self.eav - 1
			a[i] = 0.5 * (self.eav + self.eap)
			self.eav = self.eap

		#les particules avancent de dti
		for i in range(self.ifirst, self.ilast):
			E = a[i]
			AA = (self.x[i] + E + r2 * self.v[i]) * exp(r2 * self.dti)
			BB = 2. * (self.x[i] + E - self.v[i]/r2) * exp(-self.dti/r2)
			self.x[i] = ((AA + BB) * tier) - E
			self.v[i] = (AA * r2 - BB/r2) * tier

			if self.x[i] > self.x[self.m-1]:
				#self.epolar self.epolar + self.gravplas * self.ma[i]
				self.epolar = self.epolar + 1
				self.x[i] = self.x[0] + self.x[i] - self.x[self.m-1]

			if self.x[i] < self.x[0] :
				#self.epolar self.epolar - self.gravplas * self.ma[i]
				self.epolar = self.epolar - 1
				self.x[i] = self.x[self.m-1] + self.x[i] - self.x[0]

 #************************************************************************************************

	def ordonne(self):
		T=[]
		for i in range(self.ifirst, self.ilast):
			T.append([self.x[i],self.v[i],self.name[i]])

		T = self.tri_fusion(T)

		for i in range(self.ifirst, self.ilast):
			self.x[i],self.v[i],self.name[i]=T[i-1][0],T[i-1][1],T[i-1][2]

 #************************************************************************************************
	def wbande(self):
		print("enregistrement a tecr = {:7.3f}".format(self.tecr))

		self.f.write("#   n={:<5d} m={:<5d}  ifirst={:<5d} ilast={:<5d} tecr={:<7.3f} \n".format(self.m, self.n, self.ifirst, self.ilast, self.tecr))

		for i in range(self.ifirst, self.ilast):
			self.f.write("   {:>2.9f}    \t {:>4.9f}     \t {:>5}\n".format(self.x[i],self.v[i],self.name[i]))

		self.f.write("\n\n")

    #************************************************************************************************

	def init(self):
		self.m = self.n + 2
		self.ifirst = 1
		self.ilast=self.n + 1
		self.dti = 0.001
		self.dtsor = 1.
		self.tstop = 15.
		self.gravplas=1.
		self.pvit = 100.

		#allocation pour les tableaux
		self.x = [0] * self.m
		self.v = [0] * self.m
		self.mi = [0] * self.m
		self.ma = [0] * self.m
		self.name = [0] * self.m
		"""self.x = numpy.zeros((self.m), dtype='d')
		self.v = numpy.zeros((self.m), dtype='d')
		self.mi = numpy.zeros((self.m), dtype='d')
		self.ma = numpy.zeros((self.m), dtype='d')
		self.name = numpy.zeros((self.m), dtype='l')"""

		print("simulation :",self.fichier)
		print("{:19.15f} : pas de temps ".format(self.dti))
		print("n={:8d} pvit={:7.1f}".format(self.n, self.pvit))
		print("tstop={:7.1f} dtsor ={:7.1f}".format(self.tstop, self.dtsor))

		# initialisation des "particules-mur"
		self.x[0] = -0.5*self.n
		self.v[0] = 0
		self.mi[0] = 0
		self.ma[0] = 0
		self.name[0] = 0
		self.x[self.m-1] = 0.5*self.n
		self.v[self.m-1] = 0
		self.mi[self.m-1] = 0
		self.ma[self.m-1] = 0
		self.name[self.m-1] = 0

		# initialisation des particules

		with open("univexp01d.d",'r') as fich:
			fich.readline()
			i=1
			while i < self.ilast:
				ligne = fich.readline()
				self.v[i] = float(ligne.split()[1])
				#self.x[i] = float(ligne.split()[0])
				i+=1

		for i in range(self.ifirst, self.ilast):
			self.name[i]=i#Pour la camparaison des deux fichiers sinon i-1
			self.x[i]=self.x[0]+0.5+i-self.ifirst
			#self.v[i]=2.0*self.pvit*(0.5-random.random())
			self.mi[i]=1.0
			self.ma[i]=1.0
		self.epolar=0

		#calage du barycentre à zéro avec une vitesse moyenne  nulle
		"""for v in self.v:
			print("v=",v)
		s=0
		for i in range(self.ifirst,self.ilast):
			s+=self.v[i]
			print("s=",s)"""
		vmoy=sum(self.v[self.ifirst:self.ilast])
		print(("vmoyen = {:2.8f}").format(vmoy))

def main() :
	# Debut du decompte du temps
	start_time = time.time()

	thread_count = 4
	for i in range(thread_count):
		univex = Univexp()
		univex.start()
	univex.join()
	#plt.show()
	# Affichage du temps d execution
	print("Temps d execution : %s secondes" % (time.time() - start_time))
main()
"""import cProfile
import re
cProfile.run('main()')"""

exit()


#!/usr/bin/env python3
import os
import subprocess
import sys
from tools.common import REPO_ROOT, COVERAGE_DIR, SOURCE_DIR
from tools.util import change_dir, get_tests

COVERAGE_THRESHOLD = 100.0

def main():
    # Create build folder if it doesn't exist
    os.makedirs(COVERAGE_DIR, exist_ok=True)

    with change_dir(COVERAGE_DIR):
        # Build with coverage flags
        subprocess.run(['cmake', '-DCMAKE_BUILD_TYPE=Coverage', SOURCE_DIR], check=True)
        subprocess.run(['make', '-j8'], check=True)

        # Run tests
        for test in get_tests(build_dir=COVERAGE_DIR):
            subprocess.run([test], check=True)

        # Run gcovr
        subprocess.run(['gcovr', '-r', REPO_ROOT, '--exclude=.*google*', '--exclude=.*test*',
                        '--fail-under-line={}'.format(COVERAGE_THRESHOLD)], check=True)

if __name__ == '__main__':
    sys.exit(main())


'''
PR 5.1: Terč (reakcia na kliknutie, funkcia)

Zmeňte program tak, aby ste bodky vykresľovali vy (kliknutím myšou sa zobrazí červená bodka s R = 5). Použite funkciu KLIK.

Predtým vykreslite kruhy terča, bodové hodnotenie a nad terč uveďte aktuálne skóre. Nakreslite to tak, aby sa celý obrázok prispôsobil veľkosti plochy (aby sa vždy posunul do stredu).
'''

import tkinter as tk, math

c = tk.Canvas(width=400, height=500, background='grey')
c.pack()

r1, r2, r3 = 50, 100, 150
sx, sy = int(c['width']) // 2, int(c['height']) // 2
score = 0

def terc():
    c.create_oval(sx - r3, sy - r3, sx + r3, sy + r3, fill='black', outline='')
    c.create_oval(sx - r2, sy - r2, sx + r2, sy + r2, fill='white', outline='')
    c.create_oval(sx - r1, sy - r1, sx + r1, sy + r1, fill='black', outline='')

terc()

def osi():
    c.create_line(sx, sy - r3 - 10, sx, sy + r3 + 10, fill='silver')
    c.create_line(sx - r3 - 10, sy, sx + r3 + 10, sy, fill='silver')

osi()

def text():
    c.create_text(sx + 5, sy - r3 + 25, text='10', anchor='w', fill='orange', font='bold')
    c.create_text(sx + 5, sy - r2 + 25, text='30', anchor='w', fill='orange', font='bold')
    c.create_text(sx + 5, sy - r1 + 25, text='50', anchor='w', fill='orange', font='bold')
    c.create_text(sx, 500, text=f"Score: {score}", anchor='s', fill='red', font='sans 18 bold')

text()

def zasah(e):
    global score
    d = ((e.x - sx)**2 + (e.y - sy)**2)**0.5
    c.create_oval(e.x - 5, e.y - 5, e.x + 5, e.y + 5, fill='red', outline='')

    if d <= r1:
        score += 50
    elif d <= r2:
        score += 30
    elif d <= r3:
        score += 10

    c.create_rectangle(0, 476, 400, 500, fill='grey', outline='')
    c.create_text(sx, 500, text=f"Score: {score}", anchor='s', fill='red', font='sans 18 bold')

c.bind('<Button-1>', zasah)


import unittest
from adt import *
from adt_fancy_constructors import *

class TestAdtFancyConstructors(unittest.TestCase):
    def test_sequence_tree_from_regexes(self):
        with self.assertRaises(ValueError):
            sequence_tree_from_regexes([Char('a')]) # need two regexes for a sequence

        self.assertEqual(
            sequence_tree_from_regexes([Char('a'), Char('b')]),
            Sequence(Char('a'), Char('b'))
        )

        self.assertEqual(
            sequence_tree_from_regexes([Char('a'), Char('b'), Char('c')]),
            Sequence(Sequence(Char('a'), Char('b')), Char('c'))
        )

        self.assertEqual(
            sequence_tree_from_regexes([Char('a'), Char('b'), Char('c'), Char('d')]),
            Sequence(Sequence(Sequence(Char('a'), Char('b')), Char('c')), Char('d'))
        )

    def test_char_sequence_from_str(self):
        self.assertEqual(
            char_sequence_from_str('abcd'),
            Sequence(Sequence(Sequence(Char('a'), Char('b')), Char('c')), Char('d'))
        )


import build_js
import build_html
import collections

ytd_return = collections.namedtuple('ytd_return', 'symbol ytd_return')
stock_data = collections.namedtuple('stock_data', 'symbol name ')

top_stock_1_ret = ytd_return('AAPL', 23.34)
top_stock_2_ret = ytd_return('NFLX', 12.34)

bottom_stock_1_ret = ytd_return('IBM', -43.34)
bottom_stock_2_ret = ytd_return('CCJ', -12.34)

top_stock_1_data = stock_data('AAPL', 'Apple Inc.')
top_stock_2_data = stock_data('NFLX', 'Netflix Inc.')

bottom_stock_1_data = stock_data('IBM', 'International Business Machines')
bottom_stock_2_data = stock_data('CCJ', 'Cameco Corp.')

bottom_list = [bottom_stock_1_ret, bottom_stock_2_ret]
top_list = [top_stock_1_ret, top_stock_2_ret]
dict_data = {}
dict_data[top_stock_1_data.symbol] = top_stock_1_data
dict_data[top_stock_2_data.symbol] = top_stock_2_data
dict_data[bottom_stock_1_data.symbol] = bottom_stock_1_data
dict_data[bottom_stock_2_data.symbol] = bottom_stock_2_data

script_list = [build_js.build_javascript_files(top_list, bottom_list)]
html_file = build_html.build_html_file(top_list, bottom_list, dict_data, script_list)
build_html.launch_page(html_file)


import tkinter
import tkinter.font
import random

lotto_num = range(1,46)

def buttonClick():
    for i in range(5):
        lottoPick = map(str,random.sample(lotto_num,6))
        lottoPick = ','.join(lottoPick)
        lottoPick = str(i+1) + "회: " + lottoPick
        print(lottoPick)
        listbox.insert(i, lottoPick)
    listbox.pack()

window=tkinter.Tk()
window.title("lotto")
window.geometry("400x200+800+300")
window.resizable(False, False)

button = tkinter.Button(window, overrelief="solid",text="번호확인", width=15, command=buttonClick, repeatdelay=1000, repeatinterval=100)
button.pack()

font = tkinter.font.Font(size = 20)
listbox = tkinter.Listbox(window, selectmode='extended', height=5, font=font)
listbox.insert(0, "1회:")
listbox.insert(1, "2회:")
listbox.insert(2, "3회:")
listbox.insert(3, "4회:")
listbox.insert(4, "5회:")
listbox.pack()

window.mainloop()

파이썬과 40개의 작품들/31. 로또번호 생성기 GUI 프로그램 만들기/main31-3.py


import time as t
import requests
from math import log10

n = "https://projecteuler.net/project/resources/p099_base_exp.txt"
data = [list(map(int,d.split(","))) for d in requests.get(n).text.splitlines()]

cur = t.monotonic()

conv = lambda x: x[1]*log10(x[0])
cur = t.time()
power = [conv(d) for d in data]
print(power.index(max(power)))

print(t.time()-cur)