"""
 * Python script to figures for lesson 6.
 * Intended for lesson maintainers use only.
 *
 * usage: python make_figures.py
"""

from astropy.convolution import Gaussian2DKernel
import matplotlib.pyplot as plt
import numpy as np
import skimage.filters

# Make a Gaussian kernel image
# Outputs to `./fig/06_Gaussian_kernel.png`

# Create an array containing the evalusation of a
# Gaussian function with a standard deviation of 0.8
sigma = 0.8
z_kern = np.array(Gaussian2DKernel(sigma))

# Create co-ordinate matricies for x and y
x, y = np.meshgrid(np.arange(0, z_kern.shape[0]), np.arange(0, z_kern.shape[1]))

# Create vectors representations of x, y and z
x_long = np.reshape(x, z_kern.shape[0]*z_kern.shape[1])
y_long = np.reshape(y, z_kern.shape[0]*z_kern.shape[1])
z_kern_long = np.reshape(z_kern, z_kern.shape[0]*z_kern.shape[1])

# Plot the kernel as an image
plt.imshow(z_kern, interpolation='none', origin='upper', vmin=0, vmax=0.25)

# Label each pixel with it's z value (the kernel evaluated at that point)
for i, label in enumerate(z_kern_long):
    plt.text(x_long[i]-0.3, y_long[i], "{:.2f}".format(label), color='Red')

# Label axes
plt.xlabel('x [pixels]')
plt.ylabel('y [pixels]')
plt.colorbar()

# Save image
plt.savefig('./fig/06_Gaussian_kernel.png')
plt.clf()

# -----------

# Create an image file based
# on a hard-coded matrix representing the blue
# channel of the corner of the cat image used
# in this lesson.
# Outputs to `./fig/06_cat_corner_blue.png`

# Hard-coded array taken from lesson text
z_cat = np.array(   [[68,  82, 71, 62, 100,  98,  61],
                [90,  67, 74, 78,  91,  85,  77,],
                [50,  53, 78, 82,  72,  95, 100,],
                [87,  89, 83, 86, 100, 116, 128,],
                [89, 108, 86, 78,  92,  75, 100,],
                [90,  83, 89, 73,  68,  29,  18,],
                [77, 102, 70, 57,  30,  30,  50,]])

# Create co-ordinate matricies for x and y
x, y = np.meshgrid(np.arange(0, z_cat.shape[0]), np.arange(0, z_cat.shape[1]))

# Create vectors representations of x, y and z
x_long = np.reshape(x, z_cat.shape[0]*z_cat.shape[1])
y_long = np.reshape(y, z_cat.shape[0]*z_cat.shape[1])
z_cat_long = np.reshape(z_cat, z_cat.shape[0]*z_cat.shape[1])

# Plot the data as an image
plt.imshow(z_cat, interpolation='none', origin='upper', cmap='Blues_r')

# Label each pixel with it's z value
for i, label in enumerate(z_cat_long):
    plt.text(x_long[i]-0.3, y_long[i], label, color='Red')

# Label axes
plt.xlabel('x [pixels]')
plt.ylabel('y [pixels]')
plt.colorbar()

# Save image
plt.savefig('./fig/06_cat_corner_blue.png')
plt.clf()

# -----------

# Create an image file showing how matricies
# are combined in convolution
# Outputs to `./fig/06_combo.png`

# Build array of multiplications
eqn_array = np.char.add(z_cat.astype('str'), ' \\times ')
eqn_array = np.char.add(eqn_array, np.round(z_kern,3).astype(np.dtype('U4')))

# Make read as latex
eqn_array = np.char.add(eqn_array, '$')
eqn_array = np.char.add('$', eqn_array)

eqn_array_long = np.reshape(eqn_array, z_cat.shape[0]*z_cat.shape[1])

plt.plot()
# Label each pixel with it's z value
for i, label in enumerate(eqn_array_long):
    plt.text(x_long[i]-0.4, y_long[i]+0.1, label, color='Red', fontsize=7)
plt.xlim(-0.5, 6.5)
plt.ylim(6.5, -0.5)
plt.axes().set_aspect('equal')
plt.xlabel('x [pixels]')
plt.ylabel('y [pixels]')
plt.savefig('./fig/06_combo.png')
plt.clf()


# -*- coding: utf-8 -*-
"""
Created on Tue Jun 13 13:25:33 2017

@author: Yanyu
"""

import urllib2
import tweepy
import pandas as pd
import datetime as dt
from twitter_init import TweetScrape

class Info(TweetScrape):
    def __init__(self,usernames,flag):

        """
        Summary the basic info, like followers_count, created_at, etc., and generate an excel report.
        This class could be used to track and report the followers growth of a large amount of accounts.

        :usernames: (list) find in logger.py, choose betweeen usernamesv, usernamessuga,usernamesjjk, usernamesjimin
        :flag: (str) choose betwwen 'jimin','jungkook','v'

        """

        super(Info,self).__init__()
        self.usernames=usernames
        self.flag=flag

    def getinfo(self):
        count=len(self.usernames)
        all_info={}
        for username in self.usernames:
            count=count-1
            try:
                tweets=self.api.get_user(username)
            except Exception:
                try:
                    tweets=self.api.get_user(username)
                except Exception:
                    print (username,'wrong')
            else:
                info={}
                info['followers_count']=tweets.followers_count
                info['created_at']=tweets.created_at
                info['statuses_count']=tweets.statuses_count
                info['description']=tweets.description

                all_info[username]=info
                print (count,' ; ',username)

        all_data=pd.DataFrame.from_dict(all_info).transpose()
        if self.flag=='jimin':
            all_data.to_csv('jiminFanBase_info%s.txt'%str(dt.datetime.now())[:10],encoding='utf-8')
            part_data=all_data[['followers_count','statuses_count','created_at']].sort(['followers_count'],ascending=False)

            writer = pd.ExcelWriter('jiminFanBase_info_%s.xlsx'%str(dt.datetime.now())[:10])
            part_data.to_excel(writer,sheet_name=dt.datetime.now().strftime("%Y-%m-%d"))
            writer.save()

        elif self.flag=='jungkook':
            all_data.to_csv('jjkFanBase_info%s.txt'%str(dt.datetime.now())[:10],encoding='utf-8')
            part_data=all_data[['followers_count','statuses_count','created_at']].sort(['followers_count'],ascending=False)

            writer = pd.ExcelWriter('jjkFanBase_info%s.xlsx'%str(dt.datetime.now())[:10])
            part_data.to_excel(writer,sheet_name=dt.datetime.now().strftime("%Y-%m-%d"))
            writer.save()

        elif self.flag=='v':
            all_data.to_csv('vFanBase_info%s.txt'%str(dt.datetime.now())[:10],encoding='utf-8')
            part_data=all_data[['followers_count','statuses_count','created_at']].sort(['followers_count'],ascending=False)

            writer = pd.ExcelWriter('vFanBase_info%s.xlsx'%str(dt.datetime.now())[:10])
            part_data.to_excel(writer,sheet_name=dt.datetime.now().strftime("%Y-%m-%d"))
            writer.save()

        print (all_data[['followers_count','statuses_count','created_at']].sort(['followers_count'],ascending=False))
        return part_data


"""
Read both MCMC summaries from the Joint less and more runs, and compile them into a LaTeX table.
"""

import arviz as az
import corner
import exoplanet as xo
import matplotlib.pyplot as plt
import pymc3 as pm
import pandas as pd
import os

import twa.data as d
from twa.constants import *
from twa.plot_utils import efficient_autocorr, efficient_trace
from twa import joint
from pathlib import Path

p = Path(os.getenv("TWA_ANALYSIS_ROOT"))

df_less = pd.read_csv(p / "joint" / "rv_astro_disk_less" / "chains" / "current.csv")
df_more = pd.read_csv(p / "joint" / "rv_astro_disk_more" / "chains" / "current.csv")

# calculate rp for both cases
for df in [df_less, df_more]:
    df["rp"] = df["aOuter"] * (1 - df["eOuter"])  # au

# Assign table label with chain keyword and format precision for sampled and derived parameters.
# assemble into a block and then insert into template.

class Row:
    def __init__(self, key, label, precision=None, transformation=None, null=False):
        self.key = key  # mc label
        self.label = label  # row name in latex
        if precision is not None:
            self.precision = precision  # format string
        else:
            self.precision = "{:.2f}"

        if transformation is not None:
            self.transformation = transformation
        else:
            self.transformation = lambda x: x

        self.null = null

    def get_vals(self, df):
        """
        Calculate the mean and std off of the transformed variables.
        """
        samples = self.transformation(df[self.key])
        return (np.mean(samples), np.std(samples))

    def __call__(self, df1, df2):
        """
        Args:
            df : the DataFrame

        Output the LaTeX, applying the appropriate transformation.
        """

        if self.null:
            return (
                self.label
                + "& \\nodata\\tablenotemark{c} & \\nodata\\tablenotemark{c}\\\\"
            )
        else:
            return self.label + (
                " & $"
                + self.precision
                + " \pm "
                + self.precision
                + "$ & $"
                + self.precision
                + " \pm "
                + self.precision
                + "$\\\\"
            ).format(*self.get_vals(df1), *self.get_vals(df2))

# # sample pars
# [
#     "mparallax",
#     "aAngInner",
#     "logPInner",
#     "eInner",
#     "omegaInner",
#     "OmegaInner",
#     "cosInclInner",
#     "MAb",
#     "tPeriastronInner",
#     "logPOuter",
#     "omegaOuter",
#     "OmegaOuter",
#     "phiOuter",
#     "cosInclOuter",
#     "eOuter",
#     "gammaOuter",
#     "MB",
#     "offsetKeck",
#     "offsetFeros",
#     "offsetDupont",
#     "logjittercfa",
#     "logjitterkeck",
#     "logjitterferos",
#     "logjitterdupont",
#     "logRhoS",
#     "logThetaS",
#     "iDisk",
#     "OmegaDisk",
# ]

