# -*- coding: utf-8 -*-

from __future__ import with_statement

import base64
import email
import unittest
import time
import re
import mock
from contextlib import contextmanager

from email.header import Header
from email import charset

from flask import Flask
from flask_mail import Mail, Message, BadHeaderError, sanitize_address, PY3
from speaklater import make_lazy_string

class TestCase(unittest.TestCase):

    TESTING = True
    MAIL_DEFAULT_SENDER = "support@mysite.com"

    def setUp(self):
        self.app = Flask(__name__)
        self.app.config.from_object(self)
        self.assertTrue(self.app.testing)
        self.mail = Mail(self.app)
        self.ctx = self.app.test_request_context()
        self.ctx.push()

    def tearDown(self):
        self.ctx.pop()

    @contextmanager
    def mail_config(self, **settings):
        """
        Context manager to alter mail config during a test and restore it after,
        even in case of a failure.
        """
        original = {}
        state = self.mail.state
        for key in settings:
            assert hasattr(state, key)
            original[key] = getattr(state, key)
            setattr(state, key, settings[key])

        yield
        # restore
        for k, v in original.items():
            setattr(state, k, v)

    def assertIn(self, member, container, msg=None):
        if hasattr(unittest.TestCase, 'assertIn'):
            return unittest.TestCase.assertIn(self, member, container, msg)
        return self.assertTrue(member in container)

    def assertNotIn(self, member, container, msg=None):
        if hasattr(unittest.TestCase, 'assertNotIn'):
            return unittest.TestCase.assertNotIn(self, member, container, msg)
        return self.assertFalse(member in container)

    def assertIsNone(self, obj, msg=None):
        if hasattr(unittest.TestCase, 'assertIsNone'):
            return unittest.TestCase.assertIsNone(self, obj, msg)
        return self.assertTrue(obj is None)

    def assertIsNotNone(self, obj, msg=None):
        if hasattr(unittest.TestCase, 'assertIsNotNone'):
            return unittest.TestCase.assertIsNotNone(self, obj, msg)
        return self.assertTrue(obj is not None)

class TestInitialization(TestCase):

    def test_init_mail(self):
        mail = self.mail.init_mail(
            self.app.config,
            self.app.debug,
            self.app.testing
        )

        self.assertEquals(self.mail.state.__dict__, mail.__dict__)

class TestMessage(TestCase):

    def test_initialize(self):
        msg = Message(subject="subject",
                      recipients=["to@example.com"])
        self.assertEqual(msg.sender, self.app.extensions['mail'].default_sender)
        self.assertEqual(msg.recipients, ["to@example.com"])

    def test_recipients_properly_initialized(self):
        msg = Message(subject="subject")
        self.assertEqual(msg.recipients, [])
        msg2 = Message(subject="subject")
        msg2.add_recipient("somebody@here.com")
        self.assertEqual(len(msg2.recipients), 1)

    def test_esmtp_options_properly_initialized(self):
        msg = Message(subject="subject")
        self.assertEqual(msg.mail_options, [])
        self.assertEqual(msg.rcpt_options, [])

        msg = Message(subject="subject", mail_options=['BODY=8BITMIME'])
        self.assertEqual(msg.mail_options, ['BODY=8BITMIME'])

        msg2 = Message(subject="subject", rcpt_options=['NOTIFY=SUCCESS'])
        self.assertEqual(msg2.rcpt_options, ['NOTIFY=SUCCESS'])

    def test_sendto_properly_set(self):
        msg = Message(subject="subject", recipients=["somebody@here.com"],
                      cc=["cc@example.com"], bcc=["bcc@example.com"])
        self.assertEqual(len(msg.send_to), 3)
        msg.add_recipient("cc@example.com")
        self.assertEqual(len(msg.send_to), 3)

    def test_add_recipient(self):
        msg = Message("testing")
        msg.add_recipient("to@example.com")
        self.assertEqual(msg.recipients, ["to@example.com"])

    def test_sender_as_tuple(self):
        msg = Message(subject="testing",
                      sender=("tester", "tester@example.com"))
        self.assertEqual('tester <tester@example.com>', msg.sender)

    def test_default_sender_as_tuple(self):
        self.app.extensions['mail'].default_sender = ('tester', 'tester@example.com')
        msg = Message(subject="testing")
        self.assertEqual('tester <tester@example.com>', msg.sender)

    def test_reply_to(self):
        msg = Message(subject="testing",
                      recipients=["to@example.com"],
                      sender="spammer <spammer@example.com>",
                      reply_to="somebody <somebody@example.com>",
                      body="testing")
        response = msg.as_string()

        h = Header("Reply-To: %s" % sanitize_address('somebody <somebody@example.com>'))
        self.assertIn(h.encode(), str(response))

    def test_send_without_sender(self):
        self.app.extensions['mail'].default_sender = None
        msg = Message(subject="testing", recipients=["to@example.com"], body="testing")
        self.assertRaises(AssertionError, self.mail.send, msg)

    def test_send_without_recipients(self):
        msg = Message(subject="testing",
                      recipients=[],
                      body="testing")
        self.assertRaises(AssertionError, self.mail.send, msg)

    def test_bcc(self):
        msg = Message(sender="from@example.com",
                      subject="testing",
                      recipients=["to@example.com"],
                      body="testing",
                      bcc=["tosomeoneelse@example.com"])
        response = msg.as_string()
        self.assertNotIn("tosomeoneelse@example.com", str(response))

    def test_cc(self):
        msg = Message(sender="from@example.com",
                      subject="testing",
                      recipients=["to@example.com"],
                      body="testing",
                      cc=["tosomeoneelse@example.com"])
        response = msg.as_string()
        self.assertIn("Cc: tosomeoneelse@example.com", str(response))

    def test_attach(self):
        msg = Message(subject="testing",
                      recipients=["to@example.com"],
                      body="testing")
        msg.attach(data=b"this is a test",
                   content_type="text/plain")
        a = msg.attachments[0]
        self.assertIsNone(a.filename)
        self.assertEqual(a.disposition, 'attachment')
        self.assertEqual(a.content_type, "text/plain")
        self.assertEqual(a.data, b"this is a test")

    def test_bad_header_subject(self):
        msg = Message(subject="testing\r\n",
                      sender="from@example.com",
                      body="testing",
                      recipients=["to@example.com"])
        self.assertRaises(BadHeaderError, self.mail.send, msg)

    def test_multiline_subject(self):
        msg = Message(subject="testing\r\n testing\r\n testing \r\n \ttesting",
                      sender="from@example.com",
                      body="testing",
                      recipients=["to@example.com"])
        self.mail.send(msg)
        response = msg.as_string()
        self.assertIn("From: from@example.com", str(response))
        self.assertIn("testing\r\n testing\r\n testing \r\n \ttesting", str(response))

    def test_bad_multiline_subject(self):
        msg = Message(subject="testing\r\n testing\r\n ",
                      sender="from@example.com",
                      body="testing",
                      recipients=["to@example.com"])
        self.assertRaises(BadHeaderError, self.mail.send, msg)

        msg = Message(subject="testing\r\n testing\r\n\t",
                      sender="from@example.com",
                      body="testing",
                      recipients=["to@example.com"])
        self.assertRaises(BadHeaderError, self.mail.send, msg)

        msg = Message(subject="testing\r\n testing\r\n\n",
                      sender="from@example.com",
                      body="testing",
                      recipients=["to@example.com"])
        self.assertRaises(BadHeaderError, self.mail.send, msg)

    def test_bad_header_sender(self):
        msg = Message(subject="testing",
                      sender="from@example.com\r\n",
                      recipients=["to@example.com"],
                      body="testing")

        self.assertIn('From: from@example.com', msg.as_string())

    def test_bad_header_reply_to(self):
        msg = Message(subject="testing",
                      sender="from@example.com",
                      reply_to="evil@example.com\r",
                      recipients=["to@example.com"],
                      body="testing")

        self.assertIn('From: from@example.com', msg.as_string())
        self.assertIn('To: to@example.com', msg.as_string())
        self.assertIn('Reply-To: evil@example.com', msg.as_string())

    def test_bad_header_recipient(self):
        msg = Message(subject="testing",
                      sender="from@example.com",
                      recipients=[
                          "to@example.com",
                          "to\r\n@example.com"],
                      body="testing")

        self.assertIn('To: to@example.com', msg.as_string())

    def test_emails_are_sanitized(self):
        msg = Message(subject="testing",
                      sender="sender\r\n@example.com",
                      reply_to="reply_to\r\n@example.com",
                      recipients=["recipient\r\n@example.com"])
        self.assertIn('sender@example.com', msg.as_string())
        self.assertIn('reply_to@example.com', msg.as_string())
        self.assertIn('recipient@example.com', msg.as_string())

    def test_plain_message(self):
        plain_text = "Hello Joe,\nHow are you?"
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["to@example.com"],
                      body=plain_text)
        self.assertEqual(plain_text, msg.body)
        self.assertIn('Content-Type: text/plain', msg.as_string())

    def test_message_str(self):
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["to@example.com"],
                      body="some plain text")
        self.assertEqual(msg.as_string(), str(msg))

    def test_plain_message_with_attachments(self):
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["to@example.com"],
                      body="hello")

        msg.attach(data=b"this is a test",
                   content_type="text/plain")

        self.assertIn('Content-Type: multipart/mixed', msg.as_string())

    def test_plain_message_with_ascii_attachment(self):
        msg = Message(subject="subject",
                      recipients=["to@example.com"],
                      body="hello")

        msg.attach(data=b"this is a test",
                   content_type="text/plain",
                   filename='test doc.txt')

        self.assertIn('Content-Disposition: attachment; filename="test doc.txt"', msg.as_string())

    def test_plain_message_with_unicode_attachment(self):
        msg = Message(subject="subject",
                      recipients=["to@example.com"],
                      body="hello")

        msg.attach(data=b"this is a test",
                   content_type="text/plain",
                   filename=u'ünicöde ←→ ✓.txt')

        parsed = email.message_from_string(msg.as_string())

        self.assertIn(re.sub(r'\s+', ' ', parsed.get_payload()[1].get('Content-Disposition')), [
            'attachment; filename*="UTF8\'\'%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt"',
            'attachment; filename*=UTF8\'\'%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt'
            ])

    def test_plain_message_with_ascii_converted_attachment(self):
        with self.mail_config(ascii_attachments=True):
            msg = Message(subject="subject",
                          recipients=["to@example.com"],
                          body="hello")

            msg.attach(data=b"this is a test",
                       content_type="text/plain",
                       filename=u'ünicödeß ←.→ ✓.txt')

            parsed = email.message_from_string(msg.as_string())
            self.assertIn(
                'Content-Disposition: attachment; filename="unicode . .txt"',
                msg.as_string())

    def test_html_message(self):
        html_text = "
Hello World
"
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["to@example.com"],
                      html=html_text)

        self.assertEqual(html_text, msg.html)
        self.assertIn('Content-Type: multipart/alternative', msg.as_string())

    def test_json_message(self):
        json_text = '{"msg": "Hello World!}'
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["to@example.com"],
                      alts={'json': json_text})

        self.assertEqual(json_text, msg.alts['json'])
        self.assertIn('Content-Type: multipart/alternative', msg.as_string())

    def test_html_message_with_attachments(self):
        html_text = "
