import cv2
import numpy as np
import itertools
import random
from collections import Counter

from scipy.spatial.distance import cdist
from config import *

def closest_node(node, nodes):
    if len(nodes) == 0:
        return 0, np.Inf
    distances = cdist([node], nodes)
    return nodes[distances.argmin()], distances.min()

def add_shape(box, dict_boxes):
    center = np.mean(box, axis=0)
    center = (center[0], center[1])
    closest, distance = closest_node(center, list(dict_boxes.keys()))
    if distance > 100:
        dict_boxes[center] = box
    else:
        dict_boxes[closest] = (dict_boxes[closest] + box)/2

def makeBoxGrayAndBlurred(image):
        return cv2.GaussianBlur(cv2.cvtColor(image, cv2.COLOR_BGR2GRAY),(7, 7), 0)

def  multimode(lista):
        return Counter(lista).most_common(1)[0][0]

def getCentermostBlackPixels(originalCoords, image, bias, title = 'lol'):
        blackPixels = [[x,y] for x,y in list(itertools.product(range(image.shape[0]), range(image.shape[1]))) if image[x][y] < 100]
        y = multimode([e[1] for e in blackPixels])
        x = multimode([e[0] for e in blackPixels])
        cv2.circle(image, (y, x), 5, (0, 0, 255), -1)
        return (originalCoords[0] + y - bias[0], originalCoords[1] + x - bias[1])

def fixEdges(image):
    # if edges are the same but one do not update this one edge
    pass

def orderBoxes(boxes):
        boxCooords = [box[:2] for box in boxes]
        suma1 = list(dict(list(sorted(dict([(c[0] + c[1], [c[0], c[1]]) for c in boxCooords]).items()))).values())
        suma2 = list(dict(list(sorted(dict([(c[0] - c[1], [c[0], c[1]]) for c in boxCooords]).items()))).values())
        tl = suma1[0]
        br = suma1[-1]
        tr = suma2[-1]
        bl = suma2[0]
        return [tl, br, tr, bl]