tdeg = lambda x: x / deg
tOout = lambda x: x / deg + 360.0
texp = lambda x: np.exp(x)
tyr = lambda x: x / yr
tjd = lambda x: x + jd0 - 2450000
sampled_rows = [
    Row("PInner", r"$P_\mathrm{inner}$ [days]", precision="{:.3f}"),
    Row("aAngInner", r"$a_\mathrm{inner}$ [mas]"),
    Row("MAb", r"$M_\mathrm{Ab}$ [$M_\odot$]"),
    Row("eInner", r"$e_\mathrm{inner}$"),
    Row(
        "inclInner",
        r"$i_\mathrm{inner}$ [\degr]",
        transformation=tdeg,
        precision="{:.1f}",
    ),
    Row(
        "omegaInner",
        r"$\omega_\mathrm{Aa}$\tablenotemark{a} [\degr]",
        transformation=tdeg,
        precision="{:.0f}",
    ),
    Row(
        "OmegaInner",
        r"$\Omega_\mathrm{inner}$\tablenotemark{b} [\degr]",
        transformation=tdeg,
        precision="{:.0f}",
    ),
    Row(
        "tPeriastronInner",
        r"$T_{0,\mathrm{inner}}$ [JD - 2,450,000]",
        transformation=tjd,
    ),
    Row("POuter", r"$P_\mathrm{outer}$ [yrs]", transformation=tyr, precision="{:.0f}"),
    Row("MB", r"$M_\mathrm{B}$ [$M_\odot$]"),
    Row("eOuter", r"$e_\mathrm{outer}$", precision="{:.1f}"),
    Row(
        "inclOuter",
        r"$i_\mathrm{outer}$ [\degr]",
        transformation=tdeg,
        precision="{:.0f}",
    ),
    Row(
        "omegaOuter",
        r"$\omega_\mathrm{A}$\tablenotemark{a} [\degr]",
        transformation=tOout,
        precision="{:.0f}",
        null=True,
    ),
    Row(
        "OmegaOuter",
        r"$\Omega_\mathrm{outer}$\tablenotemark{b} [\degr]",
        transformation=tdeg,
        precision="{:.0f}",
        null=True,
    ),
    Row(
        "tPeriastronOuter",
        r"$T_{0,\mathrm{outer}}$ [JD - 2,450,000]",
        transformation=tjd,
        precision="{:.0f}",
        null=True,
    ),
    Row("mparallax", r"$\varpi$ [$\arcsec$]"),
    Row(
        "logRhoS", r"$\sigma_\rho$ [$\arcsec$]", precision="{:.3f}", transformation=texp
    ),
    Row(
        "logThetaS",
        r"$\sigma_\theta$ [$\degr$]",
        precision="{:.3f}",
        transformation=texp,
    ),
    Row("jitCfa", r"$\sigma_\mathrm{CfA}$ [km s${}^{-1}$]", precision="{:.1f}"),
    Row("jitKeck", r"$\sigma_\mathrm{Keck}$ [km s${}^{-1}$]", precision="{:.1f}"),
    Row("jitFeros", r"$\sigma_\mathrm{FEROS}$ [km s${}^{-1}$]", precision="{:.1f}"),
    Row("jitDupont", r"$\sigma_\mathrm{du\;Pont}$ [km s${}^{-1}$]", precision="{:.1f}"),
    Row("offsetKeck", r"(Keck - CfA) [km s${}^{-1}$]", precision="{:.1f}"),
    Row("offsetFeros", r"(FEROS - CfA) [km s${}^{-1}$]", precision="{:.1f}"),
    Row("offsetDupont", r"(du Pont - CfA) [km s${}^{-1}$]", precision="{:.1f}"),
]

derived_rows = [
    Row("MAa", r"$M_\mathrm{Aa}$ [$M_\odot$]"),
    Row("MA", r"$M_\mathrm{A}$ [$M_\odot$]"),
    Row("aInner", r"$a_\mathrm{inner}$ [au]", precision="{:.3f}"),
    Row("aOuter", r"$a_\mathrm{outer}$ [au]", precision="{:.0f}"),
    Row("rp", r"$r_{p,\mathrm{outer}}$ [au]", precision="{:.0f}"),
]

sampled = "\n".join([row(df_more, df_less) for row in sampled_rows])
derived = "\n".join([row(df_more, df_less) for row in derived_rows])

template = """\\begin{{deluxetable}}{{lcc}}
\\tablecaption{{Orbital parameters \\label{{tab:orbits}}.}}
\\tablehead{{\\colhead{{Parameter}} & \\colhead{{primary solution}} & \\colhead{{alternate solution}}}}
\\startdata
\\cutinhead{{Sampled}}
{sampled:}
\\cutinhead{{Derived}}
{derived:}
\\enddata
\\tablenotetext{{a}}{{The argument of periastron of the primary. $\\omega_\\mathrm{{secondary}} = \\omega_\\mathrm{{primary}} + \\pi$.}}
\\tablenotetext{{b}}{{The ascending node is identified as the point where the secondary body crosses the sky plane \\emph{{receding}} from the observer.}}
\\tablenotetext{{c}}{{Posterior is non-Gaussian; see Figure~\\ref{{fig:corner}}}}
\\end{{deluxetable}}
"""

with open("table.tex", "w") as f:
    f.write(template.format(sampled=sampled, derived=derived))

# # all pars
# [
#     "mparallax",
#     "MAb",
#     "logPOuter",
#     "offsetKeck",
#     "offsetFeros",
#     "offsetDupont",
#     "logRhoS",
#     "logThetaS",
#     "parallax",
#     "aAngInner",
#     "aInner",
#     "logPInner",
#     "PInner",
#     "eInner",
#     "omegaInner",
#     "OmegaInner",
#     "cosInclInner",
#     "inclInner",
#     "tPeriastronInner",
#     "MA",
#     "MAa",
#     "POuter",
#     "omegaOuter",
#     "OmegaOuter",
#     "phiOuter",
#     "tPeriastronOuter",
#     "cosInclOuter",
#     "inclOuter",
#     "eOuter",
#     "gammaOuter",
#     "MB",
#     "Mtot",
#     "aOuter",
#     "aAngOuter",
#     "logjittercfa",
#     "logjitterkeck",
#     "logjitterferos",
#     "logjitterdupont",
#     "jitCfa",
#     "jitKeck",
#     "jitFeros",
#     "jitDupont",
#     "iDisk",
#     "OmegaDisk",
#     "thetaDiskInner",
#     "thetaInnerOuter",
#     "thetaDiskOuter",
# ]


from logging import info

from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import QFrame, QIcon, QLabel, QTabWidget, \
                        QVBoxLayout, QAction, QCheckBox, \
                        QSizePolicy

from ufwi_rpcd.common import tr
from ufwi_conf.client.qt.toolbar import ToolBar
from ufwi_conf.client.qt.message_area import MessageArea
from ufwi_conf.client.qt.full_featured_scrollarea import FullFeaturedScrollArea
from ufwi_conf.common.user_dir import (NuauthCfg,
    LDAP, LDAPOrg, SameAsOrgAuth,
    auth_protocols, org_protocols, auth_class, org_class)
from ufwi_conf.common.user_dir import NOT_CONFIGURED
from ufwi_conf.common.user_dir.protocols import NND

from .dir_chooser import VariableConfigFrontend, AUTH, GROUP
from .tests_dialog import DirectoryTest

class NuauthFrontEnd(FullFeaturedScrollArea):
    COMPONENT = 'nuauth'
    LABEL = tr('User directory')
    REQUIREMENTS = ('nuauth',)
    ICON = ':/icons/users.png'

    def __init__(self, client, parent):
        #Parent constructor
        self.config = None
        self.auth_page = None
        self.group_page = None
        self.auth_configs = {}
        self.group_configs = {}
        self.mainwindow = parent
        self._module_disabled = False
        FullFeaturedScrollArea.__init__(self, client, parent)

        self.selected_auth = LDAP
        self.auth_configs[self.selected_auth] = LDAPOrg()
        self.selected_group = LDAP
        self.group_configs[self.selected_group] = LDAPOrg()

    @staticmethod
    def get_calls():
        """
        services called by initial multicall
        """
        return (( 'nuauth', 'getNuauthConfig'), ( 'nuauth', 'availableModules'))

    def buildInterface(self):
        available_modules = self.mainwindow.init_call('nuauth', 'availableModules')
        if available_modules is None:
            self._no_backend()
            return

        # This boolean to support server versions 4.0.12/13. The new fake
        # protocol NOT_CONFIGURED has been added recently, but other
        # protocols are unchanged
        option_no_directory_available = True

        server_auth_protocols = available_modules['auth']
        for module in auth_protocols:
            if not module in server_auth_protocols:
                if module == NOT_CONFIGURED:
                    option_no_directory_available = False
                    #we continue and simply won't show checkbox
                    continue
                info("[User directory] Unsupported auth module, server side: %s (got %s)" % (module, server_auth_protocols))
                self._ask_server_upgrade()
                return

        server_org_protocols = available_modules['group']
        for module in org_protocols:
            if not module in server_org_protocols:
                if module == NOT_CONFIGURED:
                    option_no_directory_available = False
                    #we continue and simply won't show checkbox
                    continue
                if module == NND:
                    continue
                info("[User directory] Unsupported org module, server side: %s (got %s)" % (module, server_org_protocols))
                self._ask_server_upgrade()
                return

        self.mkToolbar()

        for module in available_modules['auth']:
            if module not in auth_protocols:
                self.mainwindow.addToInfoArea(
                    tr("User authentication: cannot handle the following protocol: ") +
                    module
                )
        for module in available_modules['group']:
            if module not in org_protocols:
                self.mainwindow.addToInfoArea(
                    tr("Organization: cannot handle the following protocol: ") +
                    module
                )

        frame = QFrame()
        self.setWidget(frame)
        self.setWidgetResizable(True)
        v_box = QVBoxLayout(frame)

        title = u'<h1>%s</h1>' % self.tr('Directory Configuration')
        v_box.addWidget(QLabel(title))

        self.use_directory = QCheckBox(tr("Use a user directory"))
        self.mainwindow.writeAccessNeeded(self.use_directory)
        self.tabs = QTabWidget()