Hello World
"
        plain_text = 'Hello World'
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["to@example.com"],
                      body=plain_text,
                      html=html_text)
        msg.attach(data=b"this is a test",
                   content_type="text/plain")

        self.assertEqual(html_text, msg.html)
        self.assertIn('Content-Type: multipart/alternative', msg.as_string())

        parsed = email.message_from_string(msg.as_string())
        self.assertEqual(len(parsed.get_payload()), 2)

        body, attachment = parsed.get_payload()
        self.assertEqual(len(body.get_payload()), 2)

        plain, html = body.get_payload()
        self.assertEqual(plain.get_payload(), plain_text)
        self.assertEqual(html.get_payload(), html_text)
        self.assertEqual(base64.b64decode(attachment.get_payload()), b'this is a test')

    def test_date_header(self):
        before = time.time()
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["to@example.com"],
                      body="hello",
                      date=time.time())
        after = time.time()

        self.assertTrue(before <= msg.date <= after)
        dateFormatted = email.utils.formatdate(msg.date, localtime=True)
        self.assertIn('Date: ' + dateFormatted, msg.as_string())

    def test_msgid_header(self):
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["to@example.com"],
                      body="hello")

        # see RFC 5322 section 3.6.4. for the exact format specification
        r = re.compile(r"<\S+@\S+>").match(msg.msgId)
        self.assertIsNotNone(r)
        self.assertIn('Message-ID: ' + msg.msgId, msg.as_string())

    def test_unicode_sender_tuple(self):
        msg = Message(subject="subject",
                      sender=(u"ÄÜÖ → ✓", 'from@example.com>'),
                      recipients=["to@example.com"])

        self.assertIn('From: =?utf-8?b?w4TDnMOWIOKGkiDinJM=?= <from@example.com>', msg.as_string())

    def test_unicode_sender(self):
        msg = Message(subject="subject",
                      sender=u'ÄÜÖ → ✓ <from@example.com>>',
                      recipients=["to@example.com"])

        self.assertIn('From: =?utf-8?b?w4TDnMOWIOKGkiDinJM=?= <from@example.com>', msg.as_string())

    def test_unicode_headers(self):
        msg = Message(subject="subject",
                      sender=u'ÄÜÖ → ✓ <from@example.com>',
                      recipients=[u"Ä <t1@example.com>", u"Ü <t2@example.com>"],
                      cc=[u"Ö <cc@example.com>"])

        response = msg.as_string()
        a1 = sanitize_address(u"Ä <t1@example.com>")
        a2 = sanitize_address(u"Ü <t2@example.com>")
        h1_a = Header("To: %s, %s" % (a1, a2))
        h1_b = Header("To: %s, %s" % (a2, a1))
        h2 = Header("From: %s" % sanitize_address(u"ÄÜÖ → ✓ <from@example.com>"))
        h3 = Header("Cc: %s" % sanitize_address(u"Ö <cc@example.com>"))

        # Ugly, but there's no guaranteed order of the recipieints in the header
        try:
            self.assertIn(h1_a.encode(), response)
        except AssertionError:
            self.assertIn(h1_b.encode(), response)

        self.assertIn(h2.encode(), response)
        self.assertIn(h3.encode(), response)

    def test_unicode_subject(self):
        msg = Message(subject=make_lazy_string(lambda a: a, u"sübject"),
                      sender='from@example.com',
                      recipients=["to@example.com"])
        self.assertIn('=?utf-8?q?s=C3=BCbject?=', msg.as_string())

    def test_extra_headers(self):
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["to@example.com"],
                      body="hello",
                      extra_headers={'X-Extra-Header': 'Yes'})
        self.assertIn('X-Extra-Header: Yes', msg.as_string())

    def test_message_charset(self):
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["foo@bar.com"],
                      charset='us-ascii')

        # ascii body
        msg.body = "normal ascii text"
        self.assertIn('Content-Type: text/plain; charset="us-ascii"', msg.as_string())

        # ascii html
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["foo@bar.com"],
                      charset='us-ascii')
        msg.body = None
        msg.html = "
hello
"
        self.assertIn('Content-Type: text/html; charset="us-ascii"', msg.as_string())

        # unicode body
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["foo@bar.com"])
        msg.body = u"ünicöde ←→ ✓"
        self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string())

        # unicode body and unicode html
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["foo@bar.com"])
        msg.html = u"ünicöde ←→ ✓"
        self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string())
        self.assertIn('Content-Type: text/html; charset="utf-8"', msg.as_string())

        # unicode body and attachments
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["foo@bar.com"])
        msg.html = None
        msg.attach(data=b"foobar", content_type='text/csv')
        self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string())

        # unicode sender as tuple
        msg = Message(sender=(u"送信者", "from@example.com"),
                      subject=u"表題",
                      recipients=["foo@bar.com"],
                      reply_to=u"返信先 <somebody@example.com>",
                      charset='shift_jis')  # japanese
        msg.body = u'内容'
        self.assertIn('From: =?iso-2022-jp?', msg.as_string())
        self.assertNotIn('From: =?utf-8?', msg.as_string())
        self.assertIn('Subject: =?iso-2022-jp?', msg.as_string())
        self.assertNotIn('Subject: =?utf-8?', msg.as_string())
        self.assertIn('Reply-To: =?iso-2022-jp?', msg.as_string())
        self.assertNotIn('Reply-To: =?utf-8?', msg.as_string())
        self.assertIn('Content-Type: text/plain; charset="iso-2022-jp"', msg.as_string())

        # unicode subject sjis
        msg = Message(sender="from@example.com",
                      subject=u"表題",
                      recipients=["foo@bar.com"],
                      charset='shift_jis')  # japanese
        msg.body = u'内容'
        self.assertIn('Subject: =?iso-2022-jp?', msg.as_string())
        self.assertIn('Content-Type: text/plain; charset="iso-2022-jp"', msg.as_string())

        # unicode subject utf-8
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["foo@bar.com"],
                      charset='utf-8')
        msg.body = u'内容'
        self.assertIn('Subject: subject', msg.as_string())
        self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string())

        # ascii subject
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["foo@bar.com"],
                      charset='us-ascii')
        msg.body = "normal ascii text"
        self.assertNotIn('Subject: =?us-ascii?', msg.as_string())
        self.assertIn('Content-Type: text/plain; charset="us-ascii"', msg.as_string())

        # default charset
        msg = Message(sender="from@example.com",
                      subject="subject",
                      recipients=["foo@bar.com"])
        msg.body = "normal ascii text"
        self.assertNotIn('Subject: =?', msg.as_string())
        self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string())

    def test_empty_subject_header(self):
        msg = Message(sender="from@example.com",
                      recipients=["foo@bar.com"])
        msg.body = "normal ascii text"
        self.mail.send(msg)
        self.assertNotIn('Subject:', msg.as_string())

class TestMail(TestCase):

    def test_send(self):

        with self.mail.record_messages() as outbox:
            msg = Message(subject="testing",
                          recipients=["tester@example.com"],
                          body="test")
            self.mail.send(msg)
            self.assertIsNotNone(msg.date)
            self.assertEqual(len(outbox), 1)
            sent_msg = outbox[0]
            self.assertEqual(msg.sender, self.app.extensions['mail'].default_sender)

    def test_send_message(self):

        with self.mail.record_messages() as outbox:
            self.mail.send_message(subject="testing",
                                   recipients=["tester@example.com"],
                                   body="test")
            self.assertEqual(len(outbox), 1)
            msg = outbox[0]
            self.assertEqual(msg.subject, "testing")
            self.assertEqual(msg.recipients, ["tester@example.com"])
            self.assertEqual(msg.body, "test")
            self.assertEqual(msg.sender, self.app.extensions['mail'].default_sender)

class TestConnection(TestCase):

    def test_send_message(self):
        with self.mail.record_messages() as outbox:
            with self.mail.connect() as conn:
                conn.send_message(subject="testing",
                                  recipients=["to@example.com"],
                                  body="testing")
            self.assertEqual(len(outbox), 1)
            sent_msg = outbox[0]
            self.assertEqual(sent_msg.sender, self.app.extensions['mail'].default_sender)

    def test_send_single(self):
        with self.mail.record_messages() as outbox:
            with self.mail.connect() as conn:
                msg = Message(subject="testing",
                              recipients=["to@example.com"],
                              body="testing")
                conn.send(msg)
            self.assertEqual(len(outbox), 1)
            sent_msg = outbox[0]
            self.assertEqual(sent_msg.subject, "testing")
            self.assertEqual(sent_msg.recipients, ["to@example.com"])
            self.assertEqual(sent_msg.body, "testing")
            self.assertEqual(sent_msg.sender, self.app.extensions['mail'].default_sender)

    def test_send_many(self):
        with self.mail.record_messages() as outbox:
            with self.mail.connect() as conn:
                for i in range(100):
                    msg = Message(subject="testing",
                                  recipients=["to@example.com"],
                                  body="testing")
                    conn.send(msg)
            self.assertEqual(len(outbox), 100)
            sent_msg = outbox[0]
            self.assertEqual(sent_msg.sender, self.app.extensions['mail'].default_sender)

    def test_send_without_sender(self):
        self.app.extensions['mail'].default_sender = None
        msg = Message(subject="testing", recipients=["to@example.com"], body="testing")
        with self.mail.connect() as conn:
            self.assertRaises(AssertionError, conn.send, msg)

    def test_send_without_recipients(self):
        msg = Message(subject="testing",
                      recipients=[],
                      body="testing")
        with self.mail.connect() as conn:
            self.assertRaises(AssertionError, conn.send, msg)

    def test_bad_header_subject(self):
        msg = Message(subject="testing\n\r",
                      body="testing",
                      recipients=["to@example.com"])
        with self.mail.connect() as conn:
            self.assertRaises(BadHeaderError, conn.send, msg)

    def test_sendmail_with_ascii_recipient(self):
        with self.mail.connect() as conn:
            with mock.patch.object(conn, 'host') as host:
                msg = Message(subject="testing",
                              sender="from@example.com",
                              recipients=["to@example.com"],
                              body="testing")
                conn.send(msg)

                host.sendmail.assert_called_once_with(
                    "from@example.com",
                    ["to@example.com"],
                    msg.as_bytes() if PY3 else msg.as_string(),
                    msg.mail_options,
                    msg.rcpt_options
                )

    def test_sendmail_with_non_ascii_recipient(self):
        with self.mail.connect() as conn:
            with mock.patch.object(conn, 'host') as host:
                msg = Message(subject="testing",
                              sender="from@example.com",
                              recipients=[u'ÄÜÖ → ✓ <to@example.com>'],
                              body="testing")
                conn.send(msg)

                host.sendmail.assert_called_once_with(
                    "from@example.com",
                    ["=?utf-8?b?w4TDnMOWIOKGkiDinJM=?= <to@example.com>"],
                    msg.as_bytes() if PY3 else msg.as_string(),
                    msg.mail_options,
                    msg.rcpt_options
                )

    def test_sendmail_with_ascii_body(self):
        with self.mail.connect() as conn:
            with mock.patch.object(conn, 'host') as host:
                msg = Message(subject="testing",
                              sender="from@example.com",
                              recipients=["to@example.com"],
                              body="body")
                conn.send(msg)

                host.sendmail.assert_called_once_with(
                    "from@example.com",
                    ["to@example.com"],
                    msg.as_bytes() if PY3 else msg.as_string(),
                    msg.mail_options,
                    msg.rcpt_options
                )

    def test_sendmail_with_non_ascii_body(self):
        with self.mail.connect() as conn:
            with mock.patch.object(conn, 'host') as host:
                msg = Message(subject="testing",
                              sender="from@example.com",
                              recipients=["to@example.com"],
                              body=u"Öö")

                conn.send(msg)

                host.sendmail.assert_called_once_with(
                    "from@example.com",
                    ["to@example.com"],
                    msg.as_bytes() if PY3 else msg.as_string(),
                    msg.mail_options,
                    msg.rcpt_options
                )


