"""
Experiment metadata parsing and validation.
"""
import os.path as osp
import json
from collections import defaultdict
from lrgasp import LrgaspException, gopen, iter_to_str, existing_datafile_name
from lrgasp.objDict import ObjDict
from lrgasp.defs import Repository, Species, Challenge, DataCategory, Sample, LibraryPrep, Platform, EXPERIMENT_JSON
from lrgasp.defs import validate_symbolic_ident, is_simulation, challenge_desc, get_challenge_samples
from lrgasp.defs import MODELS_GTF, READ_MODEL_MAP_TSV, DE_NOVO_RNA_FASTA, EXPRESSION_TSV
from lrgasp.metadata_validate import Field, check_from_defs, validate_url
from lrgasp.data_sets import get_lrgasp_rna_seq_metadata, get_run_type, run_md_desc, file_md_desc

fld_notes = Field("notes", allow_empty=True, optional=True)

experiment_fields = (
    Field("experiment_id", validator=validate_symbolic_ident),
    Field("challenge_id", Challenge),
    Field("description"),
    Field("species", Species),
    Field("data_category", DataCategory),
    Field("samples", list, element_dtype=Sample),
    Field("library_preps", list, element_dtype=LibraryPrep),
    Field("platforms", list, element_dtype=Platform),
    Field("libraries", list, element_dtype=str, validator=validate_symbolic_ident),
    Field("extra_libraries", list, optional=True, allow_empty=True, element_dtype=dict),
    Field("software", list, element_dtype=dict),
    fld_notes,
)

extra_libraries_fields = (
    Field("acc"),
    Field("repository", Repository),
    fld_notes,
)

experiment_software_fields = (
    Field("name"),
    Field("version"),
    Field("description", optional=True),
    Field("url", validator=validate_url),
    Field("config"),
    fld_notes,
)

valid_file_content = frozenset(["reads", "subreads", "nanopore_signal", "R2C2_subreads"])

def get_extra_libraries(experiment_md):
    """get the extra_libraries field, or empty if not specified"""
    return experiment_md.get("extra_libraries", ())

def find_dups(lst):
    dups = []
    found = set()
    for v in lst:
        if (v in found) and (v not in dups):
            dups.append(v)
        found.add(v)
    return sorted(dups)

def get_libraries_file_metadata(rna_seq_md, experiment_md):
    """collect metadata for all library files in experiment"""
    # exception if unknown acc
    return [rna_seq_md.get_file_by_acc(file_acc)
            for file_acc in experiment_md.libraries]

def library_validate(experiment_md, rna_seq_md, file_md):
    run_md = rna_seq_md.get_run_by_acc(file_md.run_acc)
    if run_md.species != experiment_md.species:
        raise LrgaspException(f"LRGASP RNA-Seq library '{run_md_desc(run_md)}' file '{file_md_desc(file_md)}' is for species '{run_md.species}' while experiment '{experiment_md.experiment_id}' specifies species as '{experiment_md.species}'")

    # this should never happen, as the data matrix should be restrict to types we allow
    if file_md.output_type not in valid_file_content:
        raise LrgaspException(f"File '{file_md_desc(file_md)}' of output_type '{file_md.output_type}' not support for LRGASP, "
                              "valid types are {}; please contact LRGASP project if this type of file is needed".format(", ".join(sorted(valid_file_content))))

def get_file_run_type(rna_seq_md, file_md):
    return get_run_type(rna_seq_md.get_run_by_acc(file_md.run_acc))

def get_run_types(rna_seq_md, file_mds):
    "group into sets of long and short RunType,"
    long_run_types = set()
    short_run_types = set()
    for file_md in file_mds:
        rt = get_file_run_type(rna_seq_md, file_md)
        if rt.platform == Platform.Illumina:
            short_run_types.add(rt)
        else:
            long_run_types.add(rt)
    return long_run_types, short_run_types

def get_run_type_descs(rna_seq_md, file_mds):
    "produce list of string descriptions of file acc to run types for error messages"

    def _mk_desc(file_md, done):
        done.add(file_md.file_acc)
        if file_md.get("paired_with") is None:
            acc_desc = file_md.file_acc
        else:
            acc_desc = file_md.file_acc + ", " + file_md.paired_with
            done.add(file_md.paired_with)
        rt = get_file_run_type(rna_seq_md, file_md)
        return f"{acc_desc}: {rt}"

    descs = []
    done = set()  # to avoid duplicating due to paired_with
    for file_md in file_mds:
        if file_md.file_acc not in done:
            descs.append(_mk_desc(file_md, done))
    return descs

def _validate_sample_compat(rna_seq_md, experiment_md, run_type):
    valid_samples = get_challenge_samples(experiment_md.challenge_id)
    if run_type.sample not in valid_samples:
        raise LrgaspException(f"sample '{run_type.sample}' is not valid for challenge '{experiment_md.challenge_id}',"
                              " expected one of {}".format(iter_to_str(valid_samples)))

def _validate_samples_compat(rna_seq_md, experiment_md, run_types):
    """check if samples uses are compatible with challenge"""
    for run_type in run_types:
        _validate_sample_compat(rna_seq_md, experiment_md, run_type)

def _validate_data_category_compat(rna_seq_md, experiment_md, long_run_types, short_run_types, file_mds):  # noqa: C901
    """compatibility between data category and libraries in an experiments;"""
    data_category = experiment_md.data_category

    def _run_type_err_msg():
        return ("from specified library files:\n    " +
                "\n    ". join(get_run_type_descs(rna_seq_md, file_mds)))

    def _validate_short_only():
        if len(long_run_types) > 0:
            raise LrgaspException(f"{data_category} experiments must not use long-read platforms, found {len(long_run_types)}, " + _run_type_err_msg())
        if len(short_run_types) != 1:
            raise LrgaspException(f"{data_category} experiments must use one and only one short-read library/platform, found {len(short_run_types)}, " + _run_type_err_msg())

    def _validate_long_only():
        if len(long_run_types) != 1:
            raise LrgaspException(f"{data_category} experiments must use one and only one long-read library/platform, found {len(long_run_types)}, " + _run_type_err_msg())
        if len(short_run_types) != 0:
            raise LrgaspException(f"{data_category} experiments must not use short-read platforms, " + _run_type_err_msg())

    def _validate_long_short():
        if len(long_run_types) != 1:
            raise LrgaspException(f"{data_category} experiments must use one and only one long-read library/platform, found {len(long_run_types)}, " + _run_type_err_msg())
        if len(short_run_types) != 1:
            raise LrgaspException(f"{data_category} experiments must use one and only one short-read library/platform, found {len(short_run_types)}, " + _run_type_err_msg())
        if list(short_run_types)[0].sample != list(long_run_types)[0].sample:
            raise LrgaspException(f"{data_category} experiments must use the same sample for long and short libraries, got " +
                                  _run_type_err_msg())

    def _validate_long_genome():
        if experiment_md.challenge_id != Challenge.iso_detect_de_novo:
            raise LrgaspException(f"{data_category} only allowed for {challenge_desc(Challenge.iso_detect_de_novo)}")
        _validate_long_only()

    def _validate_freestyle():
        if len(experiment_md.libraries) == 0:
            raise LrgaspException(f"{data_category} experiments must use some LRGASP RNA-Seq libraries")
        # simulation not allowed
        for file_md in file_mds:
            if is_simulation(rna_seq_md.get_run_by_file_acc(file_md.file_acc).sample):
                raise LrgaspException(f"{data_category} experiments may not use simulation data '{file_md_desc(file_md)}'")

    if data_category == DataCategory.short_only:
        _validate_short_only()
    elif data_category == DataCategory.long_only:
        _validate_long_only()
    elif data_category == DataCategory.long_short:
        _validate_long_short()
    elif data_category == DataCategory.long_genome:
        _validate_long_genome()
    elif data_category == DataCategory.freestyle:
        _validate_freestyle()
    else:
        raise LrgaspException("bug")

def _validate_library_category_compat(rna_seq_md, experiment_md, file_mds, run_types):
    def _match_err(fld_name, fld_value, expect_value):
        raise LrgaspException(f"experiment '{experiment_md.experiment_id}' field '{fld_name}', '{iter_to_str(fld_value)}', must match"
                              f" '{fld_name}' for all library files, got '{iter_to_str(expect_value)}', "
                              "from specified library files:\n    " +
                              "\n    ". join(get_run_type_descs(rna_seq_md, file_mds)))

    run_samples = set([rt.sample for rt in run_types])
    if set(run_samples) != set(experiment_md.samples):
        _match_err("samples", experiment_md.samples, run_samples)
    run_library_preps = set([rt.library_prep for rt in run_types])
    if set(run_library_preps) != set(experiment_md.library_preps):
        _match_err("library_preps", experiment_md.library_preps, run_library_preps)
    run_platforms = set([rt.platform for rt in run_types])
    if run_platforms != set(experiment_md.platforms):
        _match_err("platform", experiment_md.platforms, run_platforms)

def _validate_library_compat(rna_seq_md, experiment_md, file_mds):
    """compatibility between challenge, data category and libraries in an experiments;"""
    long_run_types, short_run_types = get_run_types(rna_seq_md, file_mds)
    run_types = long_run_types | short_run_types
    _validate_samples_compat(rna_seq_md, experiment_md, run_types)
    _validate_data_category_compat(rna_seq_md, experiment_md, long_run_types, short_run_types, file_mds)
    _validate_library_category_compat(rna_seq_md, experiment_md, file_mds, run_types)

def _validate_paired_end(rna_seq_md, file_mds):
    "make sure both paired ends are there"
    paired_file_mds = [fm for fm in file_mds if fm.paired_end is not None]
    paired_acc = frozenset([fm.file_acc for fm in paired_file_mds])
    for file_md in paired_file_mds:
        if file_md.paired_with not in paired_acc:
            raise LrgaspException(f"'{file_md_desc(file_md)}' is a paired-end experiment, however the paired file '{file_md.paired_with}' is not specified in experiment_md.libraries")

def get_runs_replicates(rna_seq_md, file_mds):
    "get dict of runs to set of replicates for all libraries"
    runs_replicates = defaultdict(set)
    for file_md in file_mds:
        runs_replicates[file_md.run_acc].add(file_md.biological_replicate_number)
    runs_replicates.default_factory = None
    return runs_replicates

def _validate_replicates(rna_seq_md, experiment_md, file_mds):
    """validate that all replicates are used in an experiment"""
    runs_replicates = get_runs_replicates(rna_seq_md, file_mds)

    def _get_run_file_desc(run_md):
        """generate list of possible files"""
        rep_file_descs = []
        for rep in run_md.replicates:
            for f in rep.files:
                rep_file_descs.append(f"{f.run_acc} {f.file_acc} {f.biological_replicate_number} {f.file_type}")
        return rep_file_descs

    def _check_run(run_acc, rep_set):
        run_md = rna_seq_md.get_run_by_acc(run_acc)
        if len(rep_set) != len(run_md.replicates):
            raise LrgaspException(f"experiment must use all replicates from run {run_acc}, available replicate files:\n    "
                                  + "\n    ".join(_get_run_file_desc(run_md)))

    for run_acc, rep_set in runs_replicates.items():
        _check_run(run_acc, rep_set)

