import random
import time
import json
import discord

class character():
    # Parameterized constructor for character class object
    def __init__(self, name, level, battleclass):
        self.name = name
        self.level = level
        self.battleclass = battleclass
        self.stats = [] # Possible dict conversion with format: {"Health" : "" , "Attack" : "" , "Defense" : "" , "Speed" : "" , "Critical" : ""}
        self.health = 100

        #  Assigns character object stats variable based on characterClass
        if battleclass == "warrior":
            self.attack = random.randint(1, 20)
            self.defense = random.randint(20, 20)
            self.speed = random.randint(1, 20)
            self.critical = random.randint(1, 20)
            self.stats.insert(0, self.health)
            self.stats.insert(1, self.attack)
            self.stats.insert(2, self.defense)
            self.stats.insert(3, self.speed)
            self.stats.insert(4, self.critical)

        elif battleclass == "ranger":
            self.attack = random.randint(1, 20)
            self.defense = random.randint(1, 20)
            self.speed = random.randint(20, 20)
            self.critical = random.randint(1, 20)
            self.stats.insert(0, self.health)
            self.stats.insert(1, self.attack)
            self.stats.insert(2, self.defense)
            self.stats.insert(3, self.speed)
            self.stats.insert(4, self.critical)

        elif battleclass == "castor":
            self.attack = random.randint(20, 20)
            self.defense = random.randint(1, 20)
            self.speed = random.randint(1, 20)
            self.critical = random.randint(1, 20)
            self.stats.insert(0, self.health)
            self.stats.insert(1, self.attack)
            self.stats.insert(2, self.defense)
            self.stats.insert(3, self.speed)
            self.stats.insert(4, self.critical)

    # Prints the details of the character object
    def printDetails(self):
        embed = discord.Embed(title="Character Stats:", color=0x00ff00)
        embed.set_thumbnail(url="https://orig00.deviantart.net/f025/f/2017/277/f/2/ffbe___cloud_strife_gif_2_by_zerolympiustrife-dbpjw5t.gif")
        embed.add_field(name="Attack", value=str(self.attack), inline=True)
        embed.add_field(name="Defense", value=str(self.defense), inline=True)
        embed.add_field(name="Speed", value=str(self.speed), inline=True)
        embed.add_field(name="Luck", value=str(self.critical), inline=True)
        embed.add_field(name="Level", value=str(self.level), inline=True)
        return embed

    # Series of accessors and mutators for character objects
    def setName(self, name):
        self.name = name

    def getName(self):
        return self.name

    def setLevel(self, level):
        self.level = level

    def getLevel(self):
        return self.level

    def setBattleclass(self, battleclass):
        self.battleclass = battleclass

    def getBattleclass(self):
        return self.battleclass

    # Returns a specific stat based on index ({0 - 4} / {Health - Critical})
    def getStats(self, value):
        return self.stats[value:value + 1]

# Begin user adventure with character object creation
# characterName = input("\nEnter character name:\n")
#
#
# tempLevel = int(input("\nEnter character level:\n"))
# if tempLevel != 1:
#     boolLevelFlag = False
# else:
#     characterLevel = tempLevel
#     boolLevelFlag = True
#
# # Validate whether user has access to override character default level
# while not boolLevelFlag:
#     print("Input Alert: You are trying to create a character level higher than 1.")
#     passwordToken = input("\nEnter your admin password:\n")
#
#     if passwordToken == "admin":
#         print("Access Granted!")
#         characterLevel = tempLevel
#         boolLevelFlag = True
#
#     else:
#         print("Access Denied!")
#         tempLevel = 1
#         characterLevel = tempLevel
#         boolLevelFlag = True
#
#
# print("\nValid battle classes:"
#       "\nWarrior: Specializes in defense."
#       "\nRanger: Specializes in speed."
#       "\nCastor: Specializes in attack.")
#
# tempBattleClass = input("\nEnter your battle class:\n")
#
# # Validate user input for battle class
# while not (tempBattleClass.lower() == "warrior" or tempBattleClass.lower() == "ranger"
#            or tempBattleClass.lower() == "castor"):
#     print("You must select a valid battle class.")
#     tempBattleClass = input("\nEnter your battle class:\n")
#
# characterClass = tempBattleClass.lower()

# Function to create one of 3 random enemies using RNG
def randomEnemy(level):
    rng = random.randint(1, 3)
    if rng == 1:
        characterName = "Goblin"
        characterLevel = level
        characterClass = "warrior"

    elif rng == 2:
        characterName = "Skeleton"
        characterLevel = level
        characterClass = "ranger"

    else:
        characterName = "Witch"
        characterLevel = level
        characterClass = "castor"

    return character(characterName, characterLevel, characterClass)

# ------------------ #
# UNDER CONSTRUCTION #
# ------------------ #

# Begin user battle sequence
# def battleSequence(character, enemy):
#     print("\n" + enemy.name + " appears and challenges " + character.name + " to battle!")
#     enemy.printDetails()
#
#     while character.health > 0 and enemy.health > 0:
#         if character.health > 100:
#             character.health = 100
#
#         if enemy.health > 100:
#             enemy.health = 100
#
#         print("\n" + character.name + "'s Health: " + str(character.health))
#         print(enemy.name + "'s Health: " + str(enemy.health))
#
#         print("\nNext move:\n1. Attack\n2. Defend\n3. Flee\n")
#         choice = int(input("\nEnter the number of your choice:\n"))
#
#         # Validate user choice
#         while not (choice == 1 or choice == 2 or choice == 3):
#             print("\nPlease enter a valid choice!\n")
#             choice = int(input("\nEnter the number of your choice:\n"))
#
#         # Determine enemy turn
#         rng = random.randint(1, 2)
#
#         # Situation 1: character -> ATTACK and enemy -> ATTACK
#         if choice == 1 and rng == 1:
#
#             # Situation 1a: character attacks first
#             if character.speed > enemy.speed:
#                 print(character.name + " attacks first with Speed: " + str(character.speed) + "!")
#                 enemyDmgTaken = character.attack
#                 critRoll = random.randint(1, 100)
#
#                 if critRoll <= character.critical:
#                     print("It's super effective!")
#                     enemyDmgTaken += (character.attack / 2)
#
#                 print(character.name + " strikes " + enemy.name + " for " + str(enemyDmgTaken) + " points of damage!")
#                 enemy.health -= enemyDmgTaken
#                 print(enemy.name + "'s Health: " + str(enemy.health) + "\n")
#
#                 # enemy attacks second
#                 print(enemy.name + " attacks second with Speed: " + str(enemy.speed) + "!")
#                 characterDmgTaken = enemy.attack
#                 critRoll = random.randint(1, 100)
#
#                 if critRoll <= enemy.critical:
#                     print("It's super effective!")
#                     characterDmgTaken += (enemy.attack / 2)
#
#                 print(enemy.name + " strikes " + character.name + " for " + str(characterDmgTaken) + " points of damage!")
#                 character.health -= characterDmgTaken
#                 print(character.name + "'s Health: " + str(character.health) + "\n")
#
#             # Situation 1b: enemy attacks first
#             else:
#                 print(enemy.name + " attacks first with Speed: " + str(enemy.speed) + "!")
#                 characterDmgTaken = enemy.attack
#                 critRoll = random.randint(1, 100)
#
#                 if critRoll <= enemy.critical:
#                     print("It's super effective!")
#                     characterDmgTaken += (enemy.attack / 2)
#
#                 print(enemy.name + " strikes " + character.name + " for " + str(characterDmgTaken) + " points of damage!")
#                 character.health -= characterDmgTaken
#                 print(character.name + "'s Health: " + str(character.health) + "\n")
#
#                 # character attacks second
#                 print(character.name + " attacks second with Speed: " + str(character.speed) + "!")
#                 enemyDmgTaken = character.attack
#                 critRoll = random.randint(1, 100)
#
#                 if critRoll <= character.critical:
#                     print("It's super effective!")
#                     enemyDmgTaken += (character.attack / 2)
#
#                 print(character.name + " strikes " + enemy.name + " for " + str(enemyDmgTaken) + " points of damage!")
#                 enemy.health -= enemyDmgTaken
#                 print(enemy.name + "'s Health: " + str(enemy.health) + "\n")
#
#         # Situation 2: character -> ATTACK and enemy -> DEFEND
#         if choice == 1 and rng == 2:
#
#             # character damages enemy after factoring in enemy's defense
#             print(character.name + " attacks " + enemy.name + "!")
#             enemyDmgTaken = character.attack
#             critRoll = random.randint(1, 100)
#             if critRoll <= character.critical:
#                 print("It's super effective!")
#                 enemyDmgTaken += (character.attack / 2)
#
#             print(enemy.name + " defends against " + character.name + "'s attack!")
#             enemyDmgTaken -= enemy.defense
#             critRoll = random.randint(1, 100)
#             if critRoll <= enemy.critical:
#                 print("It's super effective!")
#                 enemyDmgTaken -= (enemy.defense / 2)
#
#             if enemyDmgTaken > 0:
#                 print(character.name + " strikes " + enemy.name + " for " + str(enemyDmgTaken) + " points of damage!")
#                 enemy.health -= enemyDmgTaken
#
#             elif enemyDmgTaken < 0:
#                 print(enemy.name + " blocks all damage from " + character.name + "!")
#                 print("+5 Health to " + enemy.name)
#                 enemy.health += 5
#
#             else:
#                 print(enemy.name + " blocks all damage from " + character.name + "!")
#
#         # Situation 3: character -> DEFEND and enemy -> ATTACK
#         if choice == 2 and rng == 1:
#
#             # enemy damages character after factoring in character's defense
#             print(enemy.name + " attacks " + character.name + "!")
#             characterDmgTaken = enemy.attack
#             critRoll = random.randint(1, 100)
#             if critRoll <= enemy.critical:
#                 print("It's super effective!")
#                 characterDmgTaken += (enemy.attack / 2)
#
#             print(character.name + " defends against " + enemy.name + "'s attack!")
#             characterDmgTaken -= character.defense
#             critRoll = random.randint(1, 100)
#             if critRoll <= character.critical:
#                 print("It's super effective!")
#                 characterDmgTaken -= (character.defense / 2)
#
#             if characterDmgTaken > 0:
#                 print(enemy.name + " strikes " + character.name + " for " + str(characterDmgTaken) + " points of damage!")
#                 character.health -= characterDmgTaken
#
#             elif characterDmgTaken < 0:
#                 print(character.name + " blocks all damage from " + enemy.name + "!")
#                 print("+5 Health to " + character.name)
#                 character.health += 5
#
#             else:
#                 print(character.name + " blocks all damage from " + enemy.name + "!")
#
#             print(character.name + "'s Health: " + str(character.health))
#
#         # Situation 4: character -> DEFEND and character -> DEFEND
#         if choice == 2 and rng == 2:
#
#             print(character.name + " and " + enemy.name +" both defend against nothing?!\n")
#             print("Both adversaries begin to taunt each other from a distance!")
#             print("The adrenaline from taunting causes both entities to heal a small amount of health!")
#             print("\n+5 Health to both " + character.name + " and " + enemy.name + "!")
#             character.health += 5
#             enemy.health += 5
#
#         # Situation 5: character -> FLEE and enemy -> ATTACK
#         if choice == 3 and rng == 1:
#
#             print(character.name + " attempts to flee from the battle!\n")
#             time.sleep(2)
#
#             # Roll to determine outcome of attempted FLEE
#             fleeRoll = random.randint(1, 100)
#             if fleeRoll <= character.speed:
#                 print("Success!\n")
#                 print(character.name + " flees from the battle to live another day!")
#                 enemy.health = 0
#
#             else:
#                 print("Fail!\n")
#                 print(character.name + " tries to flee but trips and lands on their face...")
#                 print("-5 Health to " + character.name + "\n")
#                 character.health -= 5
#
#                 # enemy attacks character
#                 print(enemy.name + " attacks " + character.name + "!")
#                 characterDmgTaken = enemy.attack
#                 critRoll = random.randint(1, 100)
#
#                 if critRoll <= enemy.critical:
#                     print("It's super effective!")
#                     characterDmgTaken += (enemy.attack / 2)
#
#                 print(enemy.name + " strikes " + character.name + " for " + str(characterDmgTaken) + " points of damage!")
#                 character.health -= characterDmgTaken
#                 print(character.name + "'s Health: " + str(character.health) + "\n")
#
#         # Situation 6: character -> FLEE and enemy -> DEFEND
#         if choice == 3 and rng == 2:
#
#             print(character.name + " attempts to flee from the battle!\n")
#             time.sleep(2)
#
#             # Roll to determine outcome of attempted FLEE
#             fleeRoll = random.randint(1, 100)
#             if fleeRoll <= character.speed:
#                 print("Success!\n")
#                 print(character.name + " flees from the battle to live another day!")
#                 enemy.health = 0
#
#             else:
#                 print("Fail!\n")
#                 print(character.name + " tries to flee but trips and lands on their face...")
#                 print(enemy.name + " laughs at " + character.name + "'s failure!\n")
#                 print("The shame causes " + character.name + " to lose additional health...")
#                 print("-10 Health to " + character.name)
#                 character.health -= 10
#                 print(character.name + "'s Health: " + str(character.health) + "\n")
#
#
#     print("\nEND OF BATTLE!\n")
#
#
#     if character.health > 0:
#         print(character.name + " wins the battle against " + enemy.name + "!\n")
#
#     else:
#         print(character.name + " loses the battle against " + enemy.name + "!\n")