"""Mixture model using EM"""
from typing import Tuple
import numpy as np
from common import GaussianMixture

def estep(X: np.ndarray, mixture: GaussianMixture) -> Tuple[np.ndarray, float]:
    """E-step: Softly assigns each datapoint to a gaussian component

    Args:
        X: (n, d) array holding the data
        mixture: the current gaussian mixture

    Returns:
        np.ndarray: (n, K) array holding the soft counts
            for all components for all examples
        float: log-likelihood of the assignment
    """
    d = X.shape[1]
    mu, var, pi = mixture
    pre_exp = (2*np.pi*var)**(d/2)
    post = np.linalg.norm(X[:,None] - mu, ord=2, axis=2)**2
    post = np.exp(-post/(2*var))
    post = post/pre_exp
    numerator = post*pi
    denominator = np.sum(numerator, axis=1).reshape(-1,1)
    post = numerator/denominator
    log_likelihood = np.sum(np.log(denominator), axis=0).item()

    return post, log_likelihood

def mstep(X: np.ndarray, post: np.ndarray) -> GaussianMixture:
    """M-step: Updates the gaussian mixture by maximizing the log-likelihood
    of the weighted dataset

    Args:
        X: (n, d) array holding the data
        post: (n, K) array holding the soft counts
            for all components for all examples

    Returns:
        GaussianMixture: the new gaussian mixture
    """
    n, d = X.shape
    K = post.shape[1]
    n_j = np.sum(post, axis=0)
    pi = n_j/n
    mu = (post.T @ X)/n_j.reshape(-1,1)
    norms = np.zeros((n, K), dtype=np.float64)
    for i in range(n):
        dist = X[i,:] - mu
        norms[i,:] = np.sum(dist**2, axis=1)
    var = np.sum(post*norms, axis=0)/(n_j*d)

    return GaussianMixture(mu, var, pi)

def run(X: np.ndarray, mixture: GaussianMixture,
        post: np.ndarray) -> Tuple[GaussianMixture, np.ndarray, float]:
    """Runs the mixture model

    Args:
        X: (n, d) array holding the data
        post: (n, K) array holding the soft counts
            for all components for all examples

    Returns:
        GaussianMixture: the new gaussian mixture
        np.ndarray: (n, K) array holding the soft counts
            for all components for all examples
        float: log-likelihood of the current assignment
    """
    old_log_likelihood = None
    new_log_likelihood = None

    while old_log_likelihood is None or (new_log_likelihood - old_log_likelihood > 1e-6*np.abs(new_log_likelihood)):

        old_log_likelihood = new_log_likelihood
        post, new_log_likelihood = estep(X, mixture)
        mixture = mstep(X, post)

    return mixture, post, new_log_likelihood


import tensorflow as tf
from tensorflow import keras
import numpy as np
import cv2
import numpy as np
from keras.models import load_model
from PIL import Image, ImageOps
import numpy as np
from tensorflow import keras
from datetime import datetime

facedetect = cv2.CascadeClassifier("/home/trang/Downloads/Face Recognition System (1)-20220622T194744Z-001/Face Recognition System (1)/Face Recognition System/haarcascade_frontalface_default.xml")

cap=cv2.VideoCapture(0)
cap.set(3, 640)
cap.set(4, 480)
font=cv2.FONT_HERSHEY_COMPLEX

model = load_model("/home/trang/Downloads/Face Recognition System (1)-20220622T194744Z-001/Face Recognition System (1)/Face Recognition System/Face.h5")

def get_className(classNo):
	if classNo==0:
		return "Gia Han"
	elif classNo==1:
		return "Linh Trang"
	elif classNo==2:
		return "Ngoc Mai"
	elif classNo==3:
		return "Nhu Quynh"
	elif classNo==4:
		return "Ha"
	elif classNo==5:
		return "Truc Anh"
	elif classNo==6:
		return "Van Minh"

while True:
	sucess, imgOrignal=cap.read()
	faces = facedetect.detectMultiScale(imgOrignal,1.3,5)
	for x,y,w,h in faces:
		crop_img=imgOrignal[y:y+h,x:x+h]
		img=cv2.resize(crop_img, (100,100))
		img=img.reshape(1, 100, 100, 3)
		prediction=model.predict(img)
		classIndex=model.predict(img)
		classes_x=np.argmax(prediction,axis=1)
		probabilityValue=np.amax(prediction)
		if classes_x==0:
			cv2.rectangle(imgOrignal,(x,y),(x+w,y+h),(0,255,0),2)
			cv2.rectangle(imgOrignal, (x,y-40),(x+w, y), (0,255,0),-2)
			cv2.putText(imgOrignal, str(get_className(classes_x)),(x,y-10), font, 0.75, (255,255,255),1, cv2.LINE_AA)
			print(str(get_className(classes_x)))
		elif classes_x==1:
			cv2.rectangle(imgOrignal,(x,y),(x+w,y+h),(0,255,0),2)
			cv2.rectangle(imgOrignal, (x,y-40),(x+w, y), (0,255,0),-2)
			cv2.putText(imgOrignal, str(get_className(classes_x)),(x,y-10), font, 0.75, (255,255,255),1, cv2.LINE_AA)
			print(str(get_className(classes_x)))

		name = str(get_className(classes_x))

		def markAttendance(name):
			with open('/home/trang/Downloads/Face Recognition System (1)-20220622T194744Z-001/Face Recognition System (1)/Face Recognition System/Diem_danh.csv','r+') as f:
				myDataList = f.readlines()
				nameList = []
				for line in myDataList:
					entry = line.split(',')
					nameList.append(entry[0])
				if name not in nameList:
					now = datetime.now()
					dtString = now.strftime('%H:%M:%S')
					f.writelines(f'\n{name},{dtString}')

		cv2.putText(imgOrignal,str(round(probabilityValue*100, 2))+"%" ,(180, 75), font, 0.75, (255,0,0),2, cv2.LINE_AA)
		markAttendance(name)

	cv2.imshow("Result",imgOrignal)
	k=cv2.waitKey(1)
	if k==ord('q'):
		break

cap.release()
cv2.destroyAllWindows()


import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go

print("dcc version:", dcc.__version__)
df = pd.read_csv('./kids_sorted_eng.csv')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

colors = ['#F8EFBA', '#ffdd59', '#F97F51', '#ff5e57', '#ef5777', '#B33771', '#82589F', '#182C61', '#58B19F', '#0be881']

app.layout = html.Div([html.Div([
    html.Div([dcc.Graph(id='graph-with-slider')], style={"width": "60%"}),
    html.Div(id='click-data', style={"width": "35%", "height": "800px"})
], style={"display": "flex", "justify-content": "center", "align-content": "center", "margin-bottom": "20px"}),

    html.Div([dcc.Slider(
        id='year-slider',
        min=df['year'].min(),
        max=df['year'].max(),
        value=df['year'].min(),
        marks={str(year): {'label': str(year), 'style': {"font-family": 'Courier New, monospace', "font-size": 16, "color": '#7f7f7f'}} for year in df['year'].unique()},
    )], style={"width": "90%", "margin": "0 auto"})])

@app.callback(
    dash.dependencies.Output('click-data', 'children'),
    [dash.dependencies.Input('graph-with-slider', 'clickData'),
     dash.dependencies.Input('year-slider', 'value')])
def display_click_data(clickData, year):
    value_city = []
    value_village = []

    if clickData:
        filtered_df = df[df.year == year]
        filtered_region = filtered_df[filtered_df.region == clickData['points'][0]['y']]
        labels = list(filtered_region.age)
        for i in range(len(filtered_region.city)):
            value_city.append(int((list(filtered_region.city)[i]).replace(" ", "")))
            value_village.append(int((list(filtered_region.village)[i]).replace(" ", "")))
        print("VALUES: ", value_city, value_village, " labels : ", labels)

        traces = []
        for i in range(len(labels)):
            if value_city[i] != 0 and value_village[i] != 0:
                traces.append(go.Scatter(
                    x=["city", "village"],
                    y=[value_city[i], value_village[i]],
                    name=labels[i],
                    marker=dict(
                        color=colors[i],
                        size=20
                    ), line=dict(width=8)
                ))
        print("traces:", traces)
        return html.Div([
            html.H5(['Age variance in ' + str(year) + ' year in ' + clickData['points'][0]['y'] + ' obl.'],
                    style={"margin": "7px auto 0 auto",
                           "text-align": "center",
                           "font-family": 'Courier New, monospace', "font-size": 24, "color": '#7f7f7f'}),
            html.Div(dcc.Graph( id='graph-external',
        figure={
            'data': traces,
            'layout': go.Layout(font=dict(family='Courier New, monospace', size=18, color='#7f7f7f'),
                                height=750,
                                hovermode='closest')
        }
    ))])

@app.callback(
    dash.dependencies.Output('graph-with-slider', 'figure'),
    [dash.dependencies.Input('year-slider', 'value')])
def update_figure(selected_year):
    filtered_df = df[df.year == selected_year]
    traces = []
    areas = []
    amount_city = []
    amount_village = []
    for i in filtered_df.region.unique():
        df_by_continent = filtered_df[filtered_df['region'] == i]
        local_amount = 0
        for j in df_by_continent['city']:
            local_amount += int(j.replace(" ", ""))
        amount_city.append(local_amount)
        local_amount = 0
        for j in df_by_continent['village']:
            local_amount += int(j.replace(" ", ""))
        amount_village.append(local_amount)
        areas.append(i)
        sorty = [y for _, y in sorted(zip(amount_city, areas))]
        sortx = sorted(amount_city)
    traces.append({
        "y": sorty,
        "x": sortx,
        "mode": "markers",
        "name": "city",
        "type": "scatter",
        "marker": dict(
            size=15,
            color=colors[7]
        ),
    })
    traces.append({
        "y": areas,
        "x": amount_village,
        "mode": "markers",
        "name": "village",
        "type": "scatter",
        "marker": dict(
            size=15,
            color=colors[1]
        )
    })

    return {
        'data': traces,
        'layout': go.Layout(
            title='Number of newborn babies in city/village',
            hovermode='closest',
            font=dict(family='Courier New, monospace', size=18, color='#7f7f7f'),
            xaxis=dict(
                       showgrid=False,
                       zeroline=False,
                       showline=False
                       ),
            yaxis=dict(
                        showgrid=False,
                        zeroline=False,
                        showline=False),
            # yaxis={'title': 'Region', 'titlefont': dict(size=20, color='black')},
            margin={'l': 170, 'b': 60, 't': 60, 'r': 0},
            height=800
        )
    }

