import torch
import torch.nn as nn
import math

class ConvNormAct(nn.Module):
    '''
    This class defines the convolution layer with normalization and a PReLU
    activation
    '''

    def __init__(self, nIn, nOut, kSize, stride=1, groups=1):
        '''
        :param nIn: number of input channels
        :param nOut: number of output channels
        :param kSize: kernel size
        :param stride: stride rate for down-sampling. Default is 1
        '''
        super().__init__()
        padding = int((kSize - 1) / 2)
        self.conv = nn.Conv1d(nIn, nOut, kSize, stride=stride, padding=padding,
                              bias=True, groups=groups)
        self.norm = nn.GroupNorm(1, nOut, eps=1e-08)
        self.act = nn.PReLU(nOut)

    def forward(self, input):
        output = self.conv(input)
        output = self.norm(output)
        return self.act(output)

class ConvNorm(nn.Module):
    '''
    This class defines the convolution layer with normalization and PReLU activation
    '''

    def __init__(self, nIn, nOut, kSize, stride=1, groups=1):
        '''
        :param nIn: number of input channels
        :param nOut: number of output channels
        :param kSize: kernel size
        :param stride: stride rate for down-sampling. Default is 1
        '''
        super().__init__()
        padding = int((kSize - 1) / 2)
        self.conv = nn.Conv1d(nIn, nOut, kSize, stride=stride, padding=padding,
                              bias=True, groups=groups)
        self.norm = nn.GroupNorm(1, nOut, eps=1e-08)

    def forward(self, input):
        output = self.conv(input)
        return self.norm(output)

class NormAct(nn.Module):
    '''
    This class defines a normalization and PReLU activation
    '''
    def __init__(self, nOut):
        '''
        :param nOut: number of output channels
        '''
        super().__init__()
        self.norm = nn.GroupNorm(1, nOut, eps=1e-08)
        self.act = nn.PReLU(nOut)

    def forward(self, input):
        output = self.norm(input)
        return self.act(output)

