from __future__ import annotations

import typing
from pathlib import Path
from typing import IO, Any, Dict, Union

import numpy as np
import PIL.Image
import skimage.transform
from typing_extensions import Literal

from cavlib.models import GooglenetModel, MobilenetModel, Model
from cavlib.typing import ArrayLike, NDArray

ModelLayer = Literal['mobilenet_12d', 'googlenet_4d', 'googlenet_5b']
MODEL_LAYER_MOBILENET_12D: ModelLayer = 'mobilenet_12d'
MODEL_LAYER_GOOGLENET_4D: ModelLayer = 'googlenet_4d'
MODEL_LAYER_GOOGLENET_5B: ModelLayer = 'googlenet_5b'
MODEL_LAYERS = [MODEL_LAYER_MOBILENET_12D, MODEL_LAYER_GOOGLENET_4D, MODEL_LAYER_GOOGLENET_5B]

CAVableImage = Union[str, Path, IO[bytes], PIL.Image.Image, ArrayLike]
ModelClassName = Literal['GooglenetModel', 'MobilenetModel']
loaded_models: Dict[str, Model] = {}

def compute_activations(
    image: CAVableImage, *, model_layer: ModelLayer = MODEL_LAYER_GOOGLENET_4D
) -> NDArray[np.float32]:
    '''compute_activations(image, model_layer='googlenet_4d')
    Calculates activations for a given image. Activations are the value of
    each neuron in the network at that layer.

    This function can be used to precalculate activations for a set of images,
    so they don't have to be calculated every time. Activations can be used
    with :func:`cavlib.CAV.score` and :class:`cavlib.train.TrainingImage`.

    :param CAVableImage image: The image whose activations we want to calculate. Accepts
        a path to an image file, an open file-like object of an image, a PIL
        Image, or a numpy array of RGB data
    :param str model_layer: The :ref:`model layer <model-layers>` to extract.

    :returns: The activation vector, as a 1D numpy array.
    '''
    pixels = get_pixels(image)

    model_layer_info = get_model_layer_info(model_layer)
    model = get_model_instance(model_layer_info.model_class_name)

    return model.get_activation_for_image(pixels, model_layer_info.layer_name)

def get_pixels(image: CAVableImage) -> NDArray[Any]:
    if isinstance(image, str) or hasattr(image, '__fspath__') or hasattr(image, 'read'):
        pil_image = PIL.Image.open(image)  # type: ignore
        array = np.array(pil_image)
    else:
        array = np.array(image)

    return crop_to_square_and_resize(array, width=224)

def get_model_instance(model_class_name: ModelClassName) -> Model:
    if model_class_name not in loaded_models:
        if model_class_name == 'GooglenetModel':
            model: Model = GooglenetModel()
        elif model_class_name == 'MobilenetModel':
            model = MobilenetModel()
        else:
            raise ValueError('unknown model class')

        loaded_models[model_class_name] = model

    return loaded_models[model_class_name]

class ModelLayerInfo(typing.NamedTuple):
    model_class_name: ModelClassName
    layer_name: str

def get_model_layer_info(model_layer: ModelLayer) -> ModelLayerInfo:
    if model_layer == 'googlenet_4d':
        return ModelLayerInfo('GooglenetModel', 'mixed4d')
    elif model_layer == 'googlenet_5b':
        return ModelLayerInfo('GooglenetModel', 'mixed5b')
    elif model_layer == 'mobilenet_12d':
        return ModelLayerInfo('MobilenetModel', 'MobilenetV1/MobilenetV1/Conv2d_12_depthwise/Relu6')
    else:
        raise ValueError(f'unknown model_layer: {model_layer}')

def crop_to_square_and_resize(image: NDArray[Any], width: int) -> NDArray[Any]:
    if image.shape[0] == width and image.shape[1] == width:
        return image

    scale_factor = max(width / image.shape[0], width / image.shape[1])

    translate_x = image.shape[1] / 2 * scale_factor - width / 2
    translate_y = image.shape[0] / 2 * scale_factor - width / 2

    # crop and resize in one operation for efficiency
    return skimage.transform.warp(
        image,
        skimage.transform.SimilarityTransform(
            scale=1/scale_factor, translation=(translate_x/scale_factor, translate_y/scale_factor)
        ),
        output_shape=(width, width),
        mode='edge',
    )


import os
import shutil
import numpy as np
import matplotlib.pyplot as plt
import pylab
from mpl_toolkits.mplot3d import Axes3D

def data_input():
    distance = []
    energy_input = []
    data_inp = open('potential.dat')
    for line in data_inp:
        distance.append(float(line.split()[0]))
        energy_input.append(float(line.split()[1]))
    data_inp.close()
    return distance, energy_input

def check_NaN(par):
    fort7 = open('fort.7', 'r')
    if fort7.read().count(par) == 0:
        fort7.close()
        return True

def fort7():
    fort7 = open('fort.7', 'r')
    fort7_str = fort7.readlines()
    dd = float(fort7_str[1].split('dd=')[1].replace('D', 'E'))
    De = round(float(fort7_str[2].split()[0].replace('D', 'E')), 3)
    Re = round(float(fort7_str[3].split()[0].replace('D', 'E')), 3)
    fort7.close()
    return dd, De, Re

def fort8(start, end, dr, r):
    fort8_file = open('fort.8', 'r')
    fort8_str = fort8_file.read()
    fort8_dat = fort8_str.splitlines()[4:]
    distance8 = []
    energy8 = []
    for line in fort8_dat[(r - start) / dr:(r - end) / dr]:
        if line.split()[1] != 'Infinity':
            distance8.append(float(line.split()[0]))
            energy8.append(float(line.split()[1].replace('D', 'E')))
            # if listTMP_8[0] == start:  # начало
            #     distance8.append(listTMP_8[0])
            #     energy8.append(listTMP_8[1])
            # elif listTMP_8[0] != end and listTMP_8[0] > start:  # всё, что между началом и концом
            #     distance8.append(listTMP_8[0])
            #     energy8.append(listTMP_8[1])
            # elif listTMP_8[0] == end:  # считана последняя точка и цикл закончен
            #     distance8.append(listTMP_8[0])
            #     energy8.append(listTMP_8[1])
            #     break
    return distance8, energy8

def difference_dat(distance, energy_input, distance8, energy8):  # для получения MDC без явного подсчёта по формуле
    difference = []
    for i in range(len(distance)):
        for j in range(len(distance8)):
            if round(distance[i], 2) == distance8[j]:  # выбираем из всех точек fort.8 те, которые были даны в эт.ф.
                DC = energy8[j] - energy_input[i]
                difference.append(abs(DC))
    return round(max(difference), 3)

def plot_ext(distance_ext, energy_ext, name):  # ф-ция для построения на нормальном уч-ке отдельная, т.к. нужны нач.т-ки
    plt.plot(distance_ext, energy_ext, '-', c='b')
    plt.xlabel('r, Å')
    plt.ylabel('V, 1/см')
    plt.savefig(name)
    # plt.close()

def plot(distance8, energy8, distance, energy_input):  # ф-я для построения на +-inf
    plt.plot(distance, energy_input, 'o', c='c')
    plt.plot(distance8, energy8, '-', c='b')
    plt.xlabel('r, Å')
    plt.ylabel('V, 1/см')
    plt.savefig(os.path.join(path, 'U(r)8.png'))
    plt.close()

def moving(path):  # перемещение в нужную папку  файлов
    shutil.move('fort.8', path)
    shutil.move('fort.7', path)
    shutil.move('out.txt', path)
    shutil.move('data_betaFit.dat', path)

def makeData():  # создание матриц для q и n
    x = np.arange(1, 9, 1)
    y = np.arange(1, 9, 1)
    xgrid, ygrid = np.meshgrid(x, y)
    return xgrid, ygrid

def three_d_plot(par, name):  # построение и сохранение
    q, n = makeData()
    fig = pylab.figure()
    axes = Axes3D(fig)
    axes.plot_surface(q, n, par)
    pylab.savefig(name)
    pylab.close()
    # shutil.move(name, r'Re_p 0, De0, Vmin0\Итог')

De = np.zeros((8, 8))  # заготовки для таблиц
MDC = np.zeros((8, 8))
dd = np.zeros((8, 8))
Re = np.zeros((8, 8))
NPR = 591  # параметры для вывода в fort.8
r = 0.1  # (можно попробовать сделать их ввод при запуске)
dr = 0.01
distance, energy_input = data_input()  # считывание первоначальных точек
data_input1 = open('data_betaFit1.dat', 'r')  # счит-е эталонного файла
for q in range(1, 9):
    for n in range(1, 9):
        data_input = open('data_betaFit.dat', 'a')  # создание вводного файл
        shutil.copy('data_betaFit1.dat', 'data_betaFit.dat')
        data_input.write('\n' + str(q) + ' 0 0 ' + str(n) + ' 0' + '\n' + str(NPR) + ' ' + str(r) + ' ' + str(dr))
        data_input.close()
        data_input1.close()
        folder_name_1 = 'Re_p 0, De0, Vmin0'
        folder_name_2 = 'q = {}\q = {}, n = {}'.format(q, q, n)
        path = os.path.join(folder_name_1, folder_name_2)
        if os.path.exists(path) == False:
            os.makedirs(path)
        os.system('betaFit.exe <data_betaFit.dat >out.txt')
        # moving(path)
        if check_NaN('NaN') == True and check_NaN('Infinity') == True:  # проверка на возможность аппроксимации
            dd[q - 1][n - 1], De[q - 1][n - 1], Re[q - 1][n - 1] = fort7()  # считывание параметров из fort.7
            distance8, energy8 = fort8(1.3, 4, dr, r)  # счит-е т-к для графика на начальном участке
            distance_minf, energy_minf = fort8(0.1, 2, dr, r)  # счит-е т-к на -inf
            distance_pinf, energy_pinf = fort8(3.95, 6, dr, r)  # счит-е т-к на +inf
            MDC[q - 1][n - 1] = difference_dat(distance, energy_input, distance8, energy8)  # получение MDC
            plot(distance8, energy8, distance, energy_input)
            plot_ext(distance_minf, energy_minf,
                     os.path.join(path, 'U(r)_minf.png'))  # построение графиков и их сохранение
            plot_ext(distance_pinf, energy_pinf, os.path.join(path, 'U(r)_pinf.png'))
        else:  # если аппр-я не удалась, то можно передать NaN или 0 (я покажу оба варианта)
            dd[q - 1][n - 1] = np.max(np.abs(dd))
            De[q - 1][n - 1] = np.max(np.abs(De))
            MDC[q - 1][n - 1] = np.max(np.abs(MDC))
            Re[q - 1][n - 1] = np.max(np.abs(Re))

# os.makedirs(r'Re_p 0, De0, Vmin0\Итог')  # создание папки для результатов
np.savetxt(r'Re_p 0, De0, Vmin0\Итог\table_dd.txt', dd)  # сохранение таблиц
np.savetxt(r'Re_p 0, De0, Vmin0\Итог\table_MDC.txt', MDC)
np.savetxt(r'Re_p 0, De0, Vmin0\Итог\table_De.txt', De)
np.savetxt(r'Re_p 0, De0, Vmin0\Итог\table_Re.txt', Re)
three_d_plot(dd, 'dd(q, n).png')  # построение поверхностей
three_d_plot(MDC, 'MDC(q, n).png')
three_d_plot(De, 'De(q, n).png')
three_d_plot(Re, 'Re(q, n).png')

print(np.argmin(dd))
print(np.min(np.abs(De)))
print(np.argmin(MDC))
print(np.min(Re))


import os
import re
import urllib

from django import template
from django.conf import settings
from django.contrib.flatpages.models import FlatPage
from django.contrib.sites.models import Site
from django.core.files.storage import default_storage
from django.db.models.loading import get_model, get_models
from django.db.models.query import QuerySet
from django.template.loader import render_to_string
from django.utils.hashcompat import md5_constructor
from django.utils.safestring import mark_safe