#        self.tabs.setTabPosition(QTabWidget.West)

        #signature: QWidget * page, const QIcon & icon, const QString & label
        self.tabs.addTab(*self.mkUsersTab(available_modules['auth']))
        self.tabs.addTab(*self.mkGroupsTab(available_modules['group']))
        self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.connect(self.use_directory, SIGNAL("stateChanged (int)"), self.use_directory_state)

        v_box.addWidget(self.use_directory)
        v_box.addWidget(self.tabs)
        self.use_directory.setVisible(option_no_directory_available)

        self.message_area = MessageArea()
        v_box.addWidget(self.message_area)

    def use_directory_state(self, state):
        noconf = (state != Qt.Checked)
        if noconf:
            self.config = NuauthCfg()
            self.auth_page.setViewData(self.config)
            self.group_page.setViewData(self.config)
        elif not self.config.isConfigured():
            self.config = NuauthCfg(auth=SameAsOrgAuth(), org=LDAPOrg())
            self.auth_page.setViewData(self.config)
            self.group_page.setViewData(self.config)
        self.tabs.setEnabled(not noconf)
        self.setModified()

    def mkToolbar(self):
        self.test_action = QAction(
            QIcon(":/icons-32/auth_protocol.png"),
            tr("Test current configuration"),
            self
            )
        self.contextual_toolbar = ToolBar((self.test_action,), self, 'User directory')

        self.connect(self.test_action, SIGNAL('triggered(bool)'), self.testdialog)

    def testdialog(self):
        dialog = DirectoryTest(self.client, self)
        dialog.exec_()

    def reemit(self, *args):
        self.setModified()

    def mkUsersTab(self, available_modules):
        self.auth_page = VariableConfigFrontend(
            available_modules,
            self.config,
            self.auth_configs,
            AUTH,
            self.mainwindow
            )
        icon = QIcon(':/icons/one_user')
        self.connect(self.auth_page, SIGNAL('modified'), self.reemit)
        self.auth_page = self.auth_page
        self.mainwindow.writeAccessNeeded(self.auth_page)
        return self.auth_page, icon, tr('Authentication')

    def mkGroupsTab(self, available_modules):
        self.group_page = VariableConfigFrontend(
            available_modules,
            self.config,
            self.group_configs,
            GROUP,
            self.mainwindow
            )
        icon = QIcon(':/icons/users')
        self.connect(self.group_page, SIGNAL('modified'), self.reemit)
        self.mainwindow.writeAccessNeeded(self.group_page)
        return self.group_page, icon, tr('Groups')

    def _ask_server_upgrade(self):
        self._disable(
            tr("Client-server version mismatch"),
            tr(
                "This version of the user interface cannot handle the current "
                 "software version of the appliance. The appliance may need upgrading."
            ),
            tr("User directory interface disabled because of a version mismatch.")
        )

    def _no_backend(self):
        self._disable(
            tr("Could not fetch user directory parameters"),
            tr(
                "Problem while fetching the server configuration "
                "for user directory"
            ),
            tr(
                "The user directory interface is disabled because the configuration "
                "could not be fetched properly."
            )
        )

    def _disable(self, title, message, main_message):
        if self._module_disabled:
            #already done
            return
        self._module_disabled = True
        msg = MessageArea()
        msg.setMessage(
            title,
            message,
            "critical"
            )
        msg.setWidth(65)
        self.setWidget(msg)
        self.mainwindow.addToInfoArea(
            main_message
            )
        self.setWidgetResizable(True)

    def fetchConfig(self):
        serialized = self.mainwindow.init_call('nuauth', 'getNuauthConfig')
        if serialized is None:
            self._no_backend()
            return

        #DON'T REMOVE: 3 is a major turn in this module conception
        if serialized['DATASTRUCTURE_VERSION'] < 3:
            self._ask_server_upgrade()
            return

        self.config = NuauthCfg.deserialize(serialized)
        self.selected_auth = self.config.auth.protocol
        self.selected_group = self.config.org.protocol

        #instantiate all data structures
        if len(self.auth_configs) == 0:
            for protocol in auth_protocols:
                if protocol == self.selected_auth:
                    self.auth_configs[protocol] = self.config.auth
                else:
                    self.auth_configs[protocol] = auth_class(protocol)()
        self.auth_configs[self.selected_auth] = self.config.auth

        if len(self.group_configs) == 0:
            for protocol in org_protocols:
                if protocol == self.selected_group:
                    continue
                self.group_configs[protocol] = org_class(protocol)()
        self.group_configs[self.selected_group] = self.config.org

    def setViewData(self):
        if self._module_disabled:
            return
        configured = self.config.isConfigured()
        self.use_directory_state(Qt.Checked if configured else Qt.Unchecked)
        self.use_directory.setChecked(configured)
        for page in (self.auth_page, self.group_page):
            if page is not None:
                page.setViewData(self.config)

    def sendConfig(self, message):
        if not self.config.isValidWithMsg(use_state=True)[0]:
            return
        serialized = self.config.serialize(downgrade=True)
        self.client.call('nuauth', 'setNuauthConfig', serialized, message)

    def isValid(self):
        ok, msg = self.config.isValidWithMsg(use_state=True)

        if not ok:
            self.error_message = msg    # used by main_window
        return ok


"""empty message

Revision ID: 483e7fd9289
Revises: None
Create Date: 2014-07-26 14:56:22.713768

"""

# revision identifiers, used by Alembic.
revision = '483e7fd9289'
down_revision = None

from alembic import op
import sqlalchemy as sa