class DilatedConv(nn.Module):
    '''
    This class defines the dilated convolution.
    '''

    def __init__(self, nIn, nOut, kSize, stride=1, d=1, groups=1):
        '''
        :param nIn: number of input channels
        :param nOut: number of output channels
        :param kSize: kernel size
        :param stride: optional stride rate for down-sampling
        :param d: optional dilation rate
        '''
        super().__init__()
        self.conv = nn.Conv1d(nIn, nOut, kSize, stride=stride, dilation=d,
                              padding=((kSize - 1) // 2) * d, groups=groups)

    def forward(self, input):
        return self.conv(input)

class DilatedConvNorm(nn.Module):
    '''
    This class defines the dilated convolution with normalized output.
    '''

    def __init__(self, nIn, nOut, kSize, stride=1, d=1, groups=1):
        '''
        :param nIn: number of input channels
        :param nOut: number of output channels
        :param kSize: kernel size
        :param stride: optional stride rate for down-sampling
        :param d: optional dilation rate
        '''
        super().__init__()
        self.conv = nn.Conv1d(nIn, nOut, kSize, stride=stride, dilation=d,
                              padding=((kSize - 1) // 2) * d, groups=groups)
        self.norm = nn.GroupNorm(1, nOut, eps=1e-08)

    def forward(self, input):
        output = self.conv(input)
        return self.norm(output)

class UBlock(nn.Module):
    '''
    This class defines the Upsampling block, which is based on the following
    principle:
        REDUCE ---> SPLIT ---> TRANSFORM --> MERGE
    '''

    def __init__(self,
                 out_channels=128,
                 in_channels=512,
                 upsampling_depth=4):
        super().__init__()
        self.proj_1x1 = ConvNormAct(out_channels, in_channels, 1,
                                    stride=1, groups=1)
        self.depth = upsampling_depth
        self.spp_dw = nn.ModuleList()
        self.spp_dw.append(DilatedConvNorm(in_channels, in_channels, kSize=5,
                                           stride=1, groups=in_channels, d=1))

        for i in range(1, upsampling_depth):
            if i == 0:
                stride = 1
            else:
                stride = 2
            self.spp_dw.append(DilatedConvNorm(in_channels, in_channels,
                                               kSize=2*stride + 1,
                                               stride=stride,
                                               groups=in_channels, d=1))
        if upsampling_depth > 1:
            self.upsampler = torch.nn.Upsample(scale_factor=2,
                                               # align_corners=True,
                                               # mode='bicubic'
                                               )
        self.conv_1x1_exp = ConvNorm(in_channels, out_channels, 1, 1, groups=1)
        self.final_norm = NormAct(in_channels)
        self.module_act = NormAct(out_channels)

    def forward(self, x):
        '''
        :param x: input feature map
        :return: transformed feature map
        '''

        # Reduce --> project high-dimensional feature maps to low-dimensional space
        output1 = self.proj_1x1(x)
        output = [self.spp_dw[0](output1)]

        # Do the downsampling process from the previous level
        for k in range(1, self.depth):
            out_k = self.spp_dw[k](output[-1])
            output.append(out_k)

        # Gather them now in reverse order
        for _ in range(self.depth-1):
            resampled_out_k = self.upsampler(output.pop(-1))
            output[-1] = output[-1] + resampled_out_k

        expanded = self.conv_1x1_exp(self.final_norm(output[-1]))

        return self.module_act(expanded + x)

class SuDORMRF(nn.Module):
    def __init__(self,
                 out_channels=128,
                 in_channels=512,
                 num_blocks=16,
                 upsampling_depth=4,
                 enc_kernel_size=21,
                 enc_num_basis=512,
                 num_sources=2):
        super(SuDORMRF, self).__init__()

        # Number of sources to produce
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.num_blocks = num_blocks
        self.upsampling_depth = upsampling_depth
        self.enc_kernel_size = enc_kernel_size
        self.enc_num_basis = enc_num_basis
        self.num_sources = num_sources

        # Appropriate padding is needed for arbitrary lengths
        self.lcm = abs(self.enc_kernel_size // 2 * 2 **
                       self.upsampling_depth) // math.gcd(
                       self.enc_kernel_size // 2,
                       2 ** self.upsampling_depth)

        # Front end
        self.encoder = nn.Sequential(*[
            nn.Conv1d(in_channels=1, out_channels=enc_num_basis,
                      kernel_size=enc_kernel_size,
                      stride=enc_kernel_size // 2,
                      padding=enc_kernel_size // 2),
            nn.ReLU(),
        ])

        # Norm before the rest, and apply one more dense layer
        self.ln = nn.GroupNorm(1, enc_num_basis, eps=1e-08)
        self.l1 = nn.Conv1d(in_channels=enc_num_basis,
                            out_channels=out_channels,
                            kernel_size=1)

        # Separation module
        self.sm = nn.Sequential(*[
            UBlock(out_channels=out_channels,
                   in_channels=in_channels,
                   upsampling_depth=upsampling_depth)
            for r in range(num_blocks)])

        if out_channels != enc_num_basis:
            self.reshape_before_masks = nn.Conv1d(in_channels=out_channels,
                                                  out_channels=enc_num_basis,
                                                  kernel_size=1)

        # Masks layer
        self.m = nn.Conv2d(in_channels=1,
                           out_channels=num_sources,
                           kernel_size=(enc_num_basis + 1, 1),
                           padding=(enc_num_basis - enc_num_basis // 2, 0))

        # Back end
        self.decoder = nn.ConvTranspose1d(
            in_channels=enc_num_basis * num_sources,
            out_channels=num_sources,
            output_padding=(enc_kernel_size // 2) - 1,
            kernel_size=enc_kernel_size,
            stride=enc_kernel_size // 2,
            padding=enc_kernel_size // 2,
            groups=num_sources)
        self.ln_mask_in = nn.GroupNorm(1, enc_num_basis, eps=1e-08)
    # Forward pass
    def forward(self, input_wav):
        # Front end
        x = self.pad_to_appropriate_length(input_wav)
        x = self.encoder(x)

        # Split paths
        s = x.clone()

        # Separation module
        x = self.ln(x)
        x = self.l1(x)
        x = self.sm(x)

        if self.out_channels != self.enc_num_basis:
            # x = self.ln_bef_out_reshape(x)
            x = self.reshape_before_masks(x)

        # Get masks and apply them
        x = self.m(x.unsqueeze(1))
        if self.num_sources == 1:
            x = torch.sigmoid(x)
        else:
            x = nn.functional.softmax(x, dim=1)
        x = x * s.unsqueeze(1)
        # Back end
        estimated_waveforms = self.decoder(x.view(x.shape[0], -1, x.shape[-1]))
        return self.remove_trailing_zeros(estimated_waveforms, input_wav)

    def pad_to_appropriate_length(self, x):
        values_to_pad = int(x.shape[-1]) % self.lcm
        if values_to_pad:
            appropriate_shape = x.shape
            padded_x = torch.zeros(
                list(appropriate_shape[:-1]) +
                [appropriate_shape[-1] + self.lcm - values_to_pad],
                dtype=torch.float32)
            padded_x[..., :x.shape[-1]] = x
            return padded_x
        return x

    @staticmethod
    def remove_trailing_zeros(padded_x, initial_x):
        return padded_x[..., :initial_x.shape[-1]]

if __name__ == "__main__":
    model = SuDORMRF(out_channels=128,
                     in_channels=512,
                     num_blocks=16,
                     upsampling_depth=4,
                     enc_kernel_size=21,
                     enc_num_basis=512,
                     num_sources=2)

    dummy_input = torch.rand(3, 1, 32079)
    estimated_sources = model(dummy_input)
    print(estimated_sources.shape)


import sys

from os_xenapi.client import exception
from os_xenapi.utils import common_function
from os_xenapi.utils import himn
from os_xenapi.utils import sshclient

OVS_NATIVE_TCP_PORT = '6640'
VXLAN_UDP_PORT = '4789'

def exit_with_error(err_msg):
    sys.stderr.write(err_msg)
    sys.exit(1)

def configure_dom0_iptables(client):
    xs_chain = 'XenServer-Neutron-INPUT'
    # Check XenServer specific chain, create if not exist
    ret, out, err = execute_iptables_cmd('filter', '-L', xs_chain,
                                         client=client,
                                         allowed_return_codes=[0, 1])
    if ret == 1:
        execute_iptables_cmd('filter', '--new', xs_chain, client=client)
        rule_spec = ('-j %s' % xs_chain)
        execute_iptables_cmd('filter', '-I', 'INPUT', rule_spec=rule_spec,
                             client=client)

    # Check XenServer rule for ovs native mode, create if not exist
    rule_spec = ('-p tcp -m tcp --dport %s -j ACCEPT'
                 % OVS_NATIVE_TCP_PORT)
    ensure_iptables('filter', xs_chain, rule_spec, client=client)

    # Check XenServer rule for vxlan, create if not exist
    rule_spec = ('-p udp -m multiport --dport %s -j ACCEPT'
                 % VXLAN_UDP_PORT)
    ensure_iptables('filter', xs_chain, rule_spec, client=client)

    # Persist iptables rules
    client.ssh('service iptables save')

def configure_himn_forwards(forwarding_interfaces, dom0_himn_ip):
    # enable forward
    # make change to be persistent
    common_function.execute(
        'sed', '-i', 's/.*net\.ipv4\.ip_forward.*=.*/net.ipv4.ip_forward=1/g',
        '/etc/sysctl.conf')
    # make it to take effective now.
    common_function.execute('sysctl', 'net.ipv4.ip_forward=1')
    eth = himn.get_local_himn_eth(dom0_himn_ip)
    if not eth:
        raise exception.NoNetworkInterfaceInSameSegment(dom0_himn_ip)
    for interface in forwarding_interfaces:
        # allow traffic from HIMN and forward traffic
        rule_spec = '-o ' + interface + ' -j MASQUERADE'
        ensure_iptables('nat', 'POSTROUTING', rule_spec)

        rule_spec = '-i ' + interface + ' -o ' + eth + ' -m state ' + \
                    '--state RELATED,ESTABLISHED -j ACCEPT'
        ensure_iptables('filter', 'FORWARD', rule_spec)

        rule_spec = '-i ' + eth + ' -o ' + interface + ' -j ACCEPT'
        ensure_iptables('filter', 'FORWARD', rule_spec)

    rule_spec = '-i ' + eth + ' -j ACCEPT'
    ensure_iptables('filter', 'INPUT', rule_spec)
    execute_iptables_cmd('filter', '-S', 'FORWARD')
    execute_iptables_cmd('nat', '-S', 'POSTROUTING')

def ensure_iptables(table, chain, rule_spec, client=None):
    ret, _, _ = execute_iptables_cmd(
        table, '-C', chain, rule_spec=rule_spec, client=client,
        allowed_return_codes=[0, 1])
    # if the return value is 1, the rule is not exists
    if ret == 1:
        execute_iptables_cmd(table, '-I', chain, rule_spec=rule_spec,
                             client=client)

def execute_iptables_cmd(table, action, chain, rule_spec=None, client=None,
                         allowed_return_codes=[0]):
    """This function is used to run iptables command.

    Users could run command to configure iptables for remote and local hosts.
    If the user want to configure remote host, the session client is needed, or
    the command would be run on local host.

    :param table: table you want you configure.
    :param rule_spec: rule spec you want to apply.
    :param client: session client to remote host you want to configure.
    :param expect_exception: When you just want to do a rule check, set this
    flag to 'True'. Then the reture value would be 'Ture' or 'False'.
    :param forwarding_interfaces: network interface list which user want to
    forward HIMN packages.
    """
    if client:
        if not rule_spec:
            rule_spec = ''
        command = ('iptables -t %(table)s %(action)s %(chain)s %(rule_spec)s'
                   % {'table': table, 'action': action,
                      'chain': chain, 'rule_spec': rule_spec})
        command = command.strip()
        return client.ssh(command, allowed_return_codes=allowed_return_codes)
    else:
        if rule_spec:
            rule_spec = rule_spec.split()
        else:
            rule_spec = []
        command = ['iptables', '-t', table, action, chain] + rule_spec
        return common_function.detailed_execute(
            *command, allowed_return_codes=allowed_return_codes)

def config_iptables(client, forwarding_interfaces=None):
    """This function is used to configure iptables on a XenServer compute node.

    :param client: session client with Dom0
    :param forwarding_interfaces: network interface list which user want to
    forward HIMN packages.
    """
    if forwarding_interfaces:
        configure_himn_forwards(forwarding_interfaces, client.ip)
    configure_dom0_iptables(client)

if __name__ == '__main__':
    if len(sys.argv) != 5:
        exit_with_error("Wrong parameters input.")
    dom0_himn_ip, user_name, password, forwarding_interfaces = sys.argv[1:]
    forwarding_interfaces = forwarding_interfaces.split()
    try:
        client = sshclient.SSHClient(dom0_himn_ip, user_name, password)
    except Exception:
        exit_with_error("Create connection failed, ip: %(dom0_himn_ip)s,"
                        " user_name: %(user_name)s" %
                        {'dom0_himn_ip': dom0_himn_ip, 'user_name': user_name})
    config_iptables(client, forwarding_interfaces)


class Vehicle:
    def __init__(self, id, col, pos, size, orientation):
        self.identification = id
        self.color = col
        self.position = pos
        self.size = size
        self.orientation = orientation
        self.isMain = False
        if(id == 1):
            self.isMain = True


class TreasureChest():
    #DECLARE question: STRING
    #DECLARE answer: INTEGER
    #DECLARE points: INTEGER
    question = ""
    answer = 0
    points = 0

    #question c)i)
    def getQuestion(self, questionNum):
        self.question = arrayTreasure[questionNum].question
        return print(self.question)

    #question c)ii)
    def checkAnswer(self, userAnswer):
        if userAnswer == answer:
            return True
        else:
            return False

    #question c)iii)
    def getPoints(self, attempts):
        if attempts == 1:
            return points
        elif attempts == 2:
            return(points//2)
        elif attempts == 3 or attempts == 4:
            return(points//4)
        else:
            return 0

#DECLARE arrayTreasure: 1D ARRAY
arrayTreasure = ["","","","","",""]

#DECLARE Questions: OBJECT
Questions = TreasureChest()

def readData():
    f = open("TreasureChestData.txt", "r")
    eof = False
    index = -1
    while eof == False:
            Questions.question = f.readline()
            Questions.answer = f.readline()
            Questions.points = f.readline()

            index += 1
            arrayTreasure[index] = TreasureChest()

            if Questions.question == "":
                eof = True

#question c)iv)
readData()

#DECLARE questionNum: INTEGER
questionNum = int(input("Please input a number between 1 and 5: "))

Questions.getQuestion(questionNum)

#DECLARE userAnswer: INTEGER
userAnswer = int(input())


#访问控制和可见性
class Student():
    #python中所有的变量，默认是公有的
    #要想让一个变量或者方法变为私有，需要给他的名字前面加__

    sum=0

    def __init__(self,name,age):
        self.name=name
        self.age=age
        #这里把成绩改为私有
        self.__score=0
    #公有实例方法
    def print_file(self):
        print(self.name)
        print(self.age)
        print(self.__score)

    def __getScore(self):
        return self.__score
    #打分函数
    def marking(self,score):
        self.__getScore()
        if(score<0):
            print('不能打负分')
            return
        self.__score=score

student =Student('wang',25)
student.marking(95)
student.print_file()

#注意：尽管我们已经把score设置为私有，但是通过以下方式依然可以赋值和访问__score
student.__score=200
#这是因为python作为动态语言，这里相当于它创建了一个新的__score，而不是类中的那个
#实例变量__score
print(student.__score)
#_student__score就是我们的私有变量，__score是刚才它自动新增的变量
print(student.__dict__)


#Given an arithmetic expression in Reverse Polish Notation, write a program to evaluate it.
#The expression is given as a list of numbers and operands. For example: [5, 3, '+'] should
#return 5 + 3 = 8. For example, [15, 7, 1, 1, '+', '-', '/', 3, '*', 2, 1, 1, '+', '+', '-']
#should return 5, since it is equivalent to ((15 / (7 - (1 + 1))) * 3) - (2 + (1 + 1)) = 5.
#You can assume the given expression is always valid.

#In comparison testing of reverse Polish notation with algebraic notation, reverse Polish has
#been found to lead to faster calculations, for two reasons. The first reason is that
#reverse Polish calculators do not need expressions to be parenthesized, fewer operations
#need to be entered to perform typical calculations. Additionally,users of reverse Polish
#calculators made fewer mistakes than for other types of calculator.[9] [10] Later research
#clarified that the increased speed from reverse Polish notation may be attributed to the smaller number of
#keystrokes needed to enter this notation, rather than to a smaller cognitive load on its users.
#[11] However, anecdotal evidence suggests that reverse Polish notation is more
#difficult for users to learn than algebraic notation.[10]

#for each token in the postfix expression:
#  if token is an operator:
#    operand_2 ← pop from the stack
#    operand_1 ← pop from the stack
#    result ← evaluate token with operand_1 and operand_2
#    push result back onto the stack
#  else if token is an operand:
#    push token onto the stack
#result ← pop from the stack

ops = {
        "+": (lambda a,b: a+b),
        "-": (lambda a,b: a-b),
        "*": (lambda a,b: a*b),
        "/": (lambda a,b: a/b)
        }

def eval(expression):
    tokens = expression.split()
    stack = []

    for token in tokens:
        if token in ops:
            operand2 = stack.pop()
            operand1 = stack.pop()
            result = ops[token](operand1,operand2)
            stack.append(result)
        else:
            stack.append(int(token))
        #Sprint (stack)
    return stack

print(eval("15 7 1 1 + - / 3 * 2 1 1 + + -"))


def MDC(pokebolas, pocoes, revives):
    if pocoes == 0 and revives == 0:
        return pokebolas

    if not pocoes:
        if revives:
            return MDC(revives, pocoes, pokebolas % revives)

    else:
        return MDC(pocoes, pokebolas % pocoes, revives)

pokebolas = int(input())
pocoes = int(input())
revives = int(input())

result = MDC(pokebolas, pocoes, revives)

if result > 1:
    print(f"Gracas a Companhia Pokemon, {result} treinadores pokemon vao receber itens do Professor!")

else:
    print("Infelizmente apenas 1 treinador pokemon ira receber os itens hoje, e com certeza nao e o atrasado do Ash.")


import users as us

def back():
  dist = ['Exit', 'Back']
  us.show_menu(dist)
  select = int(input('\nSelect Option '))
  if select == 2:
    main()

def main():
  us.main_show('user.csv', ['name', 'email'], 'users')

main()


def iswall(x): return 0 < x <= n

n, m, t = map(int, input().split())
arr = [[0]] + [list(map(int, input().split())) for _ in range(n)]
spin_info = [tuple(map(int, input().split())) for _ in range(t)]

for i, d, k in spin_info:
    # 번호가 i의 배수인 원판을 d 방향으로 k 칸 회전
    temp = 1
    if d: # 반시계
        while i*temp <= n:
            arr[i*temp] = arr[i*temp][k:] + arr[i*temp][:k]
            temp += 1
    else: # 시계
        while i*temp <= n:
            arr[i*temp] = arr[i*temp][m-k:] + arr[i*temp][:m-k]
            temp += 1

    flag = True
    for x in range(1, n+1):
        for y in range(m):
            if arr[x][y]:
                temp_flag = False
                for dx, dy in (0, 1), (1, 0), (-1, 0), (0, -1):
                    nx, ny = x + dx, (y + dy + m) % m
                    if iswall(nx) and arr[nx][ny] == arr[x][y]:
                        temp_flag = True
                        break
                if temp_flag:
                    flag = False
                    k_num = arr[x][y]
                    queue = [(x, y)]
                    arr[x][y] = 0
                    while queue:
                        new_queue = []
                        for kx, ky in queue:
                            for dx, dy in (0, 1), (1, 0), (-1, 0), (0, -1):
                                nx, ny = kx + dx, (ky + dy + m) % m
                                if iswall(nx) and arr[nx][ny] == k_num:
                                    new_queue.append((nx, ny))
                                    arr[nx][ny] = 0
                        queue = new_queue

    # 인접하면서 수가 같은 것을 모두 찾는다.
    # 있다면 - 원판에서 인접, 같은 수를 모두 지우고
    if flag:
        temp_sum = 0
        temp_cnt = 0
        for x in range(1, n+1):
            for y in range(m):
                if arr[x][y]:
                    temp_sum += arr[x][y]
                    temp_cnt += 1
        if temp_cnt:
            avg = temp_sum / temp_cnt
            for x in range(1, n+1):
                for y in range(m):
                    if arr[x][y]:
                        if arr[x][y] > avg:
                            arr[x][y] -= 1
                        elif arr[x][y] < avg:
                            arr[x][y] += 1
    # 없다면 - 원판 적힌 수의 평균을 구하고, 평균보다 큰 수에서 -1, 작은 수 +1

print(sum(sum(arr, [])))
# 다 회전시킨 후 원판에 적힌 수의 합 구하기

"""
원판을 아래와 같은 방법으로 총 T번 회전시키려고 한다.
원판의 회전 방법은 미리 정해져 있고, i번째 회전할때 사용하는 변수는 xi, di, ki이다.

번호가 xi의 배수인 원판을 di방향으로 ki칸 회전시킨다.
di가 0인 경우는 시계 방향, 1인 경우는 반시계 방향이다.

인접하면서 수가 같은 것을 모두 찾는다.
그러한 수가 있는 경우에는 원판에서 인접하면서 같은 수를 모두 지운다.
없는 경우에는 원판에 적힌 수의 평균을 구하고, 평균보다 큰 수에서 1을 빼고, 작은 수에는 1을 더한다.
원판을 T번 회전시킨 후 원판에 적힌 수의 합을 구해보자.
"""

"""
4 4 1
1 1 2 3
5 2 4 2
3 1 3 5
2 1 3 2
2 0 1
30
4 4 2
1 1 2 3
5 2 4 2
3 1 3 5
2 1 3 2
2 0 1
3 1 3
22
4 4 4
1 1 2 3
5 2 4 2
3 1 3 5
2 1 3 2
2 0 1
3 1 3
2 0 2
3 1 1
0
4 6 3
1 2 3 4 5 6
2 3 4 5 6 7
3 4 5 6 7 8
4 5 6 7 8 9
2 1 4
3 0 1
2 1 2
26
"""


from src.parseDictionary import *
# from nltk.corpus import words

"""
This file contains the Board class, which holds our crossword puzzle. The Board class also has all the methods
needed to add new words to the crossword, shift cells in the crossword, and to print a representation of the crossword.
The file also contains the Cell class, which is an inner class of Board.
There are several places in this file that we think contain errors (marked with a comment "POSSIBLE ERROR"). In these
places we give a short explanation of what we believe the error to be and how we might fix it.
"""

file = open("wordList.csv", 'r')
text = file.read()
file.close()

FILE_NAME = 'dictFile.csv'

"""
Represents the crossword grid as a 2D array.
The Board class also holds a CrosswordRepresentation, defines the Cell class, and has methods like:
- addIfValid(...)
- terminalRepresentationOfCrossWord()
- shiftElements(...)
- methods to test whether a row or column is empty, and how many rows or columns are empty
and many helper methods as well.
"""
class Board:
    def __init__(self, crossword, ARRAY_WIDTH = 10):
        self.WIDTH = ARRAY_WIDTH
        self.crossword = crossword
        self.wordList = text
        # initialize array with None
        self.boardArray = [[None for i in range(self.WIDTH)] for j in range(self.WIDTH)]

        # if an across word has already been supplied to start off the puzzle, we add the needed Cells to the array and
        # add the word to the across word dictionary.
        if len(crossword.across.keys()) != 0:
            self.startingWord = list(crossword.across.keys())[0]
            # puts the starting word the top left corner of the array (0,0) to (0,len(word)-1)
            self.addWordToArray(0, 0, self.startingWord, None, True)
            self.crossword.across[self.startingWord] = self.getCellAt(0,0)

    """
    Represents one cell within the crossword puzzle, holding coordinates in the puzzle, the character in the cell,
    the across and down word (if any) that the cell is a part of, and the index of the cell within those words.
    """
    class Cell:
        def __init__(self, acrossWord, downWord, x, y, indexInAcrossWord, indexInDownWord):
            self.acrossWord = acrossWord
            self.downWord = downWord
            self.xCoord = x
            self.yCoord = y
            self.indexInAcrossWord = indexInAcrossWord
            self.indexInDownWord = indexInDownWord

            # calculate the character that is in this cell based on any words we know the cell is part of.
            if self.acrossWord is not None:
                self.char = self.acrossWord[self.indexInAcrossWord]
            elif self.downWord is not None:
                self.char = self.downWord[self.indexInDownWord]

        """
        Setter method for the coordinates of the cell.
        Will use this when we shift the array or certain cells.
        """
        def setCoord(self, newX, newY):
            self.xCoord = newX
            self.yCoord = newY

    """
    Method that will let us view what the crossword looks like in the terminal by printing the crossword row by row.
    """
    def terminalRepresentationOfCrossword(self, usedCells = True):
        row = []
        for y in range(len(self.boardArray)):
            for x in range(len(self.boardArray)):
                cell = self.boardArray[x][y]
                if cell is not None and type(cell) is self.Cell:
                    row.append(cell.char)
                else:
                    row += ['_']
            print(row)
            row = []

    def wordCount(self):
        return len(self.crossword.across.keys()) + len(self.crossword.down.keys())

    def numIntersections(self):
        return len(self.crossword.inter)

    """
    This method is written with the assumption that we have checked that it is valid to
    add the word at this position and the area around it. This method adds the word itself to the array and the down
    or across list, and to the intersections list. It does not update any neighboring words - client code should do
    that on its own.
    """
    def addWordToArray(self, sX, sY, newWord, intersection, newWordIsAcross):

        for x in range(len(newWord)):
            if newWordIsAcross:
                self.boardArray[sX+x][sY] = self.Cell(newWord, None, sX + x, sY, x, 0)
            else:
                self.boardArray[sX][sY + x] = self.Cell(None, newWord, sX, sY + x, 0, x)

        # POSSIBLE ERROR: Sometimes, newWord will be placed right after an existing word, and in that case newWord
        # itself won't be added to the puzzle, but a combination of the esiting word and newWord will be added
        # instead. In that case, this line will put in a nonexistent across word into the puzzle.
        if newWordIsAcross:
            self.crossword.across[newWord] = self.getCellAt(sX, sY)
        else:
            self.crossword.down[newWord] = self.getCellAt(sX, sY)

    """
    Returns cell object at given x and y coordinate
    """
    def getCellAt(self, x, y):
        return self.boardArray[x][y]

    """
    Takes in an intersection and whether the word being theoretically added is an Across word. Returns true if the
    intersection would result in a valid new crossword, false otherwise. Calls another function which will add the new
    word to underlying crossword.
    """
    def addIfValid(self, interCell, intersection, newWordIsAcross):
        copyArray = [[None for i in range(self.WIDTH)] for j in range(self.WIDTH)]

        # set the variables we will need that differ based on newWordIsAcross:
        # newWord is the word that we will try to add to the puzzle
        # startingX is the x-coord of the cell where newWord will start. startingY, endingY, etc are analogous
        if newWordIsAcross:
            newWord = intersection.across
            startingX = interCell.xCoord - intersection.acrossIndex
            startingY = interCell.yCoord
        else:
            newWord = intersection.down
            startingX = interCell.xCoord
            startingY = interCell.yCoord - intersection.downIndex

        # ----------------------------------------------
        # check whether would be out of bounds of the puzzle, and couldn't be shifted to fit
        # ----------------------------------------------
        # we calculate the literal horizontal width and the vertical height of the new word (in terms of puzzle cells)
        if newWordIsAcross:
            newWordWidth = len(newWord)
            newWordHeight = 1
        else:
            newWordWidth = 1
            newWordHeight = len(newWord)
        # we use the height and width to calculate the endingX and Y of the new word
        endingX = startingX + newWordWidth-1
        endingY = startingY + newWordHeight-1

        # returns a list of the form [xShift, yShift]
        shift = self.calculateShift(newWordWidth, newWordHeight, startingX, endingX, startingY, endingY)

        # if calculateShift() returns None, there is no valid way to fit the new word into the puzzle
        if shift is None:
            return False

        # otherwise, we shift the puzzle and adjust startingX etc as needed.
        if shift != [0, 0]:
            self.shiftElements(shift[0], shift[1])
            startingX += shift[0]
            endingX += shift[0]
            startingY += shift[1]
            endingY += shift[1]

        # ---------------------------------------------------------
        # loop through each cell that the new word would inhabit and check for collisions and conflicts
        # ---------------------------------------------------------
        for i in range(len(newWord)):

            # we calculate the new character to be added
            char = newWord[i]

            # we calculate the current x and y coords and the current cell we are checking
            if newWordIsAcross:
                currentX = startingX+i
                currentY = startingY
                currentCell = self.boardArray[currentX][currentY]
            else:
                currentX = startingX
                currentY = startingY+i
                currentCell = self.boardArray[currentX][currentY]

            # ------------------------------------------------------
            # Check whether the current cell would be overwritten, and whether that would cause a conflict
            # ------------------------------------------------------
            # we use a helper method to determine whether the current cell intersects invalidly with any existing word
            if not self.intersectionIsValid(char, newWord, startingX, startingY, currentX, currentY,
                                            currentCell, i, newWordIsAcross):
                return False

            # -------------------------------------------------------------
            # Check whether any adjacent cells will cause a conflict
            # -------------------------------------------------------------
            if not self.adjCellsValid(char, startingX, startingY, currentX, currentY, i, currentCell, newWordIsAcross):
                return False

        # ------------------------------------------
        # Check the cells before and after the new word for word collisions
        # ------------------------------------------
        if not self.endsAreValid(newWord, startingX, startingY, endingX, endingY, newWordIsAcross):
            return False

        # --------------------------------------------------------------------------
        # If we made it this far without returning False, the new word is valid! Add the word to the puzzle.
        # --------------------------------------------------------------------------
        self.addWordToArray(startingX, startingY, newWord, intersection, newWordIsAcross)
        return True

    """
    A helper method for addIfValid.
    Returns True or False to indicate whether overwriting currentCell with char will cause any conflicts.
    """
    def intersectionIsValid(self, char, newWord, startingX, startingY, currentX, currentY, currentCell, i, newWordIsAcross):
        # here, if the current cell we are checking is not None (meaning it holds a character), we find the word that
        # currentCell is part of (if any) that is perpendicular to newWord, and find the index within that perpendicular
        # word that we are currently at. We do the same for any word parallel to newWord that currentCell is part of.
        if newWordIsAcross:
            if currentCell is not None:
                perpendicularWordIndex = currentCell.indexInDownWord
                perpendicularIntersectingWord = currentCell.downWord
                parallelIntersectingWord = currentCell.acrossWord
        else:
            if currentCell is not None:
                perpendicularWordIndex = currentCell.indexInAcrossWord
                perpendicularIntersectingWord = currentCell.acrossWord
                parallelIntersectingWord = currentCell.downWord

        # if the new char being added is the same as the existing one, we know it is valid since no change occurs
        if currentCell is not None and char == currentCell.char:
            return True

        # otherwise, if there is a char in the currentCell already, check whether the perpendicular word is still valid
        if currentCell is not None and perpendicularIntersectingWord is not None:

            # we use a helper method to find the new word with only the new char changed from the existing word
            collidedWord = self.getCollidedWord(char, perpendicularWordIndex, perpendicularIntersectingWord)

            # if getCollidedWord() returns None, it means the newly formed word would not be valid anymore
            if collidedWord is None:
                return False

            # update the across and down dictionaries to contain the newly formed collidedWord instead of the old word
            # but only if the two words aren't the same (if they're the same, nothing needs to be updated)
            else:
                if newWordIsAcross:
                    if collidedWord != perpendicularIntersectingWord:
                        self.crossword.down[collidedWord] = self.crossword.down[perpendicularIntersectingWord]
                        self.crossword.down.pop(perpendicularIntersectingWord)
                        # update currentCell's down word
                        self.boardArray[startingX+i][startingY].downWord = collidedWord
                        # POSSIBLE ERROR: We also should be going through the Cells of the perpendicular intersecting
                        # word and updating their downWord to be the new, changed downWord. Currently they still
                        # refer to a word that is no longer in the puzzle and this is causing errors when we later try
                        # to use those references as keys in the across or down word dicts.
                else:
                    if collidedWord != perpendicularIntersectingWord:
                        self.crossword.across[collidedWord] = self.crossword.across[perpendicularIntersectingWord]
                        self.crossword.across.pop(perpendicularIntersectingWord)
                        # update currentCell's down word
                        self.boardArray[startingX][startingY+i].acrossWord = collidedWord
                        # POSSIBLE ERROR: Same error as above, but occurs when new word is a down word instead.

        # If there is a word that overlaps and is in the same direction as the new word, we will return False since it
        # is fairly complex to check whether this word will still be valid. (In the future, we could instead check.)
        if currentCell is not None and parallelIntersectingWord is not None:
            return False

        # if we got to this line without returning False, the intersection in this currentCell is valid.
        return True

    """
    Helper method for addIfValid. Returns (if there is a valid one) the new word with a certain character changed. If
     that new word is not a valid word, this method returns None.
    This is only meant to check words that are perpendicular to the new word being added. Client code should check
    the validity of any word that is parallel to the added word, since that will involve multiple new cells
    simultaneously. (We decided to ignore them, in fact).
    """
    def getCollidedWord(self, char, perpendicularWordIndex, perpendicularWord):
        # case 1: adding char as index 0
        if perpendicularWordIndex == 0:
            collidedWord = char + perpendicularWord[1:]
        # case 2: adding char as last index
        elif perpendicularWordIndex == len(perpendicularWord)-1:
            collidedWord = perpendicularWord[:len(perpendicularWord)-1] + char
        # case 3: adding char somewhere in the middle
        else:
            collidedWord = perpendicularWord[:perpendicularWordIndex] + char \
                           + perpendicularWord[perpendicularWordIndex+1:]

        # check whether this word is in our word list
        if collidedWord not in self.wordList:
            return None
        # if it is in the word list, return it
        else:
            return collidedWord

    """
    Helper method for addIfValid.
    Returns True if adding the new char to the currentCell would not cause conflicts in any neighboring cells;
    returns False otherwise.
    """
    def adjCellsValid(self, char, startingX, startingY, currentX, currentY, i, currentCell, newWordIsAcross):
        # first, we find the two adjacent cells to currentCell - either left and right (if the new word is vertical)
        # or above and below (if the new word is horizontal.) We make sure to check that these cells exist (are not
        # off the edge of the puzzle.)
        adjCellOne = None
        adjCellTwo = None
        if newWordIsAcross:
            if currentY + 1 <= self.WIDTH - 1:
                adjCellTwo = self.boardArray[currentX][currentY + 1]
            if currentY - 1 >= 0:
                adjCellOne = self.boardArray[currentX][currentY - 1]
        else:
            if currentX+1 <= self.WIDTH - 1:
                adjCellTwo = self.boardArray[currentX+1][currentY]
            if currentX-1 >= 0:
                adjCellOne = self.boardArray[currentX-1][currentY]

        # if neither adjacent cell holds a character, there are no conflicts.
        if adjCellOne is None and adjCellTwo is None:
            return True

        # We use another helper method to calculate the "affix" for each adjacent cell - the character or word that
        # might cause a conflict with the new char being added. If an adjCell is None, the affix is ''
        if adjCellOne is not None:
            cellOneAffix = self.getCellAffix(adjCellOne, newWordIsAcross, True)
        else:
            cellOneAffix = ''
        if adjCellTwo is not None:
            cellTwoAffix = self.getCellAffix(adjCellTwo, newWordIsAcross, False)
        else:
            cellTwoAffix = ''

        # we add any affixes together with the new char to see what new word would be created by adding the char here,
        # and check to see if it is still a valid word
        affixedWord = cellOneAffix + char + cellTwoAffix
        if affixedWord not in self.wordList:
            return False

        # if it is valid, we go through and update the across and down word dictionaries to hold the correct words;
        # we also update the cells in each word that has now been changed to a new word.
        else:
            if newWordIsAcross:
                if adjCellOne is not None:
                    if adjCellOne.downWord is not None:
                        # update down-word dictionary to point to the cell where the old adjCellOne's word started
                        self.crossword.down[affixedWord] = self.crossword.down[adjCellOne.downWord]
                        # then remove the now-nonexistent down word
                        self.crossword.down.pop(adjCellOne.downWord)
                    else:
                        # update the down-word dict to point to adjCellOne itself
                        self.crossword.down[affixedWord] = adjCellOne
                # Since adjCellTwo's downword has been overwritten that downword is no longer in the crossword,
                # so we pop it off.
                if adjCellTwo is not None and adjCellTwo.downWord is not None and adjCellTwo.downWord in self.crossword.down.keys():
                    self.crossword.down.pop(adjCellTwo.downWord)
                # update the down words in the two adjCells and in currentCell
                if adjCellTwo is not None:
                    self.boardArray[currentX][currentY + 1].downWord = affixedWord
                if adjCellOne is not None:
                    self.boardArray[currentX][currentY - 1].downWord = affixedWord
                if currentCell is not None:
                    self.boardArray[startingX+i][startingY].downWord = affixedWord
                # POSSIBLE ERROR: We should be updating all the Cells in the down words of the two adjCells, so that
                # they no longer refer to an old word that no longer exists in the puzzle. This error is likely
                # causing the key errors we see, since we later pass in an old word that doesn't exist as a key.

            # this block follows much the same logic as above, but adjusted for if the new word is down instead
            else:
                if adjCellOne is not None:
                    if adjCellOne.acrossWord is not None:
                        self.crossword.across[affixedWord] = self.crossword.across[adjCellOne.acrossWord]
                        self.crossword.across.pop(adjCellOne.acrossWord)
                    else:
                        self.crossword.across[affixedWord] = adjCellOne
                if adjCellTwo is not None and adjCellTwo.acrossWord is not None and adjCellTwo.downWord in self.crossword.across.keys():
                    self.crossword.across.pop(adjCellTwo.acrossWord)
                if adjCellOne is not None:
                    self.boardArray[currentX-1][currentY].acrossWord = affixedWord
                if adjCellTwo is not None:
                    self.boardArray[currentX+1][currentY].acrossWord = affixedWord
                if currentCell is not None:
                    self.boardArray[startingX][startingY+i].acrossWord = affixedWord
                # POSSIBLE ERROR: Same error as above, but for the case where new word is down instead of across

        return True

    """
    Helper method for addIfValid.
    Takes in a adjCell, which is a cell adjacent to the current cell being checked in newWord.
    Returns the character(s) that should be added to the character in that new cell, to get a word that can be checked
    whether it is in the quarter million word list. The client code should know whether the returned chars go after or
    before the new char, and should also perform the check in the word list. This method simply returns an affix.
    newIsAcross is a boolean stating whether the new word being added is across or down
    Returns a str - can be empty if there were neither chars nor a word in the adjCell.
    """
    def getCellAffix(self, adjCell, newIsAcross, newIsToLeft):
        # check whether the adjCell has an acrossWord, downWord, or both
        hasAcross = adjCell.acrossWord is not None
        hasDown = adjCell.downWord is not None

        # if it has neither, the affix will be ""
        if (not hasAcross) and (not hasDown):
            return str("")

        if newIsAcross:
            # whether or not the cell is above or below the new character, we will always either return a character
            # in the adjCell or the word that adjCell is part of (depending on whether, repsectively, the word in the
            # adjCell is perpendicular or parallel to newWord.)
            if hasAcross and not hasDown:
                # return only the char above
                ret = str(adjCell.acrossWord[adjCell.indexInAcrossWord])
                return ret
            elif hasDown:
                # whether it has both across and down or only down, only return the part of the word that is above
                # (minus the new char)
                word = adjCell.downWord
                if newIsToLeft:
                    return str(word[:adjCell.indexInDownWord+1])
                else:
                    return str(word[adjCell.indexInDownWord:])
        else:
            # this is a very similar idea to above, but now adjCell is just to the right or left of new character
            # instead of either above or below the cell in newWord.
            if hasDown and not hasAcross:
                ret = str(adjCell.downWord[adjCell.indexInDownWord])
                return ret
            elif hasAcross:
                word = adjCell.acrossWord
                if newIsToLeft:
                    return str(word[:adjCell.indexInAcrossWord+1])  # Do we need the +1 - is syntax inclusive:exclusive?
                else:
                    return str(word[adjCell.indexInAcrossWord:])

    """
    Helper method for addIfValid. Returns True if the two cells before and after the new word do not cause any
    conflicts that would make adding the new word invalid; returns False otherwise.
    """
    def endsAreValid(self, newWord, startingX, startingY, endingX, endingY, newWordIsAcross):
        # similarly to in adjCellsValid(), we find the two cells adjacent to the start and end of the new word
        startCell = None
        endCell = None
        if newWordIsAcross:
            if startingX-1 >= 0:
                startCell = self.boardArray[startingX - 1][startingY]
            if endingX+1 <= self.WIDTH-1:
                endCell = self.boardArray[endingX + 1][endingY]
        else:
            if startingY-1 >= 0:
                startCell = self.boardArray[startingX][startingY - 1]
            if endingY+1 <= self.WIDTH-1:
                endCell = self.boardArray[endingY][endingY + 1]

        # we only need to check for conflicts if one or both of the cells contains a char
        if startCell is not None or endCell is not None:

            # get the "affix" for both the cells (the char or word that we might need to combine with the newWord to
            # check whether putting them next to each other combines into a valid word or not)
            if startCell is not None:
                startAffix = self.getCellAffix(startCell, newWordIsAcross, True)
            else:
                startAffix = ""
            if endCell is not None:
                endAffix = self.getCellAffix(endCell, newWordIsAcross, False)
            else:
                endAffix = ""

            # combine the affixes with newWord to get the whole long word that would be formed by inserting newWord
            affixedWord = startAffix + newWord + endAffix

            # check whether this new long word is a valid word
            if affixedWord not in self.wordList:
                return False

            # if it is a valid word, update the entries in across and down dicts and in the affected Cells to match
            else:
                if newWordIsAcross:
                    if startCell is not None:
                        if startCell.acrossWord is not None:
                            # update the down-word dict to point to the startCell's word's begininning, if it exists
                            self.crossword.across[affixedWord] = self.crossword.across[startCell.acrossWord]
                            # also delete the old, now-non-existent down word
                            self.crossword.across.pop(startCell.acrossWord)
                    else:
                        # if startCell didn't have a down word, we update the dict to point to startCell itself instead
                        self.crossword.down[affixedWord] = startCell
                    # We update the start and end cells to hold the correct across word
                    if startCell is not None:
                        self.boardArray[startingX-1][startingY].acrossWord = affixedWord
                    if endCell is not None:
                        self.boardArray[endingX+1][endingY].acrossWord = affixedWord
                    # POSSIBLE ERROR: We should also be updating all the Cells in any word that startCell and endCell
                    # are a part of, so that those cells no longer reference a word that doesn't exist in the puzzle.
                    # This is probably causing key errors later when the old word is passed in to the across or down
                    # word dictionary.
                else:
                    if startCell is not None and startCell.downWord is not None:
                        # update the crossword down dict first
                        self.crossword.down[affixedWord] = self.crossword.down[startCell.downWord]
                        self.crossword.down.pop(startCell.downWord)
                    if endCell is not None and endCell.downWord is not None:
                        self.crossword.down.pop(endCell.downWord)
                    # add the new down word to crossword down dict if we didn't already
                    if startCell is not None and startCell.downWord is None:
                        self.crossword.down[affixedWord] = startCell
                    # update the two cells (all the new cells will be updated in another method, addWordToArray() )
                    if startCell is not None:
                        self.boardArray[startingX][startingY - 1].downWord = affixedWord
                    if endCell is not None:
                        self.boardArray[endingX][endingY + 1].downWord = affixedWord
                    # POSSIBLE ERROR: Same error as above.
        return True

    """
    Helper method for addIfValid. Computes and returns a length-2 array in the form [x, y] of the needed amount to
    shift the puzzle to fit the new word.
    If there is no way to fit the word, returns None. Returns [0, 0] if the puzzle does not need to be shifted.
    """
    def calculateShift(self, newWordWidth, newWordHeight, startingX, endingX, startingY, endingY):
        # if the word is too long to fit in the puzzle at all, we can't shift it and it's not a valid word to add.
        if newWordWidth > self.WIDTH or newWordHeight > self.WIDTH:
            return None

        xShift = 0
        yShift = 0

        if startingX < 0:
            # check if there are enough empty columns to shift the puzzle
            if self.getEmptyColsToRight() >= abs(startingX):
                xShift = abs(startingX)
            else:
                return None
        if endingX > self.WIDTH - 1:
            # same check, but for the right instead of left side of the puzzle.
            if self.getEmptyColsToLeft() >= abs(self.WIDTH - 1 - endingX):
                xShift = self.WIDTH - 1 - endingX
            else:
                return None
        if startingY < 0:
            # check the top of the puzzle
            if self.getEmptyRowsBelow() >= abs(startingY):
                yShift = abs(startingY)
            else:
                return None
        if endingY > self.WIDTH - 1:
            # check the bottom of the puzzle
            if self.getEmptyRowsAbove() >= abs(self.WIDTH - 1 - endingY):
                yShift = self.WIDTH - 1 - endingY
            else:
                return None

        return [xShift, yShift]

    """
    Shifts everything in the array by copying things over in another array.
    Will shift things over x to the right and y down.
    If either are negative then it's just the opposite direction.
    Also checks if the shift is valid.
    """
    def shiftElements(self, xShift, yShift):
        shiftedArray = [[None for i in range(self.WIDTH)] for j in range(self.WIDTH)]
        for x in range(self.WIDTH):
            for y in range(self.WIDTH):
                # caluclate the new x and y coords of this cell
                newX = x+xShift
                newY = y+yShift
                # if there is a char in this cell, shift it to the new position
                if self.boardArray[x][y] is not None and 0 <= newX < self.WIDTH and 0 <= newY < self.WIDTH:
                    shiftedArray[newX][newY] = self.boardArray[x][y]
                    shiftedArray[newX][newY].setCoord(newX, newY)
                # but return False if the shift would put us out of bounds
                elif self.boardArray[x][y] is not None \
                        and ((0 > newX or newX >= self.WIDTH) or (0 > newY or newY >= self.WIDTH)):
                    return False
        self.boardArray = shiftedArray
        return True

    """
    Returns true or false
    """
    def colIsEmpty(self, col):
        if col < self.WIDTH:
            for x in range(self.WIDTH):
                if self.boardArray[col][x] is not None:  # If is not None then there's an element in the column.
                    return False
            return True
        else:
            return False

    """
    Returns true or false
    """
    def rowIsEmpty(self, row):
        if row < self.WIDTH:
            for x in range(self.WIDTH):
                if self.boardArray[x][row] is not None:  # If is not None then there's an element in the column
                    return False
            return True
        else:
            return False

    """
    Returns the number of empty rows at the top of the puzzle
    """
    def getEmptyRowsAbove(self):
        row = 0
        count = 0
        while row < self.WIDTH and self.rowIsEmpty(row):
            row += 1
            count += 1
        return count

    """
    Returns the number of empty rows at the bottom of the puzzle
    """
    def getEmptyRowsBelow(self):
        row = self.WIDTH - 1
        count = 0
        while row >= 0 and self.rowIsEmpty(row):
            count += 1
            row -= 1
        return count

    """
    Returns the number of empty columns on the left side of the puzzle
    """
    def getEmptyColsToLeft(self):
        col = 0
        count = 0
        while col < self.WIDTH and self.colIsEmpty(col):
            col += 1
            count += 1
        return count

    """
    Returns the number of empty columns on the right side of the puzzle
    """
    def getEmptyColsToRight(self):
        col = self.WIDTH - 1
        count = 0
        while col >= 0 and self.colIsEmpty(col):
            col -= 1
            count += 1
        return count


#!/usr/bin/python3

import cgi
import base
from connect import connect
import settings
import os.path
import sys

if __name__ == "__main__":
    form = cgi.FieldStorage()
    attach_id = base.cleanCGInumber(form.getvalue('attach_id'))

    db=connect(0)
    cur=db.cursor()

    if(attach_id != 0):
        cur.execute("SELECT test_id, attachmime, originalname, attach FROM Attachments WHERE attach_id=%d" % (attach_id));

    if not cur.with_rows:
        print("Content-type: text/html\n")
        base.header("Attachment Request Error")
        base.top()
        print('
')
        print("
Attachment not available
")
        print('
')
        base.bottom()
    else:
        thevals=cur.fetchall()
        f = thevals[0][3]
        attpath=settings.getAttachmentPathFor(thevals[0][0],attach_id)
        if not f:
            print("Content-type: text/html\n")
            base.header("Attachment Request Error")
            base.top()
            print("
Attachment not found
")
            base.bottom()
        else:
            print('Content-type: %s \n\n' % (thevals[0][1]))
            print(f.decode("utf-8"))

    cur.close()

def save(attach_id):
    db=connect(0)
    cur=db.cursor()

    cur.execute("SELECT test_id, attachmime, originalname FROM Attachments WHERE attach_id=%d" % (attach_id));

    if not cur.with_rows:
        print("
Attachment not available
")
    else:
        thevals=cur.fetchall();
        attpath=settings.getAttachmentPathFor(thevals[0][0],attach_id)
        if not os.path.isfile(attpath):
            print("
Attachment not found
")
        else:
            statinfo = os.stat(attpath)
            sys.stdout.write(file(attpath,"rb").read() )
    cur.close()


class PID:
  def __init__(self, Ts, Tf):
    self.Ts = Ts
    self.Tf = Tf
    self.yf_prev = 20
    self.AlmActive = False
    self.PV = 0
    self.e_k1 = 0
    self.u_i_k = 0

  def __LowPassFilter(self, y):
    a = self.Ts/(self.Tf+self.Ts);
    yf = (1-a)*self.yf_prev + a*y
    self.yf_prev = yf
    return yf;

  def __trunc(self,floatNum, decimal_places):
    multiplier = 10 ** decimal_places
    return int(floatNum * multiplier) / multiplier

  def config(self, AlmLimit, Tf, Ts):
    self.Tf = Tf
    self.Ts = Ts
    if self.PV > AlmLimit:
      self.AlmActive = True

  def PV(self):
    return float(self.PV)

  def almActive(self):
    return self.AlmActive

  def ackAlm(self):
    self.AlmActive = False

  def clip(self,u, uMax, uMin):
    if u > uMax:
      u = uMax
    if u < uMin:
      u = uMin
    if u <= uMax and u >= uMin:
      u = u
    return u

  def PID(self, y_k,sp ,directAction, kp,Ti, Td, uMax, uMin):
    PV = self.__LowPassFilter(y_k)
    self.PV = self.__trunc(PV, 2)

    Gain = kp
    if directAction:
      Gain = -1*kp
    e_k = sp - self.PV        # Error
    u_p_k = Gain * e_k   	# P-Term
    u_d_k = Gain * Td * (e_k - self.e_k1) / self.Ts # D-Term
    self.e_k1 = e_k 							# Last Error
    self.u_i_k += (Gain * e_k * self.Ts) / Ti       # I-Term
    self.u_i_k = self.clip(self.u_i_k,uMax, uMin)   # Anti windup of I-term

    return self.clip(u_p_k + self.u_i_k + u_d_k, uMax, uMin)


"""initial table

Revision ID: 3726039df26e
Revises:
Create Date: 2021-11-01 16:26:47.165308

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '3726039df26e'
down_revision = None
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('sections',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=64), nullable=True),
    sa.Column('moderated', sa.Boolean(), nullable=True),
    sa.Column('premium', sa.Boolean(), nullable=True),
    sa.Column('parent_section_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['parent_section_id'], ['sections.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_sections_id'), 'sections', ['id'], unique=False)
    op.create_table('tags',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=64), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_tags_id'), 'tags', ['id'], unique=False)
    op.create_table('users',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('username', sa.String(), nullable=True),
    sa.Column('password', sa.String(), nullable=True),
    sa.Column('premium', sa.Boolean(), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
    op.create_table('articles',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('content', sa.String(length=2048), nullable=True),
    sa.Column('author_id', sa.Integer(), nullable=True),
    sa.Column('section_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['author_id'], ['users.id'], ),
    sa.ForeignKeyConstraint(['section_id'], ['sections.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_articles_id'), 'articles', ['id'], unique=False)
    op.create_table('userModerators',
    sa.Column('user_id', sa.Integer(), nullable=True),
    sa.Column('section_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['section_id'], ['sections.id'], ),
    sa.ForeignKeyConstraint(['user_id'], ['users.id'], )
    )
    op.create_table('comments',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('content', sa.String(length=256), nullable=True),
    sa.Column('response_id', sa.Integer(), nullable=True),
    sa.Column('article_id', sa.Integer(), nullable=True),
    sa.Column('author_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['article_id'], ['articles.id'], ),
    sa.ForeignKeyConstraint(['author_id'], ['users.id'], ),
    sa.ForeignKeyConstraint(['response_id'], ['comments.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_comments_id'), 'comments', ['id'], unique=False)
    op.create_table('tagArticles',
    sa.Column('tag_id', sa.Integer(), nullable=True),
    sa.Column('article_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['article_id'], ['articles.id'], ),
    sa.ForeignKeyConstraint(['tag_id'], ['tags.id'], )
    )
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('tagArticles')
    op.drop_index(op.f('ix_comments_id'), table_name='comments')
    op.drop_table('comments')
    op.drop_table('userModerators')
    op.drop_index(op.f('ix_articles_id'), table_name='articles')
    op.drop_table('articles')
    op.drop_index(op.f('ix_users_id'), table_name='users')
    op.drop_table('users')
    op.drop_index(op.f('ix_tags_id'), table_name='tags')
    op.drop_table('tags')
    op.drop_index(op.f('ix_sections_id'), table_name='sections')
    op.drop_table('sections')
    # ### end Alembic commands ###


from datetime import datetime
import pandas as pd
import os
# ====== HELIOS IMPORTS =========================================
# ------ Train/Test Function Imports ----------------------------
from helios_rl import STANDARD_RL
from helios_rl import HELIOS_SEARCH
from helios_rl import HELIOS_OPTIMIZE
# ====== LOCAL IMPORTS ==========================================
# ------ Local Environment --------------------------------------
from helios_rl.benchmark.environment.sailing_env import Environment as Sailing
# ------ Benchmark Fixed Config -----------------------------------------------
# Meta parameters
from helios_rl.benchmark.benchmark_config import benchmark_config
# Local Parameters
from helios_rl.benchmark.benchmark_config_local import benchmark_local_config
# --------------------------------------------------------------------

# Specialised main run script to call standardised benchmark problems
# - Specify which problems that are to be used and evaluated on
# - Update and import the experiment dev setup
def main(benchmark_settings:list=['sailing'],
         num_train_epi:int=0, num_test_epi:int=0):
    """Train and testing experiments for the benchmark tests."""
    for benchmark in benchmark_settings:
        ExperimentConfig = benchmark_config[benchmark]
        ProblemConfig = benchmark_local_config[benchmark]
        # Specify save dir
        time = datetime.now().strftime("%d-%m-%Y_%H-%M")
        # Create output directory if it doesn't exist
        cwd = os.getcwd()
        if not os.path.exists(cwd+'/HELIOS-BENCHMARK-output'):
            os.mkdir(cwd+'/HELIOS-BENCHMARK-output')
        save_dir = cwd+'/HELIOS-BENCHMARK-output/'+str('test')+'_'+time

        if num_train_epi != 0:
            ExperimentConfig['number_training_episodes'] = num_train_epi
        if num_test_epi != 0:
            ExperimentConfig['number_test_episodes'] = num_test_epi

        # --------------------------------------------------------------------
        # EXPERIMENTS TO DEVELOP
        # --------------------------------------------------------------------
        # # HELIOS Instruction Following
        # num_plans = 50
        # num_explor_epi = 1000
        # sim_threshold = 0.9

        # observed_states = None
        # instruction_results = None

        # helios = HELIOS_SEARCH(Config=ExperimentConfig, LocalConfig=ProblemConfig,
        #                     Environment=Sailing,
        #                     save_dir = save_dir+'/Reinforced_Instr_Experiment',
        #                     num_plans = num_plans, number_exploration_episodes=num_explor_epi, sim_threshold=sim_threshold,
        #                     feedback_increment = 0.1, feedback_repeats=1,
        #                     observed_states=observed_states, instruction_results=instruction_results)

        # # Don't provide any instruction information, will be defined by command line input
        # helios_results = helios.search(action_cap=100, re_search_override=False, simulated_instr_goal=None)

        # # Store info for next plan -> assumes we wont see the same instruction twice in one plan
        # observed_states = helios_results[0]
        # instruction_results = helios_results[1]
        # # --------------------------------------------------------------------
        # # Take Instruction path now defined with reinforced+unsupervised sub-goal locations and train to these
        # # Init experiment setup with sub-goal defined
        # reinforced_experiment = HELIOS_OPTIMIZE(Config=ExperimentConfig, LocalConfig=ProblemConfig,
        #                 Environment=Sailing,
        #                 save_dir=save_dir+'/Reinforced_Instr_Experiment', show_figures = 'No', window_size=0.1,
        #                 instruction_path=None, predicted_path=instruction_results, instruction_episode_ratio=0.05,
        #                 instruction_chain=True, instruction_chain_how='exact' )
        # reinforced_experiment.train()
        # reinforced_experiment.test()
        # --------------------------------------------------------------------
        # Flat Baselines
        flat = STANDARD_RL(Config=ExperimentConfig, LocalConfig=ProblemConfig,
                    Environment=Sailing,
                    save_dir=save_dir, show_figures = 'No', window_size=0.1)
        flat.train()
        flat.test()
        # --------------------------------------------------------------------

def run_benchmark():
    possible_benchmarks = ['sailing']
    benchmark_list = []
    while True:
        benchmark = input('Enter benchmark to run from '+ str(possible_benchmarks)+ ' (hit enter to continue): ')
        if benchmark == '':
            break
        elif benchmark not in possible_benchmarks:
            print('Invalid benchmark, please try again.')
        else:
            benchmark_list.append(benchmark)
    print("Please enter the number of ... (skip to use default) ")
    num_train_epi = int(input('\t - Training episodes: '))
    num_test_epi = int(input('\t - Testing episodes: '))
    if num_train_epi == '':
        num_train_epi = 0
    if num_test_epi == '':
        num_test_epi = 0
    main(benchmark_settings=benchmark_list, num_train_epi=num_train_epi, num_test_epi=num_test_epi)

if __name__=='__main__':
    run_benchmark()


#! python3

# Solve a two step algebra equation.
# Two steps equations are in the format ax + b = c
# You will ask the user to enter in all 3 variables: a, b and c
# You will need to display the solution for the equation

# inputs
# a, b, c
#
# outputs
# solution for x
#
# test case: 5, 1, 11 should give x = 2
import math
varA = input("we are going to solve the algebra equation: ax + b = c \nplease input a: ")
varB = input("please input b: ")
varC = input("please input c: ")

varA = int(varA)
varB = int(varB)
varC = int(varC)

x = (varC - varB) / varA

print(f"x = {x}")


import json
from datetime import datetime
from decimal import Decimal

class DateTimeEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, datetime):
            return o.isoformat()
        return json.JSONEncoder.default(self, o)

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, Decimal):
            if o % 1 > 0:
                return float(o)
            else:
                return int(o)
        return json.JSONEncoder.default(self, o)


from tkinter .ttk import *
from tkinter import *

fonte = ("Cooper Black", "14")
window = Tk()
window.title("Escolha seu avatar")
window.geometry("400x300")
rotulo1 = Label(window, text="Escolha seu avatar", pady=20, font=fonte)
rotulo1.pack()
container1 = Frame(window, pady=20, padx=10)
container1.pack()
combo = Combobox(container1)
combo["values"] = ("arlequina", "chaplin", "gueixa", "ninja", "zorro")
combo["state"] = "readonly"
combo.current(0)
combo.pack(side=LEFT)

rotulo2 = Label(container1)

window.mainloop()


from joblib import Parallel, delayed
import joblib
import os

def alkane_MC(pressure, solid, maindir, Nbeads = 4, Nchains = 250):

    import numpy as np  # Numpy
    import hs_alkane.alkane as mdl   # Fortran library we'll used to model the alkane chain
    import os

    if solid:
        state='solid'
    else:
        state='fluid'

    workingdir = f"{maindir}{state}P{pressure}"

    if not os.path.exists(workingdir):
        os.makedirs(workingdir)

    #Defining function for creating ase atoms object

    from ase import Atoms
    from ase import io

    def mk_ase_config(ibox, Nbeads, Nchains):
        'Uses the current state of the alkane model to construct an ASE atoms object'

        # Create and populate ASE object
        model_positions = np.empty([Nchains*Nbeads, 3])
        cell_vectors = mdl.box_get_cell(ibox)

        for ichain in range(0, Nchains):
            model_positions[Nbeads*ichain:Nbeads*ichain+Nbeads] = mdl.alkane_get_chain(ichain+1, ibox)

        confstring = "C"+str(Nbeads*Nchains)

        box_config = Atoms(confstring, positions=model_positions*(1.5/0.4), pbc=True, cell=cell_vectors*(1.5/0.4))

        # If we want to wrap all beads inside the box
        box_config.wrap()

        return box_config  # Returns ASE atom object

    # Initialise the simulation box and alkane module ready to hold chains

    if os.path.exists(f"{workingdir}/log{state}P{pressure}.log"):
        os.remove(f"{workingdir}/log{state}P{pressure}.log")

    logfile = open(f"{workingdir}/log{state}P{pressure}.log", "w")

    logfile.write(f"Attempting to initialise simulation box")
    print(f"ATTEMPTING TO INITIALISE BOX AT {pressure}")

    mdl.box_initialise()
    mdl.alkane_set_nbeads(Nbeads)
    mdl.alkane_set_nchains(Nchains)
    mdl.alkane_initialise()

    print(f"SUCCESSFULLY INITIALISED BOX AT {pressure}")

    logfile.write("system initialised \n")

    if os.path.exists(f"{workingdir}/equil.txt"):
        os.remove(f"{workingdir}/equil.txt")

    equilfile = open(f"{workingdir}/equil.txt", "w")

    print(f"GENERATING STRUCTURE AT {pressure}")

    if solid:
        mdl.io_read_xmol()
    else:

        boxsize = 30.0  # Initial size of cubic box
        ibox = 1
        cell_vectors = np.array([[boxsize,0.0,0.0],[0.0,boxsize,0.0],[0.0,0.0,boxsize]])
        mdl.box_set_cell(ibox, cell_vectors)

        print(f"Setting box size at {pressure}")

        # Don't use link cells when making new chains
        mdl.box_set_bypass_link_cells(1)
        mdl.box_set_use_verlet_list(0)

        print(f"box size set at {pressure}")
        growth_traj = io.Trajectory(f"{workingdir}/growingchains.traj", mode='a')

        ncopy = Nchains
        for ichain in range(ncopy):
            mdl.alkane_set_nchains(ichain+1)  # Only calculate using this and previous chains
            rb_factor = 0.0
            while rb_factor < 0.001:
                print(f"attempting to grow trial chain {ichain+1} at {pressure}")
                rb_factor, ifail = mdl.alkane_grow_chain(ichain+1, ibox,1)
                print(f"trial chain {ichain+1} grown at{pressure}")

                ####
                #growth_traj.write(atoms = mk_ase_config(ibox,Nbeads,Nbeads))
                ####

                if ifail!=0:
                    print(ifail)
                    rb_factor = 0.0

        print(f"RELINKING CELLS AT {pressure}")

        #link cells
        mdl.box_set_link_cell_length(1.6)
        mdl.box_set_bypass_link_cells(0)
        mdl.box_construct_link_cells(ibox)
        mdl.alkane_construct_linked_lists(ibox)
    print(f"STRUCTURE HAS BEEN GENERATED AT {pressure}")

    ibox = 1
    for ichain in range(0, Nchains):

    # Checks if internal geometry of chain (bonds/angles) are consistent with the model
        geom_flag = mdl.alkane_check_chain_geometry(ichain+1, ibox)
        if geom_flag != 0:
            logfile.write(f"Incorrect geometry for chain {ichain} for pressure {pressure} in state {state} \n")
            return

    # Checks if beads on any two chains overlap
    overlap_flag = mdl.alkane_check_chain_overlap(ibox)
    if overlap_flag != 0:
        logfile.write(f"Overlaps found between chains in initial configuration for pressure {pressure} in state {state} \n")
        return
    else:
        logfile.write(f"No overlaps between chains found in initial configuration for pressure {pressure} in state {state}\n")

    ##Setting up input/output for traj files

    #io.write(f"{workingdir}/configurations.dcd", mk_ase_config(ibox, Nbeads, Nchains), format="dcd")
    #io.write(f"{workingdir}/configurations.dcd", mk_ase_config(ibox, Nbeads, Nchains), format="psf")
    traj = io.Trajectory(f"{workingdir}/configs.traj", mode='a')
    traj.write(atoms = mk_ase_config(ibox,Nbeads,Nchains))

    if solid:
        mdl.alkane_set_dr_max(0.012)
        mdl.alkane_set_dt_max(0.07)
        mdl.alkane_set_dh_max(0.06)
        mdl.box_set_isotropic(0)
        mdl.alkane_set_dv_max(0.03  )

    else:
        mdl.alkane_set_dr_max(0.65)
        mdl.alkane_set_dt_max(0.43)
        mdl.alkane_set_dh_max(0.4)
        mdl.box_set_isotropic(1)
        mdl.alkane_set_dv_max(0.5)

   # Move types

    Sweeps_per_walk = 3*Nchains+1

    move_types = ['box','translate', 'rotate', 'dihedral']
    ivol = 0; itrans = 1; irot = 2; idih = 3
    moves_attempted = np.zeros(4)
    moves_accepted  = np.zeros(4)

    #adjusting mc step
    mc_adjust_interval = 1000
    interval_moves_attempted = np.zeros(4)
    interval_moves_accepted  = np.zeros(4)

    # Use the one and only simulation box
    ibox = 1

    # Initialise counters
    isweep = 0
    isweep_equil = 0

    #Equilibration parameters
    equil_sweeps = 50000 #steps to do until fully equilibrated
    equil_moves_attempted = np.zeros(4)
    equil_moves_accepted  = np.zeros(4)
    sample_sweeps= 100000 #number of steps to do once equilibrated
    equil_check = 20000
    equil = False #flag for equilibration. If true, equilibration sweeps are finished
    equil_error = 25

    ###unautomate equilibration
    ##mc step adjustment size
    equil_factor = 1.2 #factor to multiply the the step size by
    dv_min = 1e-5 #minimum step sizes for various moves, during the MC adjustment
    dr_min = 1e-5
    dt_min = 1e-5
    dh_min = 1e-5

    Max_sweeps = equil_sweeps + sample_sweeps #total number of sweeps

    # How often to sample quantities
    sample_interval = 10
    samples = list()

    # In case we're using Verlet lists rather than link cells
    mdl.alkane_construct_neighbour_list(ibox)

    # Initialise timer
    t1 = mdl.timer_init()

    #Visualisation parameters
    vis_interval = 500

    print(f"STARTING LOOP AT {pressure}")

    # Loop over the number of MC sweeps to perform
    while (isweep < Max_sweeps):

        if isweep%vis_interval == 0:
            #io.write(f'{workingdir}/configs.{isweep//vis_interval}.extxyz', mk_ase_config(ibox,Nbeads,Nchains))
            traj.write(atoms = mk_ase_config(ibox,Nbeads,Nchains))
            #output extxyz as well

        if isweep == equil_sweeps:
            equil = True
            logfile.write(f"{isweep} equilibration steps have been ran. Sampling configurations. \n")
            if ((np.std(samples[-(equil_check//sample_interval):])) > equil_error):
                logfile.write(f"Warning, system may not be fully equilibrated after {isweep} steps. \n")

        # One "sweep" is usually interpretted as one attempt to change each degree of freedom on average once.
        # here we have 3 translation + 2 rotation + 1 internal degrees of freedom per chain, plus 6 degrees
        # of freedom for the simulation cell (ignoring rotations).

        # A box move changes 1 degree of freedom
        # A chain translation move changes 3 degrees of freedom
        # A chain rotation move changes 2 degrees of freedom
        # A dihedral angle move changes 1 degree of freedom

        # Hence if we attempt each move type with a probability proportional to the number of degrees
        # of freedom it changes, we need to do 2*Nchains+6 moves to get one "sweep". Sampling in this
        # ratio isn't necessarily optimal, but it's a good starting point.

        # Calculate cumulative move ratios used to decide move type
        total_deg = 6*Nchains+6

        box_move_prob   = 6/total_deg
        trans_move_prob = box_move_prob   + 3.0*Nchains/total_deg
        rot_move_prob   = trans_move_prob + 2.0*Nchains/total_deg
        dih_move_prob   = rot_move_prob   + 1.0*Nchains/total_deg

        if isweep % 10000 == 0:
            logfile.write(f"{isweep} steps have been performed for {pressure} pressure in state {state}\n")

        # Loop over move attempts at the current sweep
        imove = 0
        while imove < 2*Nchains+6:

            #==========================#
            # Make a random trial move #
            #==========================#

            # Pick a random chain numbered from 0 to Nchains
            ichain = np.random.randint(0, high=Nchains)

            # Backup old chain positions. Note that whenever we call a routine inside the
            # hs_alkane library we need to add one to the bead/chain index to account for
            # Fortran indexing.
            current_chain = mdl.alkane_get_chain(ichain+1, ibox)
            backup_chain = current_chain.copy() # Note copy, not equivalence

            # Pick a move type at random and call the appropriate function to make that
            # trial move and return the corresponding ratio of Boltzmann factors.
            xi = np.random.random()
            if xi < box_move_prob:
                # Attempt a volume move
                itype = ivol
                boltz = mdl.alkane_box_resize(pressure, ibox, 0)
            elif xi < trans_move_prob:
                # Attempt a translation move
                itype = itrans
                boltz = mdl.alkane_translate_chain(ichain+1, ibox)
            elif xi < rot_move_prob:
                # Attempt a rotation move
                itype = irot
                boltz, quat = mdl.alkane_rotate_chain(ichain+1, ibox, 0)
            else:
                # Attempt a dihedral angle move
                itype = idih
                boltz, bead1, angle = mdl.alkane_bond_rotate(ichain+1, ibox, 1)

            # Increment attempted move counter
            moves_attempted[itype] += 1
            interval_moves_attempted[itype] +=1
            if equil:
                    equil_moves_attempted[itype] += 1

            #====================#
            # Accept/reject move #
            #====================#

            # Reject according to Metropolis criterion
            if (np.random.random() < boltz):

                # Move accepted
                moves_accepted[itype] += 1
                interval_moves_accepted[itype] += 1
                if equil:
                    equil_moves_accepted[itype] += 1

                # Update linked-list for new positions if not volume move.
                if (itype!=ivol):
                    new_chain = mdl.alkane_get_chain(ichain+1, ibox).copy()
                    for ibead in range(Nbeads):
                        mdl.alkane_update_linked_lists(ibead+1, ichain+1, ibox, backup_chain[ibead], new_chain[ibead])

            else:

                # Reject move
                if (itype!=ivol):
                    # Restore old chain if single chain move
                    for ibead in range(Nbeads):
                        current_chain[ibead] = backup_chain[ibead]
                else:
                    # Reset the box change - special fucntion for this.
                    dumboltz = mdl.alkane_box_resize(pressure, ibox, 1)

            imove += 1

        # Sample
        if isweep%sample_interval == 0:
            samples.append(mdl.box_compute_volume(ibox))

#put a minimum step size

        if not equil:      #previously 'if True:'

            if (isweep%mc_adjust_interval == 0) and (isweep != 0) :
                equilfile.write(f"\nThis is step adjustment : {isweep//mc_adjust_interval}\n")
                if (interval_moves_accepted[0]/interval_moves_attempted[0]) > 0.50:
                    mdl.alkane_set_dv_max(mdl.alkane_get_dv_max()*equil_factor)
                elif (interval_moves_accepted[0]/interval_moves_attempted[0]) < 0.30:
                    mdl.alkane_set_dv_max(max(mdl.alkane_get_dv_max()/equil_factor,dv_min))
                else:
                    pass
                equilfile.write(f"Volume move acceptance rate is {interval_moves_accepted[0]/interval_moves_attempted[0]}\
                    {interval_moves_accepted[0]},{interval_moves_attempted[0]} \nVolume move size is now {mdl.alkane_get_dv_max()} \n")

                if (interval_moves_accepted[1]/interval_moves_attempted[1]) > 0.50:
                    mdl.alkane_set_dr_max(mdl.alkane_get_dr_max()*equil_factor)
                elif (interval_moves_accepted[1]/interval_moves_attempted[1]) < 0.30:
                    mdl.alkane_set_dr_max(max(mdl.alkane_get_dr_max()/equil_factor,dr_min))
                else:
                    pass
                equilfile.write(f"Translation move acceptance rate is {interval_moves_accepted[1]/interval_moves_attempted[1]}\
                    {interval_moves_accepted[1]},{interval_moves_attempted[1]} \nTranslation move size is now {mdl.alkane_get_dr_max()} \n")

                if (interval_moves_accepted[2]/interval_moves_attempted[2]) > 0.50:
                    mdl.alkane_set_dt_max(mdl.alkane_get_dt_max()*equil_factor)
                elif (interval_moves_accepted[2]/interval_moves_attempted[2]) < 0.30:
                    mdl.alkane_set_dt_max(max(mdl.alkane_get_dt_max()/equil_factor,dt_min))
                else:
                    pass
                equilfile.write(f"Angle move acceptance rate is {interval_moves_accepted[2]/interval_moves_attempted[2]} \
                    {interval_moves_accepted[2]},{interval_moves_attempted[2]} \nAngle move size is now {mdl.alkane_get_dt_max()} \n")

                if (interval_moves_accepted[3]/interval_moves_attempted[3]) > 0.50:
                    mdl.alkane_set_dh_max(mdl.alkane_get_dh_max()*equil_factor)
                elif (interval_moves_accepted[3]/interval_moves_attempted[3]) < 0.30:
                    mdl.alkane_set_dh_max(max(mdl.alkane_get_dh_max()/equil_factor,dh_min))
                else:
                    pass
                equilfile.write(f"Dihedral move acceptance rate is {interval_moves_accepted[3]/interval_moves_attempted[3]}\
                    {interval_moves_accepted[3]},{interval_moves_attempted[3]}, \nDihedral move size is now {mdl.alkane_get_dh_max()} \n")

                interval_moves_accepted[:] = 0
                interval_moves_attempted[:] = 0

        # Increment sweep counter and progress bar
        isweep += 1
        if equil:
            isweep_equil +=1

        # if isweep%sample_interval == 0:
        #     logfile.write(f"{isweep} for {pressure} pressure in {state} state")

    ##loop ends here

    traj.close()

    # Timing
    logfile.write(f"Completed at {pressure} pressure for {state} state {isweep} sweeps in {mdl.timer_elapsed_time()} seconds. \n")

    # Report statistics
    if equil:
        for itype in range(4):
            percent = 100.0 * equil_moves_accepted[itype]/equil_moves_attempted[itype]
            #print(itype, equil_moves_accepted[itype],equil_moves_attempted[itype], percent)
            logfile.write(f"Accepted {percent} % of {move_types[itype]} moves once equilibrated \n")

    # Rerun the sanity check to make sure nothing went wrong
    overlap_flag = mdl.alkane_check_chain_overlap(ibox)
    if overlap_flag != 0:
        logfile.write(f"Overlaps found between chains in configuration at pressure \n")

    else:
        logfile.write("No overlaps between chains found in configuration \n")

    equil_samples = samples[-(sample_sweeps//sample_interval):]

    # nlags = 200
    # x = np.array(equil_samples)
    # x = x - x.mean()
    # n = len(equil_samples)
    # autocovariance = np.empty(nlags + 1)
    # autocovariance[0] = x.dot(x)
    # for i in range(nlags):
    #    autocovariance[i + 1] = x[i + 1 :].dot(x[: -(i + 1)])
    # autocovariance /= n - np.arange(nlags + 1)
    # acf = autocovariance[: nlags + 1] /autocovariance[0]

    from statsmodels.tsa.stattools import acf
    Nlags = 2000
    simple_acf=acf(equil_samples, nlags=Nlags-1, fft=False)

    thinning_par = np.where(simple_acf==min(simple_acf))[0][0]

    logfile.write(f"Thinning parameter for decorrelation is {thinning_par} \n")
    logfile.write(f"Decorrelation value of {min(simple_acf)} \n")
    logfile.write(f"Final volume = {mdl.box_compute_volume(ibox)}\n")

    equilfile.close()
    equil_subsamples = equil_samples[::thinning_par]
    subsamples = samples[::thinning_par]

    np.savetxt(f"{workingdir}/volume.txt", subsamples)
    np.savetxt(f"{workingdir}/all_volumes.txt", samples)

    np.savetxt(f"{workingdir}/acf.txt", simple_acf)

    out = open(f"{maindir}/PvV_{state}.txt","a+")

    mean_vol = np.mean(equil_subsamples)
    stderr_vol = np.std(equil_subsamples)/np.sqrt(len(equil_subsamples))
    out.write("{},{},{}\n".format(pressure, mean_vol, stderr_vol))
    out.close()

    # Save volume samples

    logfile.write(f"Attempting to destroy simulation box \n")

    mdl.alkane_destroy()
    mdl.box_destroy_link_cells()
    mdl.box_destroy()

    logfile.write(f"Simulation box destroyed \n")

    logfile.close()

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

maindir = "./"

if not os.path.exists(maindir):
    os.makedirs(maindir)
with joblib.parallel_backend("multiprocessing"):
    Parallel(n_jobs=10)(delayed(alkane_MC)(pressure = i, solid = False, maindir = maindir) for i in range(10,50,5))


"""
This code was copied from open-mmlab/mmsr.
"""

import functools
import torch
import torch.nn as nn
import torch.nn.functional as F

class ResidualBlock_noBN(nn.Module):
    '''Residual block w/o BN
    ---Conv-ReLU-Conv-+-
     |________________|
    '''

    def __init__(self, nf=64):
        super(ResidualBlock_noBN, self).__init__()
        self.conv1 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)
        self.conv2 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)

    def forward(self, x):
        identity = x
        out = F.relu(self.conv1(x), inplace=True)
        out = self.conv2(out)
        return identity + out

class MSRResNet(nn.Module):
    ''' modified SRResNet'''

    def __init__(self, in_nc=3, out_nc=3, nf=64, nb=16, upscale=4):
        super(MSRResNet, self).__init__()
        self.upscale = upscale

        self.conv_first = nn.Conv2d(in_nc, nf, 3, 1, 1, bias=True)
        basic_block = functools.partial(ResidualBlock_noBN, nf=nf)
        self.recon_trunk = nn.Sequential(*[basic_block() for _ in range(nb)])

        # upsampling
        if self.upscale == 2:
            self.upconv1 = nn.Conv2d(nf, nf * 4, 3, 1, 1, bias=True)
            self.pixel_shuffle = nn.PixelShuffle(2)
        elif self.upscale == 3:
            self.upconv1 = nn.Conv2d(nf, nf * 9, 3, 1, 1, bias=True)
            self.pixel_shuffle = nn.PixelShuffle(3)
        elif self.upscale == 4:
            self.upconv1 = nn.Conv2d(nf, nf * 4, 3, 1, 1, bias=True)
            self.upconv2 = nn.Conv2d(nf, nf * 4, 3, 1, 1, bias=True)
            self.pixel_shuffle = nn.PixelShuffle(2)

        self.HRconv = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)
        self.conv_last = nn.Conv2d(nf, out_nc, 3, 1, 1, bias=True)

        # activation function
        self.lrelu = nn.LeakyReLU(negative_slope=0.1, inplace=True)

    def forward(self, x):
        fea = self.lrelu(self.conv_first(x))
        out = self.recon_trunk(fea)

        if self.upscale == 4:
            out = self.lrelu(self.pixel_shuffle(self.upconv1(out)))
            out = self.lrelu(self.pixel_shuffle(self.upconv2(out)))
        elif self.upscale == 3 or self.upscale == 2:
            out = self.lrelu(self.pixel_shuffle(self.upconv1(out)))

        out = self.conv_last(self.lrelu(self.HRconv(out)))
        base = F.interpolate(x, scale_factor=self.upscale, mode='bilinear', align_corners=False)
        out += base
        return out

class ResidualDenseBlock_5C(nn.Module):
    def __init__(self, nf=64, gc=32, bias=True):
        super(ResidualDenseBlock_5C, self).__init__()
        # gc: growth channel, i.e. intermediate channels
        self.conv1 = nn.Conv2d(nf, gc, 3, 1, 1, bias=bias)
        self.conv2 = nn.Conv2d(nf + gc, gc, 3, 1, 1, bias=bias)
        self.conv3 = nn.Conv2d(nf + 2 * gc, gc, 3, 1, 1, bias=bias)
        self.conv4 = nn.Conv2d(nf + 3 * gc, gc, 3, 1, 1, bias=bias)
        self.conv5 = nn.Conv2d(nf + 4 * gc, nf, 3, 1, 1, bias=bias)
        self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)

    def forward(self, x):
        x1 = self.lrelu(self.conv1(x))
        x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1)))
        x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1)))
        x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1)))
        x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1))
        return x5 * 0.2 + x

class RRDB(nn.Module):
    '''Residual in Residual Dense Block'''

    def __init__(self, nf, gc=32):
        super(RRDB, self).__init__()
        self.RDB1 = ResidualDenseBlock_5C(nf, gc)
        self.RDB2 = ResidualDenseBlock_5C(nf, gc)
        self.RDB3 = ResidualDenseBlock_5C(nf, gc)

    def forward(self, x):
        out = self.RDB1(x)
        out = self.RDB2(out)
        out = self.RDB3(out)
        return out * 0.2 + x

class RRDBNet(nn.Module):
    def __init__(self, in_nc, out_nc, nf, nb, gc=32):
        super(RRDBNet, self).__init__()
        RRDB_block_f = functools.partial(RRDB, nf=nf, gc=gc)

        self.conv_first = nn.Conv2d(in_nc, nf, 3, 1, 1, bias=True)
        self.RRDB_trunk = nn.Sequential(*[RRDB_block_f() for _ in range(nb)])
        self.trunk_conv = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)
        #### upsampling
        self.upconv1 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)
        self.upconv2 = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)
        self.HRconv = nn.Conv2d(nf, nf, 3, 1, 1, bias=True)
        self.conv_last = nn.Conv2d(nf, out_nc, 3, 1, 1, bias=True)

        self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)

    def forward(self, x):
        fea = self.conv_first(x)
        trunk = self.trunk_conv(self.RRDB_trunk(fea))
        fea = fea + trunk

        fea = self.lrelu(self.upconv1(F.interpolate(fea, scale_factor=2, mode='nearest')))
        fea = self.lrelu(self.upconv2(F.interpolate(fea, scale_factor=2, mode='nearest')))
        out = self.conv_last(self.lrelu(self.HRconv(fea)))

        return out


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 18-6-4 下午3:00
# @Author  : MaybeShewill-CV
# @Site    : https://github.com/MaybeShewill-CV/attentive-gan-derainnet
# @File    : tf_ssim.py
# @IDE: PyCharm Community Edition
"""
Implement ssim compute using tensorflow
"""
import tensorflow as tf
import numpy as np

class SsimComputer(object):
    """

    """
    def __init__(self):
        """

        """
        pass

    @staticmethod
    def _tf_fspecial_gauss(size, sigma):
        """
        Function to mimic the 'fspecial' gaussian function
        :param size:
        :param sigma:
        :return:
        """
        x_data, y_data = np.mgrid[-size // 2 + 1:size // 2 + 1, -size // 2 + 1:size // 2 + 1]

        x_data = np.expand_dims(x_data, axis=-1)
        x_data = np.expand_dims(x_data, axis=-1)

        y_data = np.expand_dims(y_data, axis=-1)
        y_data = np.expand_dims(y_data, axis=-1)

        x = tf.constant(x_data, dtype=tf.float32)
        y = tf.constant(y_data, dtype=tf.float32)

        g = tf.exp(-((x ** 2 + y ** 2) / (2.0 * sigma ** 2)))
        return g / tf.reduce_sum(g)

    def compute_ssim(self, img1, img2, cs_map=False, mean_metric=True, size=11, sigma=1.5):
        """

        :param img1: image need to be gray scale
        :param img2:
        :param cs_map:
        :param mean_metric:
        :param size:
        :param sigma:
        :return:
        """
        assert img1.get_shape().as_list()[-1] == 1, 'Image must be gray scale'
        assert img2.get_shape().as_list()[-1] == 1, 'Image must be gray scale'

        window = self._tf_fspecial_gauss(size, sigma)  # window shape [size, size]
        K1 = 0.01  # origin parameter in paper
        K2 = 0.03  # origin parameter in paper
        L = 1  # depth of image (255 in case the image has a differnt scale)
        C1 = (K1 * L) ** 2
        C2 = (K2 * L) ** 2
        mu1 = tf.nn.conv2d(img1, window, strides=[1, 1, 1, 1], padding='VALID')
        mu2 = tf.nn.conv2d(img2, window, strides=[1, 1, 1, 1], padding='VALID')
        mu1_sq = mu1 * mu1
        mu2_sq = mu2 * mu2
        mu1_mu2 = mu1 * mu2
        sigma1_sq = tf.nn.conv2d(img1 * img1, window, strides=[1, 1, 1, 1], padding='VALID') - mu1_sq
        sigma2_sq = tf.nn.conv2d(img2 * img2, window, strides=[1, 1, 1, 1], padding='VALID') - mu2_sq
        sigma12 = tf.nn.conv2d(img1 * img2, window, strides=[1, 1, 1, 1], padding='VALID') - mu1_mu2
        if cs_map:
            value = (((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) *
                                                                  (sigma1_sq + sigma2_sq + C2)),
                     (2.0 * sigma12 + C2) / (sigma1_sq + sigma2_sq + C2))
        else:
            value = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) *
                                                                 (sigma1_sq + sigma2_sq + C2))

        if mean_metric:
            value = tf.reduce_mean(value)
        return value


from __future__ import absolute_import, division, print_function
from cctbx.examples.g_exp_i_alpha_derivatives \
  import parameters, gradients, pack_gradients, g_exp_i_alpha_sum
from cctbx.examples.exp_i_alpha_derivatives import least_squares
from libtbx.test_utils import approx_equal
import random
import math
import copy
from six.moves import cStringIO as StringIO
import sys
from six.moves import range
from six.moves import zip

random.seed(0)

def d_target_d_params_finite(obs, params, eps=1.e-8):
  result = []
  params_eps = copy.deepcopy(params)
  for i_param in range(len(params)):
    dx = []
    for ix in range(4):
      ts = []
      for signed_eps in [eps, -eps]:
        pi_eps = params[i_param].as_list()
        pi_eps[ix] += signed_eps
        params_eps[i_param] = parameters(*pi_eps)
        exp_sum = g_exp_i_alpha_sum(params=params_eps)
        target = least_squares(obs=obs, calc=exp_sum.f())
        ts.append(target.f())
      dx.append((ts[0]-ts[1])/(2*eps))
    result.append(gradients(*dx))
    params_eps[i_param] = params[i_param]
  return result

def d2_target_d_params_finite(obs, params, eps=1.e-8):
  result = []
  params_eps = copy.deepcopy(params)
  for i_param in range(len(params)):
    for ix in range(4):
      gs = []
      for signed_eps in [eps, -eps]:
        pi_eps = params[i_param].as_list()
        pi_eps[ix] += signed_eps
        params_eps[i_param] = parameters(*pi_eps)
        exp_sum = g_exp_i_alpha_sum(params=params_eps)
        target = least_squares(obs=obs, calc=exp_sum.f())
        dp = exp_sum.d_target_d_params(target=target)
        gs.append(pack_gradients(dp))
      result.append([(gp-gm)/(2*eps) for gp,gm in zip(gs[0],gs[1])])
    params_eps[i_param] = params[i_param]
  return result

def compare_analytical_and_finite(obs, params, out):
  grads_fin = d_target_d_params_finite(obs=obs, params=params)
  print("grads_fin:", pack_gradients(grads_fin), file=out)
  exp_sum = g_exp_i_alpha_sum(params=params)
  target = least_squares(obs=obs, calc=exp_sum.f())
  grads_ana = exp_sum.d_target_d_params(target=target)
  print("grads_ana:", pack_gradients(grads_ana), file=out)
  assert approx_equal(pack_gradients(grads_ana), pack_gradients(grads_fin))
  curvs_fin = d2_target_d_params_finite(obs=obs, params=params)
  print("curvs_fin:", curvs_fin, file=out)
  curvs_ana = list(exp_sum.d2_target_d_params(target=target))
  print("curvs_ana:", curvs_ana, file=out)
  assert approx_equal(curvs_ana, curvs_fin)
  print(file=out)

def exercise(args):
  verbose =  "--verbose" in args
  if (not verbose):
    out = StringIO()
  else:
    out = sys.stdout
  for n_params in range(2,5):
    for i_trial in range(5):
      params = []
      for i in range(n_params):
        params.append(parameters(
          g=(random.random()-0.5)*2,
          ffp=(random.random()-0.5)*2,
          fdp=(random.random()-0.5)*2,
          alpha=2*math.pi*random.random()))
      exp_sum = g_exp_i_alpha_sum(params=params)
      obs = abs(exp_sum.f())
      compare_analytical_and_finite(
        obs=obs,
        params=params,
        out=out)
      compare_analytical_and_finite(
        obs=obs*(random.random()+0.5),
        params=params,
        out=out)
  print("OK")

if (__name__ == "__main__"):
  exercise(sys.argv[1:])