from djutils.constants import SYNTAX_HIGHLIGHT_RE
from djutils.decorators import memoize
from djutils.db.managers import PublishedManager
from djutils.utils.highlighter import highlight
from djutils.utils.images import resize as img_resize

register = template.Library()

def get_fields_for_formset(formset, fields):
    """
    Utility method to grab fields that should be displayed for a FormSet
    """
    if fields is None:
        return formset.empty_form.visible_fields()
    else:
        return [
            f for f in formset.empty_form.visible_fields() \
                if f.name in fields.split(',')
        ]

@register.filter
def formset_empty_row(formset, fields=None):
    """
    Render the 'empty row' of a FormSet in the template.

    Usage::

        {{ formset|formset_empty_row:"title,pub_date,status" }}
    """
    return render_to_string('djutils/formset-empty-row.html', {
        'formset': formset,
        'form': formset.empty_form,
        'fields': get_fields_for_formset(formset, fields),
    })

@register.filter
def formset_add_row(formset, colspan=None):
    """
    Render the 'add another row' row for a formset - optional param is the
    colspan, which should be the number of visible fields + 1

    Usage::

        {{ formset|formset_add_row }}
    """
    if colspan is None:
        fields = formset.empty_form.visible_fields()
        colspan = len(fields) + 1 # add one for the 'remove' link

    return render_to_string('djutils/formset-add-row.html', {
        'formset': formset,
        'colspan': colspan,
    })

@register.filter
def formset_forms(formset, fields=None):
    """
    Render the forms in a formset as a series of <tr> elements, optional
    param is which fields should be displayed

    Usage::

        {{ formset|forms:"title,pub_date,status" }}
    """
    fields = get_fields_for_formset(formset, fields)
    col_span = len(fields) + 1 # adding one for the 'remove' link

    return mark_safe(render_to_string('djutils/formset-forms.html', {
        'formset': formset,
        'fields': [f.name for f in fields],
        'col_span': col_span,
    }).strip())

@register.filter
def formset_header_row(formset, fields=None):
    """
    Render the header row for a FormSet as a series of <th> elements, optional
    param is which fields should be displayed

    Usage::

        {{ formset|formset_header_row:"title,pub_date,status" }}
    """
    return render_to_string('djutils/formset-header-row.html', {
        'formset': formset,
        'fields': get_fields_for_formset(formset, fields)
    })

@register.filter
def dynamic_formset(formset, fields=None):
    """
    Wraps up all of the formset_ filters to generate a complete dynamic
    FormSet for the given fields.

    Usage::

        {{ formset|dynamic_formset }}
        {{ formset|dynamic_formset:"field1,field2,etc" }}
    """
    form_fields = get_fields_for_formset(formset, fields)
    col_span = len(form_fields) + 1 # adding one for the 'remove' link

    return render_to_string('djutils/formset-dynamic.html', {
        'formset': formset,
        'fields': fields,
        'col_span': col_span,
    })

@register.filter
def popular_tags(ctype, limit=None):
    """
    Given a string representation of a model (and assuming that tags is the
    reference to a TaggableManager) return a queryset of the most commonly
    used tags

    {% for tag in "media.photos"|popular_tags:5 %}
      ... do something with tag ...
    {% endfor %}
    """
    model = get_model(*ctype.split('.', 1))
    tag_qs = model.tags.most_common()
    if limit:
        tag_qs = tag_qs[:int(limit)]
    return tag_qs

def _model_to_queryset(model):
    if isinstance(model, QuerySet):
        return model

    if isinstance(model, basestring):
        model = get_model(*model.split('.'))

    if isinstance(model._default_manager, PublishedManager):
        return model._default_manager.published()
    else:
        return model._default_manager.all()

@register.filter
def latest(model_or_qs, date_field='id'):
    """
    Given a model string or a queryset, return the 'newest' instances based
    on the provided field (default is "id")

    {% for obj in "media.photos"|latest:"pub_date"|slice:":5" %}
      ... iterate over the 5 newest photos ...
    {% endfor %}
    """
    return _model_to_queryset(model_or_qs).order_by('-%s' % date_field)

@register.filter
def alpha(model, field='title'):
    """
    Given a model string or a queryset, return the instances ordered
    alphabetically on the provided field (default is "title")

    {% for obj in "blog.entries"|alpha:"title" %}
      ... iterate over blog entries alphabetically ...
    {% endfor %}
    """
    return _model_to_queryset(model).order_by('%s' % field)

@register.filter
def call_manager(model_or_obj, method):
    """
    Given a model or object, call a manager method

    {% for obj in "blog.entries"|call_manager:"published" %}
      ...
    {% endfor %}
    """
    # load up the model if we were given a string
    if isinstance(model_or_obj, basestring):
        model_or_obj = get_model(*model_or_obj.split('.'))

    # figure out the manager to query
    if isinstance(model_or_obj, QuerySet):
        manager = model_or_obj
    else:
        manager = model_or_obj._default_manager

    return getattr(manager, method)()

@register.filter
def tumble(models_and_dates, limit=5):
    """
    Generate a tumble for one or more models:

    {% for activity in "blog.entries:pub_date,media.photos:pub_date,github.commit:commit_date"|tumble:5 %}
      {{ show the 5 most recent things I did }}
    {% endfor %}
    """
    models_and_dates = models_and_dates.split(',')

    tumble = []

    for model_date in models_and_dates:
        model, date_field = model_date.split(':')
        queryset = _model_to_queryset(model).order_by('-%s' % date_field)[:limit]

        for obj in queryset:
            tumble.append((getattr(obj, date_field), obj))

    tumble.sort(reverse=True)

    return [r[1] for r in tumble]

@register.filter
def syntax_highlight(text):
    """
    Automatically syntax-highlight text between
    <code> tags.

    Usage:
    {{ entry.body|syntax_highlight|linebreaks }}
    """
    return mark_safe(re.sub(
        SYNTAX_HIGHLIGHT_RE,
        syntax_highlight_callback,
        text
    ))

def syntax_highlight_callback(match_object):
    data = match_object.group(4)
    data = data.replace('>', '>').replace('<', '<').replace('&', '&')
    return highlight(data)

@register.filter
def gravatar(email, size=80):
    """
    Return the url for a gravatar given an email address
    """
    return 'http://www.gravatar.com/avatar.php?%s' % urllib.urlencode({
        'gravatar_id': md5_constructor(email).hexdigest(),
        'size': str(size)
    })

@register.filter
def as_template(obj, template=None):
    """
    Render a model instance using the given template, defaulting to
        includes/app_label.module_name.html
    """
    if not template:
        template = 'includes/%s.html' % str(obj._meta)

    return mark_safe(render_to_string(template, {'object': obj}))

@memoize
def get_media_url_regex():
    media_url = settings.MEDIA_URL.rstrip('/')
    return re.compile(r'^%s/([^\s]+\.(jpg|gif|png))' % media_url, re.I)

@register.filter
def resize(url, width):
    """
    Return a url to a resized version of the given image at the url -- only
    works for files hosted on your MEDIA_ROOT --- CACHE THIS HEAVILY
    """
    width = int(width)

    regex = get_media_url_regex()

    # check to see if the url is one we can handle, otherwise just return the
    # full size image
    url_match = re.match(regex, url)
    if not url_match:
        return url

    # get the path, i.e. images/photos/kitties.jpg
    image_path = url_match.groups()[0]

    # create the entire url as it would be on site, minus the filename
    base_url, ext = url.rsplit('.', 1)

    # create the file path minus the extension
    base_path, ext = image_path.rsplit('.', 1)

    append = '_%s.%s' % (width, ext)

    new_path = '%s%s' % (base_path, append)

    if not default_storage.exists(new_path):
        # open the original to calculate its width and height
        img_resize(image_path, new_path, width)

    return '%s%s' % (base_url, append)

INLINE_REGEX = re.compile('<inline (?P<attrs>[^>]+)>')
KV_REGEX = re.compile('(\w+)=[\"\']?([^\"\']+)[\"\']?\s*')

@register.filter
def parse_inlines(text):
    """
    Replace <inline type="photo" id="val" class=""> with an embedded object
    rendered using the template as inlines/<model>.html
    """
    text = re.sub(INLINE_REGEX, inline_callback, text)
    return mark_safe(text)

def inline_callback(match_object):
    attrs = match_object.groupdict()['attrs']
    attr_dict = dict([item.groups() for item in KV_REGEX.finditer(attrs)])

    inline_type = attr_dict.pop('type')
    inline_pk = attr_dict.pop('id')

    if '.' in inline_type:
        model_class = get_model(*inline_type.split('.'))
    else:
        for model in get_models():
            if model._meta.module_name == inline_type:
                model_class = model
                break

        if not model_class:
            raise template.TemplateSyntaxError('Could not find model %s' % inline_type)

    try:
        obj = model_class._default_manager.get(pk=inline_pk)
    except model_class.DoesNotExist:
        raise template.TemplateSyntaxError('Unable to load %s with pk %s' % (inline_type, inline_pk))

    module_name = model_class._meta.module_name
    data = {
        'object': obj,
        'module_name': module_name,
        'attrs': attr_dict
    }

    return render_to_string('inlines/%s.html' % module_name, data)

@register.filter
def flatpage_for_url(url):
    try:
        return FlatPage.objects.get(url=url)
    except FlatPage.DoesNotExist:
        pass


from flask import Flask,jsonify,request
app=Flask(__name__)
info=[
    {
        'contact':'9987644456',
        'Name': 'Raju',
        'id': 1,
        'Done': False,

    },
    {
        'Contact': '9090992224',
        'Name': 'Rahul',
        'id':2,
        'Done': False,

    },
]

@app.route('/')
def hello_world():
    return "hello who ever you are"
@app.route('/add-data', methods='POST')
def add_task():
    if not request.json:
     return jsonify({
        'status': "error",
        'message': "please provide data"
        },400)
contact={
    'id': tasks[-1] ['id']+1,
    'contact':request.json.get('Contact', ""),
    'Name': request.json('Name'),
    'Done': False
}


from typing import Dict, List
import numpy as np
from transformers import BatchEncoding
from dataclasses import dataclass
from transformers import AutoTokenizer
import torch
import math
from torch.optim import Optimizer
from typing import Iterable, Tuple
from torch import nn
import random
import string