if __name__ == '__main__':
    app.run_server(debug=True, port=8060, host='0.0.0.0')


from calendar import c
import sys

sys.path.append('.')

from matplotlib import pyplot as plt
import pandas as pd

def plot_courses(data, numerical_columns, course1, course2):
	if not course1 in numerical_columns.array or not course2 in numerical_columns.array:
		print(f'{course1} or {course2} isn\'t a numerical column in the dataset', file=sys.stderr)
		return
	courses = [ data[course1], data[course2] ]
	plt.scatter(courses[0].values, courses[1].values, alpha=0.3)
	plt.plot([0, 1], [0, 1], 'r')

	plt.xlabel(course1)
	plt.ylabel(course2)
	plt.title(f'{course1} vs {course2}')
	fig = plt.gcf()
	fig.set_size_inches(10, 10)

def plot_all(data, numerical_columns):
	i = 0
	for label, col in data[numerical_columns].iteritems():
		ok = False
		for label2, col2 in data[numerical_columns].iteritems():
			if not ok:
				ok = (label == label2)
				continue
			plt.scatter(i, col.subtract(col2).abs().mean() * 4)
			plt.annotate(f'{label} - {label2}', (i, col.subtract(col2).abs().mean() * 4))
			i += 1

	plt.xlabel('Index')
	plt.ylabel('Mean of differences')
	plt.title('Difference between courses')
	fig = plt.gcf()
	fig.set_size_inches(20, 20)

def main():
	if len(sys.argv) != 4 and len(sys.argv) != 2:
		print(f'Usage: {sys.argv[0]} <csv_file> [course1 <course2>]', file=sys.stderr)
		return
	data = pd.DataFrame()
	try:
		data = pd.read_csv(sys.argv[1])
	except:
		print('Invalid dataset file', file=sys.stderr)
		return
	numerical_columns = data.select_dtypes("number").columns
	data[numerical_columns] = (data[numerical_columns] - data[numerical_columns].min()) / (data[numerical_columns].max() - data[numerical_columns].min())
	if len(sys.argv) == 4:
		plot_courses(data, numerical_columns, sys.argv[2], sys.argv[3])
	else:
		plot_all(data, numerical_columns)
	fig = plt.gcf()
	fig.savefig('scatter_plot.png')
if __name__ == '__main__':
	main()


import setuptools

with open("README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setuptools.setup(
    name="transbigdata",
    version="0.1.0",
    author="Qing Yu",
    author_email="qingyu0815@foxmail.com",
    description="A tool for transportation big data",
    long_description=long_description,
    long_description_content_type="text/markdown",
    license="BSD",
    url="https://github.com/ni1o1/transbigdata",
    project_urls={
        "Bug Tracker": "https://github.com/ni1o1/transbigdata/issues",
    },
    install_requires=[
        "geopandas","matplotlib","plot_map","CoordinatesConverter"
        ],
    classifiers=[
        "Environment :: Web Environment",
        "Intended Audience :: Developers",
        "Operating System :: OS Independent",
        "Framework :: Matplotlib",
        "Topic :: Text Processing :: Indexing",
        "Topic :: Utilities",
        "Topic :: Internet",
        "Topic :: Software Development :: Libraries :: Python Modules",
        "Programming Language :: Python",
        "Programming Language :: Python :: 3.8",
    ],
    package_dir={"": "src"},
    packages=setuptools.find_packages(where="src"),
    python_requires=">=3.6",
)


from typing import Dict, List

# Name:  Add Bold Tag in String
# Link: https://leetcode.com/problems/add-bold-tag-in-string/
# Method: Trie and array of bolded indexes, check words starting at index
# Time: O(w + n*m)
# Space: O(w + n)
# Difficulty: Mediun
# Notes: w = characters in all words, m = longest word

class Solution:
    def addBoldTag(self, s: str, words: List[str]) -> str:
        return Solution.bolden_word(s, words)

    @staticmethod
    def words_to_trie(words: List[str]) -> Dict:
        trie = {}
        for word in words:
            trie_head = trie
            for ltr in word:
                trie_head = trie_head.setdefault(ltr, {})
            trie_head["_is_finish"] = True
        return trie

    @staticmethod
    def bolden_word(s: str, words: List[str]) -> str:
        is_bold = [False for _ in range(len(s))]
        trie = Solution.words_to_trie(words)
        for i in range(len(s)):
            Solution.bold_words_starting_at(i, s, trie, is_bold)

        res = ""
        i = 0
        while i < len(s):
            if is_bold[i]:
                res += "<b>"
                while i < len(s) and is_bold[i]:
                    res += s[i]
                    i += 1
                res += "</b>"
            else:
                res += s[i]
                i += 1
        return res

    @staticmethod
    def bold_words_starting_at(idx, s, trie, is_bold):
        start = idx
        trie_head = trie
        while idx < len(s) and s[idx] in trie_head:
            # print(f"Looking for {idx=} next={trie_head[s[idx]].keys()} ltrs: {s[idx]=}")
            if "_is_finish" in trie_head[s[idx]]:
                for j in range(start, idx + 1):
                    is_bold[j] = True
            trie_head = trie_head[s[idx]]
            idx += 1


from django.urls import path
from . import views

app_name = "products"
urlpatterns = [
    path('', views.ProductList.as_view(), name="home"),
    path('new/', views.ProductCreate.as_view(), name="create"),
    path('<int:pk>/', views.ProductDetail.as_view(), name="detail"),
    path('<int:pk>/edit/', views.ProductEdit.as_view(), name="edit"),
    path('<int:pk>/delete/', views.ProductDelete.as_view(), name="delete"),
    path('csv/', views.generate_csv_view, name="generate_csv"),
]


import torch
from torch.nn.functional import leaky_relu, dropout
from torch.nn import Linear, ReLU, BatchNorm1d, ModuleList, L1Loss, LeakyReLU, Dropout, MSELoss,SELU
from torch_geometric.nn import Sequential, SAGEConv, Linear, to_hetero,HeteroConv,GATConv,GINEConv

class GNNEncoder(torch.nn.Module):
    def __init__(self,hidden_channels,out_channels,num_layers):
        super().__init__()

        self.hidden_channels = hidden_channels
        self.out_channels = out_channels
        self.num_layers = num_layers
        metadata = (['node1', 'node2', 'node3'],
                    [('node2', 'to', 'node3'),
                     ('node1', 'to', 'node2'),
                     ('node3', 'rev_to', 'node2'),
                     ('node2', 'rev_to', 'node1')])
        self.convs = torch.nn.ModuleList()
        layers = (self.hidden_channels, self.out_channels)
        for i in range(self.num_layers):
            conv = HeteroConv({
                edge_type: SAGEConv((-1, -1), layers[i])
                for edge_type in metadata[1]
            })
            self.convs.append(conv)

    def forward(self, x_dict, edge_index_dict):

        for conv in self.convs:
            x_dict = conv(x_dict, edge_index_dict)
            x_dict = {key: leaky_relu(x) for key, x in x_dict.items()}

        return x_dict

class EdgeDecoder(torch.nn.Module):
    def __init__(self, hidden_channels,out_channels):
        super().__init__()

        self.hidden_channels = hidden_channels
        self.out_channels = out_channels

        self.network = torch.nn.Sequential(
            Linear(-1, hidden_channels),
            SELU(),
            Dropout(p=0.2),
            Linear(hidden_channels, out_channels),
            SELU(),
            Dropout(p=0.2),
            Linear(out_channels, 32),
            SELU(),
            Dropout(p=0.2),
            Linear(32, 1)
        )

    def forward(self, z_dict, edge_index_dict,edge_attr_dict):
        ship, cust = edge_index_dict[('node1', 'to', 'node2')]
        cust,prod = edge_index_dict[('node2', 'to', 'node3')]
        attr1 = edge_attr_dict[('node1', 'to', 'node2')]
        attr2 = edge_attr_dict[('node2', 'to', 'node3')]
        # Concatenate the embeddings and edge attributed
        z1 = leaky_relu(z_dict['node1'][ship])
        z2 = leaky_relu(z_dict['node2'][cust])
        z3 = leaky_relu(z_dict['node3'][prod])
        z4 = leaky_relu(torch.cat([z1,z2,z3,attr1,attr2], dim=-1))
        z4 = self.network(z4)

        return z4.view(-1)


from __future__ import unicode_literals

import re

from django.utils.translation import gettext as _

HEADER_RE = re.compile(r'''
(?P<author>.*?)
'''.strip(), re.IGNORECASE | re.MULTILINE | re.DOTALL);

def finalise_markup(post):
    return HEADER_RE.sub(replace_headers, post)

def replace_headers(matchobj):
    author = matchobj.group('author')
    if author:
        quote_title = _("%(author)s has written:") % {'author': author}
    else:
        quote_title = _("Quoted message:")
    return '{}'.format(quote_title)


# Following Links in Python
from urllib.request import Request, urlopen
from bs4 import BeautifulSoup
import ssl

# Ignore SSL certificate errors
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

#url = "http://py4e-data.dr-chuck.net/known_by_Fikret.html"
url = input("Enter url: ")
count = input("Enter count: ")
position = input("Enter position: ")
n = 0
count = int(count)
position = int(position)
# Retrieve all of the anchor tags
while n <= count:
    req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
    html = urlopen(req).read()
    soup = BeautifulSoup(html, "html.parser")
    tags = soup('a')
    # Look at the parts of a tag
    print("Retrieving: ",url)
    url = tags[position-1].get('href', None)
    n += 1

Using Python to Access Web Data/week4_assignment/extract.py


"""
Script per calcular paràmetres de EiR.
Aleix Martínez Vinent
Maig 2020
"""
import numpy as np
import scipy.constants as sci
import cmath
C = 3*(10**8)
e0=sci.epsilon_0
mu0=sci.mu_0
pi = np.pi
m = 10**(-3)
k = 10**3
M = 10**6
G = 10**9
directivity={7:[0.782,0.138],7.5:[0.824,0.146],8:[0.865,0.157],8.5:[0.892,0.165],9:[0.918,0.169],9.5:[0.935,0.174],10:[0.943,0.179],10.5:[0.957,0.182],11:[0.964,0.185]}