def chizzledBox(box, image):
        '''
        chizzle the bounding boxes
        '''
        biasX, biasY = 50,50
        tl, br, tr, bl = orderBoxes(box)

        trGrayBlurred = cv2.GaussianBlur(cv2.cvtColor(image[tr[1] - biasX: tr[1] + biasX, tr[0] - biasY: tr[0] + biasY,:], cv2.COLOR_BGR2GRAY),(7, 7), 0)
        trBox = cv2.adaptiveThreshold(trGrayBlurred, 255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
        tlBox = cv2.adaptiveThreshold(makeBoxGrayAndBlurred(image[tl[1] - biasX: tl[1] + biasX, tl[0] - biasY: tl[0] + biasY,:]), 255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
        brBox = cv2.adaptiveThreshold(makeBoxGrayAndBlurred(image[br[1] - biasX: br[1] + biasX, br[0] - biasY: br[0] + biasY,:]), 255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
        blBox = cv2.adaptiveThreshold(makeBoxGrayAndBlurred(image[bl[1] - biasX: bl[1] + biasX, bl[0] - biasY: bl[0] + biasY,:]), 255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
        bl = getCentermostBlackPixels(bl, blBox, [biasX, biasY], "lol1")
        br = getCentermostBlackPixels(br, brBox, [biasX, biasY], "lol2")
        tl = getCentermostBlackPixels(tl, tlBox, [biasX, biasY], "lol3")
        tr = getCentermostBlackPixels(tr, trBox, [biasX, biasY], "lol4")
        box = np.array([tr, tl, bl, br])
        return box

def find_squares(img, boxes):
    image = img.copy()
    # HEURISTIC : board not smaller than 30% of the board and not bigger than 90%
    size_of_image = image.shape[0]* image.shape[1]
    lower_bound_board_size = 0.3 * size_of_image
    upper_bound_board_size = 0.9 * size_of_image

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blur = cv2.medianBlur(gray, 9)

    # Threshold and morph close
    #thresh = cv2.threshold(sharpen, 150, 255, cv2.THRESH_BINARY_INV)[1]
    trGrayBlurred = cv2.GaussianBlur(blur,(7, 7), 0)
    thresh = cv2.adaptiveThreshold(trGrayBlurred, 255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
    close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=1)
    close =  cv2.Canny(img,250,100)
    cnts, _ = cv2.findContours(close, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    i = 0
    for contour in cnts:
        i+=1
        approx = cv2.approxPolyDP(contour, 0.01* cv2.arcLength(contour, True), True)
        x, y , w, h = cv2.boundingRect(approx)
        aspectRatio = float(w)/h
        if  aspectRatio >0.9 and aspectRatio < 1.1 and cv2.contourArea(contour) > 100 and len(approx)< 10 and cv2.contourArea(contour) > lower_bound_board_size and cv2.contourArea(contour) < upper_bound_board_size:
            rect = cv2.minAreaRect(contour)
            box = cv2.boxPoints(rect)
            box = np.int0(box)
            box = chizzledBox(box, image) # match the boz so that there are no black pixels on sides
            add_shape(box, boxes)

    for box in boxes.values():
        pts = np.array(box, np.int32)
        pts = pts.reshape((-1,1,2))
        cv2.polylines(close,[pts],True,100)
    return img, thresh, boxes

def getShadowMask(img, originalBoard, maskFields):
        lower = np.array([200]) # mask is arbitrary
        upper = np.array([255])
        mask = cv2.dilate(cv2.inRange(maskFields,lower,upper), np.ones((15, 15), np.uint8)) + maskOnBases
        imgNoFields = img.copy()
        originalNoFields = originalBoard.copy()
        imgNoFields[mask > 100] = 0
        originalNoFields[mask > 100] = 0
        lights = cv2.subtract(originalNoFields, imgNoFields)
        dstLights = cv2.inpaint(lights,mask,5,cv2.INPAINT_TELEA)
        return dstLights

def _getNextRegularField(regularFieldsUnvisited, regularFieldsVisited, lastPosition, i):
        if not regularFieldsUnvisited:
                # stop if no points to visit
                return regularFieldsVisited

        sortedUnvisited = sorted(regularFieldsUnvisited.items(), key=lambda x: abs(x[0][0]  - lastPosition[0])+ abs(x[0][1] - lastPosition[1]))
        newPosition = sortedUnvisited[0][0]
        newKeyPoint = sortedUnvisited[0][1]
        regularFieldsVisited[i] = newKeyPoint
        regularFieldsUnvisited = dict(sortedUnvisited[1:])

        return _getNextRegularField(regularFieldsUnvisited, regularFieldsVisited, newPosition, i + 1)

def createHashmapRegularFields(regularFields):
        '''
        regularFields: key is position of center, value is keypoint

        Creates hashmap for regular fields for easy iteration:
                1. Get initial field (red starter)
                2. Recursive unction for searching next point - _getNextRegularField
        '''

        topFields = dict(sorted(regularFields.items(), key=lambda x: abs(x[0][1]))[:3]) # get highest ones
        topFields = dict(sorted(topFields.items(), key=lambda x: abs(x[0][0])))

        regularFieldsVisited = dict([(i, keypoint) for i, keypoint in enumerate(list(topFields.values()))])
        regularFieldsUnvisited = dict([(key, value) for key, value in regularFields.items() if value not in regularFieldsVisited.values()])
        lastPosition = regularFieldsVisited[len(regularFieldsVisited) - 1].pt

        regularFieldsVisited = _getNextRegularField(regularFieldsUnvisited, regularFieldsVisited, lastPosition, 3)

        return regularFieldsVisited

def get_blobs(frame, draw = True, title = "default gray blob detection"):
        frame_copy = frame.copy()
        frame_blurred = cv2.medianBlur(frame_copy, 3)
        frame_gray = cv2.cvtColor(frame_blurred, cv2.COLOR_BGR2GRAY)
        t_lower = 20
        t_upper = 250
        canny = cv2.Canny(frame_gray, t_lower, t_upper)
        kernel = np.ones((3, 3), np.uint8)

        # dilate the image
        dilate = cv2.dilate(canny, kernel, iterations=1)
        blobs = detector.detect(dilate)

        # overlaying info
        for blob in blobs:
                pos = blob.pt
                r = blob.size / 2

                if draw:
                        cv2.circle(frame_copy, (int(pos[0]), int(pos[1])), int(r), (255, 0, 0), 2)

        resized = cv2.resize(frame_copy, (500,500), interpolation = cv2.INTER_AREA)
        if draw:
                #cv2.imshow(title, resized)
                pass

        return frame_copy, blobs

def get_grid(empty_board):
        '''
        TODO: break down into smaller functions

        1. gets regular fields
        2. gets base coordinates
        3. gets home coordinates
        4. geterimes which base is whos etc (by use of other functions)
        5. scale to 500x500

        @return dictionnary assigning fields to numbers, as below
                REGULAR FIELDS : 1-40
                HOMES:
                - 41-44 red home
                - 45-48 blue home
                - 49-52 yellow home
                - 53-56 green home
                BASES:
                - 57-60 red base
                - 61-64 blue base
                - 65-68 yellow base
                - 69-72 green base
        @return dictionnary field number and what it means (verbally)
        '''
        empty_drawn_blobs, circles = get_blobs(empty_board, title = "try on empty", draw = False)
        board_width, board_height, _ = empty_board.shape
        fields = dict()
        for circle in circles:
                pos = circle.pt
                r = circle.size/2
                if r > R_CIRCLE_FIELD * (1 - R_CIRCLE_ACCEPTANCE_THERESHOLD) and  r < R_CIRCLE_FIELD * (1 + R_CIRCLE_ACCEPTANCE_THERESHOLD):
                        fields[(int(pos[0]), int(pos[1]))] =  circle

        # GET BASE
        # random needed since else keys wont't be uniqe
        temp_tl_br = sorted({key[0] + key[1] + random.random(): val for key, val in fields.items()}.items())
        temp_tr_bl = sorted({key[0] + board_height - key[1] + random.random(): val for key, val in fields.items()}.items())

        # top left
        tl_base =dict([(i,val) for i,val in enumerate(list(dict(temp_tl_br[:4]).values()))])

        # bottom right
        br_base = dict([(i,val) for i,val in enumerate(list(dict(temp_tl_br[-4:]).values()))])

        # top right
        tr_base  = dict([(i,val) for i,val in enumerate(list(dict(temp_tr_bl[-4:]).values()))])

        # bottom left
        bl_base  = dict([(i,val) for i,val in enumerate(list(dict(temp_tr_bl[:4]).values()))])

        # GET HOME
        # EXPLANATION OF BOARD NOTATION:

        #  __                  __
        # |tl|       tr       |tr|
        #            tr
        #            tr
        #            tr
        # tl tl tl tl  br br br br br
        #            bl
        #            bl
        #  __        bl        __
        # |bl|       bl       |br|

        temp_y_axis = dict(sorted(fields.items(), key=lambda x: abs(x[0][0] - board_width/2))[:10]) # get middle 10 elements y
        temp_x_axis = dict(sorted(fields.items(), key=lambda x: abs(x[0][1] - board_height/2))[:10]) # get middle 10 elements x

        temp_y_axis = dict(sorted(temp_y_axis.items(), key=lambda x: x[0][1] - board_height/2)[1:9]) # get middle 10 elements y
        temp_x_axis = dict(sorted(temp_x_axis.items(), key=lambda x: x[0][0] - board_width/2)[1:9]) # get middle 10 elements x

        tr_home = dict([(i,val) for i,val in enumerate(list(temp_y_axis.values())[:4])])
        bl_home = dict([(i,val) for i,val in enumerate(reversed(list(temp_y_axis.values())[4:]))])

        tl_home = dict([(i,val) for i,val in enumerate(list(temp_x_axis.values())[:4])])
        br_home = dict([(i,val) for i,val in enumerate(reversed(list(temp_x_axis.values())[4:]))])

        base_and_home = [br_home, tl_home, bl_home, tr_home, tr_base, tl_base, bl_base, br_base]
        base_and_home_keypoints = list(itertools.chain.from_iterable( [list(dictionnary.values()) for dictionnary in base_and_home] ))

        # get regular fields
        regularFields = dict([(key, value) for key, value in fields.items() if value not in base_and_home_keypoints])
        regularFieldsNumbered = createHashmapRegularFields(regularFields)

        # directory with hash with modulo 40 for regular points

        # assuming tr is blue, br is green, bl is yellow and tl is red - read board will be rotated
        # each filed gets assigned a number and then the number is checked (what it means)
        ListOfFieldDictionnaries = [regularFieldsNumbered, tl_home, tr_home, bl_home, br_home, tl_base, tr_base, bl_base, br_base]
        ListOfDescription = ['RegularField']*40 + ['Red Home']*4 +  ['Blue Home']*4 +['Yellow Home']*4 +['Green Home']*4 +['Red Base']*4 +['Blue Base']*4 +['Yellow Base']*4 +['Green Base']*4
        DescriptionDictionnaryFinal = dict([(i + 1, description) for i, description in enumerate(ListOfDescription)])
        FieldDictionnaryFinal = dict()
        l = 1

        scale = 1000/(empty_board.shape[0]+ empty_board.shape[1])

        for dictionnary in ListOfFieldDictionnaries:
                for i, keypoint in dictionnary.items():
                        keypoint.pt = (keypoint.pt[0]*scale, keypoint.pt[1]*scale)
                        keypoint.size = keypoint.size*scale
                        FieldDictionnaryFinal[i + l] = keypoint
                l = l + i + 1

        # scaled to 500x500 for easier processing

        return FieldDictionnaryFinal, DescriptionDictionnaryFinal

def createDescriptiveBoard(FieldNumbering):
        '''
        returns 500x500 field with encoded values of fields for nice and quick lookup
        '''
        masks = [np.zeros((500,500)) for _ in range(72)]
        for i, keypoint in FieldNumbering.items():
                pos = keypoint.pt
                r = keypoint.size/2
                cv2.circle(masks[i-1], (int(pos[0]), int(pos[1])), int(r), i, -1)

        return masks

def createMaskFieldBoardExistance(FieldNumbering):
        '''
        returns 500x500 field with encoded values of fields for nice and quick lookup
        '''
        masks = [np.zeros((500,500)) for _ in range(72)]
        for i, keypoint in enumerate(FieldNumbering.values()):
                pos = keypoint.pt
                r = keypoint.size/2
                cv2.circle(masks[i], (int(pos[0]), int(pos[1])), int(r), 255, -1)

        return masks

def getFieldsMask(masks):
        tempMask = masks[0]
        for mask in masks[1:]:
                tempMask += mask
        return tempMask

if __name__ == "__main__":

    prev = cv2.imread('version2.png')
    FieldNumbering, FieldDescription = get_grid(prev)
    masks = createMaskFieldBoardExistance(FieldNumbering)


def read_file(filename):
	lines = []
	with open(filename, 'r', encoding='utf-8-sig') as f:
			for line in f:
				lines.append(line.strip())
	return lines

def convert(lines):
	person = None
	allen_word_count = 0
	allen_sticker_count = 0
	allen_image_count = 0
	viki_word_count = 0
	viki_sticker_count = 0
	viki_image_count = 0
	for line in lines:
		msg = line.split(' ')
		time = msg[0]
		person = msg[1]
		if person == 'Allen':
			if msg[2] == '貼圖':
				allen_sticker_count += 1
			elif msg[2] == '圖片':
				allen_image_count += 1
			else:
				for m in msg[2:]:
					allen_word_count += len(m)
		elif person == 'Viki':
			if msg[2] == '貼圖':
				viki_sticker_count += 1
			elif msg[2] == '圖片':
				viki_image_count += 1
			else:
				for m in msg[2:]:
					viki_word_count += len(m)
	print('allen說了', allen_word_count, '個字')
	print('allen傳了', allen_sticker_count, '個貼圖')
	print('allen傳了', allen_image_count, '張圖片')

	print('viki說了', viki_word_count, '個字')
	print('viki傳了', viki_sticker_count, '個貼圖')
	print('viki傳了', viki_image_count, '張圖片')

def main():
	lines = read_file('LINE-Viki.txt')
	convert(lines)

main()


from http import HTTPStatus

from django.test import Client, TestCase
from django.urls import reverse

from posts.models import Group, Post, User

class PostURLTests(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.user = User.objects.create_user(username='Vasya')
        cls.group = Group.objects.create(
            title='Название группы',
            slug='group-slug',
            description='Описание группы',
        )
        cls.post = Post.objects.create(
            author=cls.user,
            text='Текст поста',
        )

    def setUp(self):
        self.user = User.objects.get(username='Vasya')
        self.authorized_client = Client()
        self.authorized_client.force_login(self.user)
        self.new_user = User.objects.create(username='Sasha')
        self.not_author = Client()
        self.not_author.force_login(self.new_user)

    def test_urls_exist_at_desired_location_(self):
        """Проверка доступа и шаблона страницы для не/автор-ых польз-ей."""
        responses_and_templates_urls = (
            (self.client.get(reverse('posts:index')), 'posts/index.html'),
            (self.client.get(reverse(
                ('posts:group_list'),
                args=['group-slug'])), 'posts/group_list.html'),
            (self.client.get(reverse(
                ('posts:profile'), args=['Vasya'])), 'posts/profile.html'),
            (self.client.get(reverse(
                ('posts:post_detail'), args=['1'])), 'posts/post_detail.html'),
            (self.authorized_client.get(reverse(
                'posts:post_create')), 'posts/post_create.html'),
            (self.authorized_client.get(reverse(
                ('posts:post_edit'), args=['1'])), 'posts/post_create.html'),
        )
        for response, template in responses_and_templates_urls:
            with self.subTest(response=response, template=template):
                self.assertEqual(response.status_code, HTTPStatus.OK)
                self.assertTemplateUsed(response, template)

    def test_edit_create_redirect_anonymous_on_login(self):
        """Страницы edit и create перенаправит анонимного
        пользователя на страницу логина.
        """
        urls = (
            (reverse(('posts:post_edit'), args=['1'])),
            (reverse('posts:post_create')),
        )
        redir_login = reverse('users:login')
        for url in urls:
            with self.subTest(url=url):
                response = self.client.get(url)
                self.assertRedirects(response, f'{redir_login}?next={url}')

    def test_redirect_edit_post_not_author(self):
        """Страница /edit/ перенаправит не автора поста на страницу поста.
        """
        url = reverse(('posts:post_edit'), args=['1'])
        response = self.not_author.get(url)
        self.assertRedirects(response, reverse(
            ('posts:post_detail'), args=['1']))

    def test_unexisting_page(self):
        '''Проверка на несуществующую страницу.'''
        response = self.client.get('not')
        self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND)


from tqdm import tqdm
import editdistance
from collections import defaultdict
from datasets import DatasetDict, Dataset
from src.utils import read_data
from preprocess.utils import get_answer_indices,clean_text, anls_metric_str, better_subfinder, get_answer_indices_by_enumeration

from datasets import load_from_disk
"""
    Convert the DocVQA dataset into dataset cache, which is a dictionary of Dataset objects.
    At the same time, we extract the answer spans.
"""

def extract_start_end_index_v1(current_answers, words):
    """
    Adapted from https://github.com/anisha2102/docvqa/blob/master/create_dataset.py
    :param current_answers:
    :param words:
    :return:
    """
    ## extracting the start, end index
    processed_answers = []
    ## remove duplicates because of the case of multiple answers
    current_answers = list(set(current_answers))
    all_not_found = True
    for ans_index in range(len(current_answers)):
        current_ans = current_answers[ans_index]
        start_index, end_index, extracted_answer = get_answer_indices(words, current_answers[ans_index])
        ans = current_ans.lower()
        extracted_answer = clean_text(extracted_answer)
        ans = clean_text(ans)
        dist = editdistance.eval(extracted_answer.replace(' ', ''),
                                 ans.replace(' ', '')) if extracted_answer != None else 1000
        if start_index == -1:
            start_index = -1
            end_index = -1
            extracted_answer = ""
        if dist > 5:
            start_index = -1
            end_index = -1
        if start_index == -1 or len(extracted_answer) > 150 or extracted_answer == "":
            start_index = -1
            end_index = -1
            extracted_answer = ""
        if start_index != -1:
            all_not_found = False
        processed_answers.append({
            "start_word_position": start_index,
            "end_word_position": end_index,
            "gold_answer": current_ans,
            "extracted_answer": extracted_answer})
    return processed_answers, all_not_found

def extract_start_end_index_v2(current_answers, words):
    """
    Follwing https://github.com/redthing1/layoutlm_experiments/blob/main/llm_tests/llm_tests/prep_docvqa_xqa.py.
    :param current_answers:
    :param words:
    :return:
    """
    ## extracting the start, end index
    processed_answers = []
    ## remove duplicates because of the case of multiple answers
    current_answers = list(set(current_answers))
    all_not_found = True
    for ans_index in range(len(current_answers)):
        current_ans = current_answers[ans_index]
        match, word_idx_start, word_idx_end = better_subfinder(
            words, current_ans.lower()
        )
        if not match:
            for i in range(len(current_ans)):
                if len(current_ans) == 1:
                    # this method won't work for single-character answers
                    break  # break inner loop
                # drop the ith character from the answer
                answer_i = current_ans[:i] + current_ans[i + 1:]
                # print('Trying: ', i, answer, answer_i, answer_i.lower().split())
                # check if we can find this one in the context
                match, word_idx_start, word_idx_end = better_subfinder(
                    words, answer_i.lower(), try_hard=True
                )
                if match:
                    break  # break inner
        if match:
            assert word_idx_start != -1 and word_idx_end != -1
            extracted_answer = " ".join(words[word_idx_start:word_idx_end + 1])
        else:
            word_idx_start = -1
            word_idx_end = -1
            extracted_answer = ""
        ## end index is inclusive
        if word_idx_start != -1:
            all_not_found = False
        processed_answers.append({
            "start_word_position": word_idx_start,
            "end_word_position": word_idx_end,
            "gold_answer": current_ans,
            "extracted_answer": extracted_answer})
    return processed_answers, all_not_found

def extract_start_end_index_v3(current_answers, words, threshold = 3):
    """
    Directly enumerate all the spans, really expensive, there is a threshold of tolerance
    :param current_answers:
    :param words:
    :param threshold: for example, the length of answer token is L, we search all the spans
                        whose length is within [L-threshold, L+threshold]
    :return:
    """
    ## extracting the start, end index
    processed_answers = []
    ## remove duplicates because of the case of multiple answers
    current_answers = list(set(current_answers))
    all_not_found = True
    for ans_index in range(len(current_answers)):
        current_ans = current_answers[ans_index]
        start_index, end_index, extracted_answer = get_answer_indices_by_enumeration(words, current_answers[ans_index], threshold)
        ans = current_ans.lower()
        extracted_answer = clean_text(extracted_answer)
        ans = clean_text(ans)
        dist = editdistance.eval(extracted_answer.replace(' ', ''),
                                 ans.replace(' ', '')) if extracted_answer != None else 1000
        if start_index == -1:
            start_index = -1
            end_index = -1
            extracted_answer = ""
        if dist > 5:
            start_index = -1
            end_index = -1
        if start_index == -1 or len(extracted_answer) > 150 or extracted_answer == "":
            start_index = -1
            end_index = -1
            extracted_answer = ""
        if start_index != -1:
            all_not_found = False
        processed_answers.append({
            "start_word_position": start_index,
            "end_word_position": end_index,
            "gold_answer": current_ans,
            "extracted_answer": extracted_answer})
    return processed_answers, all_not_found

def normalize_bbox(bbox, width, height):
    return [
        int(1000 * (bbox[0] / width)),
        int(1000 * (bbox[1] / height)),
        int(1000 * (bbox[2] / width)),
        int(1000 * (bbox[3] / height)),
    ]

def convert_docvqa_to_cache(train_file, val_file, test_file, lowercase:bool, read_msr_ocr: bool = False,
                            extraction_method="v1") -> DatasetDict:
    data_dict = {}
    for file in [train_file, val_file, test_file]:
        new_all_data = defaultdict(list)
        data = read_data(file)
        split = data["dataset_split"]
        objs = data['data']
        num_answer_span_found = 0
        all_original_accepted = []
        all_extracted_not_clean = []
        msr_data = None
        msr_image_name2width_height = None
        if read_msr_ocr:
            if "test" not in split:
                msr_file = "data/docvqa_proc_train_t3_ocr" if split == "train" else "data/docvqa_proc_val_t3_msread_ocr"
                msr_data = load_from_disk(msr_file)
            else:
                msr_file = "data/docvqa/test/test_v1.0_msr.json"
                msr_data = read_data(msr_file)
        # # Note: for natural order:
        # if read_msr_ocr:
        #     image_name2width_height_file = f"data/docvqa/{split}_image_name2width_height.json"
        #     ocr_info_file = f"data/docvqa/{split}_v1.0_msr_order_natural_more_info.json"
        #     msr_data = read_data(ocr_info_file)
        #     msr_image_name2width_height = read_data(image_name2width_height_file)
        # # Note: for segment level:
        # if read_msr_ocr:
        #     ocr_info_file = f"data/docvqa/{split}/{split}_v1.0_msr_seg_True.json"
        #     msr_data = read_data(ocr_info_file)
        for obj_idx, obj in tqdm(enumerate(objs), desc="reading {}".format(split), total=len(objs)):
            msr_obj = None
            if msr_data is not None:
                msr_obj = msr_data[obj_idx]
                assert obj['questionId'] == msr_obj['questionId']
            new_answers = None
            for key in obj.keys():
                if key == "question":
                    new_all_data[key].append(obj[key].lower() if lowercase else obj[key])
                elif key == "answers":
                    answers = obj[key]
                    new_answers = []
                    for ans in answers:
                        new_answers.append(ans.lower() if lowercase else ans)
                    new_answers = list(set(new_answers))
                    ## not added yet, later after processing, we add start and end as well.
                    new_all_data["original_answer"].append(new_answers)
                else:
                    new_all_data[key].append(obj[key])
            if new_answers is None:
                # this only applies to test set.
                new_all_data["original_answer"].append(["dummy answer"])

            ocr_file = f"data/docvqa/{split}/ocr_results/{obj['ucsf_document_id']}_{obj['ucsf_document_page_no']}.json"
            ocr_data = read_data(ocr_file)
            assert len(ocr_data['recognitionResults']) == 1
            if msr_obj is None:
                all_text = ' '.join([line['text'] for line in ocr_data['recognitionResults'][0]['lines']])
                text, layout = [], []
                for line in ocr_data["recognitionResults"][0]["lines"]:
                    for word in line["words"]:
                        x1, y1, x2, y2, x3, y3, x4, y4 = word['boundingBox']
                        new_x1 = min([x1, x2, x3, x4])
                        new_x2 = max([x1, x2, x3, x4])
                        new_y1 = min([y1, y2, y3, y4])
                        new_y2 = max([y1, y2, y3, y4])
                        if word["text"].startswith("http") or word["text"] == "":
                            continue
                        text.append(word["text"].lower() if lowercase else word["text"])
                        layout.append([new_x1, new_y1, new_x2, new_y2])
            else:

                if msr_image_name2width_height is not None:
                    normalized_layout = []
                    image_name = msr_obj['image'].split('/')[1]
                    for coord in msr_obj['layout']:
                        normalized_layout.append(normalize_bbox(coord, msr_image_name2width_height[image_name]['width'], msr_image_name2width_height[image_name]['height']))
                    layout = normalized_layout
                else:
                    layout = msr_obj['boxes'] if "boxes" in msr_obj else msr_obj["layout"]
                all_text = ' '.join(msr_obj['words'])
                text = [word.lower() if lowercase else word for word in msr_obj['words']]

            new_all_data['ocr_text'].append(all_text)
            new_all_data['words'].append(text)
            new_all_data['layout'].append(layout)
            if new_answers is not None:
                ## lowercase everything for matching
                before_processed_text = [w.lower() for w in text]
                before_processed_new_answers = [a.lower() for a in new_answers]
                if extraction_method == "v1":
                    processed_answers, all_not_found = extract_start_end_index_v1(before_processed_new_answers, before_processed_text)
                elif extraction_method == "v2":
                    processed_answers, all_not_found = extract_start_end_index_v2(before_processed_new_answers, before_processed_text)
                elif extraction_method == "v1_v2":
                    processed_answers, all_not_found = extract_start_end_index_v1(before_processed_new_answers, before_processed_text)
                    if all_not_found:
                        processed_answers, _ = extract_start_end_index_v2(before_processed_new_answers, before_processed_text)
                elif extraction_method == "v2_v1":
                    processed_answers, all_not_found = extract_start_end_index_v2(before_processed_new_answers, before_processed_text)
                    if all_not_found:
                        processed_answers, _ = extract_start_end_index_v1(before_processed_new_answers, before_processed_text)
            else:
                processed_answers = [{
                    "start_word_position": -1,
                    "end_word_position": -1,
                    "gold_answer": "<NO_GOLD_ANSWER>",
                    "extracted_answer": ""}]
            new_all_data['processed_answers'].append(processed_answers)

            ## Note: just to count the stat
            for ans in processed_answers:
                if ans['start_word_position'] != -1:
                    num_answer_span_found += 1
                    break
            #NOTE: check the current extracted ANLS
            current_extracted_not_clean = []
            for ans in processed_answers:
                if ans['start_word_position'] != -1:
                    # candidate_tokens = text[ans['start_word_position']:ans['end_word_position']+1]
                    # candidate_tokens = [clean_text(x) for x in candidate_tokens]
                    current_extracted_not_clean.append(' '.join(text[ans['start_word_position']:ans['end_word_position']+1]))
            if len(current_extracted_not_clean) > 0:
                # _, anls = anls_metric_str(predictions=[current_extracted_not_clean], gold_labels=[new_answers])
                all_extracted_not_clean.append(current_extracted_not_clean)
                all_original_accepted.append(new_answers)
        # NOTE: check all extracted ANLS
        if "test" not in file:
            _, anls = anls_metric_str(predictions=all_extracted_not_clean, gold_labels=all_original_accepted)
            print(f"Current ANLS: {anls}")
        total_num = len(new_all_data["questionId"])
        print(f"{split} has {total_num} questions, "
              f"extractive answer found: {num_answer_span_found} "
              f"answer not found: {total_num - num_answer_span_found}", flush=True)
        data_dict[split] = Dataset.from_dict(new_all_data)
    all_data = DatasetDict(data_dict)
    return all_data

if __name__ == '__main__':
    all_lowercase = True
    read_msr = True ## default False, for data with MSR OCR, please contact me.
    answer_extraction_methods = ["v3"] ## default v1, v2, v1_v2, v2_v1
    for answer_extraction_method in answer_extraction_methods:
        dataset = convert_docvqa_to_cache("data/docvqa/train/train_v1.0.json",
                                          "data/docvqa/val/val_v1.0.json",
                                          "data/docvqa/test/test_v1.0.json",
                                          lowercase=all_lowercase,read_msr_ocr=read_msr,
                                          extraction_method=answer_extraction_method)
        cached_filename = f"data/docvqa_cached_extractive_all_lowercase_{all_lowercase}_msr_{read_msr}_extraction_{answer_extraction_method}_enumeration"
        dataset.save_to_disk(cached_filename)

        ## for natural order
        # dataset = convert_docvqa_to_cache("data/docvqa/train/train_v1.0.json",
        #                                   "data/docvqa/val/val_v1.0.json",
        #                                   "data/docvqa/test/test_v1.0.json",
        #                                   lowercase=all_lowercase, read_msr_ocr=read_msr,
        #                                   extraction_method=answer_extraction_method)
        # cached_filename = f"data/docvqa_cached_extractive_all_lowercase_{all_lowercase}_msr_{read_msr}_extraction_{answer_extraction_method}_natural_order"
        # dataset.save_to_disk(cached_filename)

        ## segment useless
        # dataset = convert_docvqa_to_cache("data/docvqa/train/train_v1.0.json",
        #                                   "data/docvqa/val/val_v1.0.json",
        #                                   "data/docvqa/test/test_v1.0.json",
        #                                   lowercase=all_lowercase,read_msr_ocr=read_msr,
        #                                   extraction_method=answer_extraction_method)
        # cached_filename = f"data/docvqa_cached_extractive_all_lowercase_{all_lowercase}_msr_{read_msr}_extraction_{answer_extraction_method}_seg_level"
        # dataset.save_to_disk(cached_filename)


# -*- coding: utf-8 -*-
"""
Created on Fri May  8 19:45:00 2020

@author: rishabhsaxena01
"""

n = int(input())
Limit = int(input())
for _ in range(n):
    x = int(input())
    if (x >= Limit):
        print('YES')
    else:
        print('NO')

Practice Problems/Basic Programming/Basics of Input-Output/Gopi breakup.py


import xenarix as xen
import xenarix.sample as xen_s

xen.set_repository('c:\\xenarix')

set_name = 'set1'
scenSet = xen.ScenarioSet(set_name=set_name)

scen_id = 'scen1'
result_id = 'res1'
scen1 = xen.Scenario(scen_id=scen_id, result_id=result_id)

scen1.general.scenario_num = 200
scen1.general.maxyear = 5

gbm1 = xen_s.gbmconst('kospi200')
gbm2 = xen_s.gbmconst('kospi200_2')
gbm3 = xen_s.gbmconst('kospi200_3')

scen1.add_model(gbm1)
scen1.add_model(gbm2)
scen1.add_model(gbm3)

scenSet.add_scenario(scen1)

scen1.refresh_corr()
scen1.set_corr(gbm1, gbm3, 0.3)
scen1.set_corr(gbm1, gbm2, 0.5)

# print(scen1.correlation.dump())

# print(scen1.dump())

scenSet.generate()


from django import forms
from .models import Article, ArticleComment,SubjectComment,CategoryComment
from captcha.fields import ReCaptchaField

#Article Yayınlama Sistemi 1.0

#Category Yorum Sistemi 2.0 --> 3.0 için myworkandresearch:views.py sayfasına git
class CategoryCommentForm(forms.ModelForm):
    captcha = ReCaptchaField()
    class Meta:
        model = CategoryComment
        fields = [
            'name',
            'content'
        ]

#Subject Yorum Sistemi 2.0 --> 3.0 için myworkandresearch:views.py sayfasına git
class SubjectCommentForm(forms.ModelForm):
    captcha = ReCaptchaField()
    class Meta:
        model = SubjectComment
        fields = [
            'name',
            'content'
        ]

#Article Yorum Sistemi 2.0 --> 3.0 için myworkandresearch:views.py sayfasına git
class ArticleCommentForm(forms.ModelForm):
    captcha = ReCaptchaField()
    class Meta:
        model = ArticleComment
        fields = [
            'name',
            'content'
        ]


#!/usr/bin/env python3
"""
Redis client module
"""
import redis
import uuid
from typing import Union, Callable
from functools import wraps

def count_calls(method: Callable) -> Callable:
    """
    count calls decorator takes a callable method as an arg
    """
    @wraps(method)
    def wrapper(self, *args, **kwargs):
        """
        Function that will replaxce the original method
        """
        key = method.__qualname__
        self._redis.incr(key)
        return method(self, *args, **kwargs)
    return wrapper

def call_history(method: Callable) -> Callable:
    """
    Method takes a decorator callable as an arg
    """
    @wraps(method)
    def wrapper(self, *args, **kwargs):
        """
        Function that will replace the original method
        """
        input_key = f"{method.__qualname__}:inputs"
        output_key = f"{method.__qualname__}:outputs"
        self._redis.rpush(input_key, str(args))
        result = method(self, *args, **kwargs)
        self._redis.rpush(output_key, str(result))

        return result
    return wrapper

class Cache:
    """
    Caching class
    """
    def __init__(self):
        """
        Initialize the Redis client
        """
        self._redis = redis.Redis()
        self._redis.flushdb()

    @call_history
    @count_calls
    def store(self, data: Union[str, bytes, int, float]) -> str:
        """
        Method that takes a data args and returns a str
        """
        key = str(uuid.uuid4())
        self._redis.set(key, data)
        return key

    def replay(fn: Callable) -> None:
        """
        function that takes fn an arg
        """
        input_Key = f"{fn.__qualname__}:inputs"
        output_key = f"{fn.__qualname__}:outputs"

        input_history = [eval(args) for args in cache._redis.lrange(
            inputs_key, 0, -1)]
        output_history = cache._redis.lrange(output_key, 0, -1)
        print(f"{fn.__qualname__} was called {len(input_history)} times:")
        for args, output in zip(input_history, output_history):
            print(f"{fn.__qualname__}{args} -> {output.decode('utf-8')}")

    def get(self, key: str, fn: Callable = None) -> Union[
            str, bytes, int, float, None]:
        """
        Method that takes a key str arg and optional callable arg fn
        """
        data = self._redis.get(key)
        if data is not None and fn is not None:
            return fn(data)
        return data

    def get_str(self, key: str) -> Union[str, None]:
        """
        Method that will parametrize cache.get
        """
        return self.get(key, fn=lambda d: d.decode("utf-8"))

    def get_int(self, key: str) -> Union[int, None]:
        """
        Method that will automatically parametrize cache.get
        """
        return self.get(key, fn=int)


import random

def rearrange_string(sentence):
  '''
Takes in sentence as user input and returns a random sentence with the same words
  '''
  str_arr = sentence.split(' ')
  for i in range(len(str_arr)):
    rand_int = random.randint(0, len(str_arr) - 1)
    str_arr[rand_int], str_arr[i] = str_arr[i], str_arr[rand_int]
  return ' '.join(str_arr)

if __name__ == '__main__':
  print(rearrange_string('how now brown cow'))


import sys
import pandas as pd
import numpy as np
import nltk
import re
from nltk.tokenize import word_tokenize, WhitespaceTokenizer
from nltk.stem import WordNetLemmatizer
from nltk.corpus import stopwords
from sqlalchemy import create_engine
from sklearn.pipeline import Pipeline
from sklearn.multioutput import MultiOutputClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer, TfidfVectorizer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score
import pickle
nltk.download(['punkt', 'wordnet','stopwords'])

def load_data(database_filepath):
    '''
    Load data from database as dataframe
    Input:
        database_filepath: Location of sql database
    Output:
        X: Message data (features)
        y: Categories (target)

    '''
    # load data from database
    engine = create_engine('sqlite:///' + database_filepath)
    df = pd.read_sql_table('my_message_cat', engine)
    #df.drop('original', axis=1, inplace=True) # A lot of NaN values for this column do dropping it
    #df.dropna(how='any', inplace=True)
    X = df['message']
    y = df.iloc[:,4:]

    return X, y

def tokenize(text):
    '''
    input:
        text: Text data for tokenization.
    output:
        tokenized_clean: List of results after tokenization.
    '''
    # remove useless characters
    text = re.sub(r'[^a-zA-Z0-9]', ' ', text)

    #make all the characters to lower_case
    text = text.lower()

    # Tokenization of words
    words = word_tokenize(text)

    # Renoving of stop words such as the, in, is .....
    stop_word = list(set(stopwords.words('english')))
    words = [x for x in words if x not in stop_word]

    # Lemmatization can be done through both noun and verb
    lemmatizer = WordNetLemmatizer()
    lemmatized = [lemmatizer.lemmatize(x, pos='n').strip() for x in words]
    tokenized_data = [lemmatizer.lemmatize(x, pos='v').strip() for x in lemmatized]

    return tokenized_data

def build_model():
    '''
    Run Machine Learning pipeline using ifidf, random forest, and gridsearch
    Input: None
    Output:
        cv: GridSearchCV output
    '''
    pipeline = Pipeline([('vect', CountVectorizer(tokenizer=tokenize)),
                       ('tfidf', TfidfTransformer()),
                       ('clf', MultiOutputClassifier(RandomForestClassifier()))])

    parameters = {'clf__estimator__min_samples_split': [2, 4],
              #'clf__estimator__max_features': ['log2', 'auto', 'sqrt', None],
              #'clf__estimator__criterion': ['gini', 'entropy'],
              #'clf__estimator__max_depth': [None, 25, 50, 100, 150, 200],
             }

    cv = GridSearchCV(estimator=pipeline, param_grid=parameters)

    return cv

def evaluate_model(model, X_test, Y_test, y):
    '''
    Evaluate model performance using test data
    Input:
        model: Model to be evaluated
        X_test: Test data
        Y_test: Labels for Test data
    Output:
        Report of accuracy and classfication for each category
    '''
    Y_pred = model.predict(X_test)

    for o in range(len(y.columns)):
        print(y.columns[o], classification_report(Y_test.iloc[:, x].values, Y_pred[:, x]))
        print(y.columns[o], accuracy_score(Y_test.iloc[:, x].values, Y_pred[:,x]))

def save_model(model, model_filepath):
    '''
    Save model as a pickle file
    Input:
        model: Model to save as pickle file
        model_filepath: path of the output pickle file
    Output:
        Pickle file of model
    '''
    pickle.dump(model, open(model_filepath, "wb"))

def main():
    if len(sys.argv) == 3:
        database_filepath, model_filepath = sys.argv[1:]
        print('Loading data...\n    DATABASE: {}'.format(database_filepath))
        X, y = load_data(database_filepath)
        X_train, X_test, Y_train, Y_test = train_test_split(X, y, test_size=0.3)

        print('Model Buildup')
        model = build_model()

        print('Train')
        model.fit(X_train, Y_train)

        print('Evaluate')
       # evaluate_model(model, X_test, Y_test, y)

        print('Save\n    MODEL: {}'.format(model_filepath))
        save_model(model, model_filepath)

    else:
        print('Please provide the filepath of the disaster messages database '\
              'as the first argument and the filepath of the pickle file to '\
              'save the model to as the second argument. \n\nExample: python '\
              'train_classifier.py ../data/DisasterResponse.db classifier.pkl')

if __name__ == '__main__':
    main()


from tkinter import Button, Frame, ttk
import tkinter as tk
from frames.chatFrame import ChatFrame
from frames.roomFrame import RoomFrame
from network.listener import Listener
from tkinter.simpledialog import askstring

class MainFrame(tk.Tk):
    def __init__(self, name: str):
        super().__init__()
        self.active = True
        self.geometry("800x600")
        self.name = name
        self.title(f"Hello {self.name}")

        self.dict_client: dict[str, ChatFrame] = {}
        self.dict_rooms: dict[str, RoomFrame] = {}

        self.notebook = ttk.Notebook(self)
        self.notebook.pack(pady=10, expand=True)

        self.frm_main = Frame(self.notebook)
        self.notebook.add(self.frm_main, text=f"Hello {self.name}")

        self.frame_tree = Frame(self.frm_main)
        self.frame_tree.pack()

        self.cols_tree_clients = "Clients"
        self.tree_clients = ttk.Treeview(
            self.frame_tree, columns=self.cols_tree_clients, show="headings"
        )
        self.tree_clients.pack(side="left")
        self.tree_clients.heading(self.cols_tree_clients, text=self.cols_tree_clients)
        self.tree_clients.bind("<<TreeviewSelect>>", self.__client_selected)

        # ---------------------------------
        self.cols_tree_rooms = ("Room Name", "Owner", "Guest Number")
        self.tree_rooms = ttk.Treeview(
            self.frame_tree, columns=self.cols_tree_rooms, show="headings"
        )
        self.tree_rooms.pack(side="right")
        for col in self.cols_tree_rooms:
            self.tree_rooms.heading(col, text=col)
        self.tree_rooms.bind("<<TreeviewSelect>>", self.__room_selected)

        self.btn = Button(self.frm_main, text="create room", command=self.__create_room)
        self.btn.pack()

    def __del__(self):
        self.active = False

    def __create_room(self):
        room_name = askstring("Enter Room Name", "Please enter room name to create")
        if not room_name:
            return
        Listener.send_create_room(self.name, room_name)

    def __clear_clients(self):
        for i in self.tree_clients.get_children():
            self.tree_clients.delete(i)

    def __clear_rooms(self):
        for i in self.tree_rooms.get_children():
            self.tree_rooms.delete(i)

    def __clear_room_notebooks(self):
        # TODO:
        pass

    def add_chat_notebook(self, to: str):
        if self.dict_client.get(to):
            return
        c = ChatFrame(self.notebook, self.name, to)
        self.dict_client[to] = c
        self.notebook.add(c, text=f"{to} private message")

    def __client_selected(self, event):
        for selected in self.tree_clients.selection():
            item = self.tree_clients.item(selected)
            client = item["values"][0]

            if (
                not isinstance(client, str)
                or self.name == client
                or self.dict_client.get(client)
            ):
                continue

            self.add_chat_notebook(client)

    def __room_selected(self, event):
        for selected in self.tree_rooms.selection():
            item = self.tree_rooms.item(selected)
            room_name = item["values"][0]
            owner = item["values"][1]

            if self.dict_rooms.get(owner) or self.name == owner:
                continue

            Listener.send_join_room(owner)

    def update_clients(self, data: list[str]):
        self.__clear_clients()
        for d in data:
            self.tree_clients.insert("", "end", values=[d])

        l = [
            (client, frame)
            for client, frame in self.dict_client.items()
            if client not in data
        ]

        for client, frame in l:
            self.notebook.forget(frame)
            del self.dict_client[client]

    def update_rooms(self, data: list[dict]):
        self.__clear_rooms()
        owner_list = []
        for room in data:
            owner_list.append(room["owner"])
            self.tree_rooms.insert(
                "", "end", values=[room["name"], room["owner"], len(room["guests"])]
            )
            if (
                self.name in room["guests"] or self.name == room["owner"]
            ) and not self.dict_rooms.get(room["owner"]):
                c = RoomFrame(self.notebook, room["owner"], self.name)
                self.dict_rooms[room["owner"]] = c
                self.notebook.add(c, text=f"{room['name']} Room")

        l = [
            (owner, frame)
            for owner, frame in self.dict_rooms.items()
            if owner not in owner_list
        ]
        for owner, frame in l:
            self.notebook.forget(frame)
            del self.dict_rooms[owner]


#Title: RelEnthPlots2.py
#Author: Greg Wallace
#Organization: Washington State University, HYPER Lab
#Date Created: December 3, 2021
#Date Last Edited: December 6, 2021
#Purpose: Plots (and saves a printable paper-sided pdf of) the relative enthalpies of parahydrogen, orthohydrogen, normal hydrogen, and equilibrium hydrogen using ortho increments.

import CoolProp.CoolProp as CP #Source of all thermodynamic data
import math #for "floor" function
import matplotlib.pyplot as plt #For plotting
import numpy as np #For creating plot axes

#Returns equilibrium ortho fraction based off temperature and number of iterations to use.  If no iterations are given, defaults to 7, which computs accurately from 0 to 300K up to 16 decimals
#At 20K, it should be about 0.00 (0%)
#At 77K, it shoul dbe about 0.49 (49%)
#At 300K and above, it should be 0.75 (75%)
#Source of Formula: Brandt Pedrow's thesis: https://wsuwp-uploads.s3.amazonaws.com/uploads/sites/44/2016/05/b_pedrow_011423571.pdf (pdf page 21, or numbered page 8)
#Citation: Brandt Patrick Pedrow, May 2016, "Parahydrogen-Orthohydrogen Conversion on Catalyst Loaded Scrim for Vapor Cooled Shielding of Cryogenic Storage Vessels", Master Thesis, Washington State University, Pullman.
def Yo_equilib(T,N=7,T_rot=85.4):
    if N<1 or (type(N) != int):#returns if N is not an int of 1 or above
        print("Yo_equilib requires integer N value of 1 or above")
        return None
    if T<=0:#returns if T is negative or 0, because negative temps don't exist
        print("Yo_equilib requires temperature must be in positive Kelvin")
        return None
    J=0
    K_para=0.0
    K_ortho=0.0
    #T_rot=85.4#characteristic rotational temperature of hydrogen, 85.4K
    for i in range(1,N+1):
        #print(i)
        K_para=K_para+(2*J+1)*math.exp(-J*(J+1)*T_rot/T)
        J+=1
        K_ortho=K_ortho+(2*J+1)*math.exp(-J*(J+1)*T_rot/T)
        J+=1
    Yo=3*K_ortho/(K_para+3*K_ortho)
    return Yo

# def h_ortho(T,P):
    # return CP.PropsSI('H','T',T,'P',P,'orthohydrogen') #Enthalpy in (J/kg)

#Returns the Relative Enthalpy of ortho-para mixture based off mass-weighted average
def h_mix(T,P,Yo):
    h_ortho=CP.PropsSI('H','T',T,'P',P,'orthohydrogen') #Enthalpy in (J/kg)
    h_para=CP.PropsSI('H','T',T,'P',P,'parahydrogen') #Enthalpy in (J/kg)
    return (Yo*h_ortho+(1-Yo)*h_para)

#Returns the Orthohydrogen fraction of ortho-para mixture based off a relative enthalpy
def Yo_mix(T,P,h_mix):
    h_ortho=CP.PropsSI('H','T',T,'P',P,'orthohydrogen') #Enthalpy in (J/kg)
    h_para=CP.PropsSI('H','T',T,'P',P,'parahydrogen') #Enthalpy in (J/kg)
    return ((h_mix-h_para)/(h_ortho-h_para))

#Finds final temperature when hydrogen is catalyzed from a known T_initial, P, and h_mix to an equilibrium T and Yo.  Isenthalpic, isobaric process.
#Temperature between 0K and 500K
def T_isenth(P,h_mixture):
    i=0
    T_guess=150.0
    T_max=500.0
    T_min=14.0
    h_guess=1000.0
    h_err=1.0
    Yo_guess=0.375
    while abs(h_err) > 0.000001:#Ends when enthalpy error is less than 0.001J/kg
        Yo_guess=Yo_equilib(T_guess)
        h_guess=h_mix(T_guess,P,Yo_guess)
        print("Temp Guess: " + str(T_guess))
        print("Enth Guess: " + str(h_guess))
        h_err=h_guess-h_mixture
        print("Enth err: " + str(h_err))
        if h_err>0:
            T_max=T_guess
            T_guess=(T_max+T_min)/2
        else:
            T_min=T_guess
            T_guess=(T_max+T_min)/2

        if i>1000:
            print("Did not converge in 1000 iterations!")
            return None
        i+=1
    print(i)
    return T_guess

#Returns the reverse carnot refrigeration efficiency when pulling heat out at T_L and pumping it up to T_H
def COPRefrig(T_H,T_L):
    if T_L>T_H:print("Warning: T_L is higher than T_H")
    return (T_L/(T_H-T_L))
#https://en.wikipedia.org/wiki/Heat_pump_and_refrigeration_cycle

#Finds the temperature where a certain equilibrium fraction occurs.
#Temperature reasonably accurate between 14K and 400K, but less accurate at very high and very low temps because ortho fraction approaches a constant (0 or 0.75)
def T_equilib(Yo):
    if(Yo>=0.75 or Yo<=0.0):
        print("Orthohydrogen fraction must be above 0 and below 0.75, it does not exist as equilibrium outside this range.")
        return None
    i=0

    T_max=1000.0
    T_guess=T_max
    T_min=0
    Yo_err=0.25
    Yo_guess=0.375
    while abs(Yo_err) > 0.000001:#Ends when equilibrium error is less than 1/1000 of 0.1%
        if Yo_err>0:
            T_max=T_guess
            T_guess=(T_max+T_min)/2
        else:
            T_min=T_guess
            T_guess=(T_max+T_min)/2
        Yo_guess=Yo_equilib(T_guess)

        print("Temp Guess: " + str(T_guess))
        print("Yo Guess: " + str(Yo_guess))
        Yo_err=Yo_guess-Yo
        print("Yo err: " + str(Yo_err))

        if i>1000:
            print("Did not converge in 1000 iterations!")
            return None
        i+=1
    print(str(i) + " iterations")
    return T_guess

#Setting Up Coolprop:
P_ref=101325#Pa
#Parahydrogen Reference State does not need to change:
h_ref=CP.PropsSI('H','P',P_ref,'Q',0,'parahydrogen')#should be about zero
s_ref=CP.PropsSI('S','P',P_ref,'Q',0,'parahydrogen')#should be roughly zero
#print(h_ref)
#print(s_ref)
#Orthohydrogen Reference State needs to be adjusted such that its enthalpy is 702.98 kJ/kg and entropy is 0.018269 kJ/kg-K at NBP
Dmolar_ref_ortho=CP.PropsSI('Dmolar','P',P_ref,'Q',0,'orthohydrogen')#reference state must be in molar density at liquid of normal boiling point
T_ref_ortho=CP.PropsSI('T','P',P_ref,'Q',0.5,'orthohydrogen')
CP.set_reference_state('orthohydrogen',T_ref_ortho,Dmolar_ref_ortho,702.98*(2*1.00784),0.018269*(2*1.00784)) #sets so that enthalpy and entropy difference correctly correspond to ~702.98kJ/kg and 0.018269 kJ/kg-K at 20K; but function inputs must be molar
#print(CP.PropsSI('H','P',P_ref,'Q',0,'orthohydrogen'))#should be 702980 J/kg
#print(CP.PropsSI('S','P',P_ref,'Q',0,'orthohydrogen'))#should be 18.269 J/kg-K

P=101325#Pa, pressure to test at

#Temperature range to take enthalpy values at
T_low=15#K
T_high=150#K
dT=0.1#K, step size
N_T=round((T_high-T_low)/dT)+1#Number of temperatures to check
T_arr=np.linspace(T_low,T_high,N_T)#makes an array of temperatures to check between the T_low and T_high
#print(T_arr)

Yo_low=0.0#unitless
Yo_high=0.75
dYo=0.05#Step size
N_Yo=round((Yo_high-Yo_low)/dYo)+1#Number of ortho fractions to arrange
Yo_arr=np.linspace(Yo_low,Yo_high,N_Yo)#makes an array of ortho fractions to check between the Yo_low and Yo_high
#print(Yo_arr)

h_arrs=np.zeros([N_Yo,N_T])#initialize array of array of enthalpies.  Primary array is for that ortho fraction, secondary is for each temp: [Yo1[T1,T2,T3...],Yo2[T1,T2,T3...]]
h_eq=np.zeros(N_T)#initialize array for the equilibrium enthalpies.  Each value in the array is for a specific temp

Yo_Names=[]

for k in range(N_T):#For each temperature
    for i in range(N_Yo):#for each of the ortho-fraciton setpoints (ex: 0%, 5%, 10%, etc.)
        #Generate enthalpy at each temp and ortho fraction
        h_arrs[i][k]=h_mix(T_arr[k],P,Yo_arr[i])/1000#kJ/kg, do for each temp and each ortho fraction
        if 0==k:
            Yo_Names.append(str(round(Yo_arr[i]*100))+" %")#Create string name for that ortho fraction
    #Generate enthalpy at equilib at that temp
    h_eq[k]=h_mix(T_arr[k],P,Yo_equilib(T_arr[k]))/1000#kJ/kg, equilibrium,only do for each temp
#print(h_arrs)
#print(h_eq)

Yo_Names.append("Equilibrium")

#Flip the names and the enthalpy arrays (only flips the order of the ortho-fractions, the temperature relations will not change
#This is used for plotting (so that it will plot highest ortho fracitons first, and lowest last), to make the plot's key show the lines in order of greatest to smallest
Yo_Names=np.flip(Yo_Names)
h_arrs=np.flip(h_arrs,0)

#Now the Plot:
#Set font properties before generating plot
plt.rcParams.update({'font.family':"Times New Roman",'font.size':10})

fig, h_plot=plt.subplots(figsize=(7.5,10))#start plot, give size to be 7.5" wide, 10" tall
h_plot.plot(T_arr,h_eq,color='black',linewidth=1.0)#Prints equilib curve line because I want it to show up on top in the Key
for j in range(N_Yo):
    h_plot.plot(T_arr,h_arrs[j],linewidth=1.0)
h_plot.plot(T_arr,h_eq,color='black',linewidth=1.0)#Re-prints the equilib curve a second time because I want it to show up on top of the other curves
h_plot.set_xlabel('Temperature [K]')
h_plot.set_ylabel('Relative Enthalpy [kJ/kg]')
h_plot.set_xticks(np.arange(0, 300+5, 5))
h_plot.set_yticks(np.arange(-500, 2500+50, 50))
h_plot.set_xlim([15,150])
h_plot.set_ylim([-100,2501])
h_plot.grid(True)
h_plot.legend(Yo_Names,title='Ortho Fraction:',fontsize=10,loc="lower right")
plt.savefig("RelEnthPlots2.pdf",format="pdf")#Save the figure as a pdf (vector) which can be printed vertically
#File is saved in whatever the working directory is when you run the file (ex: the user folder, or the local directory, etc.)
plt.show()#Show the plot

#https://matplotlib.org/stable/gallery/lines_bars_and_markers/psd_demo.html#sphx-glr-gallery-lines-bars-and-markers-psd-demo-py
#https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.legend.html#matplotlib.axes.Axes.legend
#https://stackoverflow.com/questions/3899980/how-to-change-the-font-size-on-a-matplotlib-plot
#https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.savefig
#https://matplotlib.org/stable/api/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D.set_linewidth
#https://matplotlib.org/stable/gallery/subplots_axes_and_figures/figure_size_units.html
#https://matplotlib.org/stable/api/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D.set_linestyle