@dataclass
class DataCollatorForT5MLM:
    """
    [Copied from https://github.com/huggingface/transformers/blob/main/examples/flax/language-modeling/run_t5_mlm_flax.py]
    Data collator used for T5 span-masked language modeling.
    It is made sure that after masking the inputs are of length `data_args.max_seq_length` and targets are also of fixed length.
    For more information on how T5 span-masked language modeling works, one can take a look
    at the `official paper <https://arxiv.org/pdf/1910.10683.pdf>`__
    or the `official code for preprocessing <https://github.com/google-research/text-to-text-transfer-transformer/blob/master/t5/data/preprocessors.py>`__ .
    Args:
        tokenizer (:class:`~transformers.PreTrainedTokenizer` or :class:`~transformers.PreTrainedTokenizerFast`):
            The tokenizer used for encoding the data.
        noise_density (:obj:`float`):
            The probability with which to (randomly) mask tokens in the input.
        mean_noise_span_length (:obj:`float`):
            The average span length of the masked tokens.
        input_length (:obj:`int`):
            The expected input length after masking.
        target_length (:obj:`int`):
            The expected target length after masking.
        pad_token_id: (:obj:`int`):
            The pad token id of the model
        decoder_start_token_id: (:obj:`int):
            The decoder start token id of the model
    """

    tokenizer: AutoTokenizer
    noise_density: float
    mean_noise_span_length: float
    input_length: int
    target_length: int
    pad_token_id: int

    def __call__(self, examples: List[Dict[str, np.ndarray]]) -> BatchEncoding:
        # convert list to dict and tensorize input
        batch = BatchEncoding(
            {
                k: np.array([examples[i][k] for i in range(len(examples))])
                for k, v in examples[0].items()
            }
        )

        input_ids = batch["input_ids"]
        batch_size, expandend_input_length = input_ids.shape

        mask_indices = np.asarray(
            [
                self.random_spans_noise_mask(expandend_input_length)
                for i in range(batch_size)
            ]
        )
        labels_mask = ~mask_indices

        input_ids_sentinel = self.create_sentinel_ids(mask_indices.astype(np.int8))
        labels_sentinel = self.create_sentinel_ids(labels_mask.astype(np.int8))

        batch["input_ids"] = self.filter_input_ids(input_ids, input_ids_sentinel)
        batch["labels"] = self.filter_input_ids(input_ids, labels_sentinel)

        if batch["input_ids"].shape[-1] != self.input_length:
            raise ValueError(
                f"`input_ids` are incorrectly preprocessed. `input_ids` length is {batch['input_ids'].shape[-1]}, but"
                f" should be {self.input_length}."
            )

        if batch["labels"].shape[-1] != self.target_length:
            raise ValueError(
                f"`labels` are incorrectly preprocessed. `labels` length is {batch['labels'].shape[-1]}, but should be"
                f" {self.target_length}."
            )

        batch = {k: torch.from_numpy(v) for k, v in batch.items()}
        return batch

    def create_sentinel_ids(self, mask_indices):
        """
        Sentinel ids creation given the indices that should be masked.
        The start indices of each mask are replaced by the sentinel ids in increasing
        order. Consecutive mask indices to be deleted are replaced with `-1`.
        """
        start_indices = mask_indices - np.roll(mask_indices, 1, axis=-1) * mask_indices
        start_indices[:, 0] = mask_indices[:, 0]

        sentinel_ids = np.where(
            start_indices != 0, np.cumsum(start_indices, axis=-1), start_indices
        )
        sentinel_ids = np.where(
            sentinel_ids != 0, (len(self.tokenizer) - sentinel_ids), 0
        )
        sentinel_ids -= mask_indices - start_indices

        return sentinel_ids

    def filter_input_ids(self, input_ids, sentinel_ids):
        """
        Puts sentinel mask on `input_ids` and fuse consecutive mask tokens into a single mask token by deleting.
        This will reduce the sequence length from `expanded_inputs_length` to `input_length`.
        """
        batch_size = input_ids.shape[0]

        input_ids_full = np.where(sentinel_ids != 0, sentinel_ids, input_ids)
        # input_ids tokens and sentinel tokens are >= 0, tokens < 0 are
        # masked tokens coming after sentinel tokens and should be removed
        input_ids = input_ids_full[input_ids_full >= 0].reshape((batch_size, -1))
        input_ids = np.concatenate(
            [
                input_ids,
                np.full((batch_size, 1), self.tokenizer.eos_token_id, dtype=np.int32),
            ],
            axis=-1,
        )
        return input_ids

    def random_spans_noise_mask(self, length):
        """This function is copy of `random_spans_helper <https://github.com/google-research/text-to-text-transfer-transformer/blob/84f8bcc14b5f2c03de51bd3587609ba8f6bbd1cd/t5/data/preprocessors.py#L2682>`__ .

        Noise mask consisting of random spans of noise tokens.
        The number of noise tokens and the number of noise spans and non-noise spans
        are determined deterministically as follows:
        num_noise_tokens = round(length * noise_density)
        num_nonnoise_spans = num_noise_spans = round(num_noise_tokens / mean_noise_span_length)
        Spans alternate between non-noise and noise, beginning with non-noise.
        Subject to the above restrictions, all masks are equally likely.

        Args:
            length: an int32 scalar (length of the incoming token sequence)
            noise_density: a float - approximate density of output mask
            mean_noise_span_length: a number

        Returns:
            a boolean tensor with shape [length]
        """

        orig_length = length

        num_noise_tokens = int(np.round(length * self.noise_density))
        # avoid degeneracy by ensuring positive numbers of noise and nonnoise tokens.
        num_noise_tokens = min(max(num_noise_tokens, 1), length - 1)
        num_noise_spans = int(np.round(num_noise_tokens / self.mean_noise_span_length))

        # avoid degeneracy by ensuring positive number of noise spans
        num_noise_spans = max(num_noise_spans, 1)
        num_nonnoise_tokens = length - num_noise_tokens

        # pick the lengths of the noise spans and the non-noise spans
        def _random_segmentation(num_items, num_segments):
            """Partition a sequence of items randomly into non-empty segments.
            Args:
                num_items: an integer scalar > 0
                num_segments: an integer scalar in [1, num_items]
            Returns:
                a Tensor with shape [num_segments] containing positive integers that add
                up to num_items
            """
            mask_indices = np.arange(num_items - 1) < (num_segments - 1)
            np.random.shuffle(mask_indices)
            first_in_segment = np.pad(mask_indices, [[1, 0]])
            segment_id = np.cumsum(first_in_segment)
            # count length of sub segments assuming that list is sorted
            _, segment_length = np.unique(segment_id, return_counts=True)
            return segment_length

        noise_span_lengths = _random_segmentation(num_noise_tokens, num_noise_spans)
        nonnoise_span_lengths = _random_segmentation(
            num_nonnoise_tokens, num_noise_spans
        )

        interleaved_span_lengths = np.reshape(
            np.stack([nonnoise_span_lengths, noise_span_lengths], axis=1),
            [num_noise_spans * 2],
        )
        span_starts = np.cumsum(interleaved_span_lengths)[:-1]
        span_start_indicator = np.zeros((length,), dtype=np.int8)
        span_start_indicator[span_starts] = True
        span_num = np.cumsum(span_start_indicator)
        is_noise = np.equal(span_num % 2, 1)

        return is_noise[:orig_length]

def compute_input_and_target_lengths(inputs_length, noise_density, mean_noise_span_length):
    """This function is copy of `random_spans_helper <https://github.com/google-research/text-to-text-transfer-transformer/blob/84f8bcc14b5f2c03de51bd3587609ba8f6bbd1cd/t5/data/preprocessors.py#L2466>`__ .

    [Copied from https://github.com/huggingface/transformers/blob/main/examples/flax/language-modeling/run_t5_mlm_flax.py]
    Training parameters to avoid padding with random_spans_noise_mask.
    When training a model with random_spans_noise_mask, we would like to set the other
    training hyperparmeters in a way that avoids padding.
    This function helps us compute these hyperparameters.
    We assume that each noise span in the input is replaced by extra_tokens_per_span_inputs sentinel tokens,
    and each non-noise span in the targets is replaced by extra_tokens_per_span_targets sentinel tokens.
    This function tells us the required number of tokens in the raw example (for split_tokens())
    as well as the length of the encoded targets. Note that this function assumes
    the inputs and targets will have EOS appended and includes that in the reported length.

    Args:
        inputs_length: an integer - desired length of the tokenized inputs sequence
        noise_density: a float
        mean_noise_span_length: a float
    Returns:
        tokens_length: length of original text in tokens
        targets_length: an integer - length in tokens of encoded targets sequence
    """

    def _tokens_length_to_inputs_length_targets_length(tokens_length):
        num_noise_tokens = int(round(tokens_length * noise_density))
        num_nonnoise_tokens = tokens_length - num_noise_tokens
        num_noise_spans = int(round(num_noise_tokens / mean_noise_span_length))
        # inputs contain all nonnoise tokens, sentinels for all noise spans
        # and one EOS token.
        _input_length = num_nonnoise_tokens + num_noise_spans + 1
        _output_length = num_noise_tokens + num_noise_spans + 1
        return _input_length, _output_length

    tokens_length = inputs_length

    while _tokens_length_to_inputs_length_targets_length(tokens_length + 1)[0] <= inputs_length:
        tokens_length += 1

    inputs_length, targets_length = _tokens_length_to_inputs_length_targets_length(tokens_length)

    # minor hack to get the targets length to be equal to inputs length
    # which is more likely to have been set to a nice round number.
    if noise_density == 0.5 and targets_length > inputs_length:
        tokens_length -= 1
        targets_length -= 1
    return tokens_length, targets_length

class AdamWScale(Optimizer):
    """
    This AdamW implementation is copied from Huggingface.
    We modified it with Adagrad scaling by rms of a weight tensor

    Implements Adam algorithm with weight decay fix as introduced in [Decoupled Weight Decay
    Regularization](https://arxiv.org/abs/1711.05101).

    Parameters:
        params (`Iterable[nn.parameter.Parameter]`):
            Iterable of parameters to optimize or dictionaries defining parameter groups.
        lr (`float`, *optional*, defaults to 1e-3):
            The learning rate to use.
        betas (`Tuple[float,float]`, *optional*, defaults to (0.9, 0.999)):
            Adam's betas parameters (b1, b2).
        eps (`float`, *optional*, defaults to 1e-6):
            Adam's epsilon for numerical stability.
        weight_decay (`float`, *optional*, defaults to 0):
            Decoupled weight decay to apply.
        correct_bias (`bool`, *optional*, defaults to `True`):
            Whether or not to correct bias in Adam (for instance, in Bert TF repository they use `False`).
        no_deprecation_warning (`bool`, *optional*, defaults to `False`):
            A flag used to disable the deprecation warning (set to `True` to disable the warning).
    """

    def __init__(
        self,
        params: Iterable[nn.parameter.Parameter],
        lr: float = 1e-3,
        betas: Tuple[float, float] = (0.9, 0.999),
        eps: float = 1e-6,
        weight_decay: float = 0.0,
        correct_bias: bool = True,
    ):
        if lr < 0.0:
            raise ValueError(f"Invalid learning rate: {lr} - should be >= 0.0")
        if not 0.0 <= betas[0] < 1.0:
            raise ValueError(f"Invalid beta parameter: {betas[0]} - should be in [0.0, 1.0)")
        if not 0.0 <= betas[1] < 1.0:
            raise ValueError(f"Invalid beta parameter: {betas[1]} - should be in [0.0, 1.0)")
        if not 0.0 <= eps:
            raise ValueError(f"Invalid epsilon value: {eps} - should be >= 0.0")
        defaults = dict(lr=lr, betas=betas, eps=eps, weight_decay=weight_decay, correct_bias=correct_bias)
        super().__init__(params, defaults)

    @staticmethod
    def _rms(tensor):
        return tensor.norm(2) / (tensor.numel() ** 0.5)

    def step(self, closure=None):
        """
        Performs a single optimization step.

        Arguments:
            closure (`Callable`, *optional*): A closure that reevaluates the model and returns the loss.
        """
        loss = None
        if closure is not None:
            loss = closure()

        for group in self.param_groups:
            for p in group["params"]:
                if p.grad is None:
                    continue
                grad = p.grad.data
                if grad.is_sparse:
                    raise RuntimeError("Adam does not support sparse gradients, please consider SparseAdam instead")

                state = self.state[p]
                beta1, beta2 = group["betas"]

                # State initialization
                if len(state) == 0:
                    state["step"] = 0
                    # Exponential moving average of gradient values
                    state["exp_avg"] = torch.zeros_like(p.data)
                    # Exponential moving average of squared gradient values
                    state["exp_avg_sq"] = torch.zeros_like(p.data)

                exp_avg, exp_avg_sq = state["exp_avg"], state["exp_avg_sq"]

                state["step"] += 1

                # Decay the first and second moment running average coefficient
                # In-place operations to update the averages at the same time
                exp_avg.mul_(beta1).add_(grad, alpha=(1.0 - beta1))
                exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
                denom = exp_avg_sq.sqrt().add_(group["eps"])

                step_size = group["lr"]
                if group["correct_bias"]:  # No bias correction for Bert
                    bias_correction1 = 1.0 - beta1 ** state["step"]
                    bias_correction2 = 1.0 - beta2 ** state["step"]
                    step_size = step_size * math.sqrt(bias_correction2) / bias_correction1

                # /Adapt Step from Adafactor
                step_size = step_size * max(1e-3, self._rms(p.data))
                # /Adapt Step from Adafactor

                p.data.addcdiv_(exp_avg, denom, value=-step_size)

                # Just adding the square of the weights to the loss function is *not*
                # the correct way of using L2 regularization/weight decay with Adam,
                # since that will interact with the m and v parameters in strange ways.
                #
                # Instead we want to decay the weights in a manner that doesn't interact
                # with the m/v parameters. This is equivalent to adding the square
                # of the weights to the loss with plain (non-momentum) SGD.
                # Add weight decay at the end (fixed version)
                if group["weight_decay"] > 0.0:
                    p.data.add_(p.data, alpha=(-group["lr"] * group["weight_decay"]))

        return loss