def log_periodic(fmin,fmax,dire):
    """
    Entrada:
    \t - Frequencia minima [Hz]
    \t - Frequencia maxima [Hz]
    \t - Directivitat [dBi]

    Sortida:
    \t - Vector de longitud dels dipols [m]
    \t - Vector de longituds entre dipols [m]

    """
    N = int(np.log10(fmin/fmax)/(np.log10(directivity[dire][0]))+1)
    L = [C/fmin]
    S = []
    for i in range(0,N):
        L.append(directivity[int(dire)][0]*L[i])
        S.append(2*directivity[int(dire)][1]*L[i])
    print(f"\n \n Nombre de branques: {len(L)} \n \n")
    return L,S

def horn(G,f,a,b,dB=True):
    """
    Entrada:
    \t - Guany [dBi]
    \t - Frequencia del guany [Hz]
    \t - a [m]
    \t - b [m]

    Sortida:
    \t - xi
    \t - roe [m]
    \t - roh [m]
    \t - a1 [m]
    \t - b1 [m]
    \t - pe [m]
    \t - ph [m]

    """
    if dB == True:
        G0=10**(G/10)
    xi=G0/(2*pi*np.sqrt(2*pi))
    wl=C/f
    pujar = False
    mod = 0.5
    X1=((np.sqrt(2*xi)-(b/wl))**2)*(2*xi-1)
    X2=(((G0/(2*pi))*(np.sqrt(3/(2*pi)))*(1/np.sqrt(xi))-(a/wl))**2)*(((G0**2)/(6*(pi**3)))*(1/xi)-1)
    D=np.abs(X1-X2)+1
    xant=0
    xalt=0
    xbaix=0

    """Obtencio de xi"""
    while np.abs(X1-X2)>0.0005:
        xant = xi
        Dant = D
        D = np.abs(X1-X2)

        if Dant > D:                       # Si diferència entre X1 i X2 era major abans, anem per bon camí
            mod*=0.9                                # Disminuim increment per no passar-nos i anar interpolant valor
            if pujar == False:
                xi -=mod
            else:
                xi+=mod

        else:                              # Si diferencia era menor abans, ens estem allunyant del valor correcte. Si abans haviem pujat ara haurem de baixar el valor de xi.
            mod*=0.91                                # Fem un increment major perque el d'abans era incorrecte i així tornem cap enrrera.
            if pujar == False:                              # Si abans haviem baixat, hem de tornar cap enrrera i pujar
                xi+=mod
                pujar = True
            else:                                           # Si abans haviem pujat, hem de tornar cap enrrera i baixar
                xi-=mod
                pujar = False
        X1=((np.sqrt(2*xi)-(b/wl))**2)*(2*xi-1)
        X2=(((G0/(2*pi))*(np.sqrt(3/(2*pi)))*(1/np.sqrt(xi))-(a/wl))**2)*(((G0**2)/(6*(pi**3)))*(1/xi)-1)

    print(f"\n \n Valor de x: {xi} \n ")
    print(str(X1) + " = " + str(X2))

    """Obtencio dels demes parametres"""
    roe=xi*wl
    roh=(wl/xi)*(G0**2)/(8*(pi**3))
    a1=(G0/(2*pi))*wl*np.sqrt(3/(2*pi*xi))
    b1=np.sqrt(2*xi)*wl
    pe=(b1-b)*np.sqrt(-(1/4)+(roe/b1)**2)
    ph=(a1-a)*np.sqrt(-(1/4)+(roh/a1)**2)
    return xi,roe,roh,a1,b1,pe,ph

def patch(f,er,d):
    """
    Dissenya una antena de patch a partir dels seguents parametres:
    \t - Frequencia de funcionament [Hz]
    \t - Permitivitat del substrat [F/m]
    \t - Gruixor del substrat [m]

    """
    wl=C/f
    W = (1/(2*f*np.sqrt(e0*mu0)))*np.sqrt(2/(er+1))
    ereff = (er+1)/2+(er-1)/(2*np.sqrt(1+(12*d/W)))
    dL = 0.412*d*(ereff + 0.3)*((W/d)+0.264)/((ereff-0.258)*((W/d)+0.8))
    L = (1/(2*f*np.sqrt(ereff)*np.sqrt(e0*mu0)))-(2*dL)
    Za = (90*(er**2)*((L/W)**2))/(er-1)
    df = (16*(er-1)*L*d)/(3*np.sqrt(2)*(er**2)*wl*W)
    if W > 3*wl:
        return W,dL,L,ereff,8*(W/wl),Za,df

    else:
        return W,dL,L,ereff,8.2,Za,df

def max_range(f,Pt,Dt,Dr,Pl):
    """
    Calcula distància màxima que pot viatjar un senyal transmes per una antena per ser rebuda per una altra a partir dels seguents parametres:
    \t - Frequencia de funcionament [Hz]
    \t - Potencia transmesa [W]
    \t - Directivitat antena transmissora [dBi]
    \t - Directivitat antena receptora [dBi]
    \t - Sensibilitat d'antena receptora [dB]

    Retorna distancia en km.
    """
    L0=Dt+Dr + 10*np.log10(Pt*1000)-Pl
    r=10**((L0-32.5-20*np.log10(f/(10**6)))/20)
    return r

def min_sensibility(f,r,Pt,Dt,Dr):
    """
    Calcula sensibilitat mínima [dBi] que ha de tenir antena per rebre un senyal transmes per una antena a una certa distancia a partir dels seguents parametres:
    \t - Frequencia de funcionament [Hz]
    \t - Distància entre les dues antenes [m]
    \t - Potencia transmesa [W]
    \t - Directivitat antena transmissora [dBi]
    \t - Directivitat antena receptora [dBi]
    \t - Sensibilitat d'antena receptora [dBm]

    """
    return 10*np.log10(Pt*1000)+Dt+Dr-(32.5+20*np.log10(f/(10**6))+20*np.log10(r/1000))

def recieved_power(f,r1,CS,Pt,Dt,B=False,Dr=0,r2=0):
    """
    Calcula potencia rebuda per un radar. Dos modes de funcionament: per mateixa antena o per dues.

    1 sola antena:
    \t - Frequencia de funcionament [Hz]
    \t - Distància de l'objecte [m]
    \t - Cross Section de l'objecte [m^2]
    \t - Potencia transmesa [W]
    \t - Directivitat antena [dBi]
    \t - True (1 sola antena)

    2 antenes:
    \t - Frequencia de funcionament [Hz]
    \t - Distància entre radar emissor i l'objecte [m]
    \t - Cross Section de l'objecte [m^2]
    \t - Potencia transmesa [W]
    \t - Directivitat antena emissora [dBi]
    \t - False (1 sola antena)
    \t - Directivitat antena receptora [dBi]
    \t - Distància entre radar receptor i l'objecte [m]

    """
    wl=C/f
    Dt=10**(Dt/10)
    Dr=10**(Dr/10)
    if B==True:
        Pl=(Pt*CS*(Dt**2)*(wl**2))/(((4*pi)**3)*(r1**4))
    else:
        Pl=((Pt*CS*Dr*Dt)/(4*pi))*((wl/(4*pi*r1*r2))**2)
    return 10*np.log10(Pl)

def cross_section(f,r1,Pt,Pl,Dt,B=False, dB=False,Dr=0,r2=0):
    """
    Calcula cross section d'un objecte observat per un radar. Dos modes de funcionament: per mateixa antena o per dues.

    1 sola antena:
    \t - Frequencia de funcionament [Hz]
    \t - Distància de l'objecte [m]
    \t - Potencia transmesa [W]
    \t - Potencia rebuda [W]
    \t - Directivitat antena [dBi]
    \t - True (1 sola antena)
    |t - Directivitat expressat en dB [bool]

    2 antenes:
    \t - Frequencia de funcionament [Hz]
    \t - Distància entre radar emissor i l'objecte [m]
    \t - Potencia transmesa [W]
    \t - Potencia rebuda [W]
    \t - Directivitat antena emissora [dBi]
    \t - False (2 antenes)
    \t - Directivitat antena receptora [dBi]
    \t - Distància entre radar receptor i l'objecte [m]
    |t - Directivitat expressat en dB [bool]

    """
    wl=C/f
    if dB == True:
        Dt=10**(Dt/10)
        Dr=10**(Dr/10)

    if B==True:
        CS=(Pl/Pt)*((((4*pi)**3)*(r1**4))/((Dt**2)*(wl**2)))
    else:
        CS=(Pl/Pt)*(((4*pi)/(Dt*Dr))*(1/((wl/(4*pi*r1*r2))**2)))
    return CS
"""
def polar_to_rect_S(S11,S12,S21,S22,radians = False):
    if radians == False:
        if S11[1]>=0:
            S11[1]=S11[1]*(pi/180)
        else:
            S11[1]=(365-S11[1])*(pi/180)
        if S12[1]>=0:
            S12[1]=S12[1]*(pi/180)
        else:
            S12[1]=(365-S12[1])*(pi/180)
        if S21[1]>=0:
            S21[1]=S21[1]*(pi/180)
        else:
            S21[1]=(365-S21[1])*(pi/180)
        if S22[1]>=0:
            S22[1]=S22[1]*(pi/180)
        else:
            S22[1]=(365-S22[1])*(pi/180)

    c1 = [cmath.rect(S11[0],S11[1]), cmath.rect(S12[0],S12[1])]
    c2 = [cmath.rect(S21[0],S21[1]), cmath.rect(S22[0],S22[1])]
    x = [c1, c2]

    return x
"""

def polar_to_rect_S(S11,S12,S21,S22,radians = False):
    if radians == False:
            S11[1]=np.deg2rad(S11[1])
            S12[1]=np.deg2rad(S12[1])
            S21[1]=np.deg2rad(S21[1])
            S22[1]=np.deg2rad(S22[1])
    c1 = [cmath.rect(S11[0],S11[1]), cmath.rect(S12[0],S12[1])]
    c2 = [cmath.rect(S21[0],S21[1]), cmath.rect(S22[0],S22[1])]
    x = [c1, c2]

    return x

def polar_to_rect(P, radians = False):
    if radians == False:
        if P[1]>=0:
            P[1]=P[1]*(pi/180)
        else:
            P[1]=(365-P[1])*(pi/180)
        return cmath.rect(P[0],P[1])

def rect_to_polar(X,todegrees=True):
    P = cmath.polar(X)
    if todegrees==False:
        return P
    else:
        C = (P[0], P[1]*(180/pi))
        return C

def transistor_amplifier(Z0,Zs,Zl,S):
    LS = ((Zs-Z0)/(Zs+Z0))
    LL = ((Zl-Z0)/(Zl+Z0))
    Lin = S[0][0] + ((S[0][1]*S[1][0]*LL)/(1-(S[1][1]*LL)))
    Lout = S[1][1] + ((S[0][1]*S[1][0]*LS)/(1-(S[0][0]*LS)))

    if (LL == LS):
        GT = np.abs(S[1][0])**2
    else:
        GT = ((np.abs(S[1][0])**2)*(1-(np.abs(LS)**2))*(1-(np.abs(LL)**2)))/((np.abs(1-(LS*Lin))**2)*(np.abs(1-(S[1][1]*LL))**2))

    return GT, LS, LL, Lin, Lout, 10*np.log10(GT)