def _validate_libraries(experiment_md):
    rna_seq_md = get_lrgasp_rna_seq_metadata()
    dups = find_dups(experiment_md.libraries)
    if len(dups) > 0:
        raise LrgaspException(f"duplicate accession in libraries: {dups}")

    # per library validation
    file_mds = get_libraries_file_metadata(rna_seq_md, experiment_md)
    for file_md in file_mds:
        library_validate(experiment_md, rna_seq_md, file_md)

    # cross-library file validations
    _validate_paired_end(rna_seq_md, file_mds)
    _validate_library_compat(rna_seq_md, experiment_md, file_mds)
    _validate_replicates(rna_seq_md, experiment_md, file_mds)

def _validate_extra_libraries(experiment_md):
    if experiment_md.data_category not in (DataCategory.long_short, DataCategory.freestyle):
        raise LrgaspException("experiment extra_libraries may only be specified for 'long_short' or 'freestyle' experiments")
    dups = find_dups([el.acc for el in experiment_md.extra_libraries])
    if len(dups) > 0:
        raise LrgaspException(f"duplicate accession in extra libraries: {dups}")
    for ilib in range(len(experiment_md.extra_libraries)):
        check_from_defs(f"experiment_md.extra_libraries[{ilib}]",
                        extra_libraries_fields, experiment_md.extra_libraries[ilib])

def _validate_software(experiment_md):
    for isoft in range(len(experiment_md.software)):
        check_from_defs(f"experiment_md.software[{isoft}]",
                        experiment_software_fields, experiment_md.software[isoft])

def experiment_validate(experiment_md):
    _validate_libraries(experiment_md)
    if len(get_extra_libraries(experiment_md)) > 0:
        _validate_extra_libraries(experiment_md)
    _validate_software(experiment_md)

def load(experiment_json):
    """load and validate experiment metadata"""
    try:
        with gopen(experiment_json) as fh:
            experiment_md = json.load(fh, object_pairs_hook=ObjDict)
    except json.decoder.JSONDecodeError as ex:
        raise LrgaspException(f"parse of experiment metadata (JSON) failed: {experiment_json}") from ex
    try:
        check_from_defs("experiment", experiment_fields, experiment_md)
        # add non-serialized fields after field check
        experiment_md.experiment_dir = None
        experiment_md.experiment_json = experiment_json
        experiment_validate(experiment_md)
    except LrgaspException as ex:
        raise LrgaspException(f"validation of experiment metadata failed: {experiment_json}") from ex

    return experiment_md

def load_from_entry(entry_md, experiment_id):
    """load and validation experiment metadata given a entry,
    add experiment_dir to metadata"""
    experiment_dir = osp.join(entry_md.entry_dir, experiment_id)
    experiment_json = osp.join(experiment_dir, EXPERIMENT_JSON)
    try:
        experiment_md = load(experiment_json)
        # add non-serialized fields after field check
        experiment_md.experiment_dir = osp.normpath(experiment_dir)
    except (LrgaspException, FileNotFoundError, ValueError) as ex:
        raise LrgaspException(f"error parse metadata for entry '{entry_md.entry_id}', experiment '{experiment_id}' (obtained from entry.json): {experiment_json}") from ex

    if experiment_md.experiment_id != osp.basename(experiment_dir):
        raise LrgaspException(f"experiment_id and directory name must be the same; '{experiment_md.experiment_id}' is in '{osp.basename(experiment_dir)}' ({experiment_dir})")

    return experiment_md

def get_models_gtf(experiment_md):
    """get path to models.gtf file or None if not valid for this experiment.
    Will not include .gz extension or check for existence."""
    if experiment_md.challenge_id in (Challenge.iso_detect_ref, Challenge.iso_quant):
        return existing_datafile_name(osp.join(experiment_md.experiment_dir, MODELS_GTF))
    else:
        return None

def get_read_model_map_tsv(experiment_md):
    """get path to read_model_map.gtf file or None if not valid for this
    experiment. Will not include .gz extension or check for existence."""
    if experiment_md.challenge_id in (Challenge.iso_detect_ref, Challenge.iso_detect_de_novo):
        return existing_datafile_name(osp.join(experiment_md.experiment_dir, READ_MODEL_MAP_TSV))
    else:
        return None

def get_rna_fasta(experiment_md):
    """get path to RNA fasta file or None if not valid for this
    experiment. Will not include .gz extension or check for existence."""
    if experiment_md.challenge_id is Challenge.iso_detect_de_novo:
        return existing_datafile_name(osp.join(experiment_md.experiment_dir, DE_NOVO_RNA_FASTA))
    else:
        return None

def get_expression_tsv(experiment_md):
    """get path to expression TSV file or None if not valid for this
    experiment. Will not include .gz extension or check for existence."""
    if experiment_md.challenge_id is Challenge.iso_quant:
        return existing_datafile_name(osp.join(experiment_md.experiment_dir, EXPRESSION_TSV))
    else:
        return None


from typing import Any
import torch
import torch.nn.functional as F
from torch.cuda.amp import autocast
from Exceptions import (
    DeviceCannotSupportHalfPrecisionException,
    DeviceChangingException,
    HalfPrecisionChangingException,
    NotEnoughDataExtimateF0,
)
from mods.log_control import VoiceChangaerLogger
from voice_changer.DiffusionSVC.inferencer.Inferencer import Inferencer
from voice_changer.DiffusionSVC.pitchExtractor.PitchExtractor import PitchExtractor

from voice_changer.RVC.embedder.Embedder import Embedder

from voice_changer.common.VolumeExtractor import VolumeExtractor
from torchaudio.transforms import Resample

from voice_changer.utils.Timer import Timer

logger = VoiceChangaerLogger.get_instance().getLogger()