def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.create_table('trade',
    sa.Column('id', sa.String(length=32), nullable=False),
    sa.Column('creator', sa.String(length=32), nullable=True),
    sa.Column('creator_flair', sa.String(length=256), nullable=True),
    sa.Column('target', sa.String(length=32), nullable=True),
    sa.Column('target_flair', sa.String(length=256), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    ### end Alembic commands ###

def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('trade')
    ### end Alembic commands ###


import copy
from abc import ABC
from collections import OrderedDict
from typing import Optional

import numpy as np

from kenning.core.dataset import Dataset
from kenning.core.model import ModelWrapper
from kenning.utils.resource_manager import PathOrURI

class PyTorchWrapper(ModelWrapper, ABC):
    """
    Base model wrapper for PyTorch models.
    """

    def __init__(
        self,
        model_path: PathOrURI,
        dataset: Dataset,
        from_file: bool = True,
        model_name: Optional[str] = None,
    ):
        super().__init__(model_path, dataset, from_file, model_name)
        import torch

        self.device = torch.device(
            "cuda" if torch.cuda.is_available() else "cpu"
        )

    def load_weights(self, weights: OrderedDict):
        """
        Loads the model state using ``weights``.
        It assumes that the model structure is already defined.

        Parameters
        ----------
        weights : OrderedDict
            Dictionary used to load model weights.
        """
        self.model.load_state_dict(copy.deepcopy(weights))
        self.model.eval()

    def create_model_structure(self):
        """
        Recreates the model structure.
        Every PyTorchWrapper subclass has to implement its own architecture.
        """
        raise NotImplementedError

    def load_model(self, model_path: PathOrURI):
        import torch

        input_data = torch.load(self.model_path, map_location=self.device)

        # If the file contains only the weights
        # we have to recreate the model's structure
        # Otherwise we just load the model
        if isinstance(input_data, OrderedDict):
            self.create_model_structure()
            self.load_weights(input_data)
        elif isinstance(input_data, torch.nn.Module):
            self.model = input_data

    def save_to_onnx(self, model_path: PathOrURI):
        import torch

        self.prepare_model()
        x = tuple(
            torch.randn(spec["shape"], device="cpu")
            for spec in self.get_io_specification()["input"]
        )

        torch.onnx.export(
            self.model.to(device="cpu"),
            x,
            model_path,
            opset_version=11,
            input_names=[
                spec["name"] for spec in self.get_io_specification()["input"]
            ],
            output_names=[
                spec["name"] for spec in self.get_io_specification()["output"]
            ],
        )

    def save_model(self, model_path: PathOrURI, export_dict: bool = True):
        import torch

        self.prepare_model()
        if export_dict:
            torch.save(self.model.state_dict(), model_path)
        else:
            torch.save(self.model, model_path)

    def preprocess_input(self, X):
        import torch

        return torch.Tensor(np.array(X)).to(self.device)

    def postprocess_outputs(self, y):
        import torch

        if isinstance(y, torch.Tensor):
            return y.detach().cpu().numpy()
        if isinstance(y, np.ndarray):
            return y
        if isinstance(y, list):
            return [self.postprocess_outputs(_y) for _y in y]
        raise NotImplementedError

    def run_inference(self, X):
        self.prepare_model()
        return self.model(X)

    def get_framework_and_version(self):
        import torch

        return ("torch", torch.__version__)

    def get_output_formats(self):
        return ["onnx", "torch"]


import wikipediaapi
from wikibaseintegrator import WikibaseIntegrator
from wikibaseintegrator.wbi_config import config as wbi_config
from wikibaseintegrator import wbi_helpers
import requests

wbi_config[
    "USER_AGENT"
] = "MyWikibaseBot/1.0 (https://www.wikidata.org/wiki/User:Ada-2023-project-adaptateurs)"

def get_gender_wiki(wikiID, job):
    dictory = {"director": "P57", "producer": "P162", "writer": "P58"}
    key = dictory[job]
    wiki_wiki = wikipediaapi.Wikipedia("ada (ada@epfl.ch)", "en")
    wbi = WikibaseIntegrator()
    page_id = wiki_wiki.page(wikiID)
    page_name = requests.get(
        f"http://en.wikipedia.org/w/api.php?action=query&pageids={wikiID}&format=json"
    ).json()["query"]["pages"][
        wikiID
    ]  # ['title']
    qid_movie = requests.get(
        f"https://en.wikipedia.org/w/api.php?action=query&prop=pageprops&titles={page_name}&format=json"
    ).json()["query"]["pages"][wikiID]["pageprops"]["wikibase_item"]
    movie_item = wbi.item.get(entity_id=qid_movie)
    qid_directors = []
    for element in movie_item.get_json()["claims"]["P57"]:
        qid_directors.append(element["mainsnak"]["datavalue"]["value"]["id"])
    genders = []
    for element in qid_directors:
        genders.append(
            wbi.item.get(entity_id=element).get_json()["claims"]["P21"][0]["mainsnak"][
                "datavalue"
            ]["value"]["id"]
        )
    return genders

def get_gender_imdb(imdb_id, job, provided_name):
    try:
        imdb_id = '"tt' + imdb_id + '"'
        dictory = {"director": "P57", "producer": "P162", "writer": "P58"}
        key = dictory[job]
        wbi = WikibaseIntegrator()
        query = wbi_helpers.execute_sparql_query(
            "SELECT ?film ?filmLabel WHERE {?film wdt:P345 "
            + imdb_id
            + '.SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }}'
        )
        query = query["results"]["bindings"][0]["film"]["value"].split("/")[-1]

        movie_item = wbi.item.get(entity_id=query)
        qid_directors = []

        for element in movie_item.get_json()["claims"][key]:
            qid_directors.append(element["mainsnak"]["datavalue"]["value"]["id"])
        genders = []
        for element in qid_directors:
            item_get = wbi.item.get(entity_id=element).get_json()
            name = item_get["labels"]["en"]["value"]
            item = item_get["claims"]
            gender = item["P21"][0]["mainsnak"]["datavalue"]["value"]["id"]
            try:
                if name.lower() == provided_name.lower():
                    return gender
            except:
                first_name_id = item["P735"][0]["mainsnak"]["datavalue"]["value"]["id"]
                last_name_id = item["P734"][0]["mainsnak"]["datavalue"]["value"]["id"]
                first_name = wbi.item.get(entity_id=first_name_id).get_json()["labels"][
                    "en"
                ]["value"]
                last_name = wbi.item.get(entity_id=last_name_id).get_json()["labels"][
                    "en"
                ]["value"]
                name = first_name + " " + last_name
                provided_name = provided_name.split(" ")
                provided_name = provided_name[0] + " " + provided_name[-1]
                if name.lower() == provided_name.lower():
                    return gender
        return None
    except:
        return None

def get_IMDB_ID(wikiID):
    wiki_wiki = wikipediaapi.Wikipedia("ada (ada@epfl.ch)", "en")
    wbi = WikibaseIntegrator()
    page_id = wiki_wiki.page(wikiID)
    page_name = requests.get(
        f"http://en.wikipedia.org/w/api.php?action=query&pageids={wikiID}&format=json"
    ).json()["query"]["pages"][wikiID]["title"]
    qid_movie = requests.get(
        f"https://en.wikipedia.org/w/api.php?action=query&prop=pageprops&titles={page_name}&format=json"
    ).json()["query"]["pages"][wikiID]["pageprops"]["wikibase_item"]
    movie_item = wbi.item.get(entity_id=qid_movie)
    IMDB_ID = movie_item.get_json()["claims"]["P345"][0]["mainsnak"]["datavalue"][
        "value"
    ]
    return IMDB_ID

def get_gender_id(QID):
    if QID == None:
        return 0.0
    dictory = {
        "Q6581097": 2.0,
        "Q6581072": 1.0,
        "Q1052281": 1.0,
        "Q2449503": 2.0,
        "Q15145779": 1.0,
        "Q15145778": 2.0,
        "Q121307094": 2.0,
        "Q121307100": 1.0,
    }
    try:
        value = dictory[QID]
        return value
    except KeyError:
        return 0.0


import requests
from ..api.error import IdentifierError
BASE_URL = 'https://monitoringapi.solaredge.com'

class Battery(object):

    """
        Object for getting API details about batteries related to a site
    """
    def __init__(self, client):
        self.client = client

    def get_site_storage_data(self, site_id, startTime, endTime):
        """
            Returns the Storage data(Battery) for a specific site.
            Parameters:
                site_id (int): the ID of a site location (can be fetched from the get_sites function)
                startTime (datetime): the start date and time of the data.
                endTime (datetime): the end date and time of the data.
            Returns:
                response (JSON): a JSON dictionary containing the battery data
        """
        if not site_id:
            raise IdentifierError("This API call needs to have a site_id.")
        if not startTime:
            raise IdentifierError("This API call needs to have startTime.")
        if not endTime:
            raise IdentifierError("This API call needs to have endTime.")

        api_endpoint = '/site/%s/storageData' % site_id
        full_api_url = BASE_URL + api_endpoint
        response = requests.get(full_api_url, params={
            'api_key': self.client.get_api_key(),
            'startTime': startTime.strftime("%Y-%m-%d %H:%M:%S"),
            'endTime': endTime.strftime("%Y-%m-%d %H:%M:%S")
        })
        return response.json()


#!/usr/bin/env python3

import argparse
import json
from operator import ge
import os
import s2sphere
import subprocess
import time
import urllib.request
import unittest

import diagonal_b6 as b6

# TODO: Harmonise these IDs with those in src/diagonal.works/diagonal/test/world.go
COAL_DROPS_YARD_WEST_BUILDING_ID = 222021572
COAL_DROPS_YARD_ENCLOSURE_ID = 500008118
JUBILEE_GREENWAY_ID = 380856
STABLE_STREET_BRIDGE_ID = 140633010
STABLE_STREET_BRIDGE_NORTH_END_ID = 1447052073
STABLE_STREET_BRIDGE_SOUTH_END_ID = 1540349979
VERMUTERIA_NODE_ID = 6082053666
GRANARY_SQUARE_WAY_ID = 222021571
LIGHTERMAN_WAY_ID = 427900370

BUILDINGS_IN_GRANARY_SQUARE = 13
HIGHWAYS_IN_GRANARY_SQUARE = 117
BIKE_PARKING_IN_GRANARY_SQUARE = 11
FOUNTAINS_IN_GRANARY_SQUARE = 4 # Within the pedestrian square itself defined by WKT below not the entire area
STABLE_STREET_BRIDGE_NORTH_END_DEGREE = 7 # After it has been connected

GRANARY_SQUARE_POLYGON_WKT="POLYGON ((-0.1260475 51.5357019,-0.1261001 51.5355674,-0.1261596 51.5354153,-0.1262097 51.535287,-0.1259034 51.5352365,-0.1259462 51.5351347,-0.1255806 51.5350765,-0.1255202 51.5350667,-0.1255004 51.5350372,-0.1254536 51.5349963,-0.1254346 51.5350013,-0.1252611 51.535049,-0.125219 51.5350629,-0.124904 51.5350121,-0.1247915 51.5350326,-0.124709 51.5350541,-0.1247491 51.5351308,-0.1247727 51.5351758,-0.1246766 51.5353808,-0.1246363 51.5354737,-0.125082 51.5355458,-0.1259754 51.5356902,-0.1260475 51.5357019))"
GRANARY_SQUARE_MULTIPOLYGON_WKT="MULTIPOLYGON (((-0.1260475 51.5357019,-0.1261001 51.5355674,-0.1261596 51.5354153,-0.1262097 51.535287,-0.1259034 51.5352365,-0.1259462 51.5351347,-0.1255806 51.5350765,-0.1255202 51.5350667,-0.1255004 51.5350372,-0.1254536 51.5349963,-0.1254346 51.5350013,-0.1252611 51.535049,-0.125219 51.5350629,-0.124904 51.5350121,-0.1247915 51.5350326,-0.124709 51.5350541,-0.1247491 51.5351308,-0.1247727 51.5351758,-0.1246766 51.5353808,-0.1246363 51.5354737,-0.125082 51.5355458,-0.1259754 51.5356902,-0.1260475 51.5357019)))"

EARTH_RADIUS_METERS = 6371.01 * 1000.0

def angle_to_meters(angle):
	return angle.radians * EARTH_RADIUS_METERS

class B6Test(unittest.TestCase):

    def __init__(self, name, connection):
        unittest.TestCase.__init__(self, name)
        self.connection = connection

    def test_get_tag(self):
        name = b6.find_feature(b6.osm_way_area_id(LIGHTERMAN_WAY_ID)).get("name")
        self.assertEqual(("name", "The Lighterman"), self.connection(name))

    def test_find_areas(self):
        names = [building.get_string("name") for (id, building) in self.connection(b6.find_areas(b6.keyed("#building")))]
        self.assertEqual(len(names), BUILDINGS_IN_GRANARY_SQUARE)

    def test_find_point_by_id(self):
        area = self.connection(b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)))
        self.assertEqual(area.id.value, STABLE_STREET_BRIDGE_SOUTH_END_ID)

    def test_find_area_by_id(self):
        area = self.connection(b6.find_area(b6.osm_way_area_id(COAL_DROPS_YARD_WEST_BUILDING_ID)))
        self.assertEqual(area.id.value, COAL_DROPS_YARD_WEST_BUILDING_ID)

    def test_find_non_existant_id(self):
        self.assertEqual(self.connection(b6.find_point(b6.osm_node_id(42))), None)

    def test_find_area_by_wrong_id_type(self):
        with self.assertRaises(Exception): # TODO: Make more specific
            self.connection(b6.find_area(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)))

    def test_uk_ons_boundary_id(self):
        self.assertEqual(b6.uk_ons_boundary_id("E01000953").value, 76343044687353)

    def test_area_str(self):
        area = self.connection(b6.find_area(b6.osm_way_area_id(COAL_DROPS_YARD_WEST_BUILDING_ID)))
        self.assertEqual("<Area /area/openstreetmap.org/way/222021572>", str(area))

    def test_relation_members(self):
        greenway = self.connection(b6.find_relation(b6.osm_relation_id(JUBILEE_GREENWAY_ID)))
        paths = ([str(m) for m in greenway.members() if m.is_path()])
        self.assertGreater(len(paths), 10)
        self.assertLess(len(paths), 800)

    def test_or_query(self):
        names = [amenity.get_string("name") for (id, amenity) in self.connection(b6.find(b6.tagged("#amenity", "restaurant").or_(b6.tagged("#amenity", "cafe"))))]
        self.assertIn("Le Cafe Alain Ducasse", names)

    def test_point_degree(self):
        self.assertEqual(self.connection(b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_NORTH_END_ID)).degree()), STABLE_STREET_BRIDGE_NORTH_END_DEGREE)
        degrees = [degree for (id, degree) in self.connection(b6.find_points(b6.within_cap(b6.ll(51.535241, -0.124364), 100)).degree())]
        for d in degrees:
            self.assertGreaterEqual(d, 0)
            self.assertLess(d, 10)

    def test_send_evaluated_feature_back_to_server(self):
        point = self.connection(b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_NORTH_END_ID)))
        degree = self.connection(b6.degree(point))
        self.assertEqual(degree, self.connection(b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_NORTH_END_ID)).degree()))

    def test_path_lengths(self):
        lengths = [length for (id, length) in self.connection(b6.find_paths(b6.keyed("#highway")).length())]
        self.assertGreater(len(lengths), 0)
        for l in lengths:
            self.assertGreater(l, 0)
            self.assertLess(l, 1000)

    def test_relation_names(self):
        names = [r.get_string("name") for (id, r) in self.connection(b6.find_relations(b6.keyed("#route")))]
        self.assertIn("Jubilee Greenway", names)

    def test_reachable_areas_from_point(self):
        expression = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).reachable("walk", 200.0, b6.keyed("#amenity")).get_string("name")
        names = set([name for (_, name) in self.connection(expression)])
        self.assertIn("The Lighterman", names)

    def test_reachable_with_distance(self):
        small = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).reachable("walk", 100.0, b6.keyed("#amenity")).count()
        large = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).reachable("walk", 200.0, b6.keyed("#amenity")).count()
        self.assertGreater(self.connection(large), self.connection(small))

    def test_paths_to_reach(self):
        expression = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).paths_to_reach("walk", 200.0, b6.keyed("#amenity"))
        paths = list(self.connection(expression))
        self.assertGreaterEqual(len(paths), 4)
        for path, count in paths:
            self.assertGreaterEqual(count, 1)
            self.assertLess(count, 100)

    def test_accessible(self):
        origins = [b6.find_feature(b6.osm_node_id(STABLE_STREET_BRIDGE_NORTH_END_ID))]
        destinatations = list(self.connection(b6.accessible(origins, b6.keyed("entrance"), 500, {"mode": "walk"})))
        self.assertGreater(len(destinatations), 2)

    def test_closest_from_point(self):
        expression = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).closest("walk", 1000.0, b6.tagged("#amenity", "pub"))
        pub = self.connection(expression)
        self.assertEqual("The Lighterman", pub.get_string("name"))

    def test_closest_from_point_distance(self):
        expression = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).closest_distance("walk", 1000.0, b6.tagged("#amenity", "pub"))
        distance = self.connection(expression)
        self.assertGreater(distance, 100.0)
        self.assertLess(distance, 105.0)

    def test_closest_from_area(self):
        expression = b6.find_area(b6.osm_way_area_id(COAL_DROPS_YARD_WEST_BUILDING_ID)).closest("walk", 1000.0, b6.tagged("#amenity", "pub"))
        pub = self.connection(expression)
        self.assertEqual("The Lighterman", pub.get_string("name"))

    def test_closest_from_point_non_existant(self):
        expression = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).closest("walk", 1000.0, b6.tagged("#amenity", "nonexistant"))
        self.assertEqual(None, self.connection(expression))

    def test_containing_areas_from_point(self):
        expression = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).reachable_points("walk", 1000.0, b6.all()).containing_areas(b6.keyed("#shop")).get_string("name")
        names = set([name for (_, name) in self.connection(expression)])
        self.assertIn("Coal Drops Yard", names)

    def test_containing_areas_from_area(self):
        areas = self.connection(b6.find_area(b6.osm_way_area_id(COAL_DROPS_YARD_WEST_BUILDING_ID)).reachable_points("walk", 1000.0, b6.all()).containing_areas(b6.all()))
        self.assertGreater(len(areas), 10)

    def test_count_features(self):
        self.assertEqual(self.connection(b6.find_points(b6.tagged("#amenity", "bicycle_parking")).count()), BIKE_PARKING_IN_GRANARY_SQUARE)
        self.assertEqual(self.connection(b6.find_paths(b6.keyed("#highway")).count()), HIGHWAYS_IN_GRANARY_SQUARE)
        self.assertEqual(self.connection(b6.find_areas(b6.keyed("#building")).count()), BUILDINGS_IN_GRANARY_SQUARE)

    def test_divide_count_features(self):
        self.assertAlmostEqual(self.connection(b6.find_points(b6.tagged("#amenity", "bicycle_parking")).count().divide(10.0)), BIKE_PARKING_IN_GRANARY_SQUARE / 10.0)

    def test_filter(self):
        filtered = self.connection(b6.find_areas(b6.keyed("#amenity")).filter(lambda a: b6.matches(a, b6.keyed("addr:postcode"))))
        self.assertGreater(len(filtered), 0)
        for (_, feature) in filtered:
            self.assertNotEqual(feature.get_string("addr:postcode"), "")

    def test_filter_with_implicit_function(self):
        filtered = self.connection(b6.find(b6.tagged("#amenity", "restaurant")).filter(b6.tagged("cuisine", "indian")).map(lambda f: b6.get_string(f, "name")))
        self.assertEqual(["Dishoom"], [name for (id, name) in filtered])

    def test_to_geojson_collection(self):
        geojson = self.connection(b6.to_geojson_collection(b6.find_areas(b6.keyed("#building"))))
        self.assertGreater(len(geojson["features"]), 4)
        for feature in geojson["features"]:
            self.assertIn("#building", feature["properties"])

    def test_to_geojson_with_feature(self):
        closest = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).closest("walk", 1000.0, b6.tagged("#amenity", "pub"))
        geojson = self.connection(b6.to_geojson(closest))
        self.assertEqual(geojson["type"], "Feature")

    def test_to_geojson_with_missing_feature(self):
        geojson = self.connection(b6.to_geojson(b6.find_point(b6.osm_node_id(1))))
        self.assertEqual(len(geojson["features"]), 0)

    def test_search_within_wkt_polygon(self):
        granarySquare = b6.wkt(GRANARY_SQUARE_POLYGON_WKT)
        self.assertEqual(self.connection(b6.find_areas(b6.tagged("#amenity", "fountain").and_(b6.intersecting(granarySquare))).count()), FOUNTAINS_IN_GRANARY_SQUARE)

    def test_search_within_wkt_multipolygon(self):
        granarySquare = b6.wkt(GRANARY_SQUARE_MULTIPOLYGON_WKT)
        self.assertEqual(self.connection(b6.find_areas(b6.tagged("#amenity", "fountain").and_(b6.intersecting(granarySquare))).count()), FOUNTAINS_IN_GRANARY_SQUARE)

    def test_add_tags(self):
        applied = self.connection(b6.add_tags(b6.find_areas(b6.keyed("#building")).map(lambda building: b6.tag("diagonal:colour", building.get_string("building:levels")))))
        self.assertEqual(len(applied), BUILDINGS_IN_GRANARY_SQUARE)

    def test_add_tags_with_filter(self):
        applied = self.connection(b6.add_tags(b6.find_paths(b6.tagged("#highway", "footway")).filter(b6.keyed("bicycle")).map(lambda h: b6.tag("#bicycle", h.get_string("bicycle")))))
        self.assertGreater(len(applied), 0)
        self.assertEqual(self.connection(b6.find_paths(b6.keyed("#bicycle")).count()), len(applied))

    def test_search_for_newly_added_tag(self):
        reachable = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).reachable("walk", 1000.0, b6.keyed("#amenity"))
        modified = self.connection(b6.add_tags(reachable.map(lambda building: b6.tag("#reachable", "yes"))))
        self.assertGreater(len(modified), 1)
        self.assertEqual(self.connection(b6.find(b6.keyed("#reachable")).count()), len(modified))

    def test_sample_points_along_path(self):
        count = len(self.connection(b6.find_path(b6.osm_way_id(STABLE_STREET_BRIDGE_ID)).sample_points(1.0)))
        self.assertGreater(count, 20)
        self.assertLess(count, 40)

    def test_sample_points_along_paths(self):
        count = 0
        center = s2sphere.LatLng.from_degrees(51.53539, -0.12537)
        for _, ll in self.connection(b6.find_paths(b6.keyed("#highway")).sample_points_along_paths(20.0)):
            d = s2sphere.LatLng.from_degrees(ll[0], ll[1]).get_distance(center)
            self.assertLess(angle_to_meters(d), 500.0)
            count += 1
        self.assertGreater(count, 300)
        self.assertLess(count, 350)

    def test_sightline(self):
        a1 = self.connection(b6.ll(51.53557, -0.12585).sightline(250.0).area())
        a2 = self.connection(b6.ll(51.53557, -0.12585).cap_polygon(250.0).area())
        self.assertGreater(a1/a2, 0.20)
        self.assertLess(a1/a2, 0.30)

    def test_sightline_geojson(self):
        geojson = self.connection(b6.to_geojson(b6.ll(51.53557, -0.12585).sightline(250.0)))
        self.assertEqual(geojson["type"], "Feature")
        self.assertEqual(geojson["geometry"]["type"], "Polygon")
        bridge = b6.osm_way_id(STABLE_STREET_BRIDGE_ID)
        geojson = self.connection(b6.to_geojson_collection(b6.find_path(bridge).sample_points(5.0).map(lambda point: b6.sightline(point, 250.0))))
        self.assertGreater(len(geojson["features"]), 5)
        self.assertLess(len(geojson["features"]), 10)

    def test_s2_points(self):
        count = len(self.connection(b6.find_area(b6.osm_way_area_id(GRANARY_SQUARE_WAY_ID)).s2_points(21, 21)))
        self.assertGreater(count, 400)
        self.assertLess(count, 500)

    def test_rectangle_polygon(self):
        area = self.connection(b6.rectangle_polygon(b6.ll(51.5146, -0.1140), b6.ll(51.5124, -0.0951)).area())
        self.assertGreater(area, 300000)
        self.assertLess(area, 400000)

    def test_s2_grid(self):
        topLeft = (51.5146, -0.1140)
        bottomRight = (51.5124, -0.0951)
        grid = self.connection(b6.rectangle_polygon(b6.ll(*topLeft), b6.ll(*bottomRight)).s2_grid(21))
        rect = s2sphere.LatLngRect.from_point_pair(s2sphere.LatLng.from_degrees(*topLeft), s2sphere.LatLng.from_degrees(*bottomRight))
        for _, token in grid:
            cell = s2sphere.Cell(s2sphere.CellId.from_token(token))
            self.assertEqual(cell.level(), 21)
            self.assertTrue(rect.intersects(cell.get_rect_bound()))

    def test_s2_covering(self):
        topLeft = (51.5146, -0.1140)
        bottomRight = (51.5124, -0.0951)
        covering = self.connection(b6.rectangle_polygon(b6.ll(*topLeft), b6.ll(*bottomRight)).s2_covering(0, 30))
        rect = s2sphere.LatLngRect.from_point_pair(s2sphere.LatLng.from_degrees(*topLeft), s2sphere.LatLng.from_degrees(*bottomRight))
        count = 0
        for _, token in covering:
            count += 1
            cell = s2sphere.Cell(s2sphere.CellId.from_token(token))
            self.assertTrue(rect.intersects(cell.get_rect_bound()))
        self.assertLess(count, 10)

    def test_s2_center(self):
        # TODO: Rewrite without using string() once we've sorted out node types
        (lat, lng) = self.connection(b6.s2_center("487604b4fbdc"))
        ll = s2sphere.LatLng.from_degrees(lat, lng)
        expected = s2sphere.LatLng.from_degrees(51.5126733, -0.1140124)
        self.assertLess(ll.get_distance(expected).radians, 0.000001)

    def test_join_paths(self):
        a = self.connection(b6.find_path(b6.osm_way_id(377974549)))
        b = self.connection(b6.find_path(b6.osm_way_id(834245629)))
        joined = self.connection(b6.find_path(b6.osm_way_id(377974549)).join(b6.find_path(b6.osm_way_id(834245629))))
        self.assertLess(abs((joined.length_meters() / (a.length_meters() + b.length_meters())) - 1.0), 0.0001)

    def test_ordered_join_paths(self):
        a = self.connection(b6.find_path(b6.osm_way_id(377974549)))
        b = self.connection(b6.find_path(b6.osm_way_id(834245629)))
        joined = self.connection(b6.find_path(b6.osm_way_id(377974549)).ordered_join(b6.find_path(b6.osm_way_id(834245629))))
        self.assertLess(abs((joined.length_meters() / (a.length_meters() + b.length_meters())) - 1.0), 0.0001)

    def test_points(self):
        points = self.connection(b6.find_path(b6.osm_way_id(STABLE_STREET_BRIDGE_ID)).points())
        expected = s2sphere.LatLng.from_degrees(51.535035, -0.1247934)
        ll = s2sphere.LatLng.from_degrees(*points[0][1])
        self.assertLess(ll.get_distance(expected).radians, 0.000001)

    def test_point_features(self):
        points = self.connection(b6.find_path(b6.osm_way_id(STABLE_STREET_BRIDGE_ID)).point_features())
        self.assertEqual(len(points), 2)
        self.assertEqual(b6.osm_node_id(STABLE_STREET_BRIDGE_NORTH_END_ID), points[0][0])

    def test_paths_by_point(self):
        paths = self.connection(b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_NORTH_END_ID)).point_paths())
        self.assertIn(b6.osm_way_id(STABLE_STREET_BRIDGE_ID), [id for (id, _) in paths])

    def test_interpolate(self):
        (lat, lng) = self.connection(b6.find_path(b6.osm_way_id(377974549)).interpolate(0.5))
        ll = s2sphere.LatLng.from_degrees(lat, lng)
        expected = s2sphere.LatLng.from_degrees(51.5361869, -0.1258445)
        self.assertLess(ll.get_distance(expected).radians, 0.000001)

    def test_snap_area_edges(self):
        building = b6.find_area(b6.osm_way_area_id(COAL_DROPS_YARD_WEST_BUILDING_ID))
        original_area = self.connection(building.area())
        snapped_area = self.connection(b6.snap_area_edges(building, b6.keyed("#highway"), 40.0).area())
        self.assertGreater(snapped_area, original_area)

    def test_geojson_map_areas(self):
        building = b6.find_area(b6.osm_way_area_id(COAL_DROPS_YARD_WEST_BUILDING_ID))
        original_area = self.connection(building.area())
        snapped = b6.to_geojson(building).map_geometries(b6.apply_to_area(lambda a: b6.snap_area_edges(a, b6.keyed("#highway"), 40.0)))
        areas = snapped.geojson_areas().map(lambda a: a.area())
        _, snapped_area = self.connection(areas)[0]
        self.assertGreater(snapped_area, original_area)

    def test_collect_areas(self):
        collected_areas = self.connection(b6.collect_areas(b6.find_areas(b6.keyed("#building"))).area())
        summed_areas = 0
        for _, area in self.connection(b6.find_areas(b6.keyed("#building")).map(lambda b: b.area())):
            summed_areas += area
        self.assertLess((collected_areas - summed_areas)/summed_areas, 0.0001)

    def test_distance_to_point_meters(self):
        distance = self.connection(b6.find_path(b6.osm_way_id(377974549)).distance_to_point_meters(b6.ll(51.53586, -0.12564)))
        self.assertGreater(distance, 24.0)
        self.assertLess(distance, 25.0)

    def test_centroid(self):
        expected = b6.ll(51.5352611, -0.1243803)
        distance = self.connection(b6.distance_meters(b6.find_area(b6.osm_way_area_id(LIGHTERMAN_WAY_ID)).centroid(), expected))
        self.assertLess(distance, 0.1)

    def test_centroids(self):
        origin = b6.ll(51.5352611, -0.1243803)
        distances = self.connection(b6.find_areas(b6.keyed("#building")).centroid().map(lambda p: p.distance_meters(origin)))
        for f, distance in distances:
            self.assertLess(distance, 1000.0)

    def test_find_building_intersecting_point(self):
        point = b6.ll(51.5352611, -0.1243803)
        features = self.connection(b6.find(b6.and_(b6.tagged("#building", "yes"), b6.intersecting(point))))
        self.assertIn("The Lighterman", [f.get_string("name") for (_, f) in features])

    def test_map_area(self):
        areas = list(self.connection(b6.find_areas(b6.keyed("#building")).map(lambda building: building.area())))
        self.assertEqual(len(areas), BUILDINGS_IN_GRANARY_SQUARE)
        for (_, area) in areas:
            self.assertGreater(area, 50)
            self.assertLess(area, 10000)

    def test_path_length(self):
        bridge = self.connection(b6.find_path(b6.osm_way_id(STABLE_STREET_BRIDGE_ID)))
        self.assertGreater(bridge.length_meters(), 20.0)
        self.assertLess(bridge.length_meters(), 30.0)

    def test_connect_points(self):
        modified = self.connection(b6.find_point(b6.osm_node_id(VERMUTERIA_NODE_ID)).connect(b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_NORTH_END_ID))))
        self.assertEqual(len(modified), 1)
        for (_, id) in modified:
            self.assertTrue(id.is_path())
            self.assertEqual(id.namespace, b6.NAMESPACE_DIAGONAL_ACCESS_POINTS)

    def test_connect_point_to_network(self):
        modified = self.connection(b6.find_point(b6.osm_node_id(VERMUTERIA_NODE_ID)).connect_to_network())
        # The attempt to connect the point to the network fails, as the sample area
        # used by the tests is too small for us to consider any street as being part
        # of the street network, but the test at least verifies the wrapping of
        # network connection code
        self.assertEqual(len(modified), 0)

    def test_connect_area_to_network(self):
        modified = self.connection(b6.find_area(b6.osm_way_area_id(LIGHTERMAN_WAY_ID)).connect_to_network())
        self.assertEqual(len(modified), 0)

    def test_import_geojson_point(self):
        g = {
            "type": "FeatureCollection",
            "features": [
                {
                    "type": "Feature",
                    "geometry": {
                        "type": "Point",
                        "coordinates": [-0.1249292, 51.5352547],
                    },
                    "properties": {
                        "name": "Ruby Violet Truck",
                    }
                }
            ]
        }
        ids = self.connection(b6.import_geojson(b6.parse_geojson(json.dumps(g)), "diagonal.works/test"))
        self.assertEqual(len(ids), 1)
        self.assertEqual(self.connection(b6.find_point(ids[0][1]).get_string("name")), "Ruby Violet Truck")

    def test_import_geojson_path(self):
        g = {
            "type": "FeatureCollection",
            "features": [
                {
                    "type": "Feature",
                    "geometry": {
                        "type": "LineString",
                        "coordinates": [[-0.1251651, 51.5349089], [-0.1251580, 51.5347263]],
                    },
                    "properties": {
                        "bridge": "yes",
                    }
                }
            ]
        }
        ids = self.connection(b6.import_geojson(b6.parse_geojson(json.dumps(g)), "diagonal.works/test"))
        self.assertEqual(len(ids), 1)
        self.assertEqual(self.connection(b6.find_path(ids[0][1])).get_string("bridge"), "yes")

    def test_import_geojson_polygon(self):
        g = {
            "type": "FeatureCollection",
            "features": [
                {
                    "type": "Feature",
                    "geometry": {
                        "type": "Polygon",
                        "coordinates": [[[-0.1243817, 51.5354124], [-0.1243411, 51.5351416], [-0.1242415, 51.5353736]]],
                    },
                    "properties": {
                        "building": "yes",
                    }
                }
            ]
        }
        ids = self.connection(b6.import_geojson(b6.parse_geojson(json.dumps(g)), "diagonal.works/test"))
        self.assertEqual(len(ids), 1)
        self.assertEqual(self.connection(b6.find_area(ids[0][1])).get_string("building"), "yes")

    def test_import_geojson_multipolygon(self):
        g = {
            "type": "FeatureCollection",
            "features": [
                {
                    "type": "Feature",
                    "geometry": {
                        "type": "MultiPolygon",
                        "coordinates": [
                            [[[-0.1243817, 51.5354124], [-0.1243411, 51.5351416], [-0.1242415, 51.5353736]]],
                            [[[-0.1239823, 51.5358407], [-0.1240998, 51.5355521], [-0.1238063, 51.5358096]]],
                        ],
                    },
                    "properties": {
                        "building": "yes",
                    }
                }
            ]
        }
        ids = self.connection(b6.import_geojson(b6.parse_geojson(json.dumps(g)), "diagonal.works/test"))
        self.assertEqual(len(ids), 1)
        self.assertEqual(self.connection(b6.find_area(ids[0][1])).get_string("building"), "yes")

    def test_import_geojson_file(self):
        ids = self.connection(b6.import_geojson_file("data/tests/granary-square.geojson", "diagonal.works/test"))
        self.assertGreater(len(ids), 0)
        self.assertGreater(self.connection(b6.find_area(ids[0][1]).area()), 100.0)

    def test_parse_geojson_file(self):
        areas = b6.parse_geojson_file("data/tests/granary-square.geojson").geojson_areas()
        area = self.connection(b6.convex_hull(areas).area())
        self.assertGreater(area, 2400.0)
        self.assertLess(area, 2500.0)

    def test_reachable_with_changed_world(self):
        close_road = b6.remove_tag(b6.osm_way_id(STABLE_STREET_BRIDGE_ID), "#highway")
        reachable = b6.find_point(b6.osm_node_id(STABLE_STREET_BRIDGE_SOUTH_END_ID)).reachable("walk", 200.0, b6.keyed("#amenity")).get_string("name")
        before = len(self.connection(reachable))
        after = len(self.connection(b6.with_change(close_road, lambda: reachable)))
        self.assertGreater(before, after)

    def test_remove_tags(self):
        roads = b6.find(b6.keyed("#highway"))
        before = self.connection(roads.count())
        close_roads = b6.remove_tags(roads.map(lambda road: "#highway"))
        after = self.connection(b6.with_change(close_roads, lambda: roads.count()))
        self.assertGreater(before, 0)
        self.assertEqual(after, 0)

    def test_merge_changes(self):
        roads = b6.find(b6.keyed("#highway"))
        before = self.connection(roads.count())
        close_roads = b6.merge_changes(roads.map(lambda h: b6.remove_tag(h, "#highway")))
        after = self.connection(b6.with_change(close_roads, lambda: roads.count()))
        self.assertGreater(before, 0)
        self.assertEqual(after, 0)

    def test_get_tags_from_list_of_ids(self):
        names = b6.map([b6.osm_way_area_id(id) for id in (LIGHTERMAN_WAY_ID, GRANARY_SQUARE_WAY_ID)], lambda f: b6.get_string(f, "name"))
        expected = [(0, "The Lighterman"), (1, "Granary Square")]
        self.assertEqual(expected, self.connection(names))

    def test_make_tags_from_list_of_strings(self):
        tags = b6.map(["primary", "secondary"], lambda v: b6.tag("#highway", v))
        expected = [(0, ("#highway", "primary")), (1, ("#highway", "secondary"))]
        self.assertEqual(expected, self.connection(tags))

    def test_convex_hull_from_list_of_lat_lngs(self):
        caps = b6.map([b6.ll(51.535387, -0.125277), b6.ll(51.537088, -0.125781)], lambda c: b6.cap_polygon(c, 20.0))
        areas = self.connection(caps.map(b6.area))
        hull_area = self.connection(b6.convex_hull(caps).area())
        self.assertGreater(hull_area, sum([a for _, a in areas]))

    def test_collection(self):
        ids = b6.collection(b6.pair(0, b6.osm_way_area_id(GRANARY_SQUARE_WAY_ID)), b6.pair(1, b6.osm_way_area_id(LIGHTERMAN_WAY_ID)))
        areas = self.connection(ids.map(lambda id: b6.area(b6.find_area(id))))
        for (i, (j, area)) in enumerate(areas):
            self.assertEqual(i, j)
            self.assertGreater(area, 0.0)
            self.assertLess(area, 6000.0)

    def test_map_literal_collection_from_dict(self):
        collection = {
            b6.tag("highway", "motorway"): 3,
            b6.tag("highway", "primary"): 7,
        }
        result = self.connection(b6.map(collection, lambda count: b6.add(count, 1)))
        self.assertEqual([4, 8], sorted([count for ((key, value), count) in result]))

    def test_map_literal_collection_from_list(self):
        collection = [36, 42]
        result = self.connection(b6.map(collection, lambda count: b6.add(count, 1)))
        self.assertEqual([37, 43], sorted([count for (key, count) in result]))

    def test_flatten(self):
        parks = b6.tagged("#leisure", "park")
        grass = b6.tagged("#landuse", "grass")
        parks_count = self.connection(b6.find(parks).count())
        grass_count = self.connection(b6.find(grass).count())
        self.assertGreater(parks_count, 0)
        self.assertGreater(grass_count, 0)
        parks_and_grass = b6.map([parks, grass], lambda q: b6.find(q)).flatten()
        self.assertEqual(parks_count + grass_count, self.connection(parks_and_grass.count()))

    def test_add_relation(self):
        id = b6.id_to_relation_id("diagonal.works/test", b6.osm_way_id(STABLE_STREET_BRIDGE_ID))
        add = b6.add_relation(id, [b6.tag("#route", "bicycle")], {b6.osm_way_id(STABLE_STREET_BRIDGE_ID): "forwards"})
        route = self.connection(b6.with_change(add, lambda: b6.find_feature(id).get_string("#route")))
        self.assertEqual("bicycle", route)

    def test_materialise(self):
        collection_id = b6.FeatureID(b6.FEATURE_TYPE_COLLECTION, "diagonal.works/test", 1)
        roads = b6.find(b6.keyed("#highway"))
        n = self.connection(b6.with_change(b6.materialise(collection_id, lambda: roads), lambda: b6.count(b6.find_feature(collection_id))))
        self.assertGreater(n, 100)
        self.assertLess(n, 200)
        collection = self.connection(b6.with_change(b6.materialise(collection_id, lambda: roads), lambda: b6.find_feature(collection_id)))
        self.assertEqual(n, len(collection))
        self.assertEqual(n, len(list(collection)))
        self.assertIn(b6.osm_way_id(STABLE_STREET_BRIDGE_ID), [id for (id, _) in collection])

    def test_materialise_includes_expression(self):
        collection_id = b6.FeatureID(b6.FEATURE_TYPE_COLLECTION, "diagonal.works/test", 1)
        expression_id = b6.FeatureID(b6.FEATURE_TYPE_EXPRESSION, "diagonal.works/test", 1)
        roads = b6.find(b6.keyed("#highway"))
        expression = self.connection(b6.with_change(b6.materialise(collection_id, lambda: roads), lambda: b6.find_feature(expression_id)))
        # TODO: Test expression behaviour in Python, once it exists.
        self.assertIsNotNone(expression)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--http-port", default="10080", help="Host and port on which to serve HTTP")
    parser.add_argument("--grpc-port", default="10081", help="Host and port on which to serve GRPC")
    parser.add_argument("--platform", default=None, help="Current platform, eg darwin/x86_64")
    parser.add_argument("tests", type=str, nargs="*")
    args = parser.parse_args()
    http_address = "localhost:%d" % int(args.http_port)
    grpc_address = "localhost:%d" % int(args.grpc_port)
    platform = args.platform or os.environ.get("TARGETPLATFORM", None) or ("%s/%s" % (os.uname().sysname.lower(), os.uname().machine))
    p = subprocess.Popen([
        "bin/%s/b6" % platform,
        "--http=%s" % http_address,
        "--grpc=%s" % grpc_address,
        "--world=osm:data/tests/granary-square.osm.pbf"
    ])

    ready, response = wait_until_ready(p, http_address)
    if not ready:
        print("Server is not ready: %s" % response)
        return
    connection = b6.connect_insecure(grpc_address)
    suite = unittest.TestSuite()
    if len(args.tests) > 0:
        for test in args.tests:
            suite.addTest(B6Test(test, connection))
    else:
        for method in dir(B6Test):
            if method.startswith("test_"):
                suite.addTest(B6Test(method, connection))
    runner = unittest.TextTestRunner()
    runner.run(suite)
    p.terminate()
    p.wait()