def tokenize_function(examples, tokenizer, in_length):
    tokenizer_out = tokenizer(
        text=examples["text"],
        return_attention_mask=False,
    )

    input_ids = tokenizer_out["input_ids"]

    concatenated_ids = np.concatenate(input_ids)

    total_length = concatenated_ids.shape[0]
    total_length = (total_length // in_length) * in_length

    concatenated_ids = concatenated_ids[:total_length].reshape(-1, in_length)
    result = {"input_ids": concatenated_ids}

    return result

from transformers.data.data_collator import *
@dataclass
class DataCollatorForNI:
    tokenizer: PreTrainedTokenizerBase
    padding: Union[bool, str, PaddingStrategy] = True
    max_source_length: Optional[int] = None
    max_target_length: Optional[int] = None
    pad_to_multiple_of: Optional[int] = None
    label_pad_token_id: int = -100
    return_tensors: str = "pt"
    add_task_name: bool = False
    add_task_definition: bool = True
    num_pos_examples: int = 0
    num_neg_examples: int = 0
    add_explanation: bool = False
    tk_instruct: bool = False
    text_only: bool = False

    def __call__(self, batch, return_tensors=None):

        if return_tensors is None:
            return_tensors = self.return_tensors

        sources = []
        for instance in batch:
            if self.tk_instruct:
                all_valid_encodings = [
                    # instruction only
                    {
                        "add_task_name": False,
                        "add_task_definition": True,
                        "num_pos_examples": 0,
                        "num_neg_examples": 0,
                        "add_explanation": False,
                    },
                    # example only
                    {
                        "add_task_name": False,
                        "add_task_definition": False,
                        "num_pos_examples": 2,
                        "num_neg_examples": 0,
                        "add_explanation": False,
                    },
                    # instruction + pos examples
                    {
                        "add_task_name": False,
                        "add_task_definition": True,
                        "num_pos_examples": 2,
                        "num_neg_examples": 0,
                        "add_explanation": False,
                    },
                    # instruction + pos examples + neg examples
                    {
                        "add_task_name": False,
                        "add_task_definition": True,
                        "num_pos_examples": 2,
                        "num_neg_examples": 2,
                        "add_explanation": False,
                    },
                    # instruction + pos (w. explanation)
                    {
                        "add_task_name": False,
                        "add_task_definition": True,
                        "num_pos_examples": 2,
                        "num_neg_examples": 0,
                        "add_explanation": True,
                    },
                ]
                encoding_schema = random.choice(all_valid_encodings)
                add_task_name = encoding_schema["add_task_name"]
                add_task_definition = encoding_schema["add_task_definition"]
                num_pos_examples = encoding_schema["num_pos_examples"]
                num_neg_examples = encoding_schema["num_neg_examples"]
                add_explanation = encoding_schema["add_explanation"]
            else:
                add_task_name = self.add_task_name
                add_task_definition = self.add_task_definition
                num_pos_examples = self.num_pos_examples
                num_neg_examples = self.num_neg_examples
                add_explanation = self.add_explanation

            task_input = ""
            # add the input first.
            task_input += "Now complete the following example -\n"
            task_input += f"Input: {instance['Instance']['input'].strip()}"
            if not task_input[-1] in string.punctuation:
                task_input += "."
            task_input += "\n"
            task_input += "Output: "

            task_name = ""
            if add_task_name:
                task_name += instance["Task"] + ". "

            definition = ""
            if add_task_definition:
                if isinstance(instance["Definition"], list):
                    definition = (
                        "Definition: " + instance["Definition"][0].strip()
                    )
                else:
                    definition = "Definition: " + instance["Definition"].strip()
                if not definition[-1] in string.punctuation:
                    definition += "."
                definition += "\n\n"

            # try to add positive examples.
            pos_examples = []
            for idx, pos_example in enumerate(
                instance["Positive Examples"][:num_pos_examples]
            ):
                pos_example_str = f" Positive Example {idx+1} -\n"
                pos_example_str += f"Input: {pos_example['input'].strip()}"
                if not pos_example_str[-1] in string.punctuation:
                    pos_example_str += "."
                pos_example_str += "\n"
                pos_example_str += f" Output: {pos_example['output'].strip()}"
                if not pos_example_str[-1] in string.punctuation:
                    pos_example_str += "."
                pos_example_str += "\n"
                if add_explanation and "explanation" in pos_example:
                    pos_example_str += (
                        f" Explanation: {pos_example['explanation'].strip()}"
                    )
                    if not pos_example_str[-1] in string.punctuation:
                        pos_example_str += "."
                    pos_example_str += "\n"
                pos_example_str += "\n"
                if (
                    len(
                        self.tokenizer(
                            definition
                            + " ".join(pos_examples)
                            + pos_example_str
                            + task_input
                        )["input_ids"]
                    )
                    <= self.max_source_length
                ):
                    pos_examples.append(pos_example_str)
                else:
                    break

            # try to add negative examples.
            neg_examples = []
            for idx, neg_example in enumerate(
                instance["Negative Examples"][:num_neg_examples]
            ):
                neg_example_str = f" Negative Example {idx+1} -\n"
                neg_example_str += f"Input: {neg_example['input'].strip()}"
                if not neg_example_str[-1] in string.punctuation:
                    neg_example_str += "."
                neg_example_str += "\n"
                neg_example_str += f" Output: {neg_example['output'].strip()}"
                if not neg_example_str[-1] in string.punctuation:
                    neg_example_str += "."
                neg_example_str += "\n"
                if add_explanation and "explanation" in neg_example:
                    neg_example_str += (
                        f" Explanation: {neg_example['explanation'].strip()}"
                    )
                    if not neg_example_str[-1] in string.punctuation:
                        neg_example_str += "."
                    neg_example_str += "\n"
                neg_example_str += "\n"
                if (
                    len(
                        self.tokenizer(
                            definition
                            + " ".join(pos_examples)
                            + " ".join(neg_examples)
                            + neg_example_str
                            + task_input
                        )["input_ids"]
                    )
                    <= self.max_source_length
                ):
                    neg_examples.append(neg_example_str)
                else:
                    break

            source = (
                task_name
                + definition
                + "".join(pos_examples)
                + "".join(neg_examples)
                + task_input
            )
            tokenized_source = self.tokenizer(source)["input_ids"]
            if len(tokenized_source) <= self.max_source_length:
                sources.append(source)
            else:
                sources.append(
                    self.tokenizer.decode(
                        tokenized_source[: self.max_source_length],
                        skip_special_tokens=True,
                    )
                )

        if self.text_only:
            model_inputs = {"inputs": sources}
        else:
            model_inputs = self.tokenizer(
                sources,
                max_length=self.max_source_length,
                padding=self.padding,
                return_tensors=self.return_tensors,
                truncation=True,
                pad_to_multiple_of=self.pad_to_multiple_of,
            )

        if "output" in batch[0]["Instance"] and batch[0]["Instance"]["output"]:
            # Randomly select one reference if multiple are provided.
            labels = [random.choice(ex["Instance"]["output"]) for ex in batch]
            if self.text_only:
                model_inputs["labels"] = labels
            else:
                labels = self.tokenizer(
                    labels,
                    max_length=self.max_target_length,
                    padding=self.padding,
                    return_tensors=self.return_tensors,
                    truncation=True,
                    pad_to_multiple_of=self.pad_to_multiple_of,
                )
                label_mask = labels["attention_mask"].bool()
                model_inputs["labels"] = labels["input_ids"].masked_fill(
                    ~label_mask, self.label_pad_token_id
                )
        else:
            model_inputs["labels"] = None

        return model_inputs


from FuntionsForModule import FunctionsFor5HW
import pathlib

print('Уникальные значения в словарях')
profile_path = pathlib.Path('profile.txt')
profile_path.absolute()

with open(profile_path, 'r') as profile:
        user_name = profile.readline()

print('Привет,' + user_name + '!!!')

while True:
    Number_of_dicts = int(input('Введите количество словарей на проверку уникальности: '))

    if Number_of_dicts == 0:
        print('Не валидное значение, повторите ввод.')
        continue
    else:
        break

while True:
    Number_of_keys = int(input('Введите количество ключей: '))

    if Number_of_keys == 0:
        print('Не валидное значение, повторите ввод.')
        continue
    else:
        break

List_of_dicts, Key_of_dicts = [], []

for number in range(0, Number_of_keys):
    key = input('Введите название ключа: ').title()
    Key_of_dicts.append(key)

print(Key_of_dicts)

for number in range(1, Number_of_dicts + 1):
    def get_user_data(Keys = Key_of_dicts):
        user_data = {}
        for key in Keys:
            user_data[key] = ''
        return user_data

    User_data = get_user_data()

    for key in User_data:
        User_data[key] = input('Введите значение ключа ' + key + ' : ')

    List_of_dicts.append(User_data)

print(FunctionsFor5HW.unique_values(List_of_dicts, Key_of_dicts))


import requests
import time
from openpyxl import Workbook
from bs4 import BeautifulSoup

wb = Workbook()
sheet = wb.active
count = 1
for i in range(0,100,25):
    ret = requests.get('https://movie.douban.com/top250?start=%s&filter='%(i))
    bs = BeautifulSoup(ret.text,'html.parser')
    ol = bs.find(name='ol',attrs={'class':'grid_view'})
    li_list = ol.find_all(name='li')
    sheet.title = '好评电影'
    sheet['A1'].value = '序号'
    sheet['B1'].value = '电影名称'
    sheet['C1'].value = '电影评分'
    sheet['D1'].value = '电影链接'
    sheet['E1'].value = '电影图片'
    for li in li_list:
        name = li.find(name='span',attrs={'class':'title'})
        a = li.find(name='a')
        span = li.find(name='span',attrs={'class':'rating_num'})
        img = a.find(name='img')
        count += 1
        sheet['A%s'%(count)].value = count - 1
        sheet['B%s'%(count)].value = name.text
        sheet['C%s'%(count)].value = span.text
        sheet['D%s'%(count)].value = a['href']
        sheet['E%s'%(count)].value = img['src']
    time.sleep(1)
wb.save('好评电影.xlsx')


# %%
import os

os.environ["ACCELERATE_DISABLE_RICH"] = "1"
import sys
import pandas as pd
import torch as t
from torch import Tensor, optim
import torch.nn.functional as F
from torchvision import datasets
from torch.utils.data import DataLoader, Subset
from typing import Callable, Iterable, List, Tuple, Optional
import pytorch_lightning as pl
from pytorch_lightning.loggers import CSVLogger, WandbLogger
from dataclasses import dataclass
from pathlib import Path
import numpy as np
from IPython.display import display, HTML

# Make sure exercises are in the path
chapter = r"chapter0_fundamentals"
exercises_dir = Path(f"{os.getcwd().split(chapter)[0]}/{chapter}/exercises").resolve()
section_dir = exercises_dir / "part4_optimization"
if str(exercises_dir) not in sys.path:
    sys.path.append(str(exercises_dir))
os.chdir(section_dir)

from plotly_utils import bar, imshow
from part3_resnets.solutions import (
    IMAGENET_TRANSFORM,
    get_resnet_for_feature_extraction,
    plot_train_loss_and_test_accuracy_from_metrics,
)
from part4_optimization.utils import plot_fn, plot_fn_with_points
import part4_optimization.tests as tests

device = t.device("cuda" if t.cuda.is_available() else "cpu")

MAIN = __name__ == "__main__"

# %%
import einops

def pathological_curve_loss(x: t.Tensor, y: t.Tensor, angle=0):
    # Example of a pathological curvature. There are many more possible, feel free to experiment here!
    rotations = t.tensor(
        [
            [np.cos(angle), np.sin(angle)],
            [-np.sin(angle), np.cos(angle)],
        ],
        dtype=x.dtype,
    )
    xy = t.stack([x, y], dim=-1)
    x, y = einops.einsum(rotations, xy, "a b, ... b -> a ...")
    x_loss = t.tanh(x) ** 2 + 0.01 * t.abs(x)
    y_loss = t.sigmoid(y)
    return x_loss + y_loss

if MAIN:
    plot_fn(pathological_curve_loss)

# %%
def opt_fn_with_sgd(
    fn: Callable, xy: t.Tensor, lr=0.001, momentum=0.98, n_iters: int = 100
):
    """
    Optimize the a given function starting from the specified point.

    xy: shape (2,). The (x, y) starting point.
    n_iters: number of steps.
    lr, momentum: parameters passed to the torch.optim.SGD optimizer.

    Return: (n_iters, 2). The (x,y) BEFORE each step. So out[0] is the starting point.
    """
    results = []
    optim = t.optim.SGD([xy], lr=lr, momentum=momentum)

    for i in range(n_iters):
        optim.param_groups[0]["momentum"] = momentum ** (i / n_iters)
        results.append(xy.tolist())
        val = fn(*xy)
        val.backward()
        optim.step()
        optim.zero_grad()

    return t.tensor(results)

if MAIN:
    xy = t.tensor([2.5, 2.5], requires_grad=True)
    out = opt_fn_with_sgd(pathological_curve_loss, xy, lr=0.02, momentum=0.99)
    print(out[-1])

# %%
if MAIN:
    points = []

    optimizer_list = [
        (optim.SGD, {"lr": 0.1, "momentum": 0.0}),
        (optim.SGD, {"lr": 0.02, "momentum": 0.99}),
        (optim.SGD, {"lr": 0.06, "momentum": 0.5}),
        (optim.SGD, {"lr": 0.06, "momentum": 0.5}),
    ]
    optimizer_list += [
        (optim.SGD, {"lr": 0.4, "momentum": 0.9 + x / 1000}) for x in range(0, 101, 10)
    ]
    n_iters = 500

    for optimizer_class, params in optimizer_list:
        xy = t.tensor([2.5, 2.5], requires_grad=True)
        xys = opt_fn_with_sgd(
            pathological_curve_loss,
            xy=xy,
            lr=params["lr"],
            momentum=params["momentum"],
            n_iters=n_iters,
        )

        points.append((xys, optimizer_class, params))

    plot_fn_with_points(pathological_curve_loss, points=points[-9:])

for x in points:
    print(x[0][-1])

# %% Plot the evolution of momentum
class SGD:
    def __init__(
        self,
        params: Iterable[t.nn.parameter.Parameter],
        lr: float,
        momentum: float = 0.0,
        weight_decay: float = 0.0,
    ):
        """Implements SGD with momentum.

        Like the PyTorch version, but assume nesterov=False, maximize=False, and dampening=0
            https://pytorch.org/docs/stable/generated/torch.optim.SGD.html#torch.optim.SGD

        """
        self.params = list(
            params
        )  # turn params into a list (because it might be a generator)
        self.lr = lr
        self.momentum = momentum
        self.weight_decay = weight_decay

        self.running_momentums: List[Optional[Tensor]] = [None] * len(self.params)

    def zero_grad(self) -> None:
        for param in self.params:
            param.grad = None

    @t.inference_mode()
    def step(self) -> None:
        for i, param in enumerate(self.params):
            if self.weight_decay != 0:
                grad = param.grad + self.weight_decay * param
            else:
                grad = param.grad
            assert grad is not None

            if self.momentum != 0:
                if self.running_momentums[i] is None:
                    self.running_momentums[i] = grad  # clone?
                else:
                    self.running_momentums[i].set_(
                        self.momentum * self.running_momentums[i] + grad
                    )
                grad = self.running_momentums[i]
            param.add_(grad, alpha=-self.lr)

    def __repr__(self) -> str:
        return f"SGD(lr={self.lr}, momentum={self.mu}, weight_decay={self.lmda})"

if MAIN:
    tests.test_sgd(SGD)

# %%
class RMSprop:
    def __init__(
        self,
        params: Iterable[t.nn.parameter.Parameter],
        lr: float = 0.01,
        alpha: float = 0.99,
        eps: float = 1e-08,
        weight_decay: float = 0.0,
        momentum: float = 0.0,
    ):
        """Implements RMSprop.

        Like the PyTorch version, but assumes centered=False
            https://pytorch.org/docs/stable/generated/torch.optim.RMSprop.html

        """
        self.params = list(
            params
        )  # turn params into a list (because it might be a generator)
        self.lr = lr
        self.momentum = momentum
        self.weight_decay = weight_decay
        self.alpha = alpha
        self.eps = eps

        self.v = [t.zeros_like(param) for param in self.params]
        self.b = [t.zeros_like(param) for param in self.params]

    def zero_grad(self) -> None:
        for param in self.params:
            param.grad = None

    @t.inference_mode()
    def step(self) -> None:
        for param, v, b in zip(self.params, self.v, self.b):
            grad = param.grad
            if self.weight_decay != 0:
                grad = grad + self.weight_decay * param

            v.set_(self.alpha * v + (1 - self.alpha) * grad.square())

            if self.momentum > 0:
                b.set_(self.momentum * b + grad / (v.sqrt() + self.eps))
                param.add_(b, alpha=-self.lr)
            else:
                param.add_(grad / (v.sqrt() + self.eps), alpha=-self.lr)

    def __repr__(self) -> str:
        return f"RMSprop(lr={self.lr}, eps={self.eps}, momentum={self.mu}, weight_decay={self.lmda}, alpha={self.alpha})"

if MAIN:
    tests.test_rmsprop(RMSprop)

# %%
class Adam:
    def __init__(
        self,
        params: Iterable[t.nn.parameter.Parameter],
        lr: float = 0.001,
        betas: Tuple[float, float] = (0.9, 0.999),
        eps: float = 1e-08,
        weight_decay: float = 0.0,
    ):
        """Implements Adam.

        Like the PyTorch version, but assumes amsgrad=False and maximize=False
            https://pytorch.org/docs/stable/generated/torch.optim.Adam.html
        """
        self.params = list(params)
        self.lr = lr
        self.betas = betas
        self.eps = eps
        self.weight_decay = weight_decay

        self.m = [t.zeros_like(param) for param in self.params]
        self.v = [t.zeros_like(param) for param in self.params]

        self.t = 0

    def zero_grad(self) -> None:
        for param in self.params:
            param.grad = None

    @t.inference_mode()
    def step(self) -> None:
        self.t += 1
        for param, m, v in zip(self.params, self.m, self.v):
            grad = param.grad
            assert grad is not None
            if self.weight_decay != 0:
                grad = grad + self.weight_decay * param
            m.set_(self.betas[0] * m + (1 - self.betas[0]) * grad)
            v.set_(self.betas[1] * v + (1 - self.betas[1]) * grad.square())

            mhat = m / (1 - self.betas[0] ** self.t)
            vhat = v / (1 - self.betas[1] ** self.t)

            param.add_(mhat / (vhat.sqrt() + self.eps), alpha=-self.lr)

    def __repr__(self) -> str:
        return f"Adam(lr={self.lr}, beta1={self.beta1}, beta2={self.beta2}, eps={self.eps}, weight_decay={self.lmda})"

if MAIN:
    tests.test_adam(Adam)

# %%
class AdamW:
    def __init__(
        self,
        params: Iterable[t.nn.parameter.Parameter],
        lr: float = 0.001,
        betas: Tuple[float, float] = (0.9, 0.999),
        eps: float = 1e-08,
        weight_decay: float = 0.0,
    ):
        """Implements Adam.

        Like the PyTorch version, but assumes amsgrad=False and maximize=False
            https://pytorch.org/docs/stable/generated/torch.optim.AdamW.html
        """
        self.params = list(params)
        self.lr = lr
        self.betas = betas
        self.eps = eps
        self.weight_decay = weight_decay

        self.m = [t.zeros_like(param) for param in self.params]
        self.v = [t.zeros_like(param) for param in self.params]

        self.t = 0

    def zero_grad(self) -> None:
        for param in self.params:
            param.grad = None

    @t.inference_mode()
    def step(self) -> None:
        self.t += 1
        for param, m, v in zip(self.params, self.m, self.v):
            grad = param.grad
            assert grad is not None
            if self.weight_decay != 0:
                param *= 1 - self.lr * self.weight_decay

            m.set_(self.betas[0] * m + (1 - self.betas[0]) * grad)
            v.set_(self.betas[1] * v + (1 - self.betas[1]) * grad.square())

            mhat = m / (1 - self.betas[0] ** self.t)
            vhat = v / (1 - self.betas[1] ** self.t)

            param -= self.lr * mhat / (vhat.sqrt() + self.eps)

    def __repr__(self) -> str:
        return f"AdamW(lr={self.lr}, beta1={self.beta1}, beta2={self.beta2}, eps={self.eps}, weight_decay={self.lmda})"

if MAIN:
    tests.test_adamw(AdamW)
# %%

def opt_fn(
    fn: Callable,
    xy: t.Tensor,
    optimizer_class,
    optimizer_hyperparams: dict,
    n_iters: int = 100,
):
    """Optimize the a given function starting from the specified point.

    optimizer_class: one of the optimizers you've defined, either SGD, RMSprop, or Adam
    optimzer_kwargs: keyword arguments passed to your optimiser (e.g. lr and weight_decay)
    """
    results = []
    optim = optimizer_class([xy], **optimizer_hyperparams)

    for i in range(n_iters):
        results.append(xy.tolist())
        val = fn(*xy)
        val.backward()
        optim.step()
        optim.zero_grad()

    return t.tensor(results)

# %%
if MAIN:
    points = []

    optimizer_list = [
        (SGD, {"lr": 0.03, "momentum": 0.99}),
        (RMSprop, {"lr": 0.03, "alpha": 0.99, "momentum": 0.8}),
        (Adam, {"lr": 0.3, "betas": (0.99, 0.99), "weight_decay": 0.001}),
    ]
    optimizer_list += [(AdamW, optimizer_list[-1][1])]

    for optimizer_class, params in optimizer_list:
        xy = t.tensor([2.5, 2.5], requires_grad=True)
        xys = opt_fn(
            pathological_curve_loss,
            xy=xy,
            optimizer_class=optimizer_class,
            optimizer_hyperparams=params,
            n_iters=300,
        )
        points.append((xys, optimizer_class, params))

    plot_fn_with_points(pathological_curve_loss, points=points)

# %%
def get_cifar(subset: int = 1):
    cifar_trainset = datasets.CIFAR10(
        root="./data", train=True, download=True, transform=IMAGENET_TRANSFORM
    )
    cifar_testset = datasets.CIFAR10(
        root="./data", train=False, download=True, transform=IMAGENET_TRANSFORM
    )
    if subset > 1:
        cifar_trainset = Subset(
            cifar_trainset, indices=range(0, len(cifar_trainset), subset)
        )
        cifar_testset = Subset(
            cifar_testset, indices=range(0, len(cifar_testset), subset)
        )
    return cifar_trainset, cifar_testset

# %%

if MAIN:
    cifar_trainset, cifar_testset = get_cifar()

    imshow(
        cifar_trainset.data[:15],
        facet_col=0,
        facet_col_wrap=5,
        facet_labels=[cifar_trainset.classes[i] for i in cifar_trainset.targets[:15]],
        title="CIFAR-10 images",
        height=600,
    )

# %%
if MAIN:
    cifar_trainset, cifar_testset = get_cifar(subset=1)
    cifar_trainset_small, cifar_testset_small = get_cifar(subset=10)

@dataclass
class ResNetFinetuningArgs:
    batch_size: int = 64
    max_epochs: int = 3
    max_steps: int = 500
    optimizer: t.optim.Optimizer = t.optim.Adam
    learning_rate: float = 1e-3
    log_dir: str = os.getcwd() + "/logs"
    log_name: str = "day5-resnet"
    log_every_n_steps: int = 1
    n_classes: int = 10
    subset: int = 10
    trainset: Optional[datasets.CIFAR10] = None
    testset: Optional[datasets.CIFAR10] = None

    def __post_init__(self):
        if self.trainset is None or self.testset is None:
            self.trainset, self.testset = get_cifar(self.subset)
        self.trainloader = DataLoader(
            self.trainset, shuffle=True, batch_size=self.batch_size
        )
        self.testloader = DataLoader(
            self.testset, shuffle=False, batch_size=self.batch_size
        )
        self.logger = CSVLogger(save_dir=self.log_dir, name=self.log_name)

# %%
class LitResNet(pl.LightningModule):
    def __init__(self, args: ResNetFinetuningArgs):
        super().__init__()
        self.resnet = get_resnet_for_feature_extraction(args.n_classes)
        self.args = args

    def _shared_train_val_step(
        self, batch: Tuple[t.Tensor, t.Tensor]
    ) -> Tuple[t.Tensor, t.Tensor, t.Tensor]:
        """
        Convenience function since train/validation steps are similar.
        """
        imgs, labels = batch
        logits = self.resnet(imgs)
        return logits, labels

    def training_step(
        self, batch: Tuple[t.Tensor, t.Tensor], batch_idx: int
    ) -> t.Tensor:
        """
        Here you compute and return the training loss and some additional metrics for e.g.
        the progress bar or logger.
        """
        logits, labels = self._shared_train_val_step(batch)
        loss = F.cross_entropy(logits, labels)
        self.log("train_loss", loss)
        return loss

    def validation_step(self, batch: Tuple[t.Tensor, t.Tensor], batch_idx: int) -> None:
        """
        Operates on a single batch of data from the validation set. In this step you might
        generate examples or calculate anything of interest like accuracy.
        """
        logits, labels = self._shared_train_val_step(batch)
        classifications = logits.argmax(dim=1)
        accuracy = t.sum(classifications == labels) / len(classifications)
        self.log("accuracy", accuracy)

    def configure_optimizers(self):
        """
        Choose what optimizers and learning-rate schedulers to use in your optimization.
        """
        optimizer = self.args.optimizer(
            self.resnet.out_layers.parameters(), lr=self.args.learning_rate
        )
        return optimizer

# %%
if MAIN:
    args = ResNetFinetuningArgs(
        trainset=cifar_trainset_small, testset=cifar_testset_small
    )
    model = LitResNet(args)

    trainer = pl.Trainer(
        max_epochs=args.max_epochs,
        max_steps=args.max_steps,
        logger=args.logger,
        log_every_n_steps=args.log_every_n_steps,
    )
    trainer.fit(
        model=model, train_dataloaders=args.trainloader, val_dataloaders=args.testloader
    )

    metrics = pd.read_csv(f"{trainer.logger.log_dir}/metrics.csv")

    plot_train_loss_and_test_accuracy_from_metrics(
        metrics, "Feature extraction with ResNet34"
    )

# %%
def test_resnet_on_random_input(n_inputs: int = 3):
    indices = np.random.choice(len(cifar_trainset), n_inputs).tolist()
    classes = [cifar_trainset.classes[cifar_trainset.targets[i]] for i in indices]
    imgs = cifar_trainset.data[indices]
    with t.inference_mode():
        x = t.stack(list(map(IMAGENET_TRANSFORM, imgs)))
        logits: t.Tensor = model.resnet(x)
    probs = logits.softmax(-1)
    if probs.ndim == 1:
        probs = probs.unsqueeze(0)
    for img, label, prob in zip(imgs, classes, probs):
        display(HTML(f"
Classification probabilities (true class = {label})
"))
        imshow(
            img,
            width=200,
            height=200,
            margin=0,
            xaxis_visible=False,
            yaxis_visible=False,
        )
        bar(
            prob,
            x=cifar_trainset.classes,
            template="ggplot2",
            width=600,
            height=400,
            labels={"x": "Classification", "y": "Probability"},
            text_auto=".2f",
            showlegend=False,
        )

if MAIN:
    test_resnet_on_random_input()
# %%
import wandb

@dataclass
class ResNetFinetuningArgsWandb(ResNetFinetuningArgs):
    use_wandb: bool = True
    run_name: Optional[str] = None

    def __post_init__(self):
        super().__post_init__()
        if self.use_wandb:
            self.logger = WandbLogger(
                save_dir=self.log_dir, project=self.log_name, name=self.run_name
            )

# %%
if MAIN:
    args = ResNetFinetuningArgsWandb(
        trainset=cifar_trainset_small, testset=cifar_testset_small
    )
    model = LitResNet(args)

    trainer = pl.Trainer(
        max_epochs=args.max_epochs,
        max_steps=args.max_steps,
        logger=args.logger,
        log_every_n_steps=args.log_every_n_steps,
    )
    trainer.fit(
        model=model, train_dataloaders=args.trainloader, val_dataloaders=args.testloader
    )
    wandb.finish()
# %%
if MAIN:
    # YOUR CODE HERE - fill `sweep_config`
    sweep_config = dict(
        method="random",
        metric=dict(name="accuracy", goal="maximize"),
        parameters=dict(
            batch_size=dict(values=[32, 64, 128, 256]),
            max_epochs=dict(min=1, max=4),
            learning_rate=dict(max=0.1, min=0.0001, distribution="log_uniform_values"),
        ),
    )
    # FLAT SOLUTION END

    tests.test_sweep_config(sweep_config)
# %%
def train() -> None:
    args = ResNetFinetuningArgsWandb(
        trainset=cifar_trainset_small, testset=cifar_testset_small
    )
    args.batch_size = wandb.config["batch_size"]
    args.max_epochs = wandb.config["max_epochs"]
    args.learning_rate = wandb.config["learning_rate"]

    model = LitResNet(args)

    trainer = pl.Trainer(
        max_epochs=args.max_epochs,
        max_steps=args.max_steps,
        logger=args.logger,
        log_every_n_steps=args.log_every_n_steps,
    )
    trainer.fit(
        model=model, train_dataloaders=args.trainloader, val_dataloaders=args.testloader
    )
    wandb.finish()

# %%
if MAIN:
    wandb.init()
    sweep_id = wandb.sweep(sweep=sweep_config, project='day4-resnet-sweep')
    wandb.agent(sweep_id=sweep_id, function=train, count=3)
# %%


# Copyright 2016, Blur Studio
#
# This file is part of Simplex.
#
# Simplex is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simplex is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simplex.  If not, see <http://www.gnu.org/licenses/>.

# pylint: disable=invalid-name
from __future__ import division, absolute_import, print_function
import six
from six.moves import range, zip

try:
    import numpy as np
except ImportError:
    pass

INF = float("inf")
EPS = 1e-7

########################
# Mean Value Coords
########################

def _lerp(idx, corners, p):
    # If I'm here, I know that p lies on the line
    # between the corners at idx and idx+1.
    # Return the lerp value
    ip = (idx + 1) % len(corners)
    c1 = corners[idx]
    c2 = corners[ip]
    base = c1 - c2
    proj = np.dot(p - c2, base)
    b = proj / (base ** 2).sum()
    ret = np.zeros(len(corners))
    ret[idx] = b
    ret[ip] = 1.0 - b
    return ret

def mvc(corners, p, tol=EPS):
    """Get the Mean Value Coordinates of a point p in the polygon defined by corners

    Parameters
    ----------
    corners : [int, ...]
        A list of corner indices
    p : np.array
        The array of points
    tol : float
        A small tolerance value, defaulting to the global EPS

    Returns
    -------
    : list
        The normalized list of barycentric weights for this point in the polygon

    """
    spokes = corners - p
    spokeLens = (spokes * spokes).sum(axis=1)

    # Check if p is on top of a vertex
    for i, v in enumerate(spokeLens):
        if v < tol:
            bary = np.zeros(len(corners))
            bary[i] = 1.0
            return bary

    rspokes = np.roll(spokes, -1, axis=0)
    areas = np.cross(spokes, rspokes) * 0.5
    dots = (spokes * rspokes).sum(axis=1)

    for i, v in enumerate(areas):
        if v < tol and dots[i] < 0.0:
            return _lerp(i, corners, p)

    spokeLens = spokeLens ** 0.5
    rspokeLens = np.roll(spokeLens, -1)
    t = areas / (spokeLens * rspokeLens + dots)
    rawWeights = (np.roll(t, 1) + t) / spokeLens
    return rawWeights / sum(rawWeights)

def mmvc(rawFaces, points, samples, uvToFace, tol=EPS):
    """Multi-MeanValueCoords
        Get the MVC's of many points over many faces using numpy

    Parameters
    ----------
    rawFaces : [[int, ...], ...]
        A list of face index lists
    points : np.array
        A numpy array of uv points
    samples : np.array
        A numpy array of points to get the barycentric coords for
    uvToFace : {int: [int, ...], ...}
        A dictionary of UV indices to a list of Face Indices
    tol : float
        A small tolerance value, defaulting to the global EPS

    Returns
    -------
    : {fc: (wh, barys)}
        A dictionary of face counts to a tuple containing the indices of the face indices
        checked, and the barycentric coords

    """
    uvIdxs, faceIdxs = sorted(zip(*list(uvToFace.items())))
    uvIdxs = np.array(uvIdxs)
    faces = [rawFaces[fi] for fi in faceIdxs]
    faceLens = np.array([len(i) for i in faces])
    fcs = np.unique(faceLens)

    out = {}
    for fc in fcs:
        wh = np.where(faceLens == fc)[0]
        cIdxs = np.array([faces[i] for i in wh])
        barys = np.zeros(cIdxs.shape)
        qIdxs = uvIdxs[wh]
        out[fc] = (qIdxs, barys)  # edit barys in-place

        # cornerses is a [f, fc, 2] array
        # where f is num of faces, fc is verts per face, and 2 because uv is 2d
        # pts is [f, 2] array where f is num faces
        cornerses = points[cIdxs]
        pts = samples[qIdxs]

        # Get the "spokes" from the query point to the face corners
        # and get their squared lengths
        spokes = cornerses - pts[:, None, :]
        spokeLens2 = (spokes * spokes).sum(axis=-1)

        # Handle any samples that are directly on top of a corner
        # (where the spoke length is zero)
        zeros = np.any(spokeLens2 < tol, axis=1)
        onPoint = np.where(zeros)
        offPoint = np.where(~zeros)
        if onPoint[0].size:
            pIdxs = spokeLens2[onPoint].argmin(axis=1)
            barys[onPoint, pIdxs] = 1.0

        if not offPoint[0].size:
            continue

        # Ignore those points that have already been computed
        idxs = offPoint[0]
        spokes = spokes[offPoint]
        spokeLens2 = spokeLens2[offPoint]

        # Get the signed area of each triangle created by the spokes
        # and the dot product for the angle between each
        rspokes = np.roll(spokes, -1, axis=1)
        areas = np.cross(spokes, rspokes) * 0.5
        dots = (spokes * rspokes).sum(axis=-1)

        # Handle any samples that are directly on an edge between
        # two corners. (Where the triangle area is 0, and the
        # dot product is negative)
        aareas = abs(areas)
        zeros = aareas < tol
        ndots = dots < 0.0
        zn = zeros & ndots
        toLerp = np.any(zn, axis=-1)
        onEdge = np.where(toLerp)
        offEdge = np.where(~toLerp)
        if onEdge[0].size:
            subIdx = idxs[onEdge]
            pIdxs = zn[onEdge].argmax(axis=-1)
            xIdxs = (pIdxs + 1) % fc

            ppIdxs = cIdxs[subIdx, pIdxs]
            xpIdxs = cIdxs[subIdx, xIdxs]

            cp = pts[subIdx]
            pp = points[ppIdxs]
            xp = points[xpIdxs]

            bases = pp - xp
            diff = cp - xp
            proj = (bases * diff).sum(axis=-1)
            b = proj / (bases ** 2).sum(axis=1)
            mb = 1.0 - b
            barys[subIdx, pIdxs] = b
            barys[subIdx, xIdxs] = mb

        if not offEdge[0].size:
            continue

        idxs = idxs[offEdge]
        dots = dots[offEdge]
        areas = areas[offEdge]
        spokeLens2 = spokeLens2[offEdge]
        spokeLens = spokeLens2 ** 0.5
        rspokeLens = np.roll(spokeLens, -1, axis=1)

        t = areas / (spokeLens * rspokeLens + dots)
        rawWeights = (np.roll(t, -1, axis=1) + t) / spokeLens
        b = rawWeights / rawWeights.sum(axis=1)[..., None]
        barys[idxs] = b

    ret = {}
    for qIdxs, barys in six.itervalues(out):
        for qi, b in zip(qIdxs, barys):
            ret[qi] = (uvToFace[qi], b)

    return ret

########################
# Sweep algorithm
########################

def triArea(a, b, c):
    """Use the cross-ish-product to find the area of the triangle
        Depending on the winding, the area could be positive or negative

    Parameters
    ----------
    a : [float, float, float]
        The first point
    b : [float, float, float]
        The second point
    c : [float, float, float]
        The third point

    Returns
    -------
    : float
        The area of the triangle

    """
    return (a[0] * (c[1] - b[1]) + b[0] * (a[1] - c[1]) + c[0] * (b[1] - a[1])) / 2.0

def pointInTri(p, a, b, c, tol=EPS):
    """Check that the point is inside the triangle

    Parameters
    ----------
    p : [float, float, float]
        The point to check is inside the triangle
    a : [float, float, float]
        The first point of the triangle
    b : [float, float, float]
        The second point of the triangle
    c : [float, float, float]
        The third point of the triangle
    tol : float
        A small tolerance value, defaulting to the global EPS

    Returns
    -------
    : bool
        Whether the point is inside the given triangle

    """
    area = abs(triArea(a, b, c))
    chis = abs(triArea(p, b, c)) + abs(triArea(a, p, c)) + abs(triArea(a, b, p))
    return abs(area - chis) < tol

def sweep(qPoints, uvs, tris, pBar=None):
    """Get what triangle each query point is inside

    Runs a sweep-line algorithm to check for points in triangles.
    Imagine a uv layout, and a bunch of points on the layout.
    Now imagine a vertical line sweeping across the uv plane from the left
    to the right. The sorted u-values of certain properties then handled
    one-by-one.

    The algorithm keeps track of what triangles the vertical line is currently intersecting.

    * This is done by storing the min and max u-values for each triangle.
    * If the u-value encountered is the min for a tri, that tri is added to the list
    * If the u-value encountered is the max for a tri, that tri is removed from the list

    Then, each time a query point is encountered, it only has to check the intersection list

    Parameters
    ----------
    qPoints : np.array
        The points to get barycentric coordinates of
    uvs : np.array
        The UVs we're searching
    tris : np.array
        The Nx3 array of triangle indices
    pBar : QProgressDialog, optional
        An optional progress dialog

    Returns
    -------
    : {int: int, ...}
        A dictionary of out[pointIndex] = triIndex
    : set()
        A set containing the pointIndexes that weren't found in a triangle

    """
    tpts = uvs[tris]  # [tIdx, abc, xy]

    allmxs, allmns = tpts.max(axis=1), tpts.min(axis=1)
    ymxs, ymns = allmxs[:, 1], allmns[:, 1]
    mxs, mns = allmxs[:, 0], allmns[:, 0]
    qpx = qPoints[:, 0]
    qpSIdxs, mxSIdxs, mnSIdxs = np.argsort(qpx), np.argsort(mxs), np.argsort(mns)
    qpSIdx, mxSIdx, mnSIdx = 0, 0, 0
    qpIdx, mxIdx, mnIdx = qpSIdxs[qpSIdx], mxSIdxs[mxSIdx], mnSIdxs[mnSIdx]
    qp, mx, mn = qpx[qpIdx], mxs[mxIdx], mns[mnIdx]

    out = {}
    missing = set()

    if pBar is not None:
        pBar.setValue(0)
        allVals = len(qpSIdxs) + len(mxSIdxs) + len(mnSIdxs)
        pBar.setRange(0, allVals)
        pBar.setLabelText("Sweeping ...")
        from Qt.QtWidgets import QApplication

        QApplication.processEvents()

    # skip any triangles to the left of the first query point
    # The algorithm will stop once we hit the last query point
    skip = [qp > m for m in mxs]
    activeTris = [False for _ in mxs]  # For fast membership testing
    atSet = set()  # For fast iteration

    while True:
        if pBar is not None:
            cVal = qpSIdx + mnSIdx + mxSIdx
            if cVal % 1283 == 0:  # Just a random prime
                pBar.setValue(cVal)
                pBar.setLabelText("Sweeping ...\n{0}/{1}".format(cVal, allVals))
                QApplication.processEvents()
                if pBar.wasCanceled():
                    raise RuntimeError("Cancelled!")

        if mn <= mx and mn <= qp:
            # Always add triangles first if possible
            aTriIdx = mnSIdxs[mnSIdx]
            if not activeTris[aTriIdx] and not skip[aTriIdx]:
                activeTris[aTriIdx] = True
                atSet.add(aTriIdx)

            mnSIdx += 1
            if mnSIdx != len(mnSIdxs):
                mnIdx = mnSIdxs[mnSIdx]
                mn = mns[mnIdx]
            else:
                mn = mxs[mxSIdxs[-1]] + 1

        elif qp <= mx:
            # Check query points between adding and removing
            qPoint = qPoints[qpIdx]
            yv = qPoint[1]
            # A linear search is faster than more complex collections here
            for t in atSet:
                if ymns[t] <= yv <= ymxs[t]:
                    a, b, c = uvs[tris[t]]
                    if pointInTri(qPoint, a, b, c):
                        out[qpIdx] = t
                        break
            else:
                missing.add(qpIdx)

            qpSIdx += 1
            if qpSIdx == len(qpSIdxs):
                break
            qpIdx = qpSIdxs[qpSIdx]
            qp = qpx[qpIdx]

        else:
            # always remove triangles last
            aTriIdx = mxSIdxs[mxSIdx]
            if activeTris[aTriIdx] and not skip[aTriIdx]:
                activeTris[aTriIdx] = False
                atSet.remove(aTriIdx)

            mxSIdx += 1
            if mxSIdx != len(mxSIdxs):
                mxIdx = mxSIdxs[mxSIdx]
                mx = mxs[mxIdx]

    return out, missing

########################
# Triangulation
########################

def inBox(point, mxs, mns):
    """Check if a point is inside the bounding box

    Parameters
    ----------
    point : np.array
        A 3d point
    mxs : np.array
        The max values of all the coordinates per axis
    mns : np.array
        The min values of all the coordinates per axis

    Returns
    -------
    : bool
        Whether the point is in the given bounding box

    """
    return np.all(point <= mxs) and np.all(point >= mns)

def _isEar(a, b, c, polygon, tol=EPS):
    """Check if the points a,b,c of the polygon could be their own triangle """
    signedArea = triArea(a, b, c)

    # Check that the triange is wound the correct way.
    if signedArea > 0:
        return False

    # Check that the triangle has non-zero area
    # we already know the area is negative from above
    if -signedArea < tol:
        return False

    # Check that none of the other points in the polygon are contained in triangle
    for p in polygon:
        if p not in (a, b, c):
            if pointInTri(p, a, b, c, tol=tol):
                return False
    return True

def earclip(idxs, verts):
    """Simple earclipping algorithm
        For a polygon with n points it will return n-2 triangles.

    Parameters
    ----------
    idxs : [int, ...]
        The indices that make up a polygon
    verts : np.array
        All the UV positions

    Returns
    -------
    : [[int, int, int], ...]
        A list of triangle indices

    """
    earVerts = []
    tris = []
    idxs = list(idxs)
    polygon = [tuple(verts[i]) for i in idxs]

    numPts = len(polygon)
    for i in range(numPts):
        prev = polygon[i - 2]
        cur = polygon[i - 1]
        nxt = polygon[i]
        if _isEar(prev, cur, nxt, polygon):
            earVerts.append(cur)

    while earVerts and numPts >= 3:
        ear = earVerts.pop()
        i = polygon.index(ear)
        pi, ni = i - 1, (i + 1) % numPts

        prevPt = polygon[pi]
        nxtPt = polygon[ni]
        tris.append((idxs[pi], idxs[i], idxs[ni]))

        polygon.pop(i)
        idxs.pop(i)
        numPts -= 1
        if numPts > 3:
            prePrePt = polygon[i - 2]
            nxtNxtPt = polygon[(i + 1) % numPts]

            groups = [
                (prePrePt, prevPt, nxtPt, polygon),
                (prevPt, nxtPt, nxtNxtPt, polygon),
            ]

            for group in groups:
                p = group[1]
                if _isEar(*group):
                    if p not in earVerts:
                        earVerts.append(p)
                elif p in earVerts:
                    earVerts.remove(p)
    return tris

def triangulateUVs(faces, uvs):
    """Take a set of uvFaces and uv points, and triangulate it

    Parameters
    ----------
    faces : [[int, ...], ...]
        A uv face structure
    uvs : np.array
        The uv positions

    Returns
    -------
    : np.array
        A Nx3 array of uv indexes making triangles
    : [int, ...]
        A list where the index is the triangle index, and the value is the face index
    : {(int, int): int, ...}
        A dictionary of border edge pairs to border faces

    """
    uvs = np.array(uvs)
    triMap = []
    tris = []
    borderFaceMap = {}
    # Build the naiive triangulation
    # And get the borders while I'm looping
    for f, face in enumerate(faces):
        for i in range(2, len(face)):
            triMap.append(f)
            tris.append((face[0], face[i - 1], face[i]))

        for i in range(len(face)):
            ep = (face[i - 1], face[i])
            if ep[0] > ep[1]:
                ep = (ep[1], ep[0])
            if ep in borderFaceMap:
                borderFaceMap.pop(ep)
            else:
                borderFaceMap[ep] = f

    tris = np.array(tris)
    tuvs = uvs[tris]

    # Get the area using a 2d-pseudo-cross-product
    a = tuvs[:, 0] - tuvs[:, 1]
    b = tuvs[:, 2] - tuvs[:, 1]
    signedAreas = a[:, 0] * b[:, 1] - a[:, 1] * b[:, 0]

    # Things with negative area are wound backwards in this case
    negArea = np.where(signedAreas < 0)

    # Do we need to retriangulate any of the polys
    retri = sorted(set(triMap[i] for i in negArea[0]))
    if retri:
        tris = tris.tolist()
        tmpFaceMap = {}
        # tmpFaceMap is only good while we re-triangulate
        # it has to be re-built after
        for t, f in enumerate(triMap):
            tmpFaceMap.setdefault(f, []).append(t)

        for f in retri[::-1]:
            good = earclip(faces[f], uvs)
            tidxs = tmpFaceMap[f]
            tris[tidxs[0]: tidxs[-1] + 1] = good
            triMap[tidxs[0]: tidxs[-1] + 1] = [f] * len(good)
        tris = np.array(tris)

    return tris, triMap, borderFaceMap

########################
# UV Transferring
########################

MISSING = {}

def cooSparseMul(M, v):
    """Multply the sparse matrix by the vector

    Parameters
    ----------
    M : SparseMatrix
        My own sparse matrix representation
    v : np.array
        A vector

    Returns
    -------
    : np.array
        The result of the multiplication
    """
    # Using scipy is at least 2x faster than the
    # pure numpy implementation. But even that
    # is about 5x faster than using a dense matrix
    try:
        from scipy import sparse
    except ImportError:
        # No scipy, use numpy
        # The numpy universal function (ufunc) .add.at()
        # makes this possible without any python looping
        row, col, val, shape = M
        v = v.swapaxes(0, -2)
        vshape = val.shape + tuple([1] * (len(v) - 1))
        val = val.reshape(vshape)
        out = np.zeros(shape[:1] + v.shape[1:])
        np.add.at(out, row, val * v[col])
        out = out.swapaxes(0, -2)
    else:
        row, col, val, shape = M
        spM = sparse.coo_matrix((val, (row, col)), shape=shape)
        spM = sparse.csr_matrix(spM)
        if len(v.shape) == 3:
            out = np.array([spM.dot(frm) for frm in v])
        else:
            out = spM.dot(v)
    return out

def getUvCorrelation(samples, points, faces, tol=0.0001, handleMissing=True, pBar=None):
    """Get the per-face correlation of the sample points in uv space.

    Parameters
    ----------
    samples : np.array
        The sample points
    points : np.array
        The background points
    faces : [[int, ...], ...]
        The Face list
    tol : float
        A small tolerance value, defaulting to the global EPS
    handleMissing :
         (Default value = True)
    pBar : QProgressDialog, optional
        An optional progress dialog

    Returns
    -------
    : {idx: (idx, [float, ...]), ...}
        A dictionary from the sample index to a faceIdx/cornerWeights pair

    """
    tris, triMap, borderMap = triangulateUVs(faces, points)
    swept, missing = sweep(samples, points, tris, pBar=pBar)
    uvToFace = {uvI: triMap[tI] for uvI, tI in six.iteritems(swept)}
    # import __main__
    # __main__.__dict__.update(locals())
    # raise RuntimeError("STOPPIT")
    if pBar is not None:
        pBar.setValue(0)
        pBar.setRange(0, len(uvToFace))
        pBar.setLabelText("Calculate Mean Value Coords")
        from Qt.QtWidgets import QApplication

        QApplication.processEvents()

    # mvcDict = {}
    # for i, (uvIdx, faceIdx) in enumerate(uvToFace.iteritems()):
    # if pBar is not None:
    # pBar.setValue(i)
    # pBar.setLabelText("Calculate Mean Value Coords\n{0}/{1}".format(i, len(uvToFace)))
    # QApplication.processEvents()
    # uv = samples[uvIdx]
    # corners = points[faces[faceIdx]]
    # bary = mvc(corners, uv)
    # mvcDict[uvIdx] = (faceIdx, bary)

    mvcDict = mmvc(faces, points, samples, uvToFace)

    # find the closest border
    # Just use a brute-force search
    # ... for now

    if missing and handleMissing:
        if pBar is not None:
            pBar.setValue(0)
            pBar.setLabelText("Handle Missing")
            QApplication.processEvents()
        tol = tol ** 2
        bk = list(borderMap.keys())
        borders = np.array(bk)
        bStarts = points[borders[:, 0]]
        bEnds = points[borders[:, 1]]

        bDiff = bEnds - bStarts
        bLens2 = (bDiff * bDiff).sum(axis=1)
        for mIdx in missing:
            mp = samples[mIdx]
            dSquared = _mpCheck(bStarts, bDiff, bLens2, mp)
            minIdx = np.argmin(dSquared)
            if dSquared[mIdx] < tol:
                faceIdx = borderMap[bk[minIdx]]
                corners = points[faces[faceIdx]]
                bary = mvc(corners, mp)
                mvcDict[mIdx] = (faceIdx, bary)

    return mvcDict

def _mpCheck(a, d, dr2, pt):
    """Point to Multi-segment squared distance. Uses pre-computed values """
    lerp = ((pt - a) * d).sum(axis=1) / dr2
    lerp = np.clip(lerp, 0, 1)
    xy = (lerp[:, None] * d) + a
    _dxy = xy - pt
    return (_dxy * _dxy).sum(axis=1)

def getVertCorrelation(
    sUvFaces, sUvs, tVertFaces, tUvFaces, tUvs, tol=0.0001, pBar=None
):
    """Build the vertex position correlation between two meshes
        by looking through UV-space. Handle combining multiple uvs
        per vertex, and having samples that live outside the coverage

    Parameters
    ----------
    sUvFaces : [[int, ...], ...]
        The source UVFace list
    sUvs : np.array
        The source UV positions
    tVertFaces : [[int, ...], ...]
        The target vertex face list
    tUvFaces : [[int, ...], ...]
        The target UV face list
    tUvs : np.array
        The target UV positions
    tol : float
        A small tolerance value, defaulting to the global EPS
    pBar : QProgressDialog, optional
        An optional progress dialog

    Returns
    -------
    : {idx: (idx, [float, ...]), ...}
        A dictionary from the sample index to a faceIdx/cornerWeights pair
    """
    childUvToVert = {}
    cNumVerts = -1
    for vF, uvF in zip(tVertFaces, tUvFaces):
        for vIdx, uvIdx in zip(vF, uvF):
            cNumVerts = max(cNumVerts, vIdx)
            childUvToVert[uvIdx] = vIdx

    mvcUvDict = getUvCorrelation(tUvs, sUvs, sUvFaces, tol=tol, pBar=pBar)

    mvcVertDict = {}
    for uvIdx in range(len(tUvs)):
        if uvIdx not in mvcUvDict:
            continue
        mvcVertDict.setdefault(childUvToVert[uvIdx], []).append(mvcUvDict[uvIdx])

    missing = set(range(cNumVerts)) - six.viewkeys(mvcVertDict)
    if missing:
        import time

        v = time.time()
        print(
            "Missing correspondences found. Stored in uvTransfer.MISSING[{0}]".format(v)
        )
        MISSING[v] = missing

    return mvcVertDict

def applyTransfer(parVerts, parFaces, correlation, outputSize):
    """Given a vertex corelation, a driver, and driven points,
        Apply the driver deformation to the driven. This could be
        for one frame, or many frames

    Parameters
    ----------
    parVerts : np.array
        The "parent" vertex positions
    parFaces : [[int, ...], ...]
        The "parent" face list
    correlation : {int: [int, ...], ...}
        A dict correlatng the vertIdx to its possible correlations
    outputSize : (int, ...)
        A numpy output size tuple

    Returns
    -------
    : np.array
        The new vertex positions

    """
    if len(parVerts.shape) == 2:
        parVerts = parVerts[None, ...]

    rows, cols, vals = [], [], []
    for cVertIdx, corrPoss in six.iteritems(correlation):
        if len(corrPoss) == 1:
            pFaceIdx, bary = corrPoss[0]
        else:
            # pick the one with the highest sum-of-squares
            x = [sum(bary * bary) for _, bary in corrPoss]
            idx = x.index(max(x))
            pFaceIdx, bary = corrPoss[idx]

        rows.extend([cVertIdx] * len(bary))
        vals.extend(bary)
        cols.extend(parFaces[pFaceIdx])
    M = np.array(rows), np.array(cols), np.array(vals), (outputSize, parVerts.shape[-2])
    out = cooSparseMul(M, parVerts)
    return out

def uvTransfer(
    srcFaces,
    srcUvFaces,
    srcVerts,
    srcUvs,
    tarFaces,
    tarUvFaces,
    tarVerts,
    tarUvs,
    tol=0.0001,
    pBar=None,
):
    """A helper function that transfers pre-loaded data.
        The source data will be transferred onto the tar data

    Parameters
    ----------
    srcFaces : [[int, ...], ...]
        The source vertex face list
    srcUvFaces : [[int, ...], ...]
        The source uv face list
    srcUvs : np.array
        The source UV positions
    tarFaces : [[int, ...], ...]
        The target vertex face list
    tarUvFaces : [[int, ...], ...]
        The target uv face list
    tarUvs : np.array
        The Target UV Positions
    srcVerts : np.array
        The source Vertex positions
    tarVerts : np.array
        The target Vertex positions
    tol : float
        A small tolerance value, defaulting to the global EPS
    pBar : QProgressDialog, optional
        An optional progress dialog

    Returns
    -------
    : np.array
        The new target vert positions

    """
    corr = getVertCorrelation(
        srcUvFaces, srcUvs, tarFaces, tarUvFaces, tarUvs, tol=tol, pBar=pBar
    )
    if pBar is not None:
        pBar.setValue(0)
        pBar.setLabelText("Apply Transfer")
        from Qt.QtWidgets import QApplication

        QApplication.processEvents()

    return applyTransfer(srcVerts, srcFaces, corr, len(tarVerts))

def uvTransferLoad(
    srcPath, tarPath, srcUvSet="default", tarUvSet="default", tol=0.0001, pBar=None
):
    """Transfer the shape from the source to the target through uv space
        Return the data needed to write out the result

    Parameters
    ----------
    srcPath : str
        The source mesh path (obj, abc, or smpx)
    tarPath : str
        The target mesh path (obj, abc, or smpx)
    srcUvSet : str
        The name of the uv set to use on the source
    tarUvSet : str
        The name of the uv set to use on the target
    tol : float
        A small tolerance value, defaulting to the global EPS
    pBar : QProgressDialog, optional
        An optional progress dialog

    Returns
    -------
    : np.array
        The target vertex positions
    : list
        The target vertex faces
    : np.array
        The target uvs
    : list
        The target uv faces

    """
    from blur3d.api.classes.mesh import Mesh
    from blur3d.api.classes import abc

    if srcPath.endswith(".abc") or srcPath.endswith(".smpx"):
        src = Mesh.loadAbc(srcPath, ensureWinding=False)
        srcVerts = abc.getSampleArray(abc.getMesh(srcPath))
    elif srcPath.endswith(".obj"):
        src = Mesh.loadObj(srcPath, ensureWinding=False)
        srcVerts = np.array(src.vertArray)

    if tarPath.endswith(".abc"):
        tar = Mesh.loadAbc(tarPath, ensureWinding=False)
    elif tarPath.endswith(".obj"):
        tar = Mesh.loadObj(tarPath, ensureWinding=False)

    srcFaces = src.faceVertArray
    srcUvFaces = src.uvFaceMap[srcUvSet]
    srcUvs = np.array(src.uvMap[srcUvSet])

    tarFaces = tar.faceVertArray
    tarUvFaces = tar.uvFaceMap[tarUvSet]
    tarUvs = np.array(tar.uvMap[tarUvSet])
    oldTarVerts = np.array(tar.vertArray)
    tarVerts = uvTransfer(
        srcFaces,
        srcUvFaces,
        srcVerts,
        srcUvs,
        tarFaces,
        tarUvFaces,
        oldTarVerts,
        tarUvs,
        tol=tol,
        pBar=pBar,
    )

    return tarVerts, tarFaces, tarUvs, tarUvFaces

def uvTransferFiles(
    srcPath,
    tarPath,
    outAbcPath,
    srcUvSet="default",
    tarUvSet="default",
    tol=0.0001,
    pBar=None,
):
    """Transfer the shape from the source to the target through uv space
        and write out the result

    Parameters
    ----------
    srcPath : str
        The source mesh path (obj, abc, or smpx)
    tarPath : str
        The target mesh path (obj, abc, or smpx)
    outAbcPath : str
        The path to the output .abc file
    srcUvSet : str
        The name of the uv set to use on the source
    tarUvSet : str
        The name of the uv set to use on the target
    tol : float
        A small tolerance value, defaulting to the global EPS
    pBar : QProgressDialog, optional
        An optional progress dialog

    Returns
    -------

    """
    from blur3d.api.classes import abc

    tarVerts, tarFaces, tarUvs, tarUvFaces = uvTransferLoad(
        srcPath, tarPath, srcUvSet=srcUvSet, tarUvSet=tarUvSet, tol=tol, pBar=pBar
    )
    abc.buildAbc(outAbcPath, tarVerts, tarFaces, uvs=tarUvs, uvFaces=tarUvFaces)