class Pipeline(object):
    embedder: Embedder
    inferencer: Inferencer
    pitchExtractor: PitchExtractor

    index: Any | None
    big_npy: Any | None
    # feature: Any | None

    targetSR: int
    device: torch.device
    isHalf: bool

    def __init__(
        self,
        embedder: Embedder,
        inferencer: Inferencer,
        pitchExtractor: PitchExtractor,
        # index: Any | None,
        targetSR,
        device,
        isHalf,
        resamplerIn: Resample,
        resamplerOut: Resample
    ):
        self.inferencer = inferencer
        inferencer_block_size, inferencer_sampling_rate = inferencer.getConfig()
        self.hop_size = inferencer_block_size * 16000 / inferencer_sampling_rate  # 16000はオーディオのサンプルレート。16Kで処理
        self.inferencer_block_size = inferencer_block_size
        self.inferencer_sampling_rate = inferencer_sampling_rate

        self.volumeExtractor = VolumeExtractor(self.hop_size)
        self.embedder = embedder
        self.pitchExtractor = pitchExtractor

        self.resamplerIn = resamplerIn
        self.resamplerOut = resamplerOut

        logger.info("VOLUME EXTRACTOR" + str(self.volumeExtractor))
        logger.info("GENERATE INFERENCER" + str(self.inferencer))
        logger.info("GENERATE EMBEDDER" + str(self.embedder))
        logger.info("GENERATE PITCH EXTRACTOR" + str(self.pitchExtractor))

        self.targetSR = targetSR
        self.device = device
        self.isHalf = False

    def getPipelineInfo(self):
        volumeExtractorInfo = self.volumeExtractor.getVolumeExtractorInfo()
        inferencerInfo = self.inferencer.getInferencerInfo() if self.inferencer else {}
        embedderInfo = self.embedder.getEmbedderInfo()
        pitchExtractorInfo = self.pitchExtractor.getPitchExtractorInfo()
        return {"volumeExtractor": volumeExtractorInfo, "inferencer": inferencerInfo, "embedder": embedderInfo, "pitchExtractor": pitchExtractorInfo, "isHalf": self.isHalf}

    def setPitchExtractor(self, pitchExtractor: PitchExtractor):
        self.pitchExtractor = pitchExtractor

    @torch.no_grad()
    def extract_volume_and_mask(self, audio: torch.Tensor, threshold: float):
        volume_t = self.volumeExtractor.extract_t(audio)
        mask = self.volumeExtractor.get_mask_from_volume_t(volume_t, self.inferencer_block_size, threshold=threshold)
        volume = volume_t.unsqueeze(-1).unsqueeze(0)
        return volume, mask

    def exec(
        self,
        sid,
        audio,  # torch.tensor [n]
        sr,
        pitchf,  # np.array [m]
        feature,  # np.array [m, feat]
        f0_up_key,
        k_step,
        infer_speedup,
        silence_front,
        embOutputLayer,
        useFinalProj,
        protect=0.5,
        skip_diffusion=True,
    ):
        # print("---------- pipe line --------------------")
        with Timer("pre-process", False) as t:
            audio_t = torch.from_numpy(audio).float().unsqueeze(0).to(self.device)
            audio16k = self.resamplerIn(audio_t)
            volume, mask = self.extract_volume_and_mask(audio16k, threshold=-60.0)
            sid = torch.tensor(sid, device=self.device).unsqueeze(0).long()
            n_frames = int(audio16k.size(-1) // self.hop_size + 1)
        # print("[Timer::1: ]", t.secs)

        with Timer("pre-process", False) as t:
            # ピッチ検出
            try:
                # pitch = self.pitchExtractor.extract(
                #     audio16k.squeeze(),
                #     pitchf,
                #     f0_up_key,
                #     int(self.hop_size),    # 処理のwindowサイズ (44100における512)
                #     silence_front=silence_front,
                # )
                pitch = self.pitchExtractor.extract(
                    audio,
                    sr,
                    self.inferencer_block_size,
                    self.inferencer_sampling_rate,
                    pitchf,
                    f0_up_key,
                    silence_front=silence_front,
                )
                pitch = torch.tensor(pitch[-n_frames:], device=self.device, dtype=torch.float).unsqueeze(0).long()
            except IndexError as e:  # NOQA
                raise NotEnoughDataExtimateF0()

            # tensor型調整
            feats = audio16k.squeeze()
            if feats.dim() == 2:  # double channels
                feats = feats.mean(-1)
            feats = feats.view(1, -1)
        # print("[Timer::2: ]", t.secs)

        with Timer("pre-process", False) as t:

            # embedding
            with autocast(enabled=self.isHalf):
                try:
                    feats = self.embedder.extractFeatures(feats, embOutputLayer, useFinalProj)
                    if torch.isnan(feats).all():
                        raise DeviceCannotSupportHalfPrecisionException()
                except RuntimeError as e:
                    if "HALF" in e.__str__().upper():
                        raise HalfPrecisionChangingException()
                    elif "same device" in e.__str__():
                        raise DeviceChangingException()
                    else:
                        raise e
            feats = F.interpolate(feats.permute(0, 2, 1), size=int(n_frames), mode='nearest').permute(0, 2, 1)
        # print("[Timer::3: ]", t.secs)

        with Timer("pre-process", False) as t:
            # 推論実行
            try:
                with torch.no_grad():
                    with autocast(enabled=self.isHalf):
                        audio1 = (
                            torch.clip(
                                self.inferencer.infer(
                                    audio16k,
                                    feats,
                                    pitch.unsqueeze(-1),
                                    volume,
                                    mask,
                                    sid,
                                    k_step,
                                    infer_speedup,
                                    silence_front=silence_front,
                                    skip_diffusion=skip_diffusion
                                    ).to(dtype=torch.float32),
                                -1.0,
                                1.0,
                            )
                            * 32767.5
                        ).data.to(dtype=torch.int16)
            except RuntimeError as e:
                if "HALF" in e.__str__().upper():
                    print("11", e)
                    raise HalfPrecisionChangingException()
                else:
                    raise e
        # print("[Timer::4: ]", t.secs)

        with Timer("pre-process", False) as t:  # NOQA
            feats_buffer = feats.squeeze(0).detach().cpu()
            if pitch is not None:
                pitch_buffer = pitch.squeeze(0).detach().cpu()
            else:
                pitch_buffer = None

            del pitch, pitchf, feats, sid
            audio1 = self.resamplerOut(audio1.float())
        # print("[Timer::5: ]", t.secs)
        return audio1, pitch_buffer, feats_buffer


import os
import platform
from typing import Dict, List, Tuple, Union

import pyqtgraph as pg
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtGui import QPixmap

import constants
from data import load_json_data, calculate_extrema
from gauge import GaugeWidget
from user import User, get_user_percentage

def _create_title_label() -> QtWidgets.QLabel:
    """Creates and customized tha title label"""
    lbl_title = QtWidgets.QLabel(constants.TITLE)
    lbl_title.setWordWrap(True)
    lbl_title.setAlignment(QtCore.Qt.AlignCenter)
    QtGui.QFontDatabase.addApplicationFont(
        os.path.join(os.path.dirname(__file__), constants.TITLE_FONT_PATH)
    )
    lbl_title.setFont(QtGui.QFont(constants.TITLE_FONT_NAME, constants.TITLE_FONT_SIZE))
    return lbl_title

class MainWindow(QtWidgets.QMainWindow):
    """The main window of the GUI interface.

    This class defines the loop and interactive logic of the main window of the GUI application. It
    acts as a 'manager' class that summarizes all the logic in the project.

    Instance Attributes:
      - _id_group_labels: a list of QLabel which displays of the identity group names
      - _input_fields: all the input fields, which includes the ones allowing the user to enter
      - _plt_data: the widget on which the visualization of the processed data is displayed
      - _graphical_output: a list of graphical outputs based on the user's input, which are, in
                           order: User avatar (cartoon image), Gauge, Progress bar of user in
                           identity, Textual output
                       their identities, and the selection menus specifying the fields to output.
      - _user: an instance of the User class from the user module that stores the user's identities
               and the corresponding logic
      - anxiety_data: the data processed from the data module
      - extrema: the maximum and minimum anxiety score in the processed data

    Representation Invariants:
      - len(self._graphical_output) == 4
      - len(self._id_group_labels) == constants.NUMBER_OF_IDENTITIES
      - len(self._input_fields) == constants.NUMBER_OF_IDENTITIES + 2
      - len(self.anxiety_data) == constants.NUMBER_OF_IDENTITIES
      - self.extrema[0] < self.extrema[1]
    """
    _id_group_labels: List[QtWidgets.QLabel]
    _input_fields: List[Union[QtWidgets.QSpinBox, QtWidgets.QComboBox]]
    _plt_data: pg.GraphicsLayoutWidget
    _graphical_output: List[Union[QtWidgets.QLabel, GaugeWidget, QtWidgets.QProgressBar]]
    _user: User
    anxiety_data: List[Dict[str, float]]
    extrema: Tuple[float, float]

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        # -------------------------------------- Data Fields ---------------------------------------
        # Main Layout ---------------------------------------------------------------------------- |
        grid_central = QtWidgets.QGridLayout()
        wgt_central = QtWidgets.QWidget()
        status_bar = self.statusBar()
        # Identity input ------------------------------------------------------------------------- |
        grid_id = QtWidgets.QGridLayout()
        frm_id = QtWidgets.QFrame()
        self._id_group_labels = [QtWidgets.QLabel() for _ in range(constants.NUMBER_OF_IDENTITIES)]
        self._input_fields = [
            QtWidgets.QSpinBox(),  # 0 age
            QtWidgets.QComboBox(),  # 1  gender
            QtWidgets.QComboBox(),  # 2  education
            QtWidgets.QComboBox(),  # 3  employment
            QtWidgets.QComboBox(),  # 4  country
            QtWidgets.QComboBox(),  # 5  expat
            QtWidgets.QComboBox(),  # 6  martial
            QtWidgets.QComboBox(),  # 7  risk
            QtWidgets.QComboBox(),  # 8  situation
            QtWidgets.QSpinBox(),  # 9  ido_adults
            QtWidgets.QSpinBox(),  # 10 ido_kids
            QtWidgets.QComboBox(),  # 11 data visualization selection
            QtWidgets.QComboBox()  # 12 user visualization selection
        ]
        # Visualization -------------------------------------------------------------------------- |
        # Title of program
        lbl_title = _create_title_label()
        # Data plot
        self._plt_data = pg.GraphicsLayoutWidget()
        # Graphical outputs
        self._graphical_output = [
            QtWidgets.QLabel(),  # User avatar (cartoon image)
            GaugeWidget(),  # Gauge
            QtWidgets.QProgressBar(),  # User percentage in population
            QtWidgets.QLabel()  # Textual output
        ]
        # Data storage --------------------------------------------------------------------------- |
        self._user = User([18, 'Male', 'None', 'Not employed', 'Afghanistan', 'Yes', 'Single',
                           'Yes', 'Life carries on as usual', 0, 0])
        self.anxiety_data = load_json_data(constants.REAL_DATA_JSON_FILE)
        self.extrema = calculate_extrema(self.anxiety_data)

        # --------------------------------------- Behaviour ----------------------------------------
        # Main Window ---------------------------------------------------------------------------- |
        self.setWindowTitle(constants.TITLE)
        # Graphs ----------------------------------------------------------------------------------|
        self._plt_data.setToolTip(
            'Left click to pan, middle button scroll to zoom, right click for more options')
        self._graphical_output[0].setAlignment(QtCore.Qt.AlignCenter)
        self._graphical_output[2].setRange(0, 100)
        self._graphical_output[3].setWordWrap(True)
        # Identity input ------------------------------------------------------------------------- |
        self._setup_id_group_labels()
        self._setup_intractable_values()
        self._input_fields[11].setToolTip(
            'Select the identity group whose data you want to see.')
        self._input_fields[12].setToolTip(
            'Select the identity group of which you would like to compare yourself with.')
        # Status bar
        lbl_status_bar = QtWidgets.QLabel(
            ' * Hover your mouse over the components for more details.')
        lbl_status_bar.setFont(
            QtGui.QFont(constants.BODY_FONT_NAME, constants.BODY_FONT_SIZE))
        status_bar.addWidget(lbl_status_bar)

        # ------------------------------- Connect Signals and Slots --------------------------------
        self._setup_slots()

        # ----------------------------------------- Layout -----------------------------------------
        # Identity input ------------------------------------------------------------------------- |
        for i in range(constants.NUMBER_OF_IDENTITIES):
            grid_id.addWidget(self._id_group_labels[i], i, 0)
        for i in range(len(self._input_fields) - 2):
            grid_id.addWidget(self._input_fields[i], i, 1)
        frm_id.setLayout(grid_id)
        # Main Layout ---------------------------------------------------------------------------- |
        grid_central.addWidget(frm_id, 0, 0, 3, 1)
        grid_central.addWidget(lbl_title, 0, 1, 1, 2)
        grid_central.addWidget(self._graphical_output[0], 1, 1, 2, 2)
        grid_central.addWidget(self._input_fields[11], 3, 0, 1, 1)
        grid_central.addWidget(self._graphical_output[1], 3, 1, 3, 1)
        grid_central.addWidget(self._input_fields[12], 3, 2, 1, 1)
        grid_central.addWidget(self._plt_data, 4, 0, 2, 1)
        grid_central.addWidget(self._graphical_output[2], 4, 2, 1, 1)
        grid_central.addWidget(self._graphical_output[3], 5, 2, 1, 1)

        wgt_central.setLayout(grid_central)
        self.setCentralWidget(wgt_central)

        # ---------------------------------------- Geometry ----------------------------------------
        self._setup_geometry()

        # ------------------------------------------ Look ------------------------------------------
        self._setup_fonts()
        self._setup_color()

        self._plot_data()
        self._update_output()

    def _setup_id_group_labels(self) -> None:
        tool_tips = [
            'What is your age?',
            'What is your gender?',
            'What best describes your level of education?',
            'What is your employment status?',
            'What is your current country of residence?',
            'Are you currently living outside of what you consider your home country?',
            'What is your marital status?',
            'Are you or any of your close relations (family, close friends) in a high-risk group '
            'for Coronavirus? (e.g. pregnant, elderly or due to a pre-existing medical condition)',
            'What best describes your current situation?',
            'If in relative isolation, how many other adults are staying together in the same '
            'place as you are?',
            'If in relative isolation, how many children under the age of 12 are staying together '
            'in the same place as you are?',
        ]
        for i in range(constants.NUMBER_OF_IDENTITIES):
            self._id_group_labels[i].setText(constants.IDENTITY_NAMES[i])
            self._id_group_labels[i].setToolTip(tool_tips[i])

    def _setup_intractable_values(self) -> None:
        """Initialize the values for the intractable widgets"""
        # Input fields
        self._input_fields[0].setRange(18, 110)
        for i in range(1, 9):
            self._input_fields[i].addItems(constants.IDENTITY_GROUP_OPTIONS_LIST[i])
        self._input_fields[9].setRange(0, 110)
        self._input_fields[10].setRange(0, 110)
        # Graphs
        self._input_fields[11].addItems(constants.IDENTITY_NAMES)
        self._input_fields[12].addItems(constants.IDENTITY_NAMES)

    def _setup_fonts(self) -> None:
        """Set customized fonts to all the widgets"""
        QtGui.QFontDatabase.addApplicationFont(
            os.path.join(os.path.dirname(__file__), constants.BODY_FONT_PATH)
        )
        # Labels
        for i in range(constants.NUMBER_OF_IDENTITIES):
            self._id_group_labels[i].setFont(
                QtGui.QFont(constants.BODY_FONT_NAME, constants.BODY_FONT_SIZE))
        # Intractable
        for input_field in self._input_fields:
            input_field.setFont(QtGui.QFont(constants.BODY_FONT_NAME, constants.BODY_FONT_SIZE))
        # Textual output
        self._graphical_output[3].setFont(
            QtGui.QFont(constants.BODY_FONT_NAME, constants.BODY_FONT_SIZE))

    def _setup_color(self) -> None:
        """Setup color for all the widgets"""
        style_sheet = f'background-color : {constants.BACKGROUND_COLOR.name()}; ' \
                      f'color : {constants.FOREGROUND_COLOR.name()};'
        spi_style = f'background-color : {constants.WHITE.name()}; ' \
                    f'color : {constants.FOREGROUND_COLOR.name()};'
        combo_style = f'background-color : {constants.WHITE.name()}; ' \
                      f'color : {constants.FOREGROUND_COLOR.name()}; ' \
                      f'QComboBox::drop-down {{ background-color : {constants.WHITE.name()} }}'
        progress_style = f"""
            QProgressBar {{ text-align: center;
                            background-color : {constants.WHITE.name()};
                            border-radius : 10px;
                          }}
            QProgressBar::chunk {{ background-color: {constants.PLOT_FOREGROUND.name()};
                                   margin: 0.5px;
                                   border-bottom-left-radius: 10px;
                                   border-bottom-right-radius: 10px;
                                 }} """
        # Main window
        self.setStyleSheet(style_sheet)
        if platform.platform() != 'Linux':
            self._graphical_output[3].setStyleSheet('padding : 0px 10px 0px 10px;')
        # Interactive
        for input_field in self._input_fields[1:9] + self._input_fields[11:]:
            input_field.setStyleSheet(combo_style)
        for i in [0, 9, 10]:
            self._input_fields[i].setStyleSheet(spi_style)

        self._plt_data.setBackground(constants.BACKGROUND_COLOR)
        self._graphical_output[2].setStyleSheet(progress_style)

    def _setup_geometry(self) -> None:
        """Set up the sizes of the widgets"""
        self.setFixedSize(1080, 720)
        self._graphical_output[1].setMinimumWidth(250)
        self._graphical_output[1].setMinimumHeight(250)

        self._graphical_output[0].resize(300, 200)

        for i in range(constants.NUMBER_OF_IDENTITIES):
            self._id_group_labels[i].resize(
                150,
                self._id_group_labels[i].height()
            )

    def _setup_slots(self) -> None:
        """Connect components to slots"""
        # Update self._user
        self._input_fields[0].valueChanged.connect(
            lambda: self._user.set_age(self._input_fields[0].value()))
        self._input_fields[9].valueChanged.connect(
            lambda: self._user.set_isolation_adults(self._input_fields[9].value()))
        self._input_fields[10].valueChanged.connect(
            lambda: self._user.set_isolation_kids(self._input_fields[10].value()))
        for i in [0, 9, 10]:
            self._input_fields[i].valueChanged.connect(self._update_output)

        # WARNING: Due to how Qt works with its signals and lots, the following block WILL NOT WORK
        # IN A LOOP, as every 'i' in the expression would be stuck with 8 after the initial setup,
        # meaning that we would be stuck with the 'Current Situation' input. As such, every setting
        # value connection MUST NOT be set in a loop. PLEASE DO NOT SIMPLIFY INTO THE LOOP.
        self._input_fields[1].currentIndexChanged.connect(lambda: self._user.identity.__setitem__(
            constants.IDENTITY_NAMES[1], self._input_fields[1].currentText()))
        self._input_fields[2].currentIndexChanged.connect(lambda: self._user.identity.__setitem__(
            constants.IDENTITY_NAMES[2], self._input_fields[2].currentText()))
        self._input_fields[3].currentIndexChanged.connect(lambda: self._user.identity.__setitem__(
            constants.IDENTITY_NAMES[3], self._input_fields[3].currentText()))
        self._input_fields[4].currentIndexChanged.connect(lambda: self._user.identity.__setitem__(
            constants.IDENTITY_NAMES[4], self._input_fields[4].currentText()))
        self._input_fields[5].currentIndexChanged.connect(lambda: self._user.identity.__setitem__(
            constants.IDENTITY_NAMES[5], self._input_fields[5].currentText()))
        self._input_fields[6].currentIndexChanged.connect(lambda: self._user.identity.__setitem__(
            constants.IDENTITY_NAMES[6], self._input_fields[6].currentText()))
        self._input_fields[7].currentIndexChanged.connect(lambda: self._user.identity.__setitem__(
            constants.IDENTITY_NAMES[7], self._input_fields[7].currentText()))
        self._input_fields[8].currentIndexChanged.connect(lambda: self._user.identity.__setitem__(
            constants.IDENTITY_NAMES[8], self._input_fields[8].currentText()))
        for i in range(1, 9):
            self._input_fields[i].currentIndexChanged.connect(self._update_output)

        # Update visual
        self._input_fields[11].currentIndexChanged.connect(self._plot_data)
        self._input_fields[12].currentIndexChanged.connect(self._update_output)
        # self._cbo_data_graph.currentIndexChanged.connect(self._plot_data)
        # self._cbo_user_graph.currentIndexChanged.connect(self._update_output)

    def _plot_data(self) -> None:
        """Plots the data from the csv file"""
        self._plt_data.clear()  # clear current graph

        id_index = self._input_fields[11].currentIndex()

        bar_graph = pg.BarGraphItem(
            y=list(range(len(constants.IDENTITY_GROUP_OPTIONS_LIST[id_index]))),
            x0=0,
            width=list(self.anxiety_data[id_index].values()),
            height=0.75, brush=constants.PLOT_FOREGROUND
        )

        string_axis = pg.AxisItem(orientation='left')  # Textual x-axis
        string_axis.setTextPen((0, 0, 0, 255))
        string_axis.setTicks(
            [dict(enumerate(constants.IDENTITY_GROUP_OPTIONS_LIST[id_index])).items()])

        plot_item = self._plt_data.addPlot(axisItems={'left': string_axis})
        plot_item.getAxis("bottom").setTextPen((0, 0, 0, 255))
        # plot_item.setTitle(id_group)
        plot_item.addItem(bar_graph)

    def _draw_user_avatar(self) -> None:
        """Draws the user's cartoon character based on their input identity"""
        if self._user.identity[constants.IDENTITY_NAMES[4]] == 'Côte d’Ivoire':
            flag_name = 'Cote dIvoire'
        else:
            flag_name = self._user.identity[constants.IDENTITY_NAMES[4]]
        # set hair image name
        hair_name = \
            self._user.identity[constants.IDENTITY_NAMES[0]][:2] + \
            '_' + \
            self._user.identity[constants.IDENTITY_NAMES[1]][0]

        # set clothes image name
        # unemployed (not employed, student)
        if self._user.identity[constants.IDENTITY_NAMES[3]] in (constants.DEM_EMPLOYMENT[:2]):
            clothes_name = 'unemployed'
        # employed (part time, fill time, self-employed, ~retired~)
        else:
            clothes_name = 'employed'

        pxm_flag = QtGui.QImage(
            constants.os.path.join(constants.IMAGE_PATH, f'Flags/{flag_name}.png')).convertToFormat(
            QtGui.QImage.Format_ARGB32)
        pxm_hair = QtGui.QImage(
            constants.os.path.join(constants.IMAGE_PATH, f'Character/hair/{hair_name}.png'))
        pxm_face = QtGui.QImage(
            constants.os.path.join(constants.IMAGE_PATH, 'Character/face.png'))
        pxm_cloth = QtGui.QImage(
            constants.os.path.join(constants.IMAGE_PATH, f'Character/{clothes_name}.png'))
        pmx_edu = []

        index = constants.DEM_EDU.index(self._user.identity[constants.IDENTITY_NAMES[2]])
        # PhD
        if index > 5:
            pmx_edu.append(QtGui.QImage(
                constants.os.path.join(constants.IMAGE_PATH, 'Character/school4.png')))
        # college to ~
        if index > 4:
            pmx_edu.append(QtGui.QImage(
                constants.os.path.join(constants.IMAGE_PATH, 'Character/school3.png')))
        # 12 to some college to ~
        if index > 2:
            pmx_edu.append(QtGui.QImage(
                constants.os.path.join(constants.IMAGE_PATH, 'Character/school2.png')))
        # 6 to 9 to ~
        if index > 0:
            pmx_edu.append(QtGui.QImage(
                constants.os.path.join(constants.IMAGE_PATH, 'Character/school1.png')))

        painter = QtGui.QPainter()

        painter.begin(pxm_flag)
        # cloth
        painter.drawImage(0, 0, pxm_cloth)
        # retired vest
        if self._user.identity[constants.IDENTITY_NAMES[3]] == constants.DEM_EMPLOYMENT[-1]:
            painter.drawImage(0, 0, QtGui.QImage(
                constants.os.path.join(constants.IMAGE_PATH, 'Character/retired.png')))
            painter.drawImage(0, 0, pxm_face)
        # face
        painter.drawImage(0, 0, pxm_face)
        # hair
        painter.drawImage(0, 0, pxm_hair)
        # education
        for pmx_edu_comp in pmx_edu:
            painter.drawImage(0, 0, pmx_edu_comp)

        # risk group or under isolation
        if self._user.identity[constants.IDENTITY_NAMES[7]] != constants.RISK_GROUP[1] or \
                self._user.identity[constants.IDENTITY_NAMES[8]] in constants.DEM_ISOLATION[2:]:
            painter.drawImage(0, 0, QtGui.QImage(
                constants.os.path.join(constants.IMAGE_PATH, 'Character/mask.png')))

        painter.end()

        self._graphical_output[0].setPixmap(QPixmap.fromImage(pxm_flag))

    def _update_gauge(self, percentage: float) -> None:
        """Calculate the user's anxiety score, then update the gauge

        Preconditions:
          - 0 <= percentage <= 100
          """
        self._graphical_output[1].update_value(percentage)

    def _plot_user(self, id_percentage: float) -> None:
        """Plots the user's ranking

        Preconditions:
          - 0 <= id_percentage <= 100
          """
        self._graphical_output[2].setValue(int(id_percentage))

    def _display_textual_output(self, percentage: float,
                                id_percentage: float) -> None:
        """Display the textual output of the user's anxiety data

        Preconditions:
          - 0 <= percentage <= 100
          - 0 <= id_percentage <= 100
          """
        id_group = self._user.identity[self._input_fields[12].currentText()]

        textual_output = 'You are '
        if percentage < 50:
            textual_output = textual_output + 'less likely to be anxious than ' \
                                              f'<b>{100 - percentage:.2f}%</b>'
        else:
            textual_output = textual_output + 'more likely to be anxious than ' \
                                              f'<b>{percentage:.2f}%</b>'

        textual_output = textual_output + ' of the population. You are also '
        if id_percentage < 50:
            textual_output = textual_output + 'less likely to be anxious than' \
                                              f' <b>{100 - id_percentage:.2f}%</b> '
        else:
            textual_output = textual_output + 'more likely to be anxious than' \
                                              f' <b>{id_percentage:.2f}%</b> '
        textual_output = textual_output + f'of the population who chose "{id_group}" as their ' \
                                          f'"{self._input_fields[12].currentText().lower()}" ' \
                                          'identity.'
        self._graphical_output[3].setText(textual_output)

    def _update_output(self) -> None:
        """Updates all the output (gauge, progress bar and textual)."""
        self._user.estimate_anxiety_score(self.anxiety_data)

        percentage = (self._user.get_anxiety_score() - self.extrema[0]) / \
                     (self.extrema[1] - self.extrema[0]) * 100
        id_percentage = get_user_percentage(self._user,
                                            self._input_fields[12].currentText(),
                                            self.anxiety_data)
        self._update_gauge(percentage)
        self._plot_user(id_percentage)
        self._display_textual_output(percentage, id_percentage)
        self._draw_user_avatar()

if __name__ == '__main__':
    import python_ta

    python_ta.check_all(config={
        'extra-imports': ['python_ta.contracts', 'os', 'platform', 'pyqtgraph', 'PyQt5',
                          'PyQt5.QtGui', 'constants', 'data', 'gauge', 'user'],
        'allowed-io': [],
        'max-line-length': 100,
        # 'disable': ['R1705', 'C0200']
        # E0611 (no-name-in-module): python_ta fails to find PyQt5 modules even if they exist
        'disable': ['R1705', 'C0200', 'E0611']
    })

    import python_ta.contracts

    python_ta.contracts.check_all_contracts()

    import doctest

    doctest.testmod()


import random
import warnings

import numpy as np
import torch
from mmcv.parallel import MMDataParallel, MMDistributedDataParallel
from mmcv.runner import EpochBasedRunner, build_runner
from mmseg.core import DistEvalHook, EvalHook
from mmseg.datasets import build_dataloader, build_dataset
from mmseg.utils import get_root_logger

from mmrazor.core.distributed_wrapper import DistributedDataParallelWrapper
from mmrazor.core.hooks import DistSamplerSeedHook
from mmrazor.core.optimizer import build_optimizers
from mmrazor.utils import find_latest_checkpoint

def set_random_seed(seed, deterministic=False):
    """Import `set_random_seed` function here was deprecated in v0.3 and will
    be removed in v0.5.

    Args:
        seed (int): Seed to be used.
        deterministic (bool): Whether to set the deterministic option for
            CUDNN backend, i.e., set ``torch.backends.cudnn.deterministic``
            to True and ``torch.backends.cudnn.benchmark`` to False.
            Default: False.
    """
    warnings.warn(
        'Deprecated in v0.3 and will be removed in v0.5, '
        'please import `set_random_seed` directly from `mmrazor.apis`',
        category=DeprecationWarning)

    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    if deterministic:
        torch.backends.cudnn.deterministic = True
        torch.backends.cudnn.benchmark = False

def train_mmseg_model(model,
                      dataset,
                      cfg,
                      distributed=False,
                      validate=False,
                      timestamp=None,
                      meta=None):
    """Copy from mmsegmentation and modify some codes.

    This is an ugly implementation, and will be deprecated in the future. In
    the future, there will be only one train api and no longer distinguish
    between mmclassificaiton, mmsegmentation or mmdetection.
    """
    logger = get_root_logger(cfg.log_level)

    # prepare data loaders
    dataset = dataset if isinstance(dataset, (list, tuple)) else [dataset]
    # The default loader config
    loader_cfg = dict(
        # cfg.gpus will be ignored if distributed
        num_gpus=len(cfg.gpu_ids),
        dist=distributed,
        seed=cfg.seed,
        drop_last=True)
    # The overall dataloader settings
    loader_cfg.update({
        k: v
        for k, v in cfg.data.items() if k not in [
            'train', 'val', 'test', 'train_dataloader', 'val_dataloader',
            'test_dataloader'
        ]
    })

    # The specific dataloader settings
    train_loader_cfg = {**loader_cfg, **cfg.data.get('train_dataloader', {})}
    data_loaders = [build_dataloader(ds, **train_loader_cfg) for ds in dataset]

    # put model on gpus
    if distributed:
        find_unused_parameters = cfg.get('find_unused_parameters', False)
        if cfg.get('use_ddp_wrapper', False):
            # Difference from mmsegmentation.
            # In some algorithms, the ``optimizer.step()`` is executed in
            # ``train_step``. To rebuilt reducer buckets rightly, there need to
            # use DistributedDataParallelWrapper.
            model = DistributedDataParallelWrapper(
                model,
                device_ids=[torch.cuda.current_device()],
                broadcast_buffers=False,
                find_unused_parameters=find_unused_parameters)
        else:
            # Sets the ``find_unused_parameters`` parameter in
            # torch.nn.parallel.DistributedDataParallel
            model = MMDistributedDataParallel(
                model.cuda(),
                device_ids=[torch.cuda.current_device()],
                broadcast_buffers=False,
                find_unused_parameters=find_unused_parameters)
    else:
        model = MMDataParallel(model, device_ids=cfg.gpu_ids)

    # build optimizers
    # Difference from mmdetection.
    # In some algorithms, there will be multi optimizers.
    optimizer = build_optimizers(model, cfg.optimizer)

    # build runner
    if cfg.get('runner') is None:
        cfg.runner = {'type': 'IterBasedRunner', 'max_iters': cfg.total_iters}
        warnings.warn(
            'config is now expected to have a ``runner`` section, '
            'please set ``runner`` in your config.', UserWarning)

    runner = build_runner(
        cfg.runner,
        default_args=dict(
            model=model,
            batch_processor=None,
            optimizer=optimizer,
            work_dir=cfg.work_dir,
            logger=logger,
            meta=meta))

    # register hooks
    runner.register_training_hooks(
        cfg.lr_config,
        cfg.optimizer_config,
        cfg.checkpoint_config,
        cfg.log_config,
        cfg.get('momentum_config', None),
        custom_hooks_config=cfg.get('custom_hooks', None))

    # an ugly walkaround to make the .log and .log.json filenames the same
    runner.timestamp = timestamp

    if distributed:
        if isinstance(runner, EpochBasedRunner):
            runner.register_hook(DistSamplerSeedHook())

    # register eval hooks
    if validate:
        val_dataset = build_dataset(cfg.data.val, dict(test_mode=True))
        # The specific dataloader settings
        val_loader_cfg = {
            **loader_cfg,
            'samples_per_gpu': 1,
            'shuffle': False,  # Not shuffle by default
            **cfg.data.get('val_dataloader', {}),
        }
        val_dataloader = build_dataloader(val_dataset, **val_loader_cfg)
        eval_cfg = cfg.get('evaluation', {})
        eval_cfg['by_epoch'] = cfg.runner['type'] != 'IterBasedRunner'
        eval_hook = DistEvalHook if distributed else EvalHook
        # In this PR (https://github.com/open-mmlab/mmcv/pull/1193), the
        # priority of IterTimerHook has been modified from 'NORMAL' to 'LOW'.
        runner.register_hook(
            eval_hook(val_dataloader, **eval_cfg), priority='LOW')

    resume_from = None
    if cfg.resume_from is None and cfg.get('auto_resume'):
        resume_from = find_latest_checkpoint(cfg.work_dir)
    if resume_from is not None:
        cfg.resume_from = resume_from

    if cfg.resume_from:
        runner.resume(cfg.resume_from)
    elif cfg.load_from:
        runner.load_checkpoint(cfg.load_from)
    runner.run(data_loaders, cfg.workflow)


from pip._vendor.requests.packages.urllib3.connectionpool import xrange

class RotateString(object):
    def rotateString(self, A, B):
        if len(A) != len(B):
            return False
        if len(A) == 0:
            return True

        for s in xrange(len(A)):
            if all(A[(s+i) % len(A)] == B[i] for i in xrange(len(A))):
                return True
        return False

if __name__ == '__main__':
    a = RotateString()
    a1 = "abcde"
    b1 = "bcdea"
    print(a.rotateString(a1,b1))


#!/usr/bin/env ipython3

## @file
# define log likelihood function to be called by pyMultinest
# disk version

import numpy as np
import pdb
import gi_helper as gh
from gi_class_profiles import Profiles
from gi_priors import check_bprior, check_tilt
from gi_chi import calc_chi2
import gi_physics as phys
from pylab import *
#ion()
import time

def geom_loglike(cube, ndim, nparams, gp):
    tmp_profs = Profiles(gp.ntracer_pops, gp.nbins)#, gp.nbaryon_pops, gp.nbaryon_params)#, gp.nrhonu, )

    #Normalisation constant C for sigz calculation
    off = 0
    offstep = 1
    norm = cube[off]
    off += offstep

    #Dark Matter rho parameters (rho_C, kz_C, kz_vector)

    offstep = gp.nrhonu + 1
    if gp.scan_rhonu_space:
        tmp_rho_DM_allz = np.array(cube[off:off+offstep])
    elif gp.darkmattermodel == 'const_dm':
        offstep = 1
        rho_DM_params = np.array(cube[off:off+offstep])
        rho_DM_C = rho_DM_params[0]
        tmp_rho_DM_allz = rho_DM_C * np.ones(gp.nbins+1)
    else:
        rho_DM_params = np.array(cube[off:off+offstep])
        rho_DM_C = rho_DM_params[0] #rho_C
        kz_rho_DM_allz = rho_DM_params[1:] #kz for rho across all z points [0, bin_centres]
        tmp_rho_DM_allz = phys.rho(gp.z_all_pts, abs(kz_rho_DM_allz), rho_DM_C) #outputs rho across all points

        tmp_profs.kz_rho_DM_C = kz_rho_DM_allz[0]
        tmp_profs.set_prof('kz_rho_DM_vec', kz_rho_DM_allz[1:], 0, gp)

    tmp_rho_total_allz = tmp_rho_DM_allz*1.0 # Add DM to total mass density

    tmp_profs.rho_DM_C = tmp_rho_DM_allz[0]
    tmp_profs.set_prof('rho_DM_vec', tmp_rho_DM_allz[1:], 0, gp)
    off += offstep

    #Baryons
    tmp_rho_totbaryon_allz = np.zeros(len(tmp_rho_total_allz))

    for baryon_pop in range(0, gp.nbaryon_pops):
        offstep = gp.nbaryon_params
        baryon_params = np.array(cube[off:off+offstep])
        if gp.baryonmodel == 'simplenu_baryon':
            tmp_rho_baryon_allz = phys.rho_baryon_simplenu(gp.z_all_pts, baryon_params)
        elif gp.baryonmodel == 'kz_baryon':
            print('Not implemented yet')
        tmp_profs.rho_baryon_C = tmp_rho_baryon_allz[0]
        tmp_profs.set_prof('rho_baryon_vec', tmp_rho_baryon_allz[1:], baryon_pop, gp)

        tmp_rho_totbaryon_allz += tmp_rho_baryon_allz # add this pop's density to baryon total
        tmp_rho_total_allz += tmp_rho_baryon_allz # add the baryon density to the total density

        off += offstep

    #pdb.set_trace()
    tmp_profs.rho_total_C = tmp_rho_total_allz[0]
    tmp_profs.set_prof('rho_total_vec', tmp_rho_total_allz[1:], 0, gp)

    #Tracer params, nu_C, kz_nu_C, kz_nu_vector
    for tracer_pop in range(0, gp.ntracer_pops):
        offstep = gp.nrhonu + 1
        if gp.scan_rhonu_space:
            tmp_nu_allz = np.array(cube[off:off+offstep])
        else:
            tracer_params = np.array(cube[off:off+offstep])
            nu_C = tracer_params[0]
            kz_nu_allz = tracer_params[1:] #kz for rho across all z points [0, bin_centres]
            tmp_nu_allz = phys.rho(gp.z_all_pts, kz_nu_allz, nu_C) #outputs nu across all z points

            tmp_profs.kz_nu_C = kz_nu_allz[0]
            tmp_profs.set_prof('kz_nu_vec', kz_nu_allz[1:], 0, gp)

        tmp_profs.nu_C = tmp_nu_allz[0]
        tmp_profs.set_prof('nu_vec', tmp_nu_allz[1:], tracer_pop, gp)
        off += offstep

    if off != gp.ndim:
        gh.LOG(1,'wrong subscripts in gi_class_cube')
        raise Exception('wrong subscripts in gi_class_cube')

    #Calculate Sigma (surface density)
    Sig_DM_allz = phys.Sig(gp.z_all_pts, tmp_rho_DM_allz) # Dark matter Sigma
    Sig_baryon_allz = phys.Sig(gp.z_all_pts, tmp_rho_totbaryon_allz) # total baryon Sigma
    Sig_total_allz = phys.Sig(gp.z_all_pts, tmp_rho_total_allz) # total Sigma

    tmp_profs.Sig_DM_C = Sig_DM_allz[0]
    tmp_profs.set_prof('Sig_DM_vec', Sig_DM_allz[1:], 0, gp)

    tmp_profs.Sig_baryon_C = Sig_baryon_allz[0]
    tmp_profs.set_prof('Sig_baryon_vec', Sig_baryon_allz[1:], 0, gp)

    tmp_profs.Sig_total_C = Sig_total_allz[0]
    tmp_profs.set_prof('Sig_total_vec', Sig_total_allz[1:], 0, gp)

    #Calculate sigma (velocity dispersion)
    #pdb.set_trace()
    try:
        sigz2_vec = phys.sigz2(gp.z_all_pts, Sig_total_allz, tmp_nu_allz, norm)
    except ValueError:
        raise ValueError('negative value in sig2 array')
        return
    tmp_profs.set_prof('sigz2_vec', sigz2_vec[1:], 0, gp)

    # determine log likelihood
    chi2 = calc_chi2(tmp_profs, gp)
    gh.LOG(1, '   log L = ', -chi2/2.)
    tmp_profs.chi2 = chi2

    return tmp_profs   # from   likelihood L = exp(-\chi^2/2), want log of that
## \fn geom_loglike(cube, ndim, nparams, gp)
# define log likelihood function to be called by pyMultinest and plot_profiles
# disk version
# @param cube parameter cube as defined by gi_class_cube, in physical space
# @param ndim number of dimensions, needed as argument by multinest
# @param nparams number of parameters, needed as argument by multinest
# @param gp global parameters


from typing import List

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def getAllElements(self, root1: TreeNode, root2: TreeNode) -> List[int]:
        def convertList(node: TreeNode, ls: List[int]):
            if node is None:
                return
            convertList(node.left, ls)
            ls.append(node.val)
            convertList(node.right, ls)

        list1 = []
        list2 = []
        convertList(root1, list1)
        convertList(root2, list2)

        ans = []
        i, j = 0, 0
        while i < len(list1) and j < len(list2):
            if list1[i] < list2[j]:
                ans.append(list1[i])
                i += 1
            else:
                ans.append(list2[j])
                j += 1
        return ans + list1[i:] + list2[j:]


import sys

n = int(sys.stdin.readline().rstrip())
inputs = []
for _ in range(n):
  inputs.append(int(sys.stdin.readline().rstrip()))

inputs = inputs[::-1]

stack = []
result = []
for i in range(1, n+1):
  result += "+"
  stack.append(i)
  while stack:
    if stack[-1] == inputs[-1]:
      result += "-"
      stack.pop()
      inputs.pop()
    else:
      break

if not inputs:
  for op in result:
    print(op)
else:
  print("NO")


from django.contrib.auth import get_user_model
from django.db import models

from apps.consultations.models import AvailableConsultation

class Review(models.Model):
    consultation = models.ForeignKey(
        AvailableConsultation,
        on_delete=models.CASCADE,
        related_name="reviews",
        verbose_name="Услуга",
    )
    patient = models.ForeignKey(
        get_user_model(),
        on_delete=models.CASCADE,
    )
    review = models.TextField(verbose_name="Отзыв")
    time_created = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "reviews"
        verbose_name = "Отзыв"
        verbose_name_plural = "Отзывы"
        ordering = ["-time_created"]

    def __str__(self):
        return self.consultation.title


def label2idx(result):
    results = result.copy()
    for i, result in enumerate(results):
        results[i] = int(result.split('/')[-1][1:])

    return results

def get_fact(content):
    breakers = []
    fact_starts = []

    for i, c in enumerate(content):
        if c[0] == '【' and c[-1] == '】':
            breakers.append(i)
            if '查明' in c:
                fact_starts.append(i)

    fact = []
    for start in fact_starts:
        end = breakers[breakers.index(start)+1]
        fact += content[start:end]

    return ''.join(fact)

def get_level3labels(tree):
    level3labels = []
    for t in tree:
        level1 = t['value']
        children1 = t['children']
        for child1 in children1:
            level2 = child1['value']
            children2 = child1['children']
            for child2 in children2:
                level3 = child2['value']
                level3labels.append('/'.join([level1, level2, level3]))
    return level3labels


# -*- encoding:utf-8 -*-
"""
    选股示例因子：价格拟合角度选股因子
"""
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division

import numpy as np

from Util import RegUtil
from PickStock.PickStockBase import PickStockBase, reversed_result
from Indicator.NDVolume import _calc_today_volume_rank

class PickBreak(PickStockBase):
    """拟合角度选股因子示例类"""
    def _init_self(self, **kwargs):
        """通过kwargs设置拟合角度边际条件，配置因子参数"""

        # 暂时与base保持一致不使用kwargs.pop('a', default)方式
        # fit_pick中 ang > threshold_ang_min, 默认负无穷，即默认所有都符合
        self.xd = 0
        self.vol_rank = -1.0
        self.vol_days = 0
        if 'xd' in kwargs:
            # 设置最小角度阀值
            self.xd = kwargs['xd']
        if 'vol_rank' in kwargs:
            self.vol_rank = kwargs['vol_rank']
        if 'vol_days' in kwargs:
            self.vol_days = kwargs['vol_days']

    @reversed_result
    def fit_pick(self, kl_pd, target_symbol):
        """开始根据自定义拟合角度边际参数进行选股"""
        if len(kl_pd.close) < self.xd - 1:
            return False
        today_ind = len(kl_pd.close) - 1
        volumes = kl_pd.volume[-self.vol_days:]
        today_vol_rank = _calc_today_volume_rank(volumes)
        # 今天的收盘价格达到xd天内最高价格则符合买入条件
        if kl_pd.close[today_ind] == kl_pd.close[today_ind - self.xd + 1:today_ind + 1].max() and today_vol_rank >= self.vol_rank:
            return True
        return False

    def fit_first_choice(self, pick_worker, choice_symbols, *args, **kwargs):
        raise NotImplementedError('PickRegressAng fit_first_choice unsupported now!')


def fibonacci(n):
    '''
    Return nth Fibonacci using a recursive function
    input:
        n:int -> Number to calculate nth Fibonacci
    output:
        The Fibonacci value in nth number
    >>> fibonaci(5)
    5
    '''
    if n < 2:
        return 1
    else:
        return fibonacci(n-1)+fibonacci(n-2)

def fibonacciUpTo(n):
    '''
    Return a list where elements Fibonacci will be lower than n
    input:
        n: int -> Limit number
    output:
        list: -> List with integer numbers Fibonacci lower than n
    >>>fibonacciUpTo(6)
    [0, 1, 1, 2, 3, 5]
    '''
    sequence = [0, 1]

    for i in range(1, n+1):
        next = sequence[-1]+sequence[-2]
        if(next > n):
            return sequence
        else:
            sequence.append(next)

def invalid(n):
    '''
    function that throws an exception if you choose a non-existent option
    '''
    raise Exception("Operation not found")

def custom_switch(n, opt="0"):
    '''
    Choose an option without use "sentence if or switch"
    input:
        n:int -> Number to use in both Fibonacci options
        opt:str -> Option selected by default selects the first option.
    output:
        Result of the selected option
    >>>custom_switch(5,"1")
    Result of the selected option
    '''
    ops = {
        "1": fibonacci,
        "2": fibonacciUpTo
    }

    func_selected = ops.get(opt, invalid)
    return func_selected(n)

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

'''
    1) Digit the option to use
    2) Digit the number
'''

try:
    print('''Options:
            1) Calculate nth Fibonacci
            2) List Fibonacci terms lower than n
    ''')
    opt = input('Select option: ')
    n = int(input('Digit a number upper than 0: '))

    if( type(n) != int or n < 1 ):
        raise Exception("The number should be upper than 0")

    print(f'Result for operation # {opt} with n = {n}')
    print('>>>',custom_switch(n, opt))
except Exception as e:
    print( str(e) )


__all__ = ['WeightShareTransform', 'NeuralModule', 'PretrainedModelInfo', 'ModuleType', 'OperationMode']

import uuid
from abc import abstractmethod
from collections import namedtuple
from enum import Enum
from inspect import getargvalues, getfullargspec, stack
from os import path
from typing import Any, Dict, List, Optional, Set, Tuple

from ruamel.yaml import YAML

from nemo.core.neural_factory import NeuralModuleFactory, OperationMode
from nemo.core.neural_interface import NeuralInterface
from nemo.core.neural_types import NeuralPortNameMismatchError, NeuralType, NmTensor
from nemo.package_info import __version__ as nemo_version
from nemo.utils import logging
from nemo.utils.decorators.deprecated import deprecated
from nemo.utils.neural_graph.connection import StepModulePort

YAML = YAML(typ='safe')

class ModuleType(Enum):
    """ Back-end independent module types """

    module = 0
    datalayer = 1
    trainable = 2
    loss = 3
    nontrainable = 4

class WeightShareTransform(Enum):
    """When sharing parameters, what kind of transform to apply."""

    SAME = 0
    TRANSPOSE = 1

PretrainedModelInfo = namedtuple(
    "PretrainedModleInfo", ("pretrained_model_name", "description", "parameters", "location"),
)

class NeuralModule(NeuralInterface):
    """
    Abstract class that every Neural Module must inherit from.
    """

    def __init__(self, name=None):
        # Initialize the inferface.
        super().__init__()

        # Retrieve dictionary of parameters (keys, values) passed to init.
        self._init_params = self.__extract_init_params()

        # Get object UUID.
        self._uuid = str(uuid.uuid4())

        # Register module and store the generated name.
        self._name = self._app_state.register_module(self, name)

        # Set "module" type as default.
        self._type = ModuleType.module

        # Set "both" as default operation mode.
        self._operation_mode = OperationMode.both

        # Get default factory.
        self._factory = NeuralModuleFactory.get_default_factory()
        if self._factory is None:
            raise NameError(
                "A Neural Module was instantiated without first creating a Neural Factory. Please instantiate the "
                "NeuralModuleFactory class first."
            )

        # Set module properties from factory else use defaults
        self._placement = self._factory.placement
        # If one needs to change that should override it manually.

        # Optimization level.
        self._opt_level = self._factory.optim_level

    @property
    def init_params(self) -> Dict[str, Any]:
        """
        Property returning parameters used to instantiate the module.

        Returns:
            Dictionary containing parameters used to instantiate the module.
        """
        return self._init_params

    def __extract_init_params(self) -> Dict[str, Any]:
        """
        Retrieves the dictionary of of parameters (keys, values) passed to constructor of a class derived
        (also indirectly) from the Neural Module class.

        Returns:
            Dictionary containing parameters passed to init().
        """
        # Get names of arguments of the original module init method.
        to_set_params = getfullargspec(type(self).__init__).args
        to_set_params.remove("self")

        # Create empty list of init params.
        init_params = {}

        # Get the frame "call context".
        for frame in stack()[1:]:
            # Get the current call arguments.
            localvars = getargvalues(frame[0])

            # Fill the parameters with call arguments.
            for key in to_set_params:
                if key in localvars.args:
                    init_params[key] = localvars.locals[key]

            # Remove all set keys.
            for key in init_params.keys():
                if key in to_set_params:
                    to_set_params.remove(key)

            # Check if we have set everything.
            if len(to_set_params) == 0:
                break

        # Make sure that we collected ALL (and ONLY) the signature params - if not, then there is a BUG!
        if len(to_set_params) != 0:
            raise ValueError(
                "Could not collect all the signature params! "
                f"Please file a bug on GitHub with the current stack trace so that it can be reproduced."
            )

        # print("! init_params of {}: {}\n".format(type(self).__name__, init_params))

        # Return parameters.
        return init_params

    def __validate_params(self, params: Dict[str, Any]) -> bool:
        """
        Checks whether dictionary contains parameters being primitive types (string, int, float etc.)
        or (lists of)+ primitive types.

        Args:
            params: dictionary of parameters.
        Returns:
            True if all parameters were ok, False otherwise.
        """
        ok = True

        # Iterate over parameters and check them one by one.
        for key, variable in params.items():
            if not self.__is_of_allowed_type(variable):
                logging.warning(
                    "Parameter '{}' contains a variable '{}' of type '{}' which is not allowed.".format(
                        key, variable, type(variable)
                    )
                )
                ok = False

        # Return the result.
        return ok

    def __is_of_allowed_type(self, var) -> bool:
        """
        A recursive function that checks if a given variable is of allowed type.

        Args:
            pretrained_model_name (str): name of pretrained model to use in order.
        Returns:
            True if all parameters were ok, False otherwise.
        """
        # Special case: None is also allowed.
        if var is None:
            return True

        var_type = type(var)

        # If this is list - check its elements.
        if var_type == list:
            for list_var in var:
                if not self.__is_of_allowed_type(list_var):
                    return False

        # If this is dict - check its elements.
        elif var_type == dict:
            for _, dict_var in var.items():
                if not self.__is_of_allowed_type(dict_var):
                    return False

        elif var_type not in (str, int, float, bool):
            return False

        # Well, seems that everything is ok.
        return True

    def export_to_config(self, config_file: str):
        """
        A function that exports module "configuration" (i.e. init parameters) to a YAML file.

        Args:
            config_file: path (absolute or relative) and name of the config file (YML)
        Raises:
            ValueError: An error occurred and  parameters coudn't be exported.
        """
        # Greate an absolute path.
        abs_path_file = path.expanduser(config_file)

        # Serialize the module.
        to_export = self.serialize()

        # All parameters are ok, let's export.
        with open(abs_path_file, 'w') as outfile:
            YAML.dump(to_export, outfile)

        logging.info(
            "Configuration of module `{}` ({}) exported to '{}'".format(self.name, type(self).__name__, abs_path_file)
        )

    def serialize(self) -> Dict[str, Any]:
        """
        A method serializing the whole Neural module (into a dictionary).

        Returns:
            Dictionary containing a "serialized" module.
        """
        # Create a dictionary representing the serialized object.
        serialized_module = {}

        # Add "header" with module "specification".
        serialized_module["header"] = self.__serialize_header()

        # Add init parameters.
        serialized_module["init_params"] = self._serialize_configuration()

        # Return the dictionary.
        return serialized_module

    def __serialize_header(self) -> Dict[str, Any]:
        """
        A protected method that creates a header stored later in the configuration file.

        Returns:
            Dictionary containing a header with module specification.
        """

        # Get module "full specification".
        module_full_spec = str(self.__module__) + "." + str(self.__class__.__qualname__)
        module_class_name = type(self).__name__
        # print(module_full_spec)

        # Check whether module belongs to a collection.
        spec_list = module_full_spec.split(".")

        # Do not check Neural Modules from unit tests.
        if spec_list[0] == "tests":
            # Set collection variables.
            collection_type = "tests"
            collection_version = None
        else:
            # Check if component belongs to any collection
            if len(spec_list) < 3 or (spec_list[0] != "nemo" and spec_list[1] != "collection"):
                logging.warning(
                    "Module `{}` does not belong to any collection. This won't be allowed in the next release.".format(
                        module_class_name
                    )
                )
                collection_type = "unknown"
                collection_version = None
            else:
                # Ok, set collection.
                collection_type = spec_list[2]
                collection_version = None
                # TODO: to be SET!
                # print(getattr("nemo.collections.nlp", __version__))

        # Create a "header" with module "specification".
        header = {
            "nemo_core_version": nemo_version,
            "collection_type": collection_type,
            "collection_version": collection_version,
            # "class": module_class_name, # Operating only on full_spec now.
            "full_spec": module_full_spec,
        }
        return header

    def _serialize_configuration(self) -> Dict[str, Any]:
        """
        A function that serializes the module "configuration (i.e. init parameters) to a dictionary.

        ..note:
            Thus functions should be overloaded when writing a custom module import/export.

        Returns:
            A "serialized" dictionary with module configuration.
        Raises:
            A ValueError exception in case then parameters coudn't be exported.
        """
        # Check if generic export will work.
        if not self.__validate_params(self._init_params):
            raise ValueError(
                "Generic configuration export enables to use of parameters of primitive types (string, int, float) "
                F"or (lists of/dicts of) primitive types. Please implement your own custom `export_to_config()` and "
                F"`import_from_config()` methods for your custom Module class."
            )
        # In this case configuration = init parameters.
        return self._init_params

    @classmethod
    def import_from_config(
        cls, config_file: str, section_name: str = None, name: str = None, overwrite_params: Dict = {}
    ) -> 'NeuralModule':
        """
        Class method importing the configuration file.
        Raises an ImportError exception when config file is invalid or
        incompatible (when called from a particular class).

        Args:
            config_file: path (absolute or relative) and name of the config file (YML)
            section_name: section in the configuration file storing module configuration (optional, DEFAULT: None)
            name: name of the module that will overwrite the name in the `init_params` (optional, DEFAULT: None)
            overwrite_params: Dictionary containing parameters that will be added to or overwrite (!)
            the default init parameters loaded from the configuration file (the module "init_params" section).
        Returns:
            Instance of the created NeuralModule object.
        """
        logging.info("Loading configuration of a new Neural Module from the `{}` file".format(config_file))

        # Validate the content of the configuration file (its header).
        loaded_config = cls.__validate_config_file(config_file, section_name)

        # "Deserialize" the module.
        obj = cls.deserialize(loaded_config, name, overwrite_params)

        # Return the new module.
        return obj

    @classmethod
    def __validate_config_file(cls, config_file: str, section_name: str = None) -> Dict[str, Any]:
        """
        Class method validating whether the config file has a proper content (sections, specification etc.).
        Raises an ImportError exception when config file is invalid or
        incompatible (when called from a particular class).

        Args:
            config_file: path (absolute or relative) and name of the config file (YML)
            section_name: section in the configuration file storing module configuration (optional, DEFAULT: None)
        Returns:
            A loaded configuration file (dictionary).
        """
        # Greate an absolute path.
        abs_path_file = path.expanduser(config_file)

        # Open the config file.
        with open(abs_path_file, 'r') as stream:
            loaded_config = YAML.load(stream)

        # Check section.
        if section_name is not None:
            if section_name not in loaded_config:
                raise ImportError(
                    "The loaded config `{}` doesn't contain the indicated `{}` section".format(
                        config_file, section_name
                    )
                )
            # Section exists - use only it for configuration.
            loaded_config = loaded_config[section_name]

        # Make sure that the config is valid.
        if "header" not in loaded_config:
            raise ImportError("The loaded config `{}` doesn't contain the `header` section".format(config_file))

        if "init_params" not in loaded_config:
            raise ImportError("The loaded config `{}` doesn't contain the `init_params` section".format(config_file))

        # Parse the "full specification".
        spec_list = loaded_config["header"]["full_spec"].split(".")

        # Check if config contains data of a compatible class.
        if not issubclass(cls.__deserialize_header(loaded_config["header"]), cls):
            txt = "The loaded file `{}` contains configuration of ".format(config_file)
            txt = txt + "`{}` thus cannot be used for instantiation of an object of type `{}`".format(
                spec_list[-1], cls.__name__
            )
            raise ImportError(txt)

        # Success - return configuration.
        return loaded_config

    @classmethod
    def deserialize(
        cls, configuration: Dict[str, Any], name: str = None, overwrite_params: Dict[str, Any] = {}
    ) -> 'NeuralModule':
        """
        Class method instantiating the neural module object based on the configuration (dictionary).

        Args:
            configuration: Dictionary containing proper "header" and "init_params" sections.

            name: name of the module that will overwrite the name in the `init_params` (optional, DEFAULT: None)

            overwrite_params: Dictionary containing parameters that will be added to or overwrite (!)
            the default init parameters loaded from the configuration file (the module "init_params" section).

        Returns:
            Instance of the created NeuralModule object.
        """
        # Deserialize header - get object class.
        module_class = cls.__deserialize_header(configuration["header"])

        # Update parameters with additional ones.
        configuration["init_params"].update(overwrite_params)

        # Override module name in init_params using the logic:
        #  * section_name if not none overrides init_params.name first (skipped for now, TOTHINK!)
        #  * name (if None) overrides init_params.name
        if name is not None:
            configuration["init_params"]["name"] = name

        # Get init parameters.
        init_params = cls._deserialize_configuration(configuration["init_params"])

        # Create the module instance.
        new_module = module_class(**init_params)
        logging.info(
            "Instantiated a new Neural Module named `{}` of type `{}`".format(
                new_module.name, type(new_module).__name__
            )
        )

        # Return the module instance.
        return new_module

    @classmethod
    def __deserialize_header(cls, serialized_header: Dict[str, Any]):
        """
        Method deserializes the header and extracts the module class.

        Args:
            serialized_header: Dictionary containing module header.
        Returns:
            Class of the module to be created.
        """
        # Parse the "full specification".
        spec_list = serialized_header["full_spec"].split(".")

        # Get module class from the "full specification".
        mod_obj = __import__(spec_list[0])
        for spec in spec_list[1:]:
            mod_obj = getattr(mod_obj, spec)

        # Return "class".
        return mod_obj

    @classmethod
    def _deserialize_configuration(cls, serialized_init_params: Dict[str, Any]):
        """
        A function that deserializes the module "configuration (i.e. init parameters).

        ..note:
            Thus functions should be overloaded when writing a custom module import/export.

        Args:
            serialized_init_params: List of init parameters loaded from the file.
        Returns:
            A "deserialized" list with init parameters.
        """
        # In this case configuration = init parameters.
        return serialized_init_params

    @property
    @abstractmethod
    def input_ports(self) -> Dict[str, NeuralType]:
        """
        Returns definitions of module input ports

        Returns:
          A dictionary containing module's input ports (names, NeuralTypes) mapping.
        """

    @property
    @abstractmethod
    def output_ports(self) -> Dict[str, NeuralType]:
        """
        Returns definitions of module output ports

        Returns:
          A dictionary containing module's output ports (names, NeuralTypes) mapping.
        """

    @property
    def _disabled_deployment_input_ports(self) -> Set[str]:
        """Returns names of input ports that will not be included in an export

        Returns:
          A (set) of module's input port names that are not exportable
        """
        return set([])

    @property
    def _disabled_deployment_output_ports(self) -> Set[str]:
        """Returns names of output ports that will not be included in an export

        Returns:
          A (set) of module's output port names that are not exportable
        """
        return set([])

    def _prepare_for_deployment(self) -> None:
        """Patch the module if required to prepare for deployment

        Returns:
            (Optional) input and output example tensors
        """
        return None, None

    @property
    def operation_mode(self):
        """ Returns the operation mode. """
        return self._operation_mode

    @property
    def type(self):
        """ Returns the type of module. """
        return self._type

    @operation_mode.setter
    def operation_mode(self, operation_mode: OperationMode):
        """ Sets the operation mode. """
        self._operation_mode = operation_mode

    @staticmethod
    def pretrained_storage():
        return ''

    def __call__(self, **kwargs):
        """This method allows objects to be called with their port names

        Args:
          kwargs: Input ports and their values. For example:
          ...
          mymodule1 = Subclass1_of_NeuralModule(...)
          mymodule2 = Subclass2_of_NeuralModule(...)
          ...
          out_port1, out_port2 = mymodule1(input_port1=value1,
          input_port2=value2,
          input_port3=value3)
          out_port11 = mymodule2(input_port1=out_port2)
          ...

        Returns:
          NmTensor object or tuple of NmTensor objects
        """
        # print(" Neural Module:__call__")

        # Set the operation mode of the outer graph.
        self.operation_mode = self._app_state.active_graph.operation_mode
        # The input and output ports definitions can potentially depend on the operation mode!

        # Record the operation (i.e. add a single module).
        step_number = self._app_state.active_graph.record_step(self)

        ###### PROCESS INPUTS. ######
        # Iterate through all passed parameters.
        for port_name, port_content in kwargs.items():
            # Make sure that passed arguments corresponds to one of the input port names.
            if port_name not in self.input_ports.keys():
                raise NeuralPortNameMismatchError(port_name)

            # At that point the input can be one of three types:
            # * NeuralGraph -> bind port using the default name and type.
            # * GraphInput -> check definition, if ok bind port.
            # * NmTensor -> check definition, add self as a "consumer" of a tensor (produced by other module).

            # Check what was actually passed.
            if type(port_content).__name__ == "NeuralGraph":
                # Make sure that port_content is the currently active graph!
                if port_content is not self._app_state.active_graph:
                    raise ConnectionError("Ports can be bound only by passing the active graph object!")
                # Create an alias so the logic will be more clear.
                active_graph = port_content

                # This case: we are nesting one graph into another and must bind input port of one graph in another!
                # So generally we must "copy" the of thus module to graog (the inverted logic!).

                # Copy the port "definition" (i.e. is NeuralType) using the same port name.
                active_graph.inputs[port_name] = self.input_ports[port_name]

                # Bind the neural graph input port, i.e. remember that a given graph port should pass data
                # to THIS module-port (when it finally will be connected).
                active_graph.inputs[port_name].bind(StepModulePort(step_number, self.name, port_name))

                # Please note that there are no "consumers" here - this is a "pure binding".

            elif type(port_content).__name__ == "GraphInput":

                # Check if GraphInput belongs to the active graph !
                own_port = False
                for gcontent in self._app_state.active_graph.inputs.values():
                    if gcontent is port_content:
                        own_port = True
                        break
                if not own_port:
                    raise NeuralPortNameMismatchError(port_name)

                # Compare input port definition with the received definition.
                self.input_ports[port_name].compare_and_raise_error(
                    self.__class__.__name__, port_name, port_content.ntype
                )

                # Bind the neural graph input port, i.e. remember that a given graph port should pass data
                # to THIS module-port (when it finally will be connected).
                port_content.bind(StepModulePort(step_number, self.name, port_name))

                # Please note that there are no "consumers" here - this is a "pure binding".

            elif type(port_content) is NmTensor:
                # Compare input port definition with the received definition.
                self.input_ports[port_name].compare_and_raise_error(self.__class__.__name__, port_name, port_content)

                # Ok, the goal here is to actually "connect": add self (module) as "consumer" to the input tensor.
                port_content.add_consumer(StepModulePort(step_number, self.name, port_name))
            else:
                raise TypeError(
                    "Input '{}' must be of one of three types: NeuralGraph, GraphInput or NmTensor".format(port_name)
                )

        ###### PRODUCE OUTPUTS. ######
        output_port_defs = self.output_ports
        # Create output tensors.
        if len(output_port_defs) == 1:
            # Get port name and type.
            out_name = list(output_port_defs)[0]
            out_type = output_port_defs[out_name]

            # Create a single returned tensor.
            results = NmTensor(producer=self, producer_args=kwargs, output_port_name=out_name, ntype=out_type,)

            # Bind the "default" output ports.
            self._app_state.active_graph.bind_outputs(results)
        else:
            # Create output tensors.
            output_tensors = []
            for out_name, out_type in output_port_defs.items():
                output_tensors.append(
                    NmTensor(producer=self, producer_args=kwargs, output_port_name=out_name, ntype=out_type,)
                )

            # Create a named tuple type enabling to access outputs by attributes (e.g. out.x).
            output_class_name = f'{self.__class__.__name__}Output'
            result_type = namedtuple(typename=output_class_name, field_names=output_port_defs.keys())

            # Create the returned tuple object.
            results = result_type(*output_tensors)

            # Bind the output tensors.
            self._app_state.active_graph.bind_outputs(output_tensors)

        # Return the results.
        return results

    def __str__(self):
        return self.__class__.__name__

    @abstractmethod
    def get_weights(self) -> Optional[Dict[(str, bool)]]:
        """Returns NeuralModule's weights copy.

        Returns:
          Dictionary of name -> (weights, trainable)"""
        pass

    @abstractmethod
    def set_weights(
        self,
        name2weight: Dict[(str, Tuple[str, bool])],
        name2name_and_transform: Dict[(str, Tuple[str, WeightShareTransform])] = None,
    ):
        """Sets weight from given values. For every named weight in
        name2weight,
        if weight with the same name is found in the model, it will be set to
        found value.

        WARNING: This will NOT tie weights. It will copy values.

        If ``name2name_and_transform`` is provided then if will set weights
        using
        name mapping and transform. For example, suppose ``objec1.X = 3x5
        weight``.
        Then, if ``name2name_and_transform['X']=('Y',
        WeightShareTransform.TRANSPOSE)``
        and ``Y`` is 5x3 weight and ``name2weight['Y']=Y. Then:
        ``object1.set_weights(name2weight, name2name_and_transform)`` will
        set object1.X=transpose(Y).

        Args:
          name2weight (dict): dictionary of name to (weight, trainable).
          Typically this is output of get_weights method.
          name2name_and_transform: mapping from name -> (name, transform)
        """
        pass

    @staticmethod
    def list_pretrained_models() -> Optional[List[PretrainedModelInfo]]:
        """List all available pre-trained models (e.g. weights) for this NM.

        Returns:
            A list of PretrainedModelInfo tuples.
            The pretrained_model_name field of the tuple can be used to
            retrieve pre-trained model's weights (pass it as
            pretrained_model_name argument to the module's constructor)
        """
        return None

    def get_config_dict_and_checkpoint(self, pretrained_model_name):
        """WARNING: This part is work in progress"""
        return None

    @abstractmethod
    def tie_weights_with(
        self,
        module,
        weight_names=List[str],
        name2name_and_transform: Dict[(str, Tuple[str, WeightShareTransform])] = None,
    ):
        """Ties weights between self and module. For every weight name in
        weight_names, if weight with the same name is found in self, it will
        be tied
        with a same weight from ``module``.

        WARNING: Once weights are tied, updates to one weights's weights
        will affect
        other module's weights.

        If ``name2name_and_transform`` is provided then if will set weights
        using
        name mapping and transform. For example, suppose ``objec1.X = 3x5
        weights``
        and ``object2.Y = 5x3 weights``. Then these weights can be tied like
        this:

        .. code-block:: python

          object1.tie_weights_with(object2, weight_names=['X'],
          name2name_and_transform =
          { 'X': ('Y', WeightShareTransform.TRANSPOSE)})

        Args:
            module: with which module to tie weights
            weight_names (List[str]): list of self weights' names
            name2name_and_transform: mapping from name -> (name, transform)
        """
        pass

    def is_trainable(self) -> bool:
        """
        Checks if NeuralModule is trainable.
        A NeuralModule is trainable IFF it contains at least one trainable
        weight

        Returns:
          True if module has trainable weights, False otherwise
        """
        weights = self.get_weights()
        if weights is None:
            return False
        for name, w in weights.items():
            if w[1]:
                return True
        return False

    @abstractmethod
    def save_to(self, path: str):
        """Save module state to file.

        Args:
          path (string): path to while where to save.
        """
        pass

    @abstractmethod
    def restore_from(self, path: str):
        """Restore module's state from file.

        Args:
          path (string): path to where to restore from.
        """
        pass

    @abstractmethod
    def freeze(self, weights: Set[str] = None):
        """Freeze weights

        Args:
          weights (set): set of weight names to freeze
          If None, all weights are freezed.
        """
        pass

    @abstractmethod
    def unfreeze(self, weights: Set[str] = None):
        """Unfreeze weights

        Args:
          weights (set): set of weight names to unfreeze
          If None, all weights are unfreezed.
        """
        pass

    @property
    def placement(self):
        """Module's placement. Currently CPU or GPU.
        DataParallel and ModelParallel will come later.

        Returns:
          (DeviceType) Device where NM's weights are located
        """
        return self._placement

    @property
    @deprecated(version=0.11)
    def local_parameters(self) -> Optional[Dict]:
        """Get module's parameters

        Returns:
          module's parameters
        """
        return self._init_params
        # return self._local_parameters

    @property
    def unique_instance_id(self):
        """A unique instance id for this object

        Returns:
          A uniq uuid which can be used to identify this object
        """
        return self._uuid

    @property
    def factory(self):
        """ Neural module factory which created this module
        Returns: NeuralModuleFactory instance or None
        """
        return self._factory

    @property
    @abstractmethod
    def num_weights(self):
        """Number of module's weights
        """
        pass


import sys

filename=sys.argv[1]

infile=open(filename+'_sorted.txt')
lines =infile.readlines()
infile.close()
i=0
while not lines[i][0] in '0123456789':
    i+=1

curr_length=1
count=0
outfile=open(filename+'.ibs', 'w')
while i<len(lines):
    length=int(float(lines[i].strip('\n')))
    if length==curr_length:
        count+=1
    else:
        outfile.write(str(count)+'\t'+str(curr_length)+'\n')
        count=1
        curr_length=length
    i+=1
outfile.close()