def wait_until_ready(p, http_address):
    max_attempts = 20
    for i in range(max_attempts):
        if p.poll() is not None:
            return False, "Server exited with status %d" % p.poll()
        try:
            r = urllib.request.urlopen("http://%s/healthy" % http_address)
            if r.status == 200:
                response = r.read().strip()
                ok = b"ok"
                return response == ok, "Server got response: %s, expected %s" % (response, ok)
        except urllib.error.URLError:
            pass
        print("Waiting for server (attempt %d/%d)..." % (i+1, max_attempts))
        time.sleep(0.5)
    return False, "Timeout while waiting for server to start"

if __name__ == "__main__":
    main()


import os
from dotenv import load_dotenv
from chat import ChatGPT

load_dotenv()

API_KEY = os.getenv('OPENAI_API_KEY')

_MODELS_MAP = [
    {
        'name': 'DreamShaper XL1.0',
        'description': 'realistic images, graphics, and faces.',
        'unfit': f"This model is very unfriendly to Asians, especially Chinese, Japanese and Korean women, with single eyelids, zombie faces, and I don't know where the 'traditional' clothes come from, even if I add double eyelids and bikinis as cues, it still doesn't change the stereotypical image.",
    },
    {
        'name': 'Anything V5',
        'description': 'anime, 2D',
        'unfit': "actual 3D is so bad"
    }
]