# Reading from JSON file
#  with open("data.txt") as json_file:
#      data = json.load(json_file)
#      for x in data['character']:
#          print("Character Name: " + x["name"])
#          print("Character Level: " + x["level"])
#          print("Character Class: ") + x["battleclass"]
#          print("Character Stats: " + x["stats"] + "\n")

# Saving to JSON file
#  data = {}
#  data['character'] = []
#  data['character'].append({
#      'name': characterName,
#      'level': characterLevel,
#      'battleclass': characterClass,
#      'stats': character.stats
#  })
#
#  with open("data.txt", "w") as outfile:
#      json.dump(data, outfile)

# char1 = character(characterName, characterLevel, characterClass)
# char1.printDetails()
#
# time.sleep(2) # Pause between displaying character details
#
# enemy1 = randomEnemy()
#
# time.sleep(2) # Pause before first battle sequence
#
# battleSequence(char1, enemy1)


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

from django.contrib import admin
from app import models

# Register your models here.
def register_admin(model):
    """Turn admin.site.register into a decorator."""
    def wrapper(klass):
        admin.site.register(model, klass)
        return klass
    return wrapper

@admin.register(models.journal_submission)
class JournalSubmissionAdmin(admin.ModelAdmin):
    list_display = ('user','created')
    list_filter = ('created',)
    search_fields = ['user']

@admin.register(models.embed)
class EmbedAdmin(admin.ModelAdmin):
    list_display = ('summary','url','source','message')
    list_filter = ('source',)
    search_fields = ['summary']

@admin.register(models.question)
class QuestionAdmin(admin.ModelAdmin):
    list_display = ('question','category')
    list_filter = ('category',)
    search_fields = ['question']


#!/usr/bin/python3
"""
Test for the City class
"""

from datetime import datetime
import inspect
from models import city
from models.base_model import BaseModel
import pep8
import unittest
City = city.City

c = "created_at"
u = "updated_at"

class TestStateDocStyle(unittest.TestCase):
    """test for documentation and pep8 style"""
    def setUp(cls):
        """set up for methods"""
        cls.city_func = inspect.getmembers(City, inspect.isfunction)

    def test_pep8(self):
        """test pep8 in City class"""
        pep8s = pep8.StyleGuide(quiet=True)
        result = pep8s.check_files(['models/city.py'])
        self.assertEqual(result.total_errors, 0,
                         "Found code style errors and warnings.")

    def test_pep8_test(self):
        """test pep8 for test to City class"""
        pep8s = pep8.StyleGuide(quiet=True)
        result = pep8s.check_files(['tests/test_models/test_city.py'])
        self.assertEqual(result.total_errors, 0,
                         "Found code style errors and warnings.")

    def test_module_doc(self):
        """test for the doc in module"""
        self.assertIsNot(city.__doc__, None,
                         "city.py without a docstring")
        self.assertTrue(len(city.__doc__) >= 1,
                        "city.py without a docstring")

    def test_class_doc(self):
        """test for class documentation"""
        self.assertIsNot(City.__doc__, None,
                         "City class without docstring")
        self.assertTrue(len(City.__doc__) >= 1,
                        "City class without docstring")

    def test_func_doc(self):
        """test for doc in methods"""
        for func in self.city_func:
            self.assertIsNot(func[1].__doc__, None,
                             "{:s} docstring needed in method".format(func[0]))
            self.assertTrue(len(func[1].__doc__) >= 1,
                            "{:s} docstring needed in method".format(func[0]))

class TestCityClass(unittest.TestCase):
    """test for class"""
    def test_is_subclass(self):
        """test for a subclass of BaseModel"""
        city = City()
        self.assertIsInstance(city, BaseModel)
        self.assertTrue(hasattr(city, "id"))
        self.assertTrue(hasattr(city, "created_at"))
        self.assertTrue(hasattr(city, "updated_at"))

    def test_state_id(self):
        """thest for for attribute state id for City class"""
        city = City()
        self.assertTrue(hasattr(city, "state_id"))
        self.assertEqual(city.state_id, "")

    def test_name(self):
        """test for the state_id attr"""
        city = City()
        self.assertTrue(hasattr(city, "name"))
        self.assertEqual(city.name, "")

    def test_to_dict_values(self):
        """test the values in dict"""
        time = "%Y-%m-%dT%H:%M:%S.%f"
        city = City()
        new_dict = city.to_dict()
        self.assertEqual(new_dict["__class__"], "City")
        self.assertEqual(type(new_dict[c]), str)
        self.assertEqual(type(new_dict[u]), str)
        self.assertEqual(new_dict[c], city.created_at.strftime(time))
        self.assertEqual(new_dict[u], city.updated_at.strftime(time))

    def test_str(self):
        """test for output str method"""
        city = City()
        string = "[City] ({}) {}".format(city.id, city.__dict__)
        self.assertEqual(string, str(city))


# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License.  You may obtain a copy of
# the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations under
# the License.
# ==============================================================================
"""Test IOTensor"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os
import math
import numpy
import pandas
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error

import tensorflow as tf
if not (hasattr(tf, "version") and tf.version.VERSION.startswith("2.")):
  tf.compat.v1.enable_eager_execution()
from tensorflow.keras.models import Sequential # pylint: disable=wrong-import-position
from tensorflow.keras.layers import Dense # pylint: disable=wrong-import-position
from tensorflow.keras.layers import LSTM # pylint: disable=wrong-import-position

import tensorflow_io as tfio # pylint: disable=wrong-import-position

def test_window():
  """test_window"""
  value = [[e] for e in range(100)]
  value = tfio.IOTensor.from_tensor(tf.constant(value))
  value = value.window(3)
  expected_value = [[e, e+1, e+2] for e in range(98)]
  assert numpy.all(value.to_tensor().numpy() == expected_value)

  v = tfio.IOTensor.from_tensor(tf.constant([1, 2, 3, 4, 5]))
  v = v.window(3)
  assert numpy.all(v.to_tensor().numpy() == [[1, 2, 3], [2, 3, 4], [3, 4, 5]])

def test_window_to_dataset():
  """test_window_to_dataset"""
  value = [[e] for e in range(100)]
  value = tfio.IOTensor.from_tensor(tf.constant(value))
  value = value.window(3)
  expected_value = [[e, e+1, e+2] for e in range(98)]
  dataset = value.to_dataset()
  dataset_value = [d.numpy().tolist() for d in dataset]
  assert numpy.all(dataset_value == expected_value)

def test_io_tensor_from_tensor_with_sklearn():
  """test_io_tensor_from_tensor_with_sklearn"""

  # The test example is based on:
  # https://machinelearningmastery.com/time-series-prediction-lstm-recurrent-neural-networks-python-keras/
  #
  # Both IOTensor and pandas/sklearn are used, to show the usage of IOTensor.
  airline_passengers_csv = os.path.join(
      os.path.dirname(os.path.abspath(__file__)),
      "test_csv", "airline-passengers.csv")

  dataframe = pandas.read_csv(
      airline_passengers_csv, usecols=[1], engine='python')

  numpy.random.seed(7)

  dataset = dataframe.values
  dataset = dataset.astype('float32')

  scaler = MinMaxScaler(feature_range=(0, 1))
  dataset = scaler.fit_transform(dataset)

  # split into train and test sets
  train_size = int(len(dataset) * 0.67)
  # test_size = len(dataset) - train_size
  train, test = dataset[0:train_size, :], dataset[train_size:len(dataset), :]
  print(len(train), len(test))

  # convert an array of values into a dataset matrix
  def create_dataset(dataset, look_back=1):
    data_x, data_y = [], []
    for i in range(len(dataset)-look_back-1):
      a = dataset[i:(i+look_back), 0]
      data_x.append(a)
      data_y.append(dataset[i + look_back, 0])
    return numpy.array(data_x), numpy.array(data_y)

  # reshape into X=t and Y=t+1
  look_back = 1
  train_x, train_y = create_dataset(train, look_back)
  test_x, test_y = create_dataset(test, look_back)

  # reshape input to be [samples, time steps, features]
  train_x = numpy.reshape(train_x, (train_x.shape[0], 1, train_x.shape[1]))
  test_x = numpy.reshape(test_x, (test_x.shape[0], 1, test_x.shape[1]))

  # create and fit the LSTM network
  model = Sequential()
  model.add(LSTM(4, input_shape=(1, look_back)))
  model.add(Dense(1))
  model.compile(loss='mean_squared_error', optimizer='adam')

  #model.fit(train_x, train_y, epochs=100, batch_size=1, verbose=2)

  #########################################
  ############ IOTensor Match: ############
  train_size = int(len(dataset) * 0.67)
  #test_size = len(dataset) - train_size
  dataset = dataframe.values
  dataset = dataset.astype('float32')

  scaler = MinMaxScaler(feature_range=(0, 1))
  scaler.fit(tfio.IOTensor.from_tensor(dataset))

  train, test = dataset[0:train_size, :], dataset[train_size:len(dataset), :]

  train_dataset = tfio.IOTensor.from_tensor(train)

  train_dataset = tfio.IOTensor.from_tensor(scaler.transform(train_dataset))

  train_dataset = train_dataset.window(look_back + 1)
  train_dataset = train_dataset.to_dataset()
  train_dataset = train_dataset.map(lambda e: tf.split(e, [look_back, 1]))
  train_dataset = train_dataset.map(lambda x, y: (tf.reshape(x, [1, look_back]), y))
  print("train_dataset: ", train_dataset)

  test_dataset = tfio.IOTensor.from_tensor(test)

  test_dataset = tfio.IOTensor.from_tensor(scaler.transform(test_dataset))

  test_dataset = test_dataset.window(look_back + 1)
  test_dataset = test_dataset.to_dataset()
  test_dataset = test_dataset.map(lambda e: tf.split(e, [look_back, 1]))
  test_dataset = test_dataset.map(lambda x, y: (tf.reshape(x, [1, look_back]), y))

  model.fit(train_dataset.batch(1), epochs=100, verbose=2)
  #########################################

  # make predictions
  train_predict = model.predict(train_x)
  test_predict = model.predict(test_x)

  #########################################
  ############ IOTensor Match: ############
  train_x_dataset = train_dataset.map(lambda x, y: x)
  test_x_dataset = test_dataset.map(lambda x, y: x)

  train_x_dataset = train_x_dataset.batch(1)
  test_x_dataset = test_x_dataset.batch(1)

  train_predict_dataset = model.predict(train_x_dataset)
  test_predict_dataset = model.predict(test_x_dataset)

  train_predict_dataset_value = [d.tolist() for d in train_predict_dataset]
  test_predict_dataset_value = [d.tolist() for d in test_predict_dataset]

  # Note: train_predict_dataset_value and test_predict_dataset_value
  # have one extra compared with original implementation.
  train_predict_dataset_value = train_predict_dataset_value[:-1]
  test_predict_dataset_value = test_predict_dataset_value[:-1]
  assert numpy.allclose(train_predict_dataset_value, train_predict.tolist())
  assert numpy.allclose(test_predict_dataset_value, test_predict.tolist())
  #########################################

  # invert predictions
  train_predict = scaler.inverse_transform(train_predict)
  train_y = scaler.inverse_transform([train_y])
  test_predict = scaler.inverse_transform(test_predict)
  test_y = scaler.inverse_transform([test_y])
  # calculate root mean squared error
  train_score = math.sqrt(mean_squared_error(train_y[0], train_predict[:, 0]))
  print('Train Score: %.2f RMSE' % (train_score))
  test_score = math.sqrt(mean_squared_error(test_y[0], test_predict[:, 0]))
  print('Test Score: %.2f RMSE' % (test_score))

  # shift train predictions for plotting
  train_predict_plot = numpy.empty_like(dataset)
  train_predict_plot[:, :] = numpy.nan
  train_predict_plot[look_back:len(train_predict)+look_back, :] = train_predict
  # shift test predictions for plotting
  test_predict_plot = numpy.empty_like(dataset)
  test_predict_plot[:, :] = numpy.nan
  test_predict_plot[
      len(train_predict)+(look_back*2)+1:len(dataset)-1, :] = test_predict

  # plot baseline and predictions
  #plt.plot(scaler.inverse_transform(dataset))
  #plt.plot(train_predict_plot)
  #plt.plot(test_predict_plot)
  #plt.show()


from PyQt5 import QtWidgets

class Settings(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)
        self.initUI()

    def initUI(self):

        vbox = QtWidgets.QVBoxLayout()

        wrap_vbox = QtWidgets.QVBoxLayout()
        lbl = QtWidgets.QLabel('test lbl for db2')
        wrap_vbox.addWidget(lbl)

        # Возвращаем в основной макет
        self.out_window = wrap_vbox


from sqlalchemy import create_engine, MetaData, Table
import json
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.style.use('ggplot')

def get_data(db_url, table_name='exp_table', data_column_name='datastring', statuses=[3,4,5,7], exclude = []):
    # boilerplace sqlalchemy setup
    engine = create_engine(db_url)
    metadata = MetaData()
    metadata.bind = engine
    table = Table(table_name, metadata, autoload=True)
    # make a query and loop through
    s = table.select()
    rows = s.execute()

    data = []

    for row in rows:
        # only use subjects who completed experiment and aren't excluded
        if row['status'] in statuses and row['uniqueid'] not in exclude:
            data.append(row[data_column_name])

    # Now we have all participant datastrings in a list.
    # Let's make it a bit easier to work with:

    # parse each participant's datastring as json object
    # and take the 'data' sub-object
    data = [json.loads(part)['data'] for part in data]

    # insert uniqueid field into trialdata in case it wasn't added
    # in experiment:
    for part in data:
        for record in part:
            record['trialdata']['uniqueid'] = record['uniqueid']

    # flatten nested list so we just have a list of the trialdata recorded
    # each time psiturk.recordTrialData(trialdata) was called.
    data = [record['trialdata'] for part in data for record in part]

    # Put all subjects' trial data into a dataframe object from the
    # 'pandas' python library: one option among many for analysis
    data_frame = pd.DataFrame(data)

    return data_frame

def get_learning_curve(data, block_size=30, plot=True):
    data_len = len(data)
    n_blocks = data_len/block_size
    block_indices = np.linspace(0, data_len, num=n_blocks+1, dtype=int)
    p_correct = np.zeros(n_blocks)
    for block in range(n_blocks):
        p_correct[block] = np.mean(data['hit'][block_indices[block]:block_indices[block+1]])
    if plot:
        pc, = plt.plot(range(1, n_blocks+1), p_correct)
        plt.xlabel("block", fontsize=30)
        plt.ylabel("% correct", fontsize=30)
        plt.ylim(0, 1)
        plt.xticks(range(1, n_blocks+1))
    return p_correct

def get_avg_samps(data, block_size=30, plot=True, att_feature='locSamps'):
    data_len = len(data)
    n_blocks = data_len/block_size
    block_indices = np.linspace(0, data_len, num=n_blocks+1, dtype=int)
    att_p = np.zeros(n_blocks)
    for block in range(n_blocks):
        att_p[block] = np.mean(data[att_feature][block_indices[block]:block_indices[block+1]])
    if plot:
        pc, = plt.plot(range(1, n_blocks+1), att_p)
        plt.xlabel("block", fontsize=30)
        plt.ylabel("Avg. # of samples", fontsize=30)
        plt.ylim(0, 10)
        plt.xticks(range(1, n_blocks+1))
    return att_p

def get_noise_estimates(data, plot=True):
	color_noise = data[data['colorNoise'].notnull()]['colorNoise']
	color_noise = color_noise[color_noise.keys()[0]]
	loc_noise = data[data['colorNoise'].notnull()]['locNoise']
	loc_noise = loc_noise[loc_noise.keys()[0]]
	if plot:
		color, = plt.plot(color_noise, label="Color")
		loc, = plt.plot(loc_noise, label="Location")
		plt.xlabel("# samples", fontsize=30)
		plt.ylabel("$\sigma$", fontsize=30)
		plt.ylim(0, 1)
		plt.legend(handles=[loc, color], loc=2, bbox_to_anchor=(1.05, 1))
	return loc_noise, color_noise


from flask import (
    current_app as app,
    url_for,
    render_template,
    redirect,
    request
)
from .forms import CalcularForm
from .calculos import calculo_juros_compostos
from .plotly_wrapper import create_plot_calculos
import pandas as pd

@app.route("/", methods=['GET', 'POST'])
def calcular():
    calcularForm = CalcularForm()

    if calcularForm.validate_on_submit():
        montante_inicial = calcularForm.montante_inicial.data
        aportes_mensais = calcularForm.aportes_mensais.data
        taxa_mensal = calcularForm.taxa_mensal.data
        tempo_meses = calcularForm.tempo_meses.data

        return redirect(url_for('resultado', montante=montante_inicial,aportes=aportes_mensais, taxa=taxa_mensal, tempo=tempo_meses))
    return render_template(
        'index.html',
        form=calcularForm
    )

@app.route("/resultado")

def resultado():
    capital_mensal = calculo_juros_compostos(
        montante_inicial=float(request.args['montante']),
        aportes_mensais=float(request.args['aportes']),
        taxa_mensal=float(request.args['taxa']),
        tempo_meses=int(request.args['tempo'])
    )
    plot = create_plot_calculos(round(capital_mensal, 2))
    return render_template(
        "resultado.html",
        montante_inicial=round(float(request.args['montante']), 2),
        capital_final=round(capital_mensal['Capital'].iloc[-1], 2),
        juros_acumulados=round(capital_mensal['Juros Acumulados'].iloc[-1], 2),
        total_aportado=round(capital_mensal['Valor Aportado'].iloc[-1], 2),
        plot=plot
    )

@app.route("/favicon.ico")
def favicon():
    return app.send_static_file('favicon.ico')


import redis

class RedisClient(object):
    """
    Redis客户端
    """
    _CLIENT = None

    @classmethod
    def get_client(cls):
        """
        获取客户端
        :return:
        """
        if cls._CLIENT:
            return cls._CLIENT

        pool = redis.ConnectionPool(host='localhost', port=6379, db=0)

        cls._CLIENT = redis.StrictRedis(connection_pool=pool)
        return cls._CLIENT


import unittest, sys
sys.path.extend(['.','..','../..','py'])

import h2o, h2o_cmd, h2o_import as h2i, h2o_browse as h2b
from h2o_test import find_file, dump_json, verboseprint

class Basic(unittest.TestCase):
    def tearDown(self):
        h2o.check_sandbox_for_errors()

    @classmethod
    def setUpClass(cls):
        h2o.init()

    @classmethod
    def tearDownClass(cls):
        h2o.tear_down_cloud()

    def test_simple2(self):
        # h2o-dev doesn't take ../.. type paths? make find_file return absolute path
        a_node = h2o.nodes[0]

        # import_result = a_node.import_files(path=find_file("smalldata/logreg/prostate.csv"))
        import_result = a_node.import_files(path=find_file("smalldata/poker/poker-hand-testing.data"))
        # print dump_json(import_result)

        k = import_result['keys'][0]
        # frames_result = a_node.frames(key=k[0], len=5)

        frames_result = a_node.frames(key=k)

        frame = frames_result['frames'][0]
        byteSize = frame['byteSize']
        rows = frame['rows']
        columns = frame['columns']
        for c in columns:
            label = c['label']
            missing = c['missing']
            stype = c['type']
            zeros = c['zeros']
            domain = c['domain']

        # print dump_json(frame)

        # how do you parse multiple files
        parse_result = a_node.parse(key=k)

        frame = parse_result['frames'][0]
        hex_key = frame['key']['name']

        verboseprint(hex_key, ":", dump_json(parse_result))

if __name__ == '__main__':
    h2o.unit_main()


# -*- coding: utf-8 -*-

from flask import Blueprint, redirect, url_for, render_template, request
from auth import admin_required
from apps.tenancy.admin.forms import TenancyInfoForm, TenancyForm
from apps.tenancy.models import TenancyInfo, Tenancy
from google.appengine.ext import blobstore
from apps.utils.blobstore import get_uploads
import os
from apps.file.models import File

mod = Blueprint(
    'tenancy.admin',
    __name__,
    template_folder='templates',
    url_prefix='/admin/services/tenancy'
)

@mod.route('/', methods=['GET', 'POST'], endpoint='index')
@admin_required
def index():
    tenancy_info = TenancyInfo.get_master_db()
    form = TenancyInfoForm(obj=tenancy_info)
    if request.method == 'POST' and form.validate_on_submit():
        form.populate_obj(tenancy_info)
        tenancy_info.put()
        return redirect(url_for('tenancy.admin.index'))
    return render_template(
        'tenancy/admin/index.html',
        index_active='active',
        form=form
    )

@mod.route('/tenancy/', methods=['GET'])
@admin_required
def tenancy():
    tenancies = Tenancy.query()
    return render_template(
        'tenancy/admin/tenancy.html',
        tenancy_active='active',
        tenancies=tenancies
    )

@mod.route('/tenancy/add/', methods=['GET', 'POST'], endpoint='add')
@admin_required
def tenancy_add():
    form = TenancyForm()
    url = blobstore.create_upload_url(
        url_for('tenancy.admin.add')
    )
    if request.method == 'POST':
        upload_files = get_uploads(request, 'attach_file')
        file_ = None
        if len(upload_files):
            blob_info = upload_files[0]
        else:
            blob_info = None

        if form.validate_on_submit():
            if blob_info:
                blob_info = blobstore.BlobInfo.get(blob_info.key())
                if blob_info.size:
                    file_ = File.create(
                        blob_key=blob_info.key(),
                        title=form.name.data,
                        description=form.description.data,
                        is_public=form.is_public.data,
                        filename=os.path.basename(blob_info.filename.replace('\\', '/')),
                        size=blob_info.size,
                        content_type=blob_info.content_type)
                    file_.put()
                else:
                    blob_info.delete()

        tenancy = Tenancy()
        form.populate_obj(tenancy)
        if file_:
            tenancy.file = file_.key
        tenancy.put()
        return redirect(url_for('tenancy.admin.tenancy'))

    tenancies = Tenancy.query()
    return render_template(
        'tenancy/admin/add.html',
        add_active='active',
        form=form,
        tenancies=tenancies,
        url=url
    )

@mod.route('/tenancy/<int:key_id>/', methods=['GET', 'POST'], endpoint='edit')
@admin_required
def tenancy_edit(key_id):
    tenancy = Tenancy.retrieve_by_id(key_id)
    if not tenancy:
        return redirect(url_for('tenancy.admin.tenancy'))
    if request.method == 'POST' and 'delete_tenancy' in request.form:
        tenancy.key.delete()
        return redirect(url_for('tenancy.admin.tenancy'))
    url = blobstore.create_upload_url(
        url_for('tenancy.admin.edit', key_id=key_id)
    )
    form = TenancyForm(obj=tenancy)
    if request.method == 'POST':
        upload_files = get_uploads(request, 'attach_file')
        file_ = None
        if len(upload_files):
            blob_info = upload_files[0]
        else:
            blob_info = None

        if form.validate_on_submit():
            if blob_info:
                blob_info = blobstore.BlobInfo.get(blob_info.key())
                if blob_info.size:
                    file_ = File.create(
                        blob_key=blob_info.key(),
                        title=form.name.data,
                        description=form.description.data,
                        is_public=form.is_public.data,
                        filename=os.path.basename(blob_info.filename.replace('\\', '/')),
                        size=blob_info.size,
                        content_type=blob_info.content_type)
                    file_.put()
                else:
                    blob_info.delete()

        form.populate_obj(tenancy)
        if file_:
            if tenancy.file:
                tenancy.file.delete()
            tenancy.file = file_.key
        tenancy.put()
        return redirect(url_for('tenancy.admin.tenancy'))

    return render_template(
        'tenancy/admin/tenancy_edit.html',
        tenancy=tenancy,
        form=form,
        url=url
    )


# -*- coding: utf-8 -*-
from openprocurement.api.utils.common import get_now

from datetime import timedelta
from copy import deepcopy

test_organization = {
    "name": u"Державне управління справами",
    "identifier": {
        "scheme": u"UA-EDR",
        "id": u"00037256",
        "uri": u"http://www.dus.gov.ua/"
    },
    "address": {
        "countryName": u"Україна",
        "postalCode": u"01220",
        "region": u"м. Київ",
        "locality": u"м. Київ",
        "streetAddress": u"вул. Банкова, 11, корпус 1"
    },
    "contactPoint": {
        "name": u"Державне управління справами",
        "telephone": u"0440000000"
    }
}
test_procuringEntity = test_organization.copy()
swiftsure_procuring_entity = deepcopy(test_procuringEntity)
swiftsure_procuring_entity.update({
    "additionalContactPoints": [
        {
            "name": u"Державне управління справами",
            "telephone": u"0440000000"
        }
    ]
})

contract_create_data = {
    "awardID": "376d560b2b2d452a80543865f3cab43e",
    "contractID": "a930574bf8cd405cb7f9c9ed4ca68061",
    "contractType": "ceasefire",
    "dateSigned": get_now().isoformat(),
    "merchandisingObject": "a930574bf8cd999cb7f9c9ed4ca68061",
    "period": {
        "startDate": get_now().isoformat(),
        "endDate": (get_now() + timedelta(days=30)).isoformat(),
    },
    "procuringEntity": swiftsure_procuring_entity,
    "title": "Test Contract",
    "suppliers": [test_organization],
    "value": {
     "currency": "UAH",
     "amount": 500.0,
     "valueAddedTaxIncluded": True
    },
    "items": [
        {
            "description": u"Земля для військовослужбовців",
            "classification": {
                "scheme": u"CPV",
                "id": u"66113000-5",
                "description": u"Земельні ділянки"
            },
            "unit": {
                "name": u"item",
                "code": u"44617100-9"
            },
            "quantity": 5,
            "registrationDetails": {
                "status": "unknown",
            },
            "address": {
                "countryName": u"Україна",
                "postalCode": "79000",
                "region": u"м. Київ",
                "locality": u"м. Київ",
                "streetAddress": u"вул. Банкова 1"
            }
        }
    ]
}


import argparse
import os

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.utils as vutils
from torch.autograd import Variable
from tensorboardX import SummaryWriter
from tqdm import tqdm

from model import CapsulesNet
from data import Data

def one_hot_encode(target, length):
    batch_size = target.size(0)
    one_hot_vec = torch.zeros((batch_size, length))
    for i in range(batch_size):
        one_hot_vec[i, target[i]] = 1.0

    return one_hot_vec

def main(args):
    # Set logger
    writer = SummaryWriter(log_dir=args.log_dir)

    # DATA
    data = Data(args)
    # Embed and visualize
    data.embed(writer)
    # Load data loader
    train_loader, test_loader = data.load()
    n_train_batches = len(train_loader)
    n_test_batches = len(test_loader)

    # MODEL
    model = CapsulesNet(args.n_conv_in_channel, args.n_conv_out_channel,
                        args.n_primary_unit, args.primary_unit_size,
                        args.n_classes, args.output_unit_size,
                        args.n_routing, args.regularization_scale,
                        args.input_width, args.input_height,
                        args.use_cuda)

    if args.use_cuda:
        print('[INFO] Using {} GPU(s)'.format(torch.cuda.device_count()))
        model.cuda()

    # Info
    print(model)

    # Optimizer
    optimizer = optim.Adam(model.parameters(), lr=args.learning_rate)

    # TRAINING
    # Train helper
    def train(epoch, model):
        # Switch model to train mode
        model.train()

        for i, (data, target) in enumerate(tqdm(train_loader, unit='batch')):
            # One-hot encode for labels
            target_one_hot = one_hot_encode(target, args.n_classes)

            # Wrap inputs into Variable
            data, target = Variable(data), Variable(target_one_hot)
            if args.use_cuda:
                data = data.cuda()
                target = target.cuda()

            # Forward
            optimizer.zero_grad()
            output = model(data)

            # Calculate loss
            loss, margin_loss, recon_loss = model.loss(data, output, target)

            # Backward
            loss.backward()
            optimizer.step()

            # Tensorboard log
            global_step = (epoch-1) * n_train_batches + i
            writer.add_scalar('train/total_loss', loss.data[0], global_step)
            writer.add_scalar('train/margin_loss' , margin_loss.data[0], global_step)
            writer.add_scalar('train/reconstruction_loss', recon_loss.data[0], global_step)

            # STDOUT log
            if (i+1) % args.log_interval == 0:
                template = "[Epoch {}/{}] " \
                           "[Batch {}/{}] " \
                           "Total loss: {:.6f}, Margin loss: {:.6f}, Reconstruction loss: {:.6f}"
                tqdm.write(template.format(epoch, args.epochs,
                                           i+1, n_train_batches,
                                           loss.data[0], margin_loss.data[0], recon_loss.data[0]))

    # Test helper
    def test(epoch, model):
        # Switch model to evaluate mode
        model.eval()

        loss, margin_loss, recon_loss = 0., 0., 0.
        correct = 0.

        for data, target in tqdm(test_loader):
            target_indices = target

            # One-hot encode for labels
            target_one_hot = one_hot_encode(target, args.n_classes)

            # Wrap inputs into Variable
            data, target = Variable(data, volatile=True), Variable(target_one_hot)
            if args.use_cuda:
                data = data.cuda()
                target = target.cuda()

            # Forward
            output = model(data)

            # Calculate loss, and sum up
            t_loss, m_loss, r_loss = model.loss(data, output, target,
                                                 size_average=False)
            loss += t_loss.data[0]
            margin_loss += m_loss.data[0]
            recon_loss += r_loss.data[0]

            # Count number of correct prediction
            # v_magnitude shape: [batch_size, 10, 1, 1]
            v_magnitude = torch.sqrt((output**2).sum(dim=2, keepdim=True))
            # pred shape: [batch_size, 1, 1, 1]
            pred = v_magnitude.data.max(1, keepdim=True)[1].cpu()
            correct += pred.eq(target_indices.view_as(pred)).sum()

        # Tensorboard log
        loss /= n_test_batches
        margin_loss /= n_test_batches
        recon_loss /= n_test_batches
        accuracy = correct / len(test_loader.dataset)

        global_step = epoch * n_train_batches
        writer.add_scalar('test/total_loss', loss, global_step)
        writer.add_scalar('test/margin_loss', margin_loss, global_step)
        writer.add_scalar('test/recon_loss', recon_loss, global_step)
        writer.add_scalar('test/accuracy', accuracy, global_step)

        # IMAGES RECONSTRUCTION
        if epoch % args.recon_interval == 0:
            reconstruction = model.decoder(output, target)
            # Resize to batch of images [batch_size, C, H, W]
            recon_imgs = reconstruction.view(-1, args.n_conv_in_channel,
                                             args.input_height, args.input_width)
            # Visualize in Tensorboard
            recon_grid = vutils.make_grid(recon_imgs.data,
                                          normalize=True, scale_each=True)
            original_grid = vutils.make_grid(data.data,
                                             normalize=True, scale_each=True)
            writer.add_image('test/original_{}_{}'.format(epoch, global_step),
                             original_grid, global_step)
            writer.add_image('test/reconstruction_{}_{}'.format(epoch, global_step),
                             recon_grid, global_step)

        # STDOUT log
        template = """[Test {}]
        Total loss: {:.6f}, Margin loss: {:.6f}, Reconstruction loss: {:.6f}
        Accuracy: {:.4f}%
        """
        tqdm.write(template.format(epoch,
                                   loss, margin_loss, recon_loss,
                                   accuracy * 100))

    def checkpoint(epoch, parameters):
        """Helper for saving model's parameters
        """
        if not os.path.exists(args.model_dir):
            os.mkdir(args.model_dir)
        model_path = os.path.join(args.model_dir,
                                  'epoch_{}.pth'.format(epoch))
        torch.save(parameters, model_path)
        print('[INFO] Checkpoint {} for epoch {}'.format(model_path, epoch))

    # Start training
    for epoch in range(1, args.epochs+1):
        train(epoch, model)
        test(epoch, model)

        if epoch % args.save_interval == 0:
            checkpoint(epoch, model.state_dict())

    writer.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument('--epochs', type=int, default=25)
    parser.add_argument('--learning_rate', type=float, default=0.01)
    parser.add_argument('--batch_size', type=int, default=128)

    parser.add_argument('--n_conv_in_channel', type=int, default=1)
    parser.add_argument('--n_conv_out_channel', type=int, default=256)
    parser.add_argument('--n_primary_unit', type=int, default=8)
    parser.add_argument('--primary_unit_size', type=int, default=1152)
    parser.add_argument('--n_classes', type=int, default=10)
    parser.add_argument('--output_unit_size', type=int, default=16)
    parser.add_argument('--n_routing', type=int, default=3)
    parser.add_argument('--regularization_scale', type=float, default=0.0005)
    parser.add_argument('--input_height', type=int, default=28)
    parser.add_argument('--input_width', type=int, default=28)

    parser.add_argument('--no_cuda', action='store_true', default=False)
    parser.add_argument('--log_interval', type=int, default=10)
    parser.add_argument('--save_interval', type=int, default=5)
    parser.add_argument('--recon_interval', type=int, default=5)

    parser.add_argument('--log_dir', type=str, default='logs')
    parser.add_argument('--model_dir', type=str, default='models')

    args = parser.parse_args()
    args.use_cuda = (not args.no_cuda) and torch.cuda.is_available()

    print(args)
    main(args)


#!/usr/bin/env python3

__version__ = "0.4.5"
__author__ = "Jens Luebeck"

import argparse
import copy
from math import log
import operator
import sys

from ac_util import *
import get_genes

tot_min_del = 5000  # minimum size of deletion before non-trivial
minCycleSize = 10000
compCycContCut = 50000
cycCut = 0.12
compCut = 0.3
min_upper_cn = 4.5
decomposition_strictness = 0.1

# bfb thresholds
min_score_for_bfb = 0.25
# min_fb_reads_for_bfb = 10
fb_dist_cut = 25000

#graph properties
graph_cns = defaultdict(IntervalTree)

# ------------------------------------------------------------
# Methods to compute values used in classification
def get_size(cycle, segSeqD):
    return sum(segSeqD[abs(x)][2] - segSeqD[abs(x)][1] for x in cycle)

def weightedCycleAmount(cycle, cn, segSeqD):
    # get length of cycle
    sc_length = get_size(cycle, segSeqD) / 1000.
    return sc_length * cn

def get_diff(e1, e2, segSeqD):
    p1_abs = segSeqD[abs(e1)]
    p2_abs = segSeqD[abs(e2)]
    if e1 == 0 or e2 == 0:
        return 1

    p1_end = p1_abs[2] if e1 > 0 else p1_abs[1]
    p2_start = p2_abs[1] if e2 > 0 else p2_abs[2]
    return abs(p2_start - p1_end)

def isCircular(cycle):
    circular = False if cycle[0] == 0 and cycle[-1] == 0 else True
    return circular

def isRearranged(cycle, segSeqD):
    # check if it contains regions from multiple chroms
    chromList = [segSeqD[abs(ind)][0] for ind in cycle if ind != 0]
    if len(set(chromList)) > 1:
        return True

    max_del_size = 0
    for i in range(0, len(cycle) - 1):
        if cycle[i] == 0 or cycle[i + 1] == 0:
            continue
        if cycle[i] < 0 and cycle[i + 1] > 0 or cycle[i] > 0 and cycle[i + 1] < 0:
            return True

        dist_diff = get_diff(cycle[i], cycle[i + 1], segSeqD)
        max_del_size = max(dist_diff, max_del_size)
        # tot_del_size += dist_diff
        # if tot_del_size > tot_min_del:
        #     print("Delsize")
        #     return True
    if max_del_size > tot_min_del:
        return True

    return False

def tot_rearr_edges(graphf, add_chr_tag):
    rearr_e = 0
    with open(graphf) as infile:
        for line in infile:
            if line.startswith("discordant"):
                fields = line.rstrip().rsplit()
                lbp, rbp = fields[1].split("->")
                lchrom, lpd = lbp.rsplit(":")
                rchrom, rpd = rbp.rsplit(":")
                if add_chr_tag and not lchrom.startswith("chr"):
                    lchrom = "chr" + lchrom
                    rchrom = "chr" + rchrom

                lpos, ldir = int(lpd[:-1]), lpd[-1]
                rpos, rdir = int(rpd[:-1]), rpd[-1]
                if ldir == rdir:
                    # if lchrom == rchrom and abs(rpos - lpos) < fb_dist_cut:
                    rearr_e += 1

                elif abs(rpos - lpos) > fb_dist_cut:
                    rearr_e += 1

    return rearr_e

def decompositionComplexity(graphf, cycleList, cycleCNs, segSeqD, feature_inds, exclude_inds, add_chr_tag):
    #construct intervaltree of valid regions
    hit_region_it = defaultdict(IntervalTree)
    for i in feature_inds:
        cycle = cycleList[i]
        for cv in cycle:
            if cv != 0:
                c, s, e = segSeqD[abs(cv)]
                hit_region_it[c].addi(s, e+1)

    hf_cut = 0.8
    totalGraphWeight = 0
    segs = 0
    with open(graphf) as infile:
        for line in infile:
            if line.startswith("sequence"):
                fields = line.rsplit()
                c, s, e = fields[1].rsplit(":")[0], int(fields[1].rsplit(":")[1][:-1]), int(fields[2].rsplit(":")[1][:-1])+1
                if add_chr_tag and not c.startswith('chr'):
                    c = "chr" + c

                if not hit_region_it[c][s:e]:
                    continue

                cn = float(fields[3])
                size = float(fields[5]) / 1000.

                # if cn > 1:
                segs += 1
                totalGraphWeight += (size * cn)

            elif line.startswith("BreakpointEdge"):
                break

    # cycleWeights = [0] * len(feature_inds)
    # for ind, cycle in enumerate(cycleList):
    cycleWeights = []
    new_feat_inds = set()
    for ind, cycle in enumerate(cycleList):
        if ind not in exclude_inds:
            hits = False
            for cv in cycle:
                if cv != 0:
                    c, s, e = segSeqD[abs(cv)]
                    if hit_region_it[c][s:e]:
                        hits = True
                        break
            if hits:
                wca = weightedCycleAmount(cycle, cycleCNs[ind], segSeqD)
                if ind in feature_inds:
                    new_feat_inds.add(len(cycleWeights))

                cycleWeights.append(wca)

    # scW = sorted(cycleWeights, reverse=True)
    # cf = cycleWeights[0]/totalGraphWeight

    cf = 0
    fe_ent = 0
    added_cf = 0
    cInd = 0
    if totalGraphWeight > 0:
        while cf + added_cf < hf_cut and cInd < len(cycleWeights):
            if cInd in new_feat_inds:
                cf += added_cf
                if added_cf > 0:
                    fe_ent += (added_cf * log(added_cf))

                added_cf = cycleWeights[cInd] / float(totalGraphWeight)

            cInd += 1

        cf+=added_cf
        cf = round(cf, 5)
        rf = (1 - cf)
        # print(rf, cf, totalGraphWeight)
        if rf > 0:
            fu_ent = -1 * rf * log(rf)
        else:
            fu_ent = 0

    else:
        print("Warning: total graph weight <= 0")
        fu_ent = 0

    seg_ent = log(1.0 / segs) if segs > 0 else 0
    return fu_ent - fe_ent - seg_ent, fu_ent - fe_ent, -1 * seg_ent

# Compute f (foldback fraction) from the edges in the AA graph alone
def compute_f_from_AA_graph(graphf, add_chr_tag):
    with open(graphf) as infile:
        fbCount, nonFbCount, fbEdges, maxCN = 0, 0, 0, 0
        for line in infile:
            fields = line.rstrip().rsplit()
            if line.startswith("discordant"):
                lbp, rbp = fields[1].split("->")
                lchrom, lpd = lbp.rsplit(":")
                rchrom, rpd = rbp.rsplit(":")
                if add_chr_tag and not lchrom.startswith('chr'):
                    lchrom = "chr" + lchrom
                    rchrom = "chr" + rchrom

                lpos, ldir = int(lpd[:-1]), lpd[-1]
                rpos, rdir = int(rpd[:-1]), rpd[-1]

                if lcD[lchrom][lpos] or lcD[rchrom][rpos]:
                    continue

                elif fields[0] == "discordant" and rchrom == lchrom and abs(rpos - lpos) <= 2000 and rdir == '-' and ldir == '+':
                    continue

                rSupp = int(fields[3])
                if ldir == rdir:
                    if lchrom == rchrom and abs(rpos - lpos) < fb_dist_cut:
                        fbCount += rSupp
                        fbEdges += 1

                    else:
                        nonFbCount += rSupp

                else:
                    nonFbCount += rSupp

            elif line.startswith("sequence"):
                if not lcD[fields[1].rsplit(":")[0]].overlaps(int(fields[1].rsplit(":")[1][:-1]),
                                                              int(fields[2].rsplit(":")[1][:-1])):

                    ccn = float(fields[3])
                    if ccn > maxCN:
                        maxCN = ccn

    # just return 0 if there isn't enough support
    if fbEdges < 2:
        return 0, maxCN

    return fbCount / max(1.0, float(fbCount + nonFbCount)), maxCN

def nonbfb_cycles_are_ecdna(non_bfb_cycle_inds, cycleList, segSeqD, cycleCNs):
    for ind in non_bfb_cycle_inds:
        cycle = cycleList[ind]
        length = get_size(cycle, segSeqD)

        if length > 100000 and cycleCNs[ind] > 5:
            return True

    return False

# proportion of cycles with foldbacks
def cycles_file_bfb_props(cycleList, segSeqD, cycleCNs, graphf, add_chr_tag):
    FB_breaks = 0.0
    distal_breaks = 0.0
    lin_breaks = 0.0

    bfb_weight = 0.0
    non_bfb_cycle_weight = 0.0
    tot_bfb_supp_cycles = 0

    non_bfb_cycle_inds = []
    bfb_cycle_inds = []

    for ind, ocycle in enumerate(cycleList):
        cycle = copy.copy(ocycle)
        if cycle[0] != 0:
            cycle.append(cycle[0])

        hit = False
        isBFBelem = False
        illegalBFB = False
        for a, b in zip(cycle[:-1], cycle[1:]):
            # changes direction on same chrom
            diff = get_diff(a, b, segSeqD)
            aSize = get_size([a, ], segSeqD)
            bSize = get_size([b, ], segSeqD)
            if aSize < minCycleSize and bSize < minCycleSize:
                continue

            # check if front and back are connected via everted edge
            front_to_back_connection = amp_encompassed(cycle, segSeqD, graphf, add_chr_tag)
            if front_to_back_connection:
                # print("Cycle has front to back link", cycle)
                illegalBFB = True

            else:
                if a * b < 0 and segSeqD[abs(a)][0] == segSeqD[abs(b)][0]:
                    hit = True
                    if diff < 50000:
                        isBFBelem = True
                        FB_breaks += cycleCNs[ind]

                    else:
                        distal_breaks += cycleCNs[ind]

                elif diff > tot_min_del:
                    hit = True
                    distal_breaks += cycleCNs[ind]

                if segSeqD[abs(a)][0] != segSeqD[abs(b)][0] and not (a == 0 or b == 0):
                    illegalBFB = True

        if illegalBFB:
            isBFBelem = False

        if cycle[0] == 0 and not hit and get_size(cycle,segSeqD) > 10000:
            lin_breaks += cycleCNs[ind]

        if isBFBelem:
            tot_bfb_supp_cycles += 1
            bfb_weight += cycleCNs[ind]
            bfb_cycle_inds.append(ind)

        elif cycle[0] != 0 and get_size(cycle[:-1], segSeqD) > 30000:
            non_bfb_cycle_weight += cycleCNs[ind]
            non_bfb_cycle_inds.append(ind)

    hasEC = nonbfb_cycles_are_ecdna(non_bfb_cycle_inds, cycleList, segSeqD, cycleCNs)
    # if len(cycleList) >= 5:
    #     minBFBCyclesRequired = 2
    # else:
    minBFBCyclesRequired = 2
    if FB_breaks > 1.5 and tot_bfb_supp_cycles >= minBFBCyclesRequired:
        tot = float(FB_breaks + distal_breaks + lin_breaks)
        return FB_breaks / tot, distal_breaks / tot, bfb_weight / (non_bfb_cycle_weight + bfb_weight), hasEC, \
               non_bfb_cycle_inds, bfb_cycle_inds

    return 0, 0, 0, False, [], []

# ------------------------------------------------------------
# Classifications
def cycleIsNoAmpInvalid(cycle, cn, segSeqD, isSingleton, maxCN):
    # CN flow can be split across multiple amps
    if not isSingleton:
        scale = min(args.min_cn_flow, maxCN * decomposition_strictness)
    elif maxCN > 7:
        scale = min(3., maxCN / 8.)
    else:
        scale = 2.5

    # print(cycle)
    # print("decomp cutoff",scale)

    if (cn <= scale) or (maxCN < min_upper_cn):
        return True

    length = get_size(cycle, segSeqD)
    return length < minCycleSize

def classifyConnections(cycleSet1, cycleSet2, clfs):
    cycleSet1, cycleSet2 = sorted([cycleSet1, cycleSet2], key=lambda x: len(x), reverse=True)
    csets = []
    resultDict = defaultdict(float)
    if not cycleSet2:
        for c1 in cycleSet1:
            csets.append(frozenset([clfs[c1], ]))

    else:
        for c1 in cycleSet1:
            for c2 in cycleSet2:
                csets.append(frozenset([clfs[c1], clfs[c2]]))

    distributed_edge_value = 1.0 / len(csets) if csets else 0
    for cset in csets:
        resultDict[cset] += distributed_edge_value

    return resultDict

# categories = ["No amp/Invalid", "Linear amplification", "Trivial cycle", "Complex non-cyclic", "Complex cyclic"]
def classifyAmpliconProfile(amp_profile, rearr_e, totalCompCyclicCont, totCyclicCont, force=False):
    cycSig = amp_profile["Trivial cycle"] + amp_profile["Complex cyclic"]
    if (cycSig > cycCut or totalCompCyclicCont > compCycContCut) and totCyclicCont > 10000:
        return "Cyclic"

    elif amp_profile["Complex non-cyclic"] + cycSig > compCut:
        if rearr_e < 2:
            return "Linear amplification"

        return "Complex non-cyclic"

    else:
        if max(amp_profile.values()) == 0:
            return "No amp/Invalid"

        elif amp_profile["No amp/Invalid"] > 0:
            if amp_profile["Linear amplification"] / float(amp_profile["No amp/Invalid"]) > 0.25:
                if rearr_e >= 5:
                    return "Complex non-cyclic"

                return "Linear amplification"

        if force:
            del amp_profile["No amp/Invalid"]
            if cycSig > max(amp_profile.values()):
                return "Cyclic"

        maxCat = max(amp_profile.items(), key=operator.itemgetter(1))[0]
        return maxCat

def classifyBFB(fb, cyc_sig, nonbfb_sig, bfb_cyc_ratio, maxCN):
    if fb < min_score_for_bfb or cyc_sig < 0.295 or maxCN < 4:
        return None

    # dominated by non-classical BFB cycles
    elif nonbfb_sig > 0.5 and bfb_cyc_ratio < 0.6:
        return None

    # if bfb_cyc_ratio < 0.85:
    #     # if nonbfb_sig > 0.55:
    #     #     return None
    #
    #     if args.use_BFB_linked_cyclic_class:
    #         return "BFB-linked cyclic"

    return "BFB"

# ------------------------------------------------------------
# structure metanalysis

def check_max_cn(ec_cycle_inds, cycleList, segSeqD):
    for e_ind in ec_cycle_inds:
        for c_id in cycleList[e_ind]:
            chrom, l, r = segSeqD[abs(c_id)]
            if r - l < 1000:
                continue

            for i in graph_cns[chrom][l:r]:
                if i.data > min_upper_cn:
                    return True

    return False

def get_amount_sigamp(ec_cycle_inds, cycleList, segSeqD):
    used_content = defaultdict(set)
    for e_ind in ec_cycle_inds:
        for c_id in cycleList[e_ind]:
            chrom, l, r = segSeqD[abs(c_id)]
            if not chrom:
                continue
            seg_t = IntervalTree([Interval(l, r+1)])
            olapping_low_cns = [x for x in graph_cns[chrom][l:r] if x.data < 4]
            for x in olapping_low_cns:
                seg_t.chop(x.begin, x.end)
            for x in seg_t:
                used_content[chrom] |= set(range(x.begin, x.end))

    total_sigamp = 0
    for chrom, useset in used_content.items():
        total_sigamp += len(useset)

    return total_sigamp

def clusterECCycles(cycleList, cycleCNs, segSeqD, excludableCycleIndices=None):
    padding = 500000
    indices = [x for x in range(len(cycleList)) if cycleList[x][0] != 0 and x not in excludableCycleIndices]
    clusters = []
    seenSegs = set()
    total_EC_size = 0
    for ind in indices:
        cycle = cycleList[ind]
        csize = get_size(cycle, segSeqD)
        total_EC_size+=csize
        if cycleCNs[ind] < args.min_cn_flow and csize < minCycleSize:
            continue

        cIndsToMerge = set()
        s_set = set([segSeqD[abs(s_num)] for s_num in cycle])
        s_set -= seenSegs
        if not s_set:
            continue

        for c_ind, clust_dict in enumerate(clusters):
            for s in s_set:
                if clust_dict[s[0]][s[1] - padding:s[2] + padding]:
                    cIndsToMerge.add(c_ind)
                    break

        newClusters = []
        newClust = defaultdict(IntervalTree)
        for s in s_set:
            newClust[s[0]].addi(s[1], s[2] + 1, ind)

        for c_ind, currClust in enumerate(clusters):
            if c_ind in cIndsToMerge:
                for k, v in currClust.items():
                    for ival in v:
                        newClust[k].addi(ival.begin, ival.end, ival.data)

            else:
                newClusters.append(currClust)

        newClusters.append(newClust)
        clusters = newClusters
        seenSegs |= s_set

    indexClusters = []
    # extract only the cycle indices from each cluster and return
    for clust in clusters:
        currIndexSet = set()
        for k, v in clust.items():
            for ival in v:
                currIndexSet.add(ival.data)

        if get_amount_sigamp(currIndexSet, cycleList, segSeqD) > 10000:
            indexClusters.append(currIndexSet)

    # remove those where the max CN is below threshold
    indexClusters = [x for x in indexClusters if check_max_cn(x, cycleList, segSeqD)]

    return indexClusters

# ------------------------------------------------------------

'''
Amplicon Classes:
#if not invalid

1) No amp/Invalid
2) Linear amplification
3) Complex non-cyclic
4) BFB
5) BFB-linked cylic (ecBFB)
6) Cyclic (ecDNA)

Graph edge classes:
1) No amp/Invalid
2) Non-cyclic
3) Integration
4) Hybrid - joins amplified cyclic and non cyclic
5) Cyclic

'''

mixLookups = {
    frozenset(["No amp/Invalid", ]): "No amp/Invalid",
    frozenset(["No amp/Invalid", "Linear amplification"]): "Integration",
    frozenset(["No amp/Invalid", "Trivial cycle"]): "Integration",
    frozenset(["No amp/Invalid", "Complex non-cyclic"]): "Integration",
    frozenset(["No amp/Invalid", "Complex cyclic"]): "Integration",
    frozenset(["Linear amplification"]): "Non-cyclic",
    frozenset(["Linear amplification", "Trivial cycle"]): "Integration",
    frozenset(["Linear amplification", "Complex non-cyclic"]): "Non-cyclic",
    frozenset(["Linear amplification", "Complex cyclic"]): "Integration",
    frozenset(["Trivial cycle"]): "Cyclic",
    frozenset(["Trivial cycle", "Complex non-cyclic"]): "Hybrid",
    frozenset(["Trivial cycle", "Complex cyclic"]): "Cyclic",
    frozenset(["Complex non-cyclic"]): "Non-cyclic",
    frozenset(["Complex non-cyclic", "Complex cyclic"]): "Hybrid",
    frozenset(["Complex cyclic"]): "Cyclic",
}

# (circular,complex)
categories = ["No amp/Invalid", "Linear amplification", "Trivial cycle", "Complex non-cyclic", "Complex cyclic",
              "foldback_read_prop", "BFB_bwp", "Distal_bwp", "BFB_cwp", "Amp_entropy", "Amp_decomp_entropy",
              "Amp_nseg_entropy"]
mixing_cats = ["No amp/Invalid", "Non-cyclic", "Integration", "Hybrid", "Cyclic"]

ampDefs = {(False, False): "Linear amplification", (False, True): "Complex non-cyclic",
           (True, False): "Trivial cycle", (True, True): "Complex cyclic"}

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Classify AA amplicon type")
    parser.add_argument("-i", "--input", help="Path to list of files to use. Each line formatted as: "
                        "sample_name cycles.txt graph.txt. Give this argument if not using -c and -g.")
    parser.add_argument("-c", "--cycles", help="AA-formatted cycles file")
    parser.add_argument("-g", "--graph", help="AA-formatted graph file")
    parser.add_argument("--ref", help="Reference genome name used for alignment, one of hg19, GRCh37, or GRCh38.",
                        choices=["hg19", "GRCh37", "hg38", "GRCh38"], required=True)

    parser.add_argument("--min_cn_flow", type=float, help="Minimum CN flow to consider as amplification (1.0).",
                        default=1)
    parser.add_argument("--min_size", type=float, help="Minimum cycle size (in bp) to consider as valid amplicon "
                        "(5000).", default=5000)
    parser.add_argument("-o", help="Output filename prefix")
    parser.add_argument("--plotstyle", help="Type of visualizations to produce.",
                        choices=["grouped", "individual", "noplot"], default="noplot")
    parser.add_argument("--force", help="Disable No amp/Invalid class if possible", action='store_true')
    # parser.add_argument("--use_BFB_linked_cyclic_class", help="Include the \'BFB-linked cyclic\' class",
    #                     action='store_true')
    parser.add_argument("--add_chr_tag", help="Add \'chr\' to the beginning of chromosome names in input files.",
                        action='store_true')
    # parser.add_argument("--report_genes", help="Extract list of genes from amplicons with given classification.",
    #                     choices=["ecdna", "bfb", "other", "all"], nargs='+', default=[])
    parser.add_argument("--report_complexity", help="Compute a measure of amplicon entropy for each amplicon.",
                        action='store_true')
    parser.add_argument("--verbose_classification", help="Generate verbose output with raw classification scores.",
                        action='store_true')
    parser.add_argument("--annotate_cycles_file", help="Create an annotated cycles file, indicating the classification "
                        "of the paths and cycles present.", action='store_true')
    parser.add_argument("--no_LC_filter", help="Do not filter low-complexity cycles. Not recommended to set this flag.",
                        action='store_true', default=False)
    parser.add_argument("--decomposition_strictness", help="Value between 0 and 1 reflecting how strictly to filter "
                                                           "low CN decompositions (default = 0.1). Higher values "
                                                           "filter more of the low-weight decompositions.", type=float,
                        default=0.1)
    parser.add_argument("-v", "--version", action='version', version='amplicon_classifier {version} \n Author: Jens \
                        Luebeck (jluebeck [at] ucsd.edu)'.format(version=__version__))

    args = parser.parse_args()

    if not (args.cycles and args.graph) and not args.input:
        print("Need to specify (--cycles & --graph) or --input\n")
        sys.exit(1)

    if args.ref == "hg38": args.ref = "GRCh38"
    patch_links = read_patch_regions(args.ref)
    if 0 <= args.decomposition_strictness <= 1:
        decomposition_strictness= args.decomposition_strictness
    else:
        print("--decomposition_strictness must be a value between 0 and 1")
        sys.exit(1)

    # check if aa data repo set, construct LC datatabase
    try:
        AA_DATA_REPO = os.environ["AA_DATA_REPO"] + "/" + args.ref + "/"
        fDict = {}
        with open(AA_DATA_REPO + "file_list.txt") as infile:
            for line in infile:
                fields = line.strip().rsplit()
                fDict[fields[0]] = fields[1]

        lcPath = AA_DATA_REPO + fDict["mapability_exclude_filename"]
        lcD = defaultdict(IntervalTree)
        if not args.no_LC_filter:
            lcD = buildLCDatabase(lcPath)

    except KeyError:
        sys.stderr.write("$AA_DATA_REPO not set. Please see AA installation instructions.\n")
        sys.exit(1)

    # gene_lookup = {}
    ftgd_list = []  # store list of feature gene classifications
    # gene_file_location_lookup = {"hg19": "human_hg19_september_2011/Genes_July_2010_hg19.gff",
    #                              "GRCh38": "genes_hg38.gff",
    #                              "GRCh37": "human_hg19_september_2011/Genes_July_2010_hg19.gff"}
    #
    # refGeneFileLoc = AA_DATA_REPO + gene_file_location_lookup[args.ref]

    # read the gene list
    refGeneFileLoc = AA_DATA_REPO + fDict["gene_filename"]
    gene_lookup = get_genes.parse_genes(refGeneFileLoc)

    if not args.input:
        tempName = args.cycles.rsplit("/")[-1].rsplit(".")[0]
        flist = [[tempName, args.cycles, args.graph]]
        if not args.o:
            args.o = os.path.basename(args.cycles).rsplit("_cycles.txt")[0]

    else:
        flist = readFlist(args.input)
        if not args.o:
            args.o = os.path.basename(args.input).rsplit(".")[0]

    minCycleSize = args.min_size

    AMP_dvaluesList = []
    EDGE_dvaluesList = []
    AMP_classifications = []
    sampNames = []
    cyclesFiles = []
    featEntropyD = {}
    samp_to_ec_count = defaultdict(int)
    for fpair in flist:
        if len(fpair) > 2:
            sName, cyclesFile, graphFile = fpair
            sampNames.append(sName)
            cyclesFiles.append(cyclesFile)
            ampN = cyclesFile.rstrip("_cycles.txt").rsplit("_")[-1]
            print(sName, ampN)
            segSeqD, cycleList, cycleCNs = parseCycle(cyclesFile, graphFile, args.add_chr_tag, lcD, patch_links)

        else:
            print(fpair)
            sys.stderr.write("File list not properly formatted\n")
            sys.exit(1)

        cycleTypes = []
        cycleWeights = []
        invalidInds = []
        rearrCycleInds = set()
        graph_cns = get_graph_cns(graphFile, args.add_chr_tag)
        fb_prop, maxCN = compute_f_from_AA_graph(graphFile, args.add_chr_tag)
        rearr_e = tot_rearr_edges(graphFile, args.add_chr_tag)
        totalCompCyclicCont = 0
        totCyclicCont = 0
        for ind, cycle in enumerate(cycleList):
            hasNonCircLen1 = True if len(cycle) == 3 and cycle[0] == 0 else False
            oneCycle = (len(cycleList) == 1)
            isSingleton = hasNonCircLen1 or oneCycle
            if cycleIsNoAmpInvalid(cycle, cycleCNs[ind], segSeqD, isSingleton, maxCN) and not args.force:
                invalidInds.append(ind)
                cycleTypes.append("No amp/Invalid")

            else:
                circCyc = isCircular(cycle)
                compCyc = isRearranged(cycle, segSeqD)
                if compCyc:
                    rearrCycleInds.add(ind)
                    if circCyc:
                        totalCompCyclicCont += get_size(cycle, segSeqD)

                if circCyc:
                    totCyclicCont += get_size(cycle, segSeqD)

                cycleTypes.append(ampDefs[(circCyc, compCyc)])

            currWt = weightedCycleAmount(cycle, cycleCNs[ind], segSeqD)
            cycleWeights.append(currWt)

        totalWeight = max(sum(cycleWeights), 1)
        AMP_dvaluesDict = {x: 0.0 for x in categories}
        for i, wt in zip(cycleTypes, cycleWeights):
            AMP_dvaluesDict[i] += (wt / totalWeight)

        # anything stored in AMP_dvaluesDict prior to running classify will get used in classification
        # make sure you're not putting in other properties before here.
        ampClass = classifyAmpliconProfile(AMP_dvaluesDict, rearr_e, totalCompCyclicCont, totCyclicCont)

        # decomposition/amplicon complexity
        totalEnt, decompEnt, nEnt = decompositionComplexity(graphFile, cycleList, cycleCNs, segSeqD,
                                                            range(len(cycleList)), set(), args.add_chr_tag)
        AMP_dvaluesDict["Amp_entropy"] = totalEnt
        AMP_dvaluesDict["Amp_decomp_entropy"] = decompEnt
        AMP_dvaluesDict["Amp_nseg_entropy"] = nEnt

        # now layer on the bfb classification
        # first compute some properties
        fb_prop, maxCN = compute_f_from_AA_graph(graphFile, args.add_chr_tag)

        fb_bwp, nfb_bwp, bfb_cwp, bfbHasEC, non_bfb_cycle_inds, bfb_cycle_inds = cycles_file_bfb_props(cycleList,
                                                                        segSeqD, cycleCNs, graphFile, args.add_chr_tag)
        # "foldback_read_prop", "BFB_bwp", "Distal_bwp", "BFB_cwp"
        AMP_dvaluesDict["foldback_read_prop"] = fb_prop
        AMP_dvaluesDict["BFB_bwp"] = fb_bwp
        AMP_dvaluesDict["Distal_bwp"] = nfb_bwp
        AMP_dvaluesDict["BFB_cwp"] = bfb_cwp

        bfbClass = classifyBFB(fb_prop, fb_bwp, nfb_bwp, bfb_cwp, maxCN)

        ecStat = False
        bfbStat = False
        if ampClass == "Cyclic" and not bfbClass:
            ecStat = True
            bfb_cycle_inds = []

        elif bfbClass and ampClass != "No amp/Invalid":
            bfbStat = True
            if bfbHasEC:
                ecStat = True

        else:
            bfb_cycle_inds = []

        # determine number of ecDNA present
        ecIndexClusters = []
        if ecStat:
            excludableCycleIndices = set(bfb_cycle_inds + invalidInds)
            ecIndexClusters = clusterECCycles(cycleList, cycleCNs, segSeqD, excludableCycleIndices)
            ecAmpliconCount = max(len(ecIndexClusters), 1)

        else:
            ecAmpliconCount = 0

        samp_to_ec_count[sName] += ecAmpliconCount
        # write entropy for each feature
        ecEntropies = []
        if ecAmpliconCount == 1 and not ecIndexClusters:
            ecEntropies.append((totalEnt, decompEnt, nEnt))

        for ecCycleList in ecIndexClusters:
            c_ex_I = bfb_cycle_inds if bfbStat else set()
            totalEnt, decompEnt, nEnt = decompositionComplexity(graphFile, cycleList, cycleCNs, segSeqD,
                                                                ecCycleList, c_ex_I, args.add_chr_tag)
            ecEntropies.append((totalEnt, decompEnt, nEnt))

        for ind, etup in enumerate(ecEntropies):
            featEntropyD[(sName, ampN, "ecDNA_" + str(ind+1))] = etup

        if bfbStat:
            bfb_totalEnt, bfb_decompEnt, bfb_nEnt = decompositionComplexity(graphFile, cycleList, cycleCNs, segSeqD,
                                                                            bfb_cycle_inds, set(), args.add_chr_tag)
            featEntropyD[(sName, ampN, "BFB_1")] = (bfb_totalEnt, bfb_decompEnt, bfb_nEnt)

        # get genes
        feat_gene_truncs, feat_gene_cns = get_genes.extract_gene_list(sName, ampN, gene_lookup, cycleList, segSeqD,
                                                                      bfb_cycle_inds, ecIndexClusters, invalidInds,
                                                                      bfbStat, ecStat, ampClass, graphFile,
                                                                      args.add_chr_tag, args.o)

        ftgd_list.append([sName, ampN, feat_gene_truncs, feat_gene_cns])

        # store this additional information
        AMP_classifications.append((ampClass, ecStat, bfbStat, ecAmpliconCount))
        dvalues = [AMP_dvaluesDict[x] for x in categories]
        AMP_dvaluesList.append(dvalues)

        # edge classification
        edgeTypeCountD = defaultdict(float)
        if graphFile:
            posCycleLookup = buildPosCycleLookup(cycleList, segSeqD)
            bps = parseBPG(graphFile, args.add_chr_tag, lcD)
            for bp in bps:
                lCycles, rCycles = bpgEdgeToCycles(bp, posCycleLookup)
                # indices of left and right cycles on the discordant edges, and the index-ordered list of types
                resD = classifyConnections(lCycles, rCycles, cycleTypes)
                for k, v in resD.items():
                    edgeTypeCountD[mixLookups[k]] += v

            # norm the values
            eTCDSum = float(sum(edgeTypeCountD.values()))
            for k, v in edgeTypeCountD.items():
                edgeTypeCountD[k] = v / eTCDSum

        dvalues = [edgeTypeCountD[x] for x in mixing_cats]
        EDGE_dvaluesList.append(dvalues)

        #write the annotated cycles file
        if args.annotate_cycles_file:
            outname = os.path.basename(cyclesFile).rsplit("_cycles")[0] + "_annotated_cycles.txt"
            write_annotated_corrected_cycles_file(args.o, outname, cycleList, cycleCNs, segSeqD, bfb_cycle_inds,
                                                  ecIndexClusters, invalidInds, rearrCycleInds)

    # PLOTTING
    textCategories = ["No amp/Invalid", "Linear\namplification", "Trivial\ncycle", "Complex\nnon-cyclic",
                      "Complex\ncyclic", "BFB\nfoldback"]
    if args.plotstyle == "grouped":
        from radar_plotting import *

        print("plotting")
        make_classification_radar(textCategories, AMP_dvaluesList, args.o + "_amp_class", sampNames)
        make_classification_radar(mixing_cats, EDGE_dvaluesList, args.o + "_edge_class", sampNames)

    elif args.plotstyle == "individual":
        from radar_plotting import *

        print("plotting")
        for a, e, s in zip(AMP_dvaluesList, EDGE_dvaluesList, sampNames):
            print(textCategories, a)
            make_classification_radar(textCategories, [a[:len(textCategories)], ], args.o + "_" + s + "_amp_class",
                                      sampNames)
            make_classification_radar(mixing_cats, [e, ], args.o + "_" + s + "_edge_class", sampNames)

    #OUTPUT FILE WRITING
    print("writing output files")
    write_outputs(args, ftgd_list, featEntropyD, categories, sampNames, cyclesFiles, AMP_classifications,
                  AMP_dvaluesList, mixing_cats, EDGE_dvaluesList, samp_to_ec_count)

    print("done")


from multiprocessing import Pool  #parallel processing
import multiprocessing as mp
import structure
from structure.global_constants import *
from structure.cell import Tissue, BasicSpringForceNoGrowth
import structure.initialisation as init
import sys
import os
import numpy as np
import libs.pd_lib_neutral as lib
import libs.data as data

def calc_interactions(tissue,mutant_index,n):
    """treats all cells with ancestor 'mutant_index' as cooperators
    returns:
        n (int): size of clone
        I_CC/I_CD (ints): number of cooperator-cooperator/defector interactions in population
        W_CC/W_CD (floats): number of cooperator-cooperator/defector interactions in pop. weighted by neighbour number
    """
    neighbours = tissue.mesh.neighbours
    types = tissue.properties['ancestor']==mutant_index
    I_CC,I_CD,W_CC,W_CD,N_D = 0,0,0.,0.,0
    for ctype,cell_neighbours in zip(types,neighbours):
        if ctype:
            Cneigh,neigh = float(sum(types[cell_neighbours])),float(len(cell_neighbours))
            I_CC += Cneigh
            I_CD += neigh - Cneigh
            W_CC += Cneigh/neigh
            W_CD += (neigh-Cneigh)/neigh
    return [n,I_CC,I_CD,W_CC,W_CD]

def run_sim(i):
    """run a single simulation and save interaction data for each clone"""
    rand = np.random.RandomState()
    dt=0.005*-50./MU
    tissue = lib.initialise_tissue_ancestors(l,dt,10.,10.,rand,MU)
    tissue.properties['ancestor']=np.arange(l*l)
    if init_timend is not None: tissue = lib.run(tissue,lib.simulation_ancestor_tracking(tissue,dt,init_timend/dt,init_timend/dt,rand),init_timend/dt,init_timend/dt)[-1]
    data = [calc_interactions(tissue,mutant_index,n)
                for tissue in lib.run_generator(lib.simulation_ancestor_tracking(tissue,dt,timend/dt,timestep/dt,rand,til_fix=True),timend/dt,timestep/dt)
                for mutant_index,n in enumerate(np.bincount(tissue.properties['ancestor'])) if n>=n_min]
    np.savetxt('%s/data_%d'%(outdir,i),data,fmt=('%4d','%4d','%4d','%4.6f','%4.6f'))
    return None

l = 10 # population size N = l*l
init_timend = 10. # initial simulation time to equilibrate
timestep = 12. # timesteps at which to calc interaction data (hours)
timend = 10000. # length of simulation (hours)
sim_runs = int(sys.argv[1]) # number of sims to run taken as command line arg
MU = float(sys.argv[2]) #spring constant
n_min = 1
outdir = 'interaction_data/supp_vary_MU/MU%d/raw_data'%MU
if not os.path.exists(outdir): # if the outdir doesn't exist create it
     os.makedirs(outdir)

# run simulations in parallel
cpunum=mp.cpu_count()
pool = Pool(processes=cpunum-1,maxtasksperchild=1000)
pool.map(run_sim,range(sim_runs))
pool.close()
pool.join()