def two_port_TAMP(Z0,Zs,Zl,S,unilateral=False):
    LS = ((Zs-Z0)/(Zs+Z0))
    LL = ((Zl-Z0)/(Zl+Z0))
    Lin = S[0][0] + ((S[0][1]*S[1][0]*LL)/(1-(S[1][1]*LL)))
    G0 =np.abs(S[1][0])**2
    GT = (1-(np.abs(LL)**2))/(np.abs(1-(S[1][1]*LL))**2)
    if unilateral == False:
        GS = (1-(np.abs(LS)**2))/(np.abs(1-(LS*Lin))**2)
    else:
        GS = (1-(np.abs(LS)**2))/(np.abs(1-(LS*S[0][0]))**2)
    G = GS*G0*GT
    return G, 10*np.log10(G)

def rollet(S):
    diff = S[0][0]*S[1][1]-S[0][1]*S[1][0]
    K = (1-np.abs(S[0][0])**2-np.abs(S[1][1])**2+np.abs(diff)**2)/(2*np.abs(S[0][1]*S[1][0]))
    if K > 1:
        unconditional = True
    else:
        unconditional = False

    return unconditional, K, diff

def stability_circles(S):
    diff = S[0][0]*S[1][1]-S[0][1]*S[1][0]
    CL = np.conj(S[1][1]-(diff*np.conj(S[0][0])))/(np.abs(S[1][1])**2-np.abs(diff)**2)
    RL = np.abs((S[0][1]*S[1][0])/((np.abs(S[1][1])**2)-(np.abs(diff)**2)))
    CS = np.conj(S[0][0]-(diff*np.conj(S[1][1])))/(np.abs(S[0][0])**2-np.abs(diff)**2)
    RS = np.abs((S[0][1]*S[1][0])/((np.abs(S[0][0])**2)-(np.abs(diff)**2)))
    return CL, RL, CS, RS

def LS_LL_LIN_LOUT(Z0,Zs,Zl,S):
    LS = ((Zs-Z0)/(Zs+Z0))
    LL = ((Zl-Z0)/(Zl+Z0))
    Lin = S[0][0] + ((S[0][1]*S[1][0]*LL)/(1-(S[1][1]*LL)))
    Lout = S[1][1] + ((S[0][1]*S[1][0]*LS)/(1-(S[0][0]*LS)))
    return LS, LL, Lin, Lout

def amp_max_gain(Z0,Zs,Zl,S):
    LS,LL,Lin,Lout = LS_LL_LIN_LOUT(Z0,Zs,Zl,S)
    if (Lin == np.conj(LS) and Lout == np.conj(LL)):
        conj = True
    if (S[0][1]<=0.01):
        uni = True
    if conj == True:
        GT_MAX = (1/(1-(np.abs(LS)**2)))*(np.abs(S[1][0])**2)*((1-(np.abs(LL)**2))/np.abs(1-(S[1][1]*LL))**2)
    elif uni == True:
        GT_MAX = (1/(1-(np.abs(S[0][0])**2)))*(np.abs(S[1][0])**2)*(1/np.abs(1-(S[1][1]*LL))**2)
    else:
        return 0
    return GT_MAX

def spec_gain_mal(Z0,Zs,Zl,S,Gs=1,Gs_MAX=0, Gl=1, Gl_MAX=0):
        LS,LL,Lin,Lout = LS_LL_LIN_LOUT(Z0,Zs,Zl,S)
        if Gs == 0 and Gs_MAX == 0:
            gs =(1-(np.abs(S[0][0])**2))*((1-(np.abs(LS)**2))/(np.abs(1-(S[0][0]*LS))**2))
        elif Gs_MAX == 0:
            gs = Gs/(1/(1-(np.abs(S[0][0])**2)))
        else:
            gs = Gs/Gs_MAX

        if Gl == 0 and Gl_MAX == 0:
            gl =(1-(np.abs(S[1][1])**2))*((1-(np.abs(LL)**2))/(np.abs(1-(S[1][1]*LL))**2))
        elif Gl_MAX == 0:
            gl = Gl/(1/(1-(np.abs(S[1][1])**2)))
        else:
            gl = Gl/Gl_MAX

        CS = (gs*np.conj(S[0][0]))/(1-(1-gs)*(np.abs(S[0][0])**2))
        RS = (np.sqrt(1-gs)*(1-(np.abs(S[0][0])**2)))/(1-(1-gs)*(np.abs(S[0][0])**2))
        CL = (gl*np.conj(S[1][1]))/(1-(1-gl)*(np.abs(S[1][1])**2))
        RL = (np.sqrt(1-gl)*(1-(np.abs(S[1][1])**2)))/(1-(1-gl)*(np.abs(S[1][1])**2))
        return CS, RS, CL, RL

def spec_gain(Z0,S,Gs, Gl, polar=True, dB=True, Gs_MAX=0, Gl_MAX=0):
        if dB == True:
            Gs = 10**(Gs/10)
            Gl = 10**(Gl/10)

        if Gs_MAX == 0:
            gs = Gs/(1/(1-(np.abs(S[0][0])**2)))
        else:
            gs = Gs/Gs_MAX
        if Gl_MAX == 0:
            gl = Gl/(1/(1-(np.abs(S[1][1])**2)))
        else:
            gl = Gl/Gl_MAX

        CS = (gs*np.conj(S[0][0]))/(1-(1-gs)*(np.abs(S[0][0])**2))
        RS = (np.sqrt(1-gs)*(1-(np.abs(S[0][0])**2)))/(1-(1-gs)*(np.abs(S[0][0])**2))
        CL = (gl*np.conj(S[1][1]))/(1-(1-gl)*(np.abs(S[1][1])**2))
        RL = (np.sqrt(1-gl)*(1-(np.abs(S[1][1])**2)))/(1-(1-gl)*(np.abs(S[1][1])**2))
        if polar == True:
            return rect_to_polar(CS),RS,rect_to_polar(CL),RL
        return CS, RS, CL, RL

def low_noise_amp(Z0, RN, Lopt, NF, NFmin,dB=True,polarinput=True):
    """
    Parameters
    ----------
    Z0 : float
        Characteristic impedance.
    RN : float
        Equivalent noise resistance of transistor.
    Lopt : complex (rectangular)
        OPtimum source reflection coefficient (Ls).
    NF : float
        Desired Noise Figure.
    NFmin : float
        Minimum Noise Figure of System.
    dB : boolean, optional
        True if NF and NFmin are in dB. The default is True.

    Returns
    -------
    N

    CNF : complex
        Circle center.
    RNF : float
        Radius of constant NF circle.

    """
    if polarinput == True:
        Lopt = polar_to_rect(Lopt)
    if dB==True:
        NF = 10**(NF/10)
        NFmin = 10**(NFmin/10)
    N = (np.abs(1+Lopt)**2)*((NF-NFmin)/((4*RN)/(Z0)))
    CNF = Lopt/(N+1)
    RNF = np.sqrt(N*(N+1-(np.abs(Lopt)**2)))/(N+1)
    return N,CNF,RNF

def required_PN(C,S,I,BW):
    """

    Parametres
    ----------
    C : float
        Nivell desitjat de senyal (dBm).
    S : float
        Nivell desitjat de canal adjacent (dB).
    I : float
        Nivell no desitjat de senyal de soroll (dBm).
    BW : float
        Amplada de banda del filtre IF (Hz).

    Retorna
    -------
    L : float
        Maxim soroll de fase per aconseguir un aïllament de canal adjacent de S (paràmetre entrada) (dBc/Hz).

    """
    L = C -S -I -10*np.log10(BW)
    return L

def GC_LC(PIF,PRF):
    """
    Retorna Gc (Conversion Gain) i Lc (Conversion Loss)
    """
    return 10*np.log10(PIF/PRF),20*np.log10(10*np.log10(PIF/PRF))

Transciever Parameters Calculation Libraries/Calcul_Params_EIR.py


import argparse
import sys
import os

#3- Escribir un programa en Python que acepte argumentos de línea de comando para leer un archivo de texto.
#El programa debe contar el número de palabras y líneas del archivo e imprimirlas en la salida estándar.
#Además el programa debe aceptar una opción para imprimir la longitud promedio de las palabras del archivo.
#Esta última opción no debe ser obligatoria. Si hubiese errores deben guardarse el un archivo cuyo nombre será "errors.log" usando la redirección de la salida de error.
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('archivo', type=str, help='escribir la ruta al archivo de texto')
    args = parser.parse_args()

    try:
        with open(args.archivo, 'r') as archivo:
            contenido = archivo.read()
            palabras = contenido.split()
            cantidad_palabras = len(palabras)
            lineas = contenido.split('\n')
            cantidad_lineas = len(lineas)
        print('El archivo', args.archivo, 'tiene', cantidad_palabras, 'palabras y', cantidad_lineas, 'líneas.')
    except FileNotFoundError:
        with open("errors.log", "a") as error_file:
            error_file.write("The file {} does not exist\n".format(args.archivo))
        sys.stderr.write("The file {} does not exist\n".format(args.archivo))
        sys.exit(1)

if __name__ == "__main__":
    main()


#####################################
# Author: Davi Sidarta-Oliveira
# School of Medical Sciences,University of Campinas,Brazil
# contact: davisidarta[at]fcm[dot]unicamp[dot]com
# License: MIT
######################################
# Defining eigendecomposition routines for kernels in a scikit-learn fashion

from sklearn.metrics import pairwise_distances
from warnings import warn
import numpy as np
from sklearn.utils import check_random_state
from scipy.linalg import eigh
from topo.spectral import graph_laplacian, diffusion_operator, LE
from sklearn.base import BaseEstimator, TransformerMixin
from scipy import sparse
from topo.tpgraph.kernels import Kernel
EIGEN_SOLVERS = ['dense', 'arpack', 'lobpcg']
try:
    from pyamg import smoothed_aggregation_solver
    PYAMG_LOADED = True
    EIGEN_SOLVERS.append('amg')
except ImportError:
    PYAMG_LOADED = False