def get_model_info(name, description, unfit):
    template = f"{name} is a model, It can generate {description} images, that defect is {unfit}"
    return template

model_info = []

for model in _MODELS_MAP:
    model_info.append(get_model_info(model['name'], model['description'], model['unfit']))

system_prompt = "Help me choose a model for my prompt. You just need to return the model name. model Info: " + ", ".join(model_info)

client = ChatGPT(
    key=API_KEY,
    model="gpt-4",
    system_prompt=system_prompt,
)

client.ask("A beautiful woman, dirty blonde hair with brown highlights, shoulder length hair, brown eyes, slightly wavy hair, Caucasian, in Greek armor, teenage girl, cartoon style, soft jawline, cartoon style")


from __future__ import annotations
from abc import ABC, abstractmethod
import time
import json
import numpy as np
import pyparsing as pp
from .utils import NumpyEncoder
import matplotlib.pyplot as plt

class Algorithm(ABC):
    """
    Abstract Algorithm class.

    This class defines the structure of all optimization algorithms.

    Parameters
    ----------

    objfunc: ObjectiveFunc
        Objective function to be optimized.
    search_strategy: SearchStrategy
        Search strategy that will iteratively optimize the function.
    params: ParamScheduler or dict, optional
        Dictionary of parameters to define the stopping condition and output of the algorithm.
    """

    def __init__(self, objfunc: ObjectiveFunc, search_strategy: SearchStrategy, params: Union[ParamScheduler, dict] = None, name: str = None):
        """
        Constructor of the Search class
        """

        self.params = params
        self.search_strategy = search_strategy
        self.objfunc = objfunc
        self._name = name

        if params is None:
            params = {}

        # Verbose parameters
        self.verbose = params.get("verbose", True)
        self.v_timer = params.get("v_timer", 1)

        # Stopping conditions
        self.stop_cond = params.get("stop_cond", "time_limit")
        self.stop_cond_parsed = parse_stopping_cond(self.stop_cond)

        self.progress_metric = params.get("progress_metric", self.stop_cond)
        self.progress_metric_parsed = parse_stopping_cond(self.progress_metric) if "progress_metric" in params else self.stop_cond_parsed

        self.ngen = params.get("ngen", 100)
        self.neval = params.get("neval", 1e5)
        self.time_limit = params.get("time_limit", 10.0)
        self.cpu_time_limit = params.get("cpu_time_limit", 10.0)
        self.fit_target = params.get("fit_target", 1e-10)
        if self.fit_target == 0:
            self.fit_target = 1e-10
        self.max_patience = params.get("patience", 1)
        self.patience_left = self.max_patience

        # Parallel parameters
        self.parallel = params.get("parallel", False)
        self.threads = params.get("threads", 8)

        # Metrics
        self.fit_history = []
        self.best_history = []
        self.progress = 0
        self.ended = False
        self.steps = 0
        self.prev_best_fitness = None
        self.cpu_time_spent = 0
        self.real_time_spent = 0
        self.converged_steps = 0

    @property
    def name(self):
        return self._name if self._name else self.search_strategy.name

    @name.setter
    def name(self, new_name: str):
        self._name = new_name

    def restart(self):
        """
        Resets the internal values of the algorithm and the number of evaluations of the fitness function.
        """

        self.fit_history = []
        self.best_history = []
        self.progress = 0
        self.ended = False
        self.prev_best_fitness = None
        self.cpu_time_spent = 0
        self.real_time_spent = 0
        self.objfunc.counter = 0

    def save_solution(self, file_name: str = "solution.csv"):
        """
        Save the result of an execution to a csv file in disk.

        Parameters
        ----------

        file_name: str
            Path to the file where the solution will be stored.
        """

        ind, fit = self.search_strategy.best_solution()
        np.savetxt(file_name, ind.reshape([1, -1]), delimiter=",")

    def best_solution(self) -> Tuple[Individual, float]:
        """
        Returns the best solution so far in the population.

        Returns
        -------
        best_solution: Tuple[Individual, float]
            A pair of the best individual with its fitness.
        """

        return self.search_strategy.best_solution()

    def stopping_condition(self, gen: int, real_time_start: float, cpu_time_start: float) -> bool:
        """
        Given the state of the algorithm, returns wether we have finished or not.

        Parameters
        ----------
        gen: int
            The number of generations that has passed.
        real_time_start: float
            The time in seconds that passed since the algorithm was executed.
        cpu_time_start: float
            The time in seconds that the CPU has executed code in this algorithm.

        Returns
        -------
        has_stopped: bool
            Whether the algorithm has reached its end
        """

        neval_reached = self.objfunc.counter >= self.neval

        ngen_reached = gen >= self.ngen

        real_time_reached = time.time() - real_time_start >= self.time_limit

        cpu_time_reached = time.process_time() - cpu_time_start >= self.cpu_time_limit

        if self.objfunc.mode == "max":
            target_reached = self.best_solution()[1] >= self.fit_target
        else:
            target_reached = self.best_solution()[1] <= self.fit_target

        patience_reached = self.patience_left < 0

        return process_condition(
            self.stop_cond_parsed,
            neval_reached,
            ngen_reached,
            real_time_reached,
            cpu_time_reached,
            target_reached,
            patience_reached,
        )

    def get_progress(self, gen: int, real_time_start: float, cpu_time_start: float) -> float:
        """
        Given the state of the algorithm, returns a number between 0 and 1 indicating
        how close to the end of the algorithm we are, 0 when starting and 1 when finished.

        Parameters
        ----------
        gen: int
            The number of generations that has passed.
        real_time_start: float
            The time in seconds that passed since the algorithm was executed.
        cpu_time_start: float
            The time in seconds that the CPU has executed code in this algorithm.

        Returns
        -------
        progress: float
            Indicator of how close it the algorithm to finishing, 1 means the algorithm should be stopped.
        """

        neval_reached = self.objfunc.counter / self.neval

        ngen_reached = gen / self.ngen

        real_time_reached = (time.time() - real_time_start) / self.time_limit

        cpu_time_reached = (time.process_time() - cpu_time_start) / self.cpu_time_limit

        best_fitness = self.best_solution()[1]

        # if self.objfunc.mode == "max":
        #     if self.fit_target == 0:
        #         self.fit_target = 1e-40
        #     target_reached = (best_fitness / self.fit_target
        # else:
        #     if best_fitness == 0:
        #         best_fitness = 1e-40
        #     target_reached = self.fit_target / best_fitness
        if self.objfunc.mode == "max":
            target_reached = 1 - (self.best_solution()[1] - self.fit_target) / self.fit_target
        else:
            target_reached = 1 - (self.fit_target - self.best_solution()[1]) / self.fit_target

        patience_prec = 1 - self.patience_left / self.max_patience

        return process_progress(
            self.stop_cond_parsed,
            neval_reached,
            ngen_reached,
            real_time_reached,
            cpu_time_reached,
            target_reached,
            patience_prec,
        )

    def update(self, real_time_start: float, cpu_time_start: float, pass_step: bool = True):
        """
        Updates the attributes of the optimization algorithm.
        This function should be called once per iteration of the algorithm.

        Parameters
        ----------
        real_time_start: float
            The time in seconds that passed since the algorithm was executed.
        cpu_time_start: float
            The time in seconds that the CPU has executed code in this algorithm.
        pass_step: bool
            Whether to increment the iteration counter or not.
        """

        if pass_step:
            self.steps += 1

        if self.best_solution()[1] == self.prev_best_fitness:
            self.patience_left -= 1
        else:
            self.patience_left = self.max_patience

        self.prev_best_fitness = self.best_solution()[1]

        self.progress = self.get_progress(self.steps, real_time_start, cpu_time_start)

        self.ended = self.stopping_condition(self.steps, real_time_start, cpu_time_start)

    def initialize(self):
        """
        Initializes the optimization algorithm.
        """

        self.restart()
        initial_population = self.search_strategy.initialize(self.objfunc)
        initial_population = self.search_strategy.evaluate_population(initial_population, self.objfunc, self.parallel, self.threads)
        self.search_strategy.population = initial_population

    @abstractmethod
    def step(self, time_start: float = 0, verbose: bool = False) -> Tuple[Individual, float]:
        """
        Performs an iteration of the algorithm.

        Parameters
        ----------
        time_start: float, optional
            Indicates to the algorihm how much time has already passed.
        verbose: bool, optional
            Indicates whether to show the status of the algorithm or not.

        Returns
        -------
        best_solution: Tuple[Individual, float]
            A pair of the best individual with its fitness.
        """

    def optimize(self) -> Tuple[Individual, float]:
        """
        Execute the algorithm to get the best solution possible along with its evaluation.
        It will initialize the algorithm and repeat steps of the algorithm untill the
        stopping condition is met.

        Returns
        -------
        best_solution: Tuple[Individual, float]
            A pair of the best individual with its fitness.
        """

        if self.verbose:
            self.init_info()

        self.steps = 0

        # initialize clocks
        real_time_start = time.time()
        cpu_time_start = time.process_time()
        display_timer = time.time()

        # Initizalize search strategy
        self.initialize()

        # Search until the stopping condition is met
        self.update(real_time_start, cpu_time_start, pass_step=False)

        if self.verbose:
            self.step_info(real_time_start)

        while not self.ended:
            self.step(real_time_start)

            self.update(real_time_start, cpu_time_start)

            # Display information
            if self.verbose and time.time() - display_timer > self.v_timer:
                self.step_info(real_time_start)
                display_timer = time.time()

        # Store the time spent optimizing
        self.real_time_spent = time.time() - real_time_start
        self.cpu_time_spent = time.process_time() - cpu_time_start

        return self.best_solution()

    def get_state(
        self,
        show_best_solution: bool = True,
        show_fit_history: bool = False,
        show_gen_history: bool = False,
        show_pop: bool = False,
        show_pop_details: bool = False,
    ) -> dict:
        """
        Gets the current state of the algorithm as a dictionary.

        Parameters
        ----------
        show_best_solution: bool, optional
            Save the best solution found by the algorithm.
        show_fit_history: bool, optional
            Save the fitness of the best individual of each iteration.
        show_gen_history: bool, optional
            Save the best inividual for each iteration.
        show_pop: bool, optional
            Save the entire population of the last iteration.
        show_pop_details:bool, optional
            Save the detailed information of each individual.

        Returns
        -------
        state: dict
            The complete state of the algorithm.
        """

        data = {
            "ended": self.ended,
            "progress": self.progress,
            "generation": self.steps,
            "evaluations": self.objfunc.counter,
            "real_time_spent": self.real_time_spent,
            "cpu_time_spent": self.cpu_time_spent,
            "params": self.params,
        }

        if show_best_solution:
            data["best_fitness"] = self.best_solution()[1]
            data["best_individual"] = self.search_strategy.best.get_state(show_speed=False, show_best=False)

        if show_fit_history:
            data["fit_history"] = self.fit_history

        if show_gen_history:
            data["best_history"] = self.best_history

        data["search_strat_state"] = self.search_strategy.get_state(show_pop, show_pop_details)

        return data

    def store_state(
        self,
        file_name: str = "dumped_state.json",
        readable: bool = False,
        show_best_solution: bool = True,
        show_fit_history: bool = False,
        show_gen_history: bool = False,
        show_pop: bool = False,
        show_pop_details: bool = False,
    ):
        """
        Dumps the current state of the algorithm to a JSON file.

        Parameters
        ----------
        file_name: str
            Path to the file where the json file will be stored.
        readable: bool, optional
            Indent the JSON file to make it human-readable (comes at the cost of a higher file size).
        show_best_solution: bool, optional
            Save the best solution found by the algorithm.
        show_fit_history: bool, optional
            Save the fitness of the best individual of each iteration.
        show_gen_history: bool, optional
            Save the best inividual for each iteration.
        show_pop: bool, optional
            Save the entire population of the last iteration.
        show_pop_details:bool, optional
            Save the detailed information of each individual.
        """

        dumped = json.dumps(
            self.get_state(
                show_best_solution,
                show_fit_history,
                show_gen_history,
                show_pop,
                show_pop_details,
            ),
            cls=NumpyEncoder,
            indent=4 if readable else None,
        )

        with open(file_name, "w") as fp:
            fp.write(dumped)

    def init_info(self):
        print(f"Initializing optimization of {self.objfunc.name} using {self.search_strategy.name}")
        print(f"-----------------------------{'-'*len(self.objfunc.name)}-------{'-'*len(self.search_strategy.name)}")
        print()

    def step_info(self, start_time: float = 0):
        """
        Displays information about the current state of the algotithm.

        Parameters
        ----------
        time_start: float, optional
            Indicates to the algorihm how much time has already passed.
        """

        print(f"Optimizing {self.objfunc.name} using {self.name}:")
        print(f"\tReal time Spent: {round(time.time() - start_time,2)} s")
        print(f"\tCPU time Spent:  {round(time.time() - start_time,2)} s")
        print(f"\tGeneration: {self.steps}")
        best_fitness = self.best_solution()[1]
        print(f"\tBest fitness: {best_fitness}")
        print(f"\tEvaluations of fitness: {self.objfunc.counter}")
        print()
        self.search_strategy.extra_step_info()
        print()

    def display_report(self, show_plots: bool = True):
        """
        Shows a summary of the execution of the algorithm.

        Parameters
        ----------
        show_plots: bool, optional
            Whether to display plots about the algorithm or not.
        """

        print("Number of generations:", len(self.fit_history))
        print("Real time spent: ", round(self.real_time_spent, 5), "s", sep="")
        print("CPU time spent: ", round(self.cpu_time_spent, 5), "s", sep="")
        print("Number of fitness evaluations:", self.objfunc.counter)

        best_fitness = self.best_solution()[1]
        print("Best fitness:", best_fitness)

        if show_plots:
            # Plot fitness history
            plt.axhline(y=0, color="black", alpha=0.9)
            plt.axvline(x=0, color="black", alpha=0.9)
            plt.plot(self.fit_history, "blue")
            plt.xlabel("generations")
            plt.ylabel("fitness")
            plt.title(f"{self.search_strategy.name} fitness")
            plt.show()

        self.search_strategy.extra_report(show_plots)

def parse_stopping_cond(condition_str: str) -> List[str | List]:
    """
    This function parses an expression of the form "neval or cpu_time" into
    a tree structure so that it can be futher processed.

    Parameters
    ----------
    condition_str: str
        The string to be parsed.

    Returns
    -------
    token_list: List[str | List]
        The list of tokens representing the original string.
    """

    orop = pp.Literal("and")
    andop = pp.Literal("or")
    condition = pp.oneOf(["neval", "ngen", "time_limit", "cpu_time_limit", "fit_target", "convergence"])

    expr = pp.infixNotation(condition, [(orop, 2, pp.opAssoc.RIGHT), (andop, 2, pp.opAssoc.RIGHT)])

    return expr.parse_string(condition_str).as_list()

def process_condition(
    cond_parsed: List[str | List],
    neval: int,
    ngen: int,
    real_time: float,
    cpu_time: float,
    target: float,
    patience: int,
) -> bool:
    """
    This function recieves as an input an expression for the stopping condition
    and the truth variable of the possible stopping conditions and returns wether to stop or not.

    Parameters
    ----------
    cond_parsed: List[str | List]
        The list of tokens representing the parsed stopping condition.
    neval: int
        Number of function evaluations done.
    ngen: int
        Number of iterations done by the algorithm
    real_time: float
        Time since the start of the algorithm.
    cpu_time: float
        Time dedicated by the CPU to optimizing our function.
    target: float
        Fitness target.
    patience: int
        Number of time the algorithm has reached the same fitness value in a row.

    Returns
    -------
    has_stopped: bool
        Whether the algorithm has reached its end
    """

    result = None

    if isinstance(cond_parsed, list):
        if len(cond_parsed) == 3:
            cond1 = process_condition(cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience)
            cond2 = process_condition(cond_parsed[2], neval, ngen, real_time, cpu_time, target, patience)

            if cond_parsed[1] == "or":
                result = cond1 or cond2
            elif cond_parsed[1] == "and":
                result = cond1 and cond2

        elif len(cond_parsed) == 1:
            result = process_condition(cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience)

    else:
        if cond_parsed == "neval":
            result = neval
        elif cond_parsed == "ngen":
            result = ngen
        elif cond_parsed == "time_limit":
            result = real_time
        elif cond_parsed == "cpu_time_limit":
            result = cpu_time
        elif cond_parsed == "fit_target":
            result = target
        elif cond_parsed == "convergence":
            result = patience

    return result