def eigendecompose(G, n_components=8, eigensolver='arpack', largest=True, eigen_tol=1e-4, random_state=None, verbose=False):
    """
    Eigendecomposition of a graph matrix.

    Parameters
    ----------
    G : array-like or sparse matrix, shape (n_vertices, n_vertices).

    n_components : int (optional, default 8).
        Number of eigenpairs to compute.

    eigensolver : string (optional, default 'arpack').
        Method for computing the eigendecomposition. Can be either 'arpack', 'lobpcg', 'amg' or 'dense'.
        * 'dense' :
            use standard dense matrix operations for the eigenvalue decomposition.
            For this method, G must be an array or matrix type.
            This method should be avoided for large problems.
        * 'arpack' :
            use arnoldi iteration in shift-invert mode. For this method,
            M may be a dense matrix, sparse matrix, or general linear operator.
        * 'lobpcg' :
            Locally Optimal Block Preconditioned Conjugate Gradient Method.
            A preconditioned eigensolver for large symmetric positive definite
            (SPD) generalized eigenproblems.
        * 'amg' :
            Algebraic Multigrid solver (requires ``pyamg`` to be installed)
            It can be faster on very large, sparse problems, but may also lead
            to instabilities.

    largest : bool (optional, default True).
        If True, compute the largest eigenpairs. If False, compute the smallest eigenpairs.

    eigen_tol : float (optional, default 1e-4).
        Error tolerance for the eigenvalue solver. If 0, machine precision is used.

    random_state : int or numpy.random.RandomState() (optional, default None).
        A pseudo random number generator used for the initialization of the
        lobpcg eigen vectors decomposition when eigen_solver == 'amg'.
        By default, arpack is used.

    Returns
    -------
    eigen_values : array, shape (n_vertices,)
        Eigenvalues of the graph matrix.
    eigen_vectors : array, shape (n_vertices, n_vertices)
        Eigenvectors of the graph matrix.
    """
    n_components = n_components+1
    N = G.shape[0]
    if eigensolver not in EIGEN_SOLVERS:
        raise ValueError('Unknown eigensolver %s' % eigensolver)
    random_state = check_random_state(random_state)
    if not sparse.issparse(G) and eigensolver != 'dense':
        if verbose:
            print('Dense input and `eigensolver` set to %s' %
                eigensolver + '.' + '\n Converting to CSR matrix.')
        G = sparse.csr_matrix(G)
    if eigensolver == 'dense':
        if sparse.issparse(G):
            if verbose:
                print('Sparse input and `eigensolver` set to %s' %
                    eigensolver + '.' + '\n Converting to dense array.')
            G = G.toarray()
        evals, evecs = eigh(G)
    if sparse.issparse(G):
        if not isinstance(G, sparse.csr_matrix):
            G.tocsr()
    G = G.astype(float)
    if eigensolver == 'arpack':
        if largest:
            which = 'LM'
        else:
            which = 'SM'
        evals, evecs = sparse.linalg.eigsh(
            G, k=n_components, which=which, tol=eigen_tol, maxiter=N * 5)
    elif eigensolver == 'lobpcg':
        evals, evecs = sparse.linalg.lobpcg(
            G, random_state.normal(size=(G.shape[0], n_components)), largest=largest, tol=eigen_tol, maxiter=N // 5)
    elif eigensolver == 'amg':
        if not PYAMG_LOADED:
            raise ImportError(
                'Using "amg" as eigensolver requires pyamg, which is not installed. Install it with pip install pyamg')
        # for numerical stability
        np.random.set_state(random_state.get_state())
        # Use AMG to get a preconditioner and speed up the eigenvalue problem.
        ml = smoothed_aggregation_solver(G)
        M = ml.aspreconditioner()
        n_find = min(N, 5 + 2*n_components)
        X = random_state.rand(N, n_find)
        X[:, 0] = (G.diagonal()).ravel()
        evals, evecs = sparse.linalg.lobpcg(G, X, M=M, largest=largest)
    evals = np.real(evals)
    evecs = np.real(evecs)
    if largest:
        idx = np.argsort(evals)[::-1]
    else:
        idx = np.argsort(evals)[::]
    evals = evals[idx]
    evecs = evecs[:, idx]
    return evals, evecs

class EigenDecomposition(BaseEstimator, TransformerMixin):
    """
    Scikit-learn flavored class for computing eigendecompositions of sparse symmetric matrices.
    and exploring the associated eigenvectors and eigenvalues.
    Takes as main input a `topo.tpgraph.Kernel()` object or a symmetric matrix, which can be either an adjacency/affinity matrix,
    a kernel, a graph laplacian, or a diffusion operator.

    Parameters
    ----------
    n_components : int (optional, default 10).
        Number of eigenpairs to be computed.

    method : string (optional, default 'DM').
        Method for organizing the eigendecomposition. Can be either 'top', 'bottom', 'msDM', 'DM' or 'LE'.
        * 'top' : computes the top eigenpairs of the matrix.
        * 'bottom' : computes the bottom eigenpairs of the matrix.
        * 'msDM' : computes the eigenpairs of the diffusion operator on the matrix, and multiscales them. If a `Kernel()` object is provided, will use the computed diffusion operator if available.
        * 'DM' : computes the eigenpairs of the diffusion operator on the matrix. If a `Kernel()` object is provided, will use the computed diffusion operator if available.
        * 'LE' : computes the eigenpairs of the graph laplacian on the matrix. If a `Kernel()` object is provided, will use the computed graph laplacian if available.

    eigensolver : string (optional, default 'arpack').
        Method for computing the eigendecomposition. Can be either 'arpack', 'lobpcg', 'amg' or 'dense'.
        * 'dense' :
            use standard dense matrix operations for the eigenvalue decomposition.
            For this method, M must be an array or matrix type.
            This method should be avoided for large problems.
        * 'arpack' :
            use arnoldi iteration in shift-invert mode. For this method,
            M may be a dense matrix, sparse matrix, or general linear operator.
        * 'lobpcg' :
            Locally Optimal Block Preconditioned Conjugate Gradient Method.
            A preconditioned eigensolver for large symmetric positive definite
            (SPD) generalized eigenproblems.
        * 'amg' :
            Algebraic Multigrid solver (requires ``pyamg`` to be installed)
            It can be faster on very large, sparse problems, but requires
            setting a random seed for better reproducibility.

    laplacian_type : string (optional, default 'normalized')
        The type of Laplacian to compute. Possible values are: 'normalized', 'unnormalized', 'random_walk' and 'geometric'.

    anisotropy : float (optional, default 0).
        The anisotropy (alpha) parameter in the diffusion maps literature for kernel reweighting.

    eigen_tol : float (optional, default 0.0).
        Error tolerance for the eigenvalue solver. If 0, machine precision is used.

    t : int (optional, default 1).
        Time parameter for the diffusion operator, if 'method' is 'DM'. The diffusion operator will be powered by t. Ignored for other methods.

    return_evals : bool (optional, default False).
        Whether to return the eigenvalues along with the eigenvectors.

    random_state : int or numpy.random.RandomState() (optional, default None).
        A pseudo random number generator used for the initialization of the
        lobpcg eigen vectors decomposition when eigen_solver == 'amg'.
        By default, arpack is used.

    """

    def __init__(self,
                 n_components=10,
                 method='DM',
                 eigensolver='arpack',
                 eigen_tol=1e-4,
                 drop_first=True,
                 weight=True,
                 laplacian_type='random_walk',
                 anisotropy=1,
                 t=1,
                 random_state=None,
                 return_evals=False,
                 estimate_eigengap=True,
                 enforce_min_eigs=True,
                 verbose=False):
        self.n_components = n_components
        self.method = method
        self.eigensolver = eigensolver
        self.eigen_tol = eigen_tol
        self.drop_first = drop_first
        self.laplacian_type = laplacian_type
        self.weight = weight
        self.t = t
        self.anisotropy = anisotropy
        self.random_state = random_state
        self.verbose = verbose
        self.eigenvalues = None
        self.eigenvectors = None
        self.laplacian = None
        self.diffusion_operator = None
        self.embedding = None
        self.powered_operator = None
        self.N = None
        self.D_inv_sqrt_ = None
        self.return_evals = return_evals
        self.estimate_eigengap = estimate_eigengap
        self.eigengap = None
        self.enforce_min_eigs = enforce_min_eigs

    def __repr__(self):
        if self.eigenvectors is not None:
            if (self.N is not None):
                msg = "EigenDecomposition() estimator fitted with %i samples" % (
                    self.N)
            else:
                msg = "EigenDecomposition() estimator without fitted data."
        else:
            msg = "EigenDecomposition() estimator without any fitted data."
        if self.eigenvectors is not None:
            if self.method == 'DM':
                msg += " using Diffusion Maps"
            elif self.method == 'msDM':
                msg += " using multiscale Diffusion Maps"
            elif self.method == 'LE':
                msg += " using Laplacian Eigenmaps"
            elif self.method == 'top':
                msg += " using top eigenpairs"
            elif self.method == 'bottom':
                msg += " using bottom eigenpairs"
            if self.weight:
                msg += ", weighted by the square root of the eigenvalues"
            msg += '.'
        return msg

    def fit(self, X):
        """
        Computes the eigendecomposition of the kernel matrix X following the organization set by 'method'.

        Parameters
        ----------
        X : array-like, shape (n_samples, n_samples)
            Matrix to be decomposed. Should generally be an adjacency, affinity/kernel/similarity, Laplacian matrix or a diffusion-type operator.

        Returns
        -------
        self : object
            Returns the instance itself, with eigenvectors stored at EigenDecomposition.eigenvectors
            and eigenvalues stored at EigenDecomposition.eigenvalues. If method is 'DM', stores Diffusion Maps at EigenDecomposition.embedding.
            If 'method' is 'DM' or 'LE', the diffusion operator or graph laplacian is stored at EigenDecomposition.diffusion_operator
            or EigenDecomposition.graph_laplacian, respectively.
        """
        if self.method not in ['msDM','DM', 'LE', 'top', 'bottom']:
            raise ValueError(
                "Method must be one of 'msDM','DM', 'LE', 'top', 'bottom'.")
        if self.method in ['msDM', 'DM', 'top']:
            largest = True
        else:
            largest = False
        if isinstance(X, Kernel):
            self.N = X.N
            if self.method == 'DM' or self.method == 'msDM':
                self.diffusion_operator = X.P
                if X.D_inv_sqrt_ is not None:
                    self.D_inv_sqrt_ = X.D_inv_sqrt_
                    symmetric = True
                else:
                    symmetric = False
                target = self.diffusion_operator
            elif self.method == 'LE':
                self.laplacian = X.L
                target = self.laplacian
            else:
                target = X.K
        elif isinstance(X, np.ndarray) or isinstance(X, sparse.csr_matrix):
            if self.D_inv_sqrt_ is None:
                symmetric = False

            self.N = X.shape[0]
            if self.method == 'DM':
                if symmetric:
                    self.diffusion_operator, self.D_inv_sqrt_ = diffusion_operator(
                        X, alpha=self.anisotropy, symmetric=True, return_D_inv_sqrt=True)
                else:
                    self.diffusion_operator = diffusion_operator(
                        X, alpha=self.anisotropy, symmetric=False, return_D_inv_sqrt=False)
                target = self.diffusion_operator
                if self.t is not None:
                    if self.t > 1:
                        self.powered_operator = np.linalg.matrix_power(
                            self.diffusion_operator, int(self.t))
                        target = self.powered_operator

            elif self.method == 'msDM':
                if symmetric:
                    self.diffusion_operator, self.D_inv_sqrt_ = diffusion_operator(
                        X, alpha=self.anisotropy, symmetric=True, return_D_inv_sqrt=True)
                else:
                    self.diffusion_operator = diffusion_operator(
                        X, alpha=self.anisotropy, symmetric=False, return_D_inv_sqrt=False)
                target = self.diffusion_operator

            elif self.method == 'LE':
                self.laplacian = graph_laplacian(
                    X, laplacian_type=self.laplacian_type)
                target = self.laplacian
            else:
                target = X
        evals, evecs = eigendecompose(target, eigensolver=self.eigensolver, n_components=self.n_components,
                                      largest=largest, eigen_tol=self.eigen_tol, random_state=self.random_state, verbose=self.verbose)
        if self.drop_first:
            evals = evals[1:]
            evecs = evecs[:, 1:]
        if self.estimate_eigengap:
            max_eigs = int(np.sum(evals > 0, axis=0))
            if self.method not in ['LE', 'top', 'bottom']:
                first_diff = np.diff(evals)
                eg = np.argmax(first_diff) + 1
                if max_eigs == len(evals):
                    self.eigengap = max_eigs
                else:
                    self.eigengap = eg
            else:
                self.eigengap = max_eigs
        if self.method in ['DM', 'msDM']:
            if symmetric:
                evecs = self.D_inv_sqrt_.dot(evecs)
            for i in range(evecs.shape[1]):
                evecs[:, i] = evecs[:, i] / np.linalg.norm(evecs[:, i])
            if self.method == 'DM':
                self.embedding = evecs * evals
            elif self.method == 'msDM':
                if np.shape(evecs)[1] <= 5: # use all evecs
                    self.embedding = evecs * (evals / (1 - evals))
                else:
                    use_eigs = int(np.sum(evals > 0, axis=0))
                    eigs_idx = list(range(1, int(use_eigs)))
                    eig_vals = np.ravel(evals[eigs_idx])
                    self.embedding = evecs[:, eigs_idx] * (eig_vals / (1 - eig_vals))
        else:
            self.embedding = evecs
        self.eigenvectors = evecs
        self.eigenvalues = evals
        return self

    def rescale(self, use_eigs=50):
        """
        If using multiscale diffusion maps, this rescales the mapping to a new number of eigenvectors.
        """
        if self.method != 'msDM':
            raise ValueError(
                "Rescaling is only available for multiscale diffusion maps.")
        if use_eigs > self.eigenvectors.shape[1]:
            raise ValueError(
                "Cannot rescale to more eigenvectors than are available.")
        eigs_idx = list(range(1, int(use_eigs)))
        eig_vals = np.ravel(self.eigenvalues[eigs_idx])
        self.embedding = self.eigenvectors[:,
                                       eigs_idx] * (eig_vals / (1 - eig_vals))
        return self

    def results(self, return_evals=None):
        """
        Returns the results of the decomposition.

        Returns
        -------
        evecs : array-like, shape (n_samples, n_components)
            Eigenvectors of the matrix. If using 'msDM', returns the multiscaled eigenvectors.
        evals : array-like, shape (n_components, )
            Eigenvalues of the matrix. Returned only if return_evals is True.

        """
        if self.eigenvectors is None:
            return ValueError('The estimator has not been fitted yet.')
        if self.method == 'DM' or self.method == 'msDM':
            if return_evals:
                return self.embedding, self.eigenvalues
            else:
                return self.embedding
        else:
            if return_evals:
                return self.eigenvectors, self.eigenvalues
            else:
                return self.eigenvectors

    def transform(self, X=None):
        """
        Here for scikit-learn compability.

        Parameters
        ----------

        X : array-like, shape (n_samples, n_features)
            Data matrix. Here for scikit-learn compability.

        Returns
        -------
        evecs : array-like, shape (n_samples, n_components)
            Eigenvectors of the matrix. If using 'msDM', returns the multiscaled eigenvectors.
        evals : array-like, shape (n_components, )
            Eigenvalues of the matrix. Returned only if return_evals is True.
        """

        if self.eigenvectors is None:
            return ValueError('The estimator has not been fitted yet.')
        if self.method == 'DM' or self.method == 'msDM':
            if self.return_evals:
                return self.embedding, self.eigenvalues
            else:
                return self.embedding
        else:
            if self.return_evals:
                return self.eigenvectors, self.eigenvalues
            else:
                return self.eigenvectors

    def fit_transform(self, X):
        """
        Here for scikit-learn compability. Returns the eigenvectors learned during fitting.
        Parameters
        ----------
        X : array-like, shape (n_samples, n_samples).
            Kernel matrix or Kernel() object. Ignored, here for consistency with scikit-learn API.
        Returns
        -------
        evecs : array-like, shape (n_samples, n_components)
            Eigenvectors of the matrix.
        evals : array-like, shape (n_components, )
            Eigenvalues of the matrix. Returned only if return_evals is True.
        """
        if self.eigenvectors is None:
            self.fit(X)
        return self.transform(X)

    def spectral_layout(self, X, laplacian_type='normalized', return_evals=False):
        """Given a graph compute the spectral embedding of the graph. This function calls specialized
        routines if the graph has several connected components.

        Parameters
        ----------
        X : sparse matrix
            The (weighted) adjacency matrix of the graph as a sparse matrix.
        laplacian_type : string (optional, default 'normalized').
            The type of laplacian to use. Can be 'unnormalized', 'symmetric' or 'random_walk'.
        return_evals : bool
            Whether to also return the eigenvalues of the laplacian.

        Returns
        -------
        embedding: array of shape (n_vertices, dim)
            The spectral embedding of the graph.

        evals: array of shape (dim,)
            The eigenvalues of the laplacian of the graph. Only returned if return_evals is True.
        """
        n_components, labels = sparse.csgraph.connected_components(
            X, directed=False)
        if n_components > 1:
            return multi_component_layout(
                X,
                n_components,
                labels,
                self.n_components,
                laplacian_type,
                self.random_state,
                self.eigen_tol,
                return_evals
            )
        else:
            if self.embedding is None:
                self.fit(X)
            if return_evals:
                return self.embedding, self.eigenvalues
            else:
                return self.embedding

    def plot_eigenspectrum(self):
        """
        Plots the eigenspectrum.
        """
        if self.eigenvalues is None:
            raise ValueError('The estimator has not been fitted yet.')
        try:
            import matplotlib.pyplot as plt
        except ImportError:
            raise ImportError('matplotlib is required for plotting.')
        plt.plot(range(0, len(self.eigenvalues)), self.eigenvalues)
        plt.xlabel('Eigenvalue index')
        plt.ylabel('Eigenvalue')
        plt.show()

def spectral_layout(graph, dim, random_state, laplacian_type='normalized', eigen_tol=10e-4, return_evals=False):
    """Given a graph compute the spectral embedding of the graph. This is
    simply the eigenvectors of the laplacian of the graph. Here we use the
    normalized laplacian.
    Parameters
    ----------
    graph: sparse matrix
        The (weighted) adjacency matrix of the graph as a sparse matrix.
    dim: int
        The dimension of the space into which to embed.
    random_state: numpy RandomState or equivalent
        A state capable being used as a numpy random state.
    Returns
    -------
    embedding: array of shape (n_vertices, dim)
        The spectral embedding of the graph.
    """
    n_components, labels = sparse.csgraph.connected_components(graph, directed=False)

    if n_components > 1:
        return multi_component_layout(
            graph,
            n_components,
            labels,
            dim,
            laplacian_type,
            random_state,
            eigen_tol,
            return_evals
        )

    else:
        return LE(graph, dim, laplacian_type, eigen_tol, return_evals)

def component_layout(
        W,
        n_components,
        component_labels,
        dim,
        laplacian_type='normalized',
        eigen_tol=10e-4,
        return_evals=False
):
    """Provide a layout relating the separate connected components. This is done
    by taking the centroid of each component and then performing a spectral embedding
    of the centroids.
    Parameters
    ----------
    W: numpy.ndarray, pandas.DataFrame or scipy.sparse.csr_matrix.
         Affinity or adjacency matrix.
    n_components: int
        The number of distinct components to be layed out.
    component_labels: array of shape (n_samples)
        For each vertex in the graph the label of the component to
        which the vertex belongs.
    dim: int
        The chosen embedding dimension.
    laplacian_type: string, optional (default='normalized')
        The type of laplacian to use. Can be 'unnormalized', 'normalized' or 'random_walk'.
    Returns
    -------
    component_embedding: array of shape (n_components, dim)
        The ``dim``-dimensional embedding of the ``n_components``-many
        connected components.
    """

    # cannot compute centroids from precomputed distances
    # instead, compute centroid distances using linkage
    distance_matrix = np.zeros((n_components, n_components), dtype=np.float64)
    # use single linkage
    linkage = np.min

    for c_i in range(n_components):
        dm_i = W[component_labels == c_i]
        for c_j in range(c_i + 1, n_components):
            dist = linkage(dm_i[:, component_labels == c_j])
            distance_matrix[c_i, c_j] = dist
            distance_matrix[c_j, c_i] = dist

    affinity_matrix = np.exp(-(distance_matrix ** 2))

    component_embedding, evals = LE(
        affinity_matrix, n_eigs=dim, laplacian_type=laplacian_type, eigen_tol=eigen_tol, return_evals=True)
    component_embedding /= component_embedding.max()
    if return_evals:
        return component_embedding, evals
    else:
        return component_embedding

def multi_component_layout(
    graph,
    n_components,
    component_labels,
    dim,
    laplacian_type,
    random_state,
    eigen_tol,
    return_eval_list
):

    result = np.empty((graph.shape[0], dim), dtype=np.float32)

    if n_components > 2 * dim:
        meta_embedding = component_layout(
            graph,
            n_components,
            component_labels,
            dim,
            laplacian_type,
            return_evals=False
        )
        k = dim + 1
    else:
        k = int(np.ceil(n_components / 2.0))
        base = np.hstack([np.eye(k), np.zeros((k, dim - k))])
        meta_embedding = np.vstack([base, -base])[:n_components]

    evals_list = []
    for label in range(n_components):
        component_graph = graph.tocsr()[component_labels == label, :].tocsc()
        component_graph = component_graph[:, component_labels == label].tocoo()
        distances = pairwise_distances([meta_embedding[label]], meta_embedding)
        data_range = distances[distances > 0.0].min() / 2.0
        if component_graph.shape[0] < 2 * dim:
            result[component_labels == label] = (
                random_state.uniform(
                    low=-data_range,
                    high=data_range,
                    size=(component_graph.shape[0], dim),
                )
                + meta_embedding[label]
            )
            continue

        L = graph_laplacian(graph, laplacian_type)
        k = dim + 1
        try:
            eigenvalues, eigenvectors = sparse.linalg.eigsh(
                L,
                k,
                which="SM",
                tol=eigen_tol,
                v0=np.ones(L.shape[0]),
                maxiter=graph.shape[0] * 2,
            )
            order = np.argsort(eigenvalues)[1:k]
            component_embedding = eigenvectors[:, order]
            expansion = data_range / np.max(np.abs(component_embedding))
            component_embedding *= expansion
            result[component_labels == label] = (
                component_embedding + meta_embedding[label]
            )
            evals_list.append(eigenvalues[order])
        except sparse.linalg.ArpackError:
            warn(
                "WARNING: spectral decomposition FAILED! This is likely due to too small an eigengap. Consider\n"
                "adding some noise or jitter to your data."
            )
            return None
    if return_eval_list:
        return result, evals_list
    else:
        return result