def process_progress(
    cond_parsed: List[str | List],
    neval: int,
    ngen: int,
    real_time: float,
    cpu_time: float,
    target: float,
    patience: int,
) -> float:
    """
    This function recieves as an input an expression for the stopping condition
    and the truth variable of the possible stopping conditions and returns wether to stop or not.

    Parameters
    ----------
    cond_parsed: List[str | List]
        The list of tokens representing the parsed stopping condition.
    neval: int
        Number of function evaluations done.
    ngen: int
        Number of iterations done by the algorithm
    real_time: float
        Time since the start of the algorithm.
    cpu_time: float
        Time dedicated by the CPU to optimizing our function.
    target: float
        Fitness target.
    patience: int
        Number of time the algorithm has reached the same fitness value in a row.

    Returns
    -------
    has_stopped: bool
        Indicator of how close it the algorithm to finishing, 1 means the algorithm should be stopped.
    """

    result = None

    if isinstance(cond_parsed, list):
        if len(cond_parsed) == 3:
            progress1 = process_progress(cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience)
            progress2 = process_progress(cond_parsed[2], neval, ngen, real_time, cpu_time, target, patience)

            if cond_parsed[1] == "or":
                result = max(progress1, progress2)
            elif cond_parsed[1] == "and":
                result = min(progress1, progress2)

        elif len(cond_parsed) == 1:
            result = process_progress(cond_parsed[0], neval, ngen, real_time, cpu_time, target, patience)
    else:
        if cond_parsed == "neval":
            result = neval
        elif cond_parsed == "ngen":
            result = ngen
        elif cond_parsed == "time_limit":
            result = real_time
        elif cond_parsed == "cpu_time_limit":
            result = cpu_time
        elif cond_parsed == "fit_target":
            result = target
        elif cond_parsed == "convergence":
            result = patience

    return result


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models
import django.utils.timezone
import datetime
from django.conf import settings

class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ('fertility', '0004_auto_20151216_2241'),
    ]

    operations = [
        migrations.CreateModel(
            name='Fertility',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('date', models.DateField(default=datetime.date.today, verbose_name=b'Date')),
                ('time', models.TimeField(default=django.utils.timezone.now, verbose_name=b'Time')),
                ('temperature', models.DecimalField(max_digits=6, decimal_places=2)),
                ('scale', models.CharField(max_length=1, choices=[(b'F', b'Fahrenheit'), (b'C', b'Celsius')])),
                ('mucus', models.CharField(max_length=10, choices=[(b'D', b'Dry'), (b'S', b'Sticky'), (b'C', b'Creamy'), (b'W', b'Watery'), (b'EW', b'Egg White')])),
                ('position', models.CharField(max_length=10, choices=[(b'LW', b'Low'), (b'MD', b'Medium'), (b'HI', b'High')])),
                ('texture', models.CharField(max_length=10, choices=[(b'ST', b'Soft'), (b'MD', b'Medium'), (b'FM', b'Firm')])),
                ('opening', models.CharField(max_length=10, choices=[(b'Open', b'Open'), (b'MD', b'Medium'), (b'Closed', b'Closed')])),
                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
            ],
        ),
        migrations.RemoveField(
            model_name='basalbodytemp',
            name='user',
        ),
        migrations.RemoveField(
            model_name='cervical',
            name='user',
        ),
        migrations.DeleteModel(
            name='BasalBodyTemp',
        ),
        migrations.DeleteModel(
            name='Cervical',
        ),
    ]