"""

Structures and functions for reading a knowledge base of rules for constructing argument networks.

"""

import logging
import copy
import itertools
import functools
from collections import defaultdict

from pyparsing import alphanums, alphas, delimitedList, ParseException
from pyparsing import Word, Group, Optional, Suppress, OneOrMore

from .signals import Signal
from .common import Graph

logger = logging.getLogger('argulib.kb')

class ParseError(Exception):
    """ Thrown when parsing fails. """
    pass

class RuleError(Exception):
    pass

class Literal:
    """ The class represents a (possibly negated) literal.

    The main purpose of the class is to enforce naming conventions.
    Literals follow C naming conventions - start with a letter
    optionally followed by alpha-nums + '_'.

    To create a Literal instance from string use Literal.from_str(x). If
    a literal is to be negated, the identifier should start with '-' (eg, -p).
    The parser is not smart enough to parse "--p" as "p".

    """

    def __init__(self, name, negated=False):
        """ Create a literal with a name.
        @param name: the name of the literal
        @param negated: specifies whether the literal is negated (eg, not a; default false)

        """
        self.name = name
        self.negated = negated

    def __eq__(self, other):
        return (isinstance(other, Literal) and
                self.name == other.name and
                self.negated == other.negated)

    def __lt__(self, other):
        return (self.name, self.negated) < (other.name, other.negated)

    def __neg__(self):
        return Literal(self.name, not self.negated)

    def __hash__(self):
        return hash(str(self))

    def __str__(self):
        return '%s%s' % ('-' if self.negated else '', self.name)

    def __repr__(self):
        return "<Literal: %s>" % str(self)

    @classmethod
    def from_str(cls, data):
        try:
            parsed = _literal.parseString(str(data).strip(), parseAll=True)
            return parsed[0]
        except ParseException as e:
            raise ParseError('"%s" is not a literal; %s' % (data, str(e))) from e

    @classmethod
    def from_parsed(cls, parsed):
        params = parsed[0]
        if len(params) == 1:
            return cls(params[0])
        elif len(params) == 2:
            return cls(params[1], True)

# types of rules

STRICT_RULE = 0
DEFEASIBLE_RULE = 1
ORDERING_RULE = 2

class StrictRule:
    """ The class represents a strict rule (a modus ponens). """

    _hash = None

    type = STRICT_RULE
    is_strict = True
    is_defeasible = False

    def __init__(self, antecedent, consequent, name=''):
        """ A rule has to have antecedent and a consequent.
        antecedent -- a list of Literals or None
        consequent -- a Literal
        name -- an optional name (default = '')

        """
        self.name = name
        # do some error checking to be nice
        self.antecedent = check_list_of_type(antecedent, Literal,
                                             'Antecedent must be a list of Literals')
        self.antecedent.sort()  # it is essential that the list is sorted!
        if consequent is None:
            raise RuleError("Rule has to have a consequence (None provided)")
        else:
            if not isinstance(consequent, Literal):
                raise RuleError("Consequent must be a Literal")
            self.consequent = consequent

    def __eq__(self, other):
        """ Two rules are equal if they are the same type
        and contain the same antecedent and the same consequent.
        Name does not matter.

        """
        return (self.type == other.type and
                self.antecedent == other.antecedent and
                self.consequent == other.consequent)

    def __len__(self):
        """ The length of the rule is given by the number of antecedents. """
        return len(self.antecedent)

    def __hash__(self):
        """ Just like equals, the hash only uses the antecedent and consequent.

        """
        if not self._hash:
            value = hash(self.consequent)
            for a in self.antecedent:
                value ^= hash(a)
            self._hash = value
        return self._hash

    def __lt__(self, other):
        """ self < other if self has fewer antecedents or if they are alphabetically before other. """
        if other.type == DEFEASIBLE_RULE:
            return False
        ls = len(self.antecedent)
        lo = len(other.antecedent)
        if ls != lo:
            return ls < lo
        else:
            return str(self) < str(other)

    def __str__(self):
        name = self.name + ': ' if self.name else ''
        return ('{name}{antecedent} --> {consequent}'
                .format(name=name,
                        antecedent=', '.join(map(str, self.antecedent)),
                        consequent=str(self.consequent)))

    def __repr__(self):
        return '<StrictRule %s>' % str(self)

    @classmethod
    def from_str(cls, data):
        try:
            parsed = _strict_rule.parseString(str(data).strip(), parseAll=True)
            return parsed[0]
        except Exception as e:
            raise ParseError('"%s" is not a strict rule\n\t error: %s'
                             % (data, str(e)))

    @classmethod
    def from_parsed(cls, parsed):
        if 'name' in parsed:
            name = parsed['name']
        else:
            name = ''
        if 'antecedent' in parsed:
            antecedent = list(parsed['antecedent'])
        else:
            antecedent = []
        consequent = parsed['consequent'][0]
        return cls(antecedent, consequent, name)

class DefeasibleRule:
    """ The class represents a defeasible rule.
    The difference between a strict and a defeasible rule is that defeasible
    rule captures "the most frequent case". E.g., A ==> B means that
    A usually implies B.

    Defeasible rules can have exceptions (vulnerabilities) specified in
    parenthesis between the arrow: A =(-C)=> B means that A usually implies B
    unless C is true. (E.g., an object is red if it looks red unless we shine
    on it a red light.)

    """
    _hash = None

    type = DEFEASIBLE_RULE
    is_strict = False
    is_defeasible = True

    def __init__(self, antecedent, consequent, vulnerabilities=None, name=''):
        """ A rule has to have antecedent and a consequent.
        :param antecedent: a list of Literals or None
        :param consequent: a Literal
        :param vulnerabilities: a list of Literals or None (default)
        :param name: an optional name (default = '')

        """
        self.name = name
        # do some error checking to be nice
        self.antecedent = check_list_of_type(antecedent, Literal,
                                             'Antecedent must be a list of Literals')
        self.antecedent.sort()  # it is essential that the list is sorted!

        if not isinstance(consequent, Literal):
            raise RuleError('Consequent must be a Literal but was {}'
                            .format(type(consequent)))
        self.consequent = consequent

        self.vulnerabilities = check_list_of_type(vulnerabilities, Literal,
                                                  'Vulnerabilities must be list of Literals')
        self.vulnerabilities.sort()

    def __eq__(self, other):
        """ Two rules are equal if they are the same type (rule.type ==
        DEFEASIBLE_RULE) and contain the same antecedent, the same consequent
        and the same vulnerabilities. Name does not matter.

        """
        return (self.type == other.type and
                self.antecedent == other.antecedent and
                self.consequent == other.consequent and
                self.vulnerabilities == other.vulnerabilities)

    def __len__(self):
        """ The length of the rule is given by the number of antecedents. """
        return len(self.antecedent)

    def __hash__(self):
        """ Just like equals, the hash only uses the antecedent and consequent
        and the vulnerabilities.

        """
        if not self._hash:
            value = hash(self.consequent)
            for a in self.antecedent:
                value ^= hash(a)
            for a in self.vulnerabilities:
                value ^= hash(a)
            self._hash = value
        return self._hash

    def __lt__(self, other):
        """ self < other if self has fewer antecedents, or
        fewer vulnerabilities or
        if str(self) is alphabetically before str(other).

        """
        if other.type == STRICT_RULE:
            return True
        ls = len(self.antecedent)
        lo = len(other.antecedent)
        if ls != lo:
            return ls < lo
        else:
            ls = len(self.vulnerabilities)
            lo = len(other.vulnerabilities)
            if ls != lo:
                return ls < lo
            else:
                return str(self) < str(other)

    def __str__(self):
        text = '%s: ' % self.name if self.name else ''
        if len(self.antecedent) > 0:
            text += ', '.join(map(str, self.antecedent))
        if len(self.vulnerabilities) > 0:
            text += (' =(%s)=> ' % ', '.join(map(str, self.vulnerabilities)))
        else:
            text += ' ==> '
        text += str(self.consequent)
        return text

    def __repr__(self):
        return '<DefeasibleRule %s>' % str(self)

    @classmethod
    def from_str(cls, data):
        try:
            parsed = _defeasible_rule.parseString(str(data).strip(), parseAll=True)
            return parsed[0]
        except Exception as e:
            raise ParseError('"%s" is not a defeasible rule\n\tError: %s'
                             % (repr(data), str(e)))

    @classmethod
    def from_parsed(cls, parsed):
        if 'name' in parsed:
            name = parsed['name']
        else:
            name = ''
        if 'antecedent' in parsed:
            antecedent = list(parsed['antecedent'])
        else:
            antecedent = []
        consequent = parsed['consequent'][0]
        if 'vulnerabilities' in parsed:
            vulnerabilities = list(parsed['vulnerabilities'])
        else:
            vulnerabilities = list()
        return cls(antecedent, consequent, vulnerabilities, name)

class Ordering:

    type = ORDERING_RULE

    def __init__(self, *data, direction='<'):
        """Initialise orderings from a list of tuples. """
        self.data = check_list_of_type(list(data), tuple)
        self.direction = direction

    @classmethod
    def from_str(cls, data):
        try:
            parsed = _orderings.parseString(str(data).strip(), parseAll=True)
            return parsed[0]
        except Exception as e:
            raise ParseError('"%s" is not a preference rule\n\t error: %s' % (data, str(e)))

    @classmethod
    def from_parsed(cls, parsed, ord):
        tmp = []
        for i in range(len(parsed) - 1):
            tmp.append((list(parsed[i]), list(parsed[i + 1])))
        return cls(*tmp, direction=ord)

    def __str__(self):
        return (' %s ' % self.direction).join(map(str, self.data))

    def __repr__(self):
        return '<Ordering: %s>' % str(self)

    def __eq__(self, other):
        return self.data == other.data

class Proof:
    """ A proof leading to a particular consequent. """

    def __init__(self, name, rule, proofs, kb):
        """ Create an instance of a proof concluding "consequent" given
        the rule and proofs for the antecedents of the rule.

        """
        self._hash = None
        self.name = name
        self.rule = rule
        self._proofs = proofs
        self.is_strict = all(map(lambda x: x.is_strict, self.rules))
        self.is_defeasible = not self.is_strict
        self.weakest_link = self
        if kb:
            self.update_weakest_link(kb)

    def __str__(self):
        s = ' ∧ '.join(map(str, self.subproofs))
        if not self.has_empty_antecedent():
            s = '(' + s + ')' + ' → '
        s += str(self.rule)
        return s.strip()

    def __repr__(self):
        return '<Proof %s>' % str(self)

    def __eq__(self, other):
        """ Two proofs are equal if they have the same top rule and the same sub-proofs. """
        return self.rule == other.rule and self._proofs == other._proofs

    def __hash__(self):
        if not self._hash:
            value = hash(self.rule)
            for p in self._proofs.values():
                value ^= hash(p)
            self._hash = value
        return self._hash

    def __len__(self):
        """ Return the number of rules involved in this proof. """
        max_len = list(map(len, self.subproofs))
        return 1 + max(max_len + [0])

    def __lt__(self, other):
        """ Order by length, name. """
        l1 = len(self)
        l2 = len(other)
        if l1 == l2:
            return self.name < other.name
        else:
            return l1 < l2

    @property
    def antecedents(self):
        """ Yield the antecedents of this proof. """
        return self.rule.antecedents

    @property
    def consequent(self):
        """ Return the consequent of this proof. """
        return self.rule.consequent

    @property
    def conclusion(self):
        """ Return the consequent of this proof. """
        return self.rule.consequent

    @property
    def vulnerabilities(self):
        """ Return the vulnerabilities of this proof. """
        if self.rule.is_strict:
            return []
        return self.rule.vulnerabilities

    @property
    def subproofs(self):
        """ Yield the proofs used by the top rule only. """
        return self._proofs.values()

    @property
    def proofs(self):
        """ Return the set of all proofs used by this proof. """
        tmp = map(lambda x: x.proofs, self.subproofs)  # sets of subproofs
        proofs = functools.reduce(lambda x, y: x | y, tmp, set())  # collect them
        proofs.add(self)  # add self
        return proofs

    @property
    def rules(self):
        """ Return the generator of all rules including the top one. """
        return map(lambda x: x.rule, self.proofs)

    @property
    def strict_rules(self):
        """ Return all strict rules used in this proof and its subproofs. """
        return filter(lambda r: r.type == STRICT_RULE, self.rules)

    @property
    def defeasible_rules(self):
        """ Return all defeasible rules used in this proof and its subproofs."""
        return filter(lambda r: r.type == DEFEASIBLE_RULE, self.rules)

    def has_empty_antecedent(self):
        """ Return True if the proof does not have any antecedents. """
        return len(self._proofs) == 0

    def uses_rule(self, rule):
        """ Returns True if any of the proofs use the given rule. """
        return any(map(lambda x: x == rule, self.rules))

    def uses_consequent(self, consequent):
        """ Returns True if any of the proofs leads to the given consequent. """
        return any(map(lambda x: x.consequent == consequent, self.rules))

    def update_weakest_link(self, kb):
        """ Find the weakest rule based on the preference of the knowledge base. """
        # FIXME: this actually just finds the least preferred rule wrt to itself
        #   the weakest link should really be calculated between two proofs
        #   because preferences might not be defined over every pair of rules
        self.weakest_link = self.rule
        if not self.is_strict:
            for link in [p.weakest_link for p in self.proofs]:
                if self.weakest_link.is_strict and link.is_defeasible:
                    self.weakest_link = link
                elif kb.less_preferred(link, self.weakest_link):
                    self.weakest_link = link
            logger.debug('Weakest link of {0} set to \n\t{1}'
                         .format(self, self.weakest_link))
        return self.weakest_link

class KnowledgeBaseError(Exception):
    """ Thrown when construction of knowledge base fails. """
    pass

class KnowledgeBase:
    """ A class that represents the knowledge base of rules. """

    def __init__(self, name=''):
        self.name = name
        self._rules = defaultdict(set)  # consequent : [rule]
        self._prefs = Graph()  # directed acyclic graph storing partial order
        self._proofs = defaultdict(set)  # consequent : [proofs]
        # working memory -- inferred rules + user rules
        self._wm = defaultdict(set)  # consequent : [rule]
        # signals
        self.rules_added = Signal()
        self.rules_deleted = Signal()
        self.updated = Signal()  # passes proofs (set) and flag added
        self.ordering_changed = Signal()
        # index for creating proofs
        self.proof_idx = 0
        # if True, proofs are not generated -- for batch adding/deleting
        self.batch = False

    @classmethod
    def from_file(cls, file_name):
        if file_name is None:
            return cls()
        kb = cls(file_name)
        kb.read_file(file_name)
        return kb

    def __eq__(self, other):
        # it is easier to compare the stings as the KBs can differ in proofs
        # because removing a proof still leaves the key with an empty set in
        # the `_proofs` dict.
        return str(self) == str(other)

    def __str__(self):
        s = 'Name: "%s"\n' % self.name
        s += ('Strict Rules:\n\t%s\n' %
              '\n\t'.join(map(str, sorted(self.get_strict_rules()))))
        s += ('Defeasible Rules:\n\t%s\n' %
              '\n\t'.join(map(str, sorted(self.get_defeasible_rules()))))
        s += ('Proofs:\n\t%s\n' %
              '\n\t'.join(map(str, sorted(self.proofs))))
        s += ('Preferences:\n\t%s' %
              '\n\t'.join(map(lambda x: '%s > %s' % x, self._prefs.edges)))
        return s

    __repr__ = __str__

    @property
    def rules(self):
        """ Return a generator of rules in the KB (in working memory). """
        for rules in self._wm.values():
            for r in rules:
                yield r

    @property
    def proofs(self):
        """ Return a generator of proofs in the knowledge base. """
        for proofs in self._proofs.values():
            for p in proofs:
                yield p

    def proofs_for(self, conclusion):
        """Return the set of proofs for `conclusion`. """
        return self._proofs[conclusion]

    # TODO: remove the recalc flag? or change to batch?
    def add_rule(self, rule):
        """ Try to add a new rule (possibly a string) in the knowledge base.

        Strict rule might raise KBError if it would
        make the knowledge base inconsistent.
        Ordering rule might raise KBError if it would
        make the knowledge base inconsistent.
        Malformed string can raise ParseError.

        :param rule: str or Stric or Defeasible or Ordering rule
        """
        logger.debug('adding rule "%s"' % str(rule))
        if isinstance(rule, str):
            rule = mk_rule(rule)
        if STRICT_RULE == rule.type:
            self._add_strict_rule(rule)
        elif DEFEASIBLE_RULE == rule.type:
            self._add_defeasible_rule(rule)
        elif ORDERING_RULE == rule.type:
            self.add_ordering(rule)
        else:
            msg = 'Unknown rule type for rule "%s"'
            raise KnowledgeBaseError(msg % str(rule))
        return rule

    def _add_strict_rule(self, rule):
        logger.debug('  _adding strict rule "%s"' % str(rule))
        if not rule.type == STRICT_RULE:
            raise KnowledgeBaseError('Tried to insert a non strict rule.')
        closure = self.contrapositions(rule)
        all_variants = {rule} | closure
        # create new proofs
        new_proofs = self.construct_proofs(self._proofs, all_variants)
        # check that the new proofs are consistent with the current KB
        self.check_consistency(new_proofs)
        # add to the list of rules
        self._rules[rule.consequent].add(rule)
        # add to the working memory
        for r in all_variants:
            self._wm[r.consequent].add(r)
        # add the proofs
        for p in new_proofs:
            self._proofs[p.conclusion].add(p)
        # emit signals
        self.rules_added(all_variants)
        self.updated(new_proofs, added=True)

    def _add_defeasible_rule(self, rule):
        logger.debug('  _adding defeasible rule "%s"' % str(rule))
        if not rule.type == DEFEASIBLE_RULE:
            raise KnowledgeBaseError('Tried to insert a non defeasible rule.')
        self._rules[rule.consequent].add(rule)
        self._wm[rule.consequent].add(rule)
        # create new proofs
        new_proofs = self.construct_proofs(self._proofs, {rule})
        # add the proofs
        for p in new_proofs:
            self._proofs[p.conclusion].add(p)
        # emit signals
        self.rules_added({rule})
        self.updated(new_proofs, added=True)

    def del_rule(self, rule):
        """ Delete the given rule and all proofs that use this rule. """
        # if passed as a string, parse it first
        if isinstance(rule, str):
            rule = mk_rule(rule)
        logger.debug('deleting rule "%s"' % str(rule))
        if STRICT_RULE == rule.type:
            self._del_strict_rule(rule)
        elif DEFEASIBLE_RULE == rule.type:
            self._del_defeasible_rule(rule)
        elif ORDERING_RULE == rule.type:
            self.del_ordering(rule)
        else:
            msg = 'Unknown rule type for rule "%s"'.format(str(rule))
            raise KnowledgeBaseError(msg)
        return rule

    def _del_strict_rule(self, rule):
        logger.debug('  _deleting strict rule "%s"' % str(rule))
        if rule.consequent not in self._rules:
            return
        # if the rule is in _rules, it has to be in _wm as well
        closure = self.contrapositions(rule)
        all_variants = {rule} | closure
        # proofs that use the rule should also be deleted
        proofs = set()
        # delete the rule + contrapositions from working memory
        for r in all_variants:
            if r.consequent in self._wm:
                self._wm[r.consequent].remove(r)
                for p in self.proofs:
                    if p.uses_rule(r):
                        proofs.add(p)
        for p in proofs:
            self._proofs[p.consequent].remove(p)
        # delete the rule
        self._rules[rule.consequent].remove(rule)
        # emit signals
        self.rules_deleted(closure)
        self.updated(proofs, added=False)

    def _del_defeasible_rule(self, rule):
        logger.debug('  _deleting defeasible rule "%s"' % str(rule))
        if rule.consequent not in self._rules:
            return
        # if the rule is in _rules, it has to be in _wm as well
        self._wm[rule.consequent].remove(rule)
        proofs = set()
        for p in self.proofs:
            if p.uses_rule(rule):
                proofs.add(p)
        for p in proofs:
            self._proofs[p.consequent].remove(p)
        self._rules[rule.consequent].remove(rule)
        # emit signals
        self.rules_deleted({rule})
        self.updated(proofs, added=False)

    @staticmethod
    def contrapositions(rule):
        """ Create a set of contraposition rules.
        Every strict rule have corresponding contraposition rules.
        For example:
            a --> b also means that -b --> -a
            p, q --> r has p, -r --> -q and -r, q --> -p

        """
        logger.debug('  contrapositions for rule: %s' % rule)
        rules = set()
        nc = -rule.consequent  # negation of the consequent
        idx = 0
        for a in rule.antecedent:
            idx += 1
            antecedent = [i for i in rule.antecedent if i != a]
            antecedent.append(nc)
            r = StrictRule(antecedent, -a)
            if r.name != '':
                r.name = '%s-%d' % (rule.name, idx)
            rules.add(r)
            logger.debug('\t created contraposition : %s' % r)
        return rules

    def construct_proofs(self, existing_proofs, rules):
        """ Return the set of new proofs given the existing proofs
        and new rules.

        existing_proofs -- a dict of proofs: {conclusion: {proofs} }
        rules -- a set of rules: {rule}
        """
        # if we are batch processing, don't add any proofs
        if self.batch: return set()
        logger.debug('constructing proofs for rules \n\t%s'
                     % '\n\t'.join(map(str, rules)))
        new_proofs = set()
        inferred = set()  # new conclusions
        old_size = -1
        rules = sorted(rules)
        all_proofs = copy.copy(existing_proofs)  # shallow copy is sufficient
        num_steps = 0
        while old_size != len(new_proofs):
            # how many proofs we are starting from in this iteration
            old_size = len(new_proofs)
            num_steps += 1
            for r in rules:
                logger.debug('Current rule %s' % repr(r))
                # can we skip this rule, because no new conclusions affect it?
                if num_steps > 1 and (not inferred & set(r.antecedent)):
                    # none of the inferred conclusions is in the antecedent
                    logger.debug('...this rule has no new proofs')
                    continue
                logger.debug('...this rule might have new proofs')
                # find a proof for each antecedent
                subproofs = dict()
                for a in r.antecedent:
                    if a in all_proofs:
                        subproofs[a] = all_proofs[a]
                    else:
                        break
                # do we have a proof for all antecedents?
                if len(subproofs) == len(r.antecedent):
                    tmp = self._create_proofs(r, subproofs)
                    new_proofs |= tmp
                    inferred |= set(map(lambda p: p.conclusion, new_proofs))
                    all_proofs[r.consequent] |= tmp
            # we started only with the new rules;
            # now add other rules that might be applicable
            if num_steps == 1 and new_proofs:
                rules = sorted((set(rules) | set(self.rules)))
        logger.debug('Constructed proofs in %d iterations.' % num_steps)
        return new_proofs

    def _create_proofs(self, rule, subproofs):
        """ Insert new proofs based on the rule and the subproofs.
        Subproofs have the format of a dictionary with Literals as kees and
        sets of proofs as values. Because there can be many ways to reach
        a consequent (conclusion) a proof for each of the subproofs should
        be created.

        """
        logger.debug('\t adding proofs with rule %s' % str(rule))
        logger.debug('\t\t subproofs: %s' % str(subproofs))
        new_proofs = set()
        # we need a proof for each subproof so create a cartesian product
        # to find the possible combinations
        product = itertools.product(*subproofs.values())
        for combination in product:
            contains_loop = False
            for subproof in combination:
                if subproof.uses_rule(rule):
                    # avoid loops - in case one of the subproofs uses the rule
                    contains_loop = True
                    break
            if contains_loop:
                continue
            proofs = dict()
            for sp in combination:
                proofs[sp.consequent] = sp
            p = Proof('', rule, proofs, self)
            name = self.generate_proof_name()
            p.name = name
            logger.debug('\t\tfound proof "%s"' % str(p))
            new_proofs.add(p)
        return new_proofs

    def recalculate(self):
        """ Recalculate all proofs. """
        # create new proofs
        self._proofs.clear()
        self.proof_idx = 0
        new_proofs = self.construct_proofs(self._proofs, set(self.rules))
        # add the proofs
        for p in new_proofs:
            self._proofs[p.conclusion].add(p)
        self.updated(new_proofs, False)
        return new_proofs

    def get_rules(self):
        """ Return a generator of all rules in working memory.
        These include not only user defined rules but also contrapositions.

        """
        for rule_set in self._wm.values():
            for r in rule_set:
                yield r

    def get_defeasible_rules(self):
        """ Return a generator of defeasible rules. """
        for r in self.get_rules():
            if isinstance(r, DefeasibleRule):
                yield r

    def get_strict_rules(self):
        """ Return a generator of strict rules. """
        for r in self.get_rules():
            if isinstance(r, StrictRule):
                yield r

    def get_rule_with_name(self, name):
        """ Return a rule with given name or None. """
        for r in self.get_rules():
            if r.name == name:
                return r
        return None

    def rules_with_consequent(self, consequent):
        """ Return all rules with the given consequent or None. """
        if isinstance(consequent, str):
            consequent = Literal.from_str(consequent)
        return self._wm[consequent]

    def get_proofs_for_rule(self, rule):
        """ Return a proofs that uses `rule` as the top rule or `set()`."""
        # only look at the proofs with the same consequent
        result = set()
        for p in self._proofs[rule.consequent]:
            if p.rule == rule:
                result.add(p)
        return result

    def add_ordering(self, ordering):
        """ Parse the line containing names of rules and their preferences.
        format: r1, r2 < r3 < r4, r5 ...
        Throws ParseError when the format is wrong and
        KnowledgeBaseError when preferences are inconsistent.

        """
        # TODO: how to best report failure? pass up?
        logger.debug('Adding preferences: %s' % str(ordering))
        for a, b in ordering.data:
            self.add_preference_rule(a, b, direction=ordering.direction)
        self.ordering_changed()
        self.recalculate()

    def del_ordering(self, ordering):
        """ Remove the given orderings. """
        # TODO: how to best report failure? pass up?
        for a, b in ordering.data:
            self.del_preference_rule(a, b, direction=ordering.ord)
        self.ordering_changed()
        self.recalculate()

    def add_preference_rule(self, lower, higher, direction):
        """ Insert preferences for defeasible rules.
        lower, higher - iterable of rule names; higher is preferred over lower.

        """
        # self._prefs stores the preferences (partial order) as a DAG
        # NOTE: the order is specified across defeasible rule names
        # on inserting, check that we are not creating inconsistencies
        #   and raise KBError if we are
        if direction == '<':
            edges = list(itertools.product(higher, lower))
        else:
            edges = list(itertools.product(lower, higher))
        logger.debug('  preference edges: %s' % str(edges))
        # be exception safe - first check for consistency and then add
        tmp = copy.deepcopy(self._prefs)
        for e in edges:
            po = tmp.find_path(e[1], e[0])  # possible pref order (path)
            # if po exists than this edge is inconsistent
            if po is not None:
                # inconsistency - be nice and include extra info
                ps = (' ' + direction + ' ').join(map(str, po))
                msg = ('The preference rule "%s %s %s" is not consistent with'
                       'the existing preference order: %s' %
                       (e[0], direction, e[1], ps))
                raise KnowledgeBaseError(msg)
            # if the rule is consistent, tentatively add it
            tmp.add_edge(*e)
        # all edges are consistent with respect to
        #   the existing prefs and each other
        for e in edges:
            logger.debug('  Adding preference: %s > %s' % e)
            self._prefs.add_edge(*e)

    def del_preference_rule(self, lower, higher, direction):
        """ Delete the pair of names from preferences. """
        if direction == '<':
            edges = list(itertools.product(higher, lower))
        else:
            edges = list(itertools.product(lower, higher))
        logger.debug('Deleting preference rule {0}'.format(repr(edges)))
        try:
            for e in edges:
                logger.debug('Deleting "{0}"'.format(repr(e)))
                self._prefs.del_edge(*e)
            return True
        except KeyError:
            return False

    def more_preferred(self, rule_a, rule_b):
        """ Return True if rule 'a' is more preferred than rule 'b'. """
        # a is preferred over b if there is a path from a to b
        a = rule_a.name if not isinstance(rule_a, str) else rule_a
        b = rule_b.name if not isinstance(rule_b, str) else rule_b
        path = self._prefs.find_path(a, b)
        return path is not None and path != [a]

    def less_preferred(self, rule_a, rule_b):
        """ Return True if rule 'a' is less preferred than rule 'b'. """
        return self.more_preferred(rule_b, rule_a)

    def preference_order(self, rule_a, rule_b):
        """ Return the order of preferences between rule_a and rule_b or None. """
        return self._prefs.find_path(rule_a.name, rule_b.name)

    def has_preference_for(self, rule):
        """ Return True if the rule has any preference weight. """
        return rule.name in self._prefs

    def check_consistency(self, proofs):
        """ Check that none of the strict proofs interferes
        with the existing knowledge base.

        """
        for p in proofs:
            # consistency only applies to strict proofs
            if not p.is_strict:
                continue
            if -p.consequent in self._proofs:
                counterproofs = self._proofs[-p.consequent]
                for cp in counterproofs:
                    if cp.is_strict:
                        # cp is a strict proof with an opposite conclusion
                        # which is not consistent with the proof p
                        msg = ('The proof "%s" is inconsistent with an existing'
                               ' proof "%s"' % (str(p), str(cp)))
                        raise KnowledgeBaseError(msg)

    def generate_proof_name(self):
        """ Return a name for an argument. """
        name = 'P%d' % self.proof_idx
        self.proof_idx += 1
        return name

    def save_into_file(self, file_name):
        with open(file_name, "w") as f:
            for consequent, rules in self._rules.items():
                f.write('# rules with consequent "%s":\n' % str(consequent))
                for r in rules:
                    f.write(str(r) + '\n')
            for k, vs in self._prefs.items():
                if vs:
                    f.write('{k} < {vs}\n'.format(k=k, vs=', '.join(vs)))

    def read_file(self, file_name):
        with open(file_name, "r") as f:
            self.parse_file(f)

    def parse_file(self, file):
        line_no = 0
        self.batch = True
        for line in file:
            line_no += 1
            line = line.partition('#')[0].strip()  # remove comments
            if line == '':
                continue
            try:
                self.add_rule(line)
            except Exception as e:
                msg = 'Exception on line %d: %s'
                logger.exception(msg % (line_no, str(e)))
        self.batch = False
        proofs = self.recalculate()
        self.check_consistency(proofs)

def check_list_of_type(lst, cls, msg=''):
    """ Check that the given list contains only instances of cls.
    Raise TypeError if an element is not of the given type.
    If the list is None, an empty list is returned.

    :param lst: list of items or None
    :param cls: the class of which the elements should be instances
    :param msg: error message used in exception
    :returns: the passed list
    """
    if lst is None:
        return []
    for o in lst:
        if not isinstance(o, cls):
            if not msg:
                msg = ('Elements of the list must be instances of {cls}'
                       .format(cls=str(cls)))
            raise TypeError(msg)
    if not isinstance(lst, list):
        return list(lst)
    return lst

def print_proofs(proofs):
    for c, ps in proofs.items():
        print(str(c) + ':')
        for p in ps:
            print('  ' + str(p))

def mk_rule(rule):
    """ Take a string and create an a Strict or a Defeasible rule. """
    if isinstance(rule, str):
        return _rule_grammar.parseString(rule.strip())[0]
    elif isinstance(rule, StrictRule):
        return rule
    elif isinstance(rule, DefeasibleRule):
        return rule
    elif isinstance(rule, Ordering):
        return rule
    else:
        msg = 'mk_rule expects a string but received "%s"'
        raise TypeError(msg % repr(rule))

# ########################## parsing related functions ######################## #

_literal = Group(Optional(Word('-')) + Word(alphas, alphanums + '_'))
_literal.setParseAction(Literal.from_parsed)

_literals = delimitedList(_literal)
_antecedent = _literals
_vulnerabilities = _literals
_consequent = _literal
_rule_name = Word(alphas + '_', alphanums + '_')
_rule_names = delimitedList(_rule_name)

_strict_rule = (Optional(_rule_name.setResultsName("name") + ':') +
                Optional(Group(_antecedent).setResultsName("antecedent")) +
                "-->" + Group(_consequent).setResultsName("consequent"))

_strict_rule.setParseAction(StrictRule.from_parsed)

_defeasible_rule = (Optional(_rule_name.setResultsName("name") + ':') +
                    Optional(Group(_antecedent).setResultsName("antecedent")) + '=' +
                    Optional('(' + Group(_vulnerabilities).setResultsName("vulnerabilities") + ')') +
                    '=>' + Group(_consequent).setResultsName("consequent"))

_defeasible_rule.setParseAction(DefeasibleRule.from_parsed)

def _mk_l_ordering(_, __, tokens):
    return Ordering.from_parsed(tokens, '<')

def _mk_r_ordering(_, __, tokens):
    return Ordering.from_parsed(tokens, '>')

_lt, _gt = map(Suppress, '<>')
_l_orderings = Group(_rule_names) + OneOrMore(_lt + Group(_rule_names))
_l_orderings.setParseAction(_mk_l_ordering)
_r_orderings = Group(_rule_names) + OneOrMore(_gt + Group(_rule_names))
_r_orderings.setParseAction(_mk_r_ordering)

_orderings = _l_orderings | _r_orderings

_rule_grammar = _strict_rule | _defeasible_rule | _orderings | _literal

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


from psycopg2.extensions import AsIs

from odoo import fields, models, tools

AVAILABLE_STATES = [
    ("draft", "Draft"),
    ("open", "Todo"),
    ("cancel", "Cancelled"),
    ("done", "Held"),
    ("pending", "Pending"),
]

class CrmPhonecallReport(models.Model):
    """Generate BI report based on phonecall."""

    _name = "crm.phonecall.report"
    _description = "Phone calls by user"
    _auto = False

    user_id = fields.Many2one(comodel_name="res.users", string="User", readonly=True)
    team_id = fields.Many2one(comodel_name="crm.team", string="Team", readonly=True)
    priority = fields.Selection(
        selection=[("0", "Low"), ("1", "Normal"), ("2", "High")]
    )
    nbr_cases = fields.Integer(string="# of Cases", readonly=True)
    state = fields.Selection(AVAILABLE_STATES, string="Status", readonly=True)
    create_date = fields.Datetime(readonly=True, index=True)
    delay_close = fields.Float(
        string="Delay to close",
        digits=(16, 2),
        readonly=True,
        group_operator="avg",
        help="Number of Days to close the case",
    )
    duration = fields.Float(digits=(16, 2), readonly=True, group_operator="avg")
    delay_open = fields.Float(
        string="Delay to open",
        digits=(16, 2),
        readonly=True,
        group_operator="avg",
        help="Number of Days to open the case",
    )
    partner_id = fields.Many2one(
        comodel_name="res.partner", string="Partner", readonly=True
    )
    company_id = fields.Many2one(
        comodel_name="res.company", string="Company", readonly=True
    )
    opening_date = fields.Datetime(readonly=True, index=True)
    date_closed = fields.Datetime(string="Close Date", readonly=True, index=True)

    def _select(self):
        select_str = """
            select
                id,
                c.date_open as opening_date,
                c.date_closed as date_closed,
                c.state,
                c.user_id,
                c.team_id,
                c.partner_id,
                c.duration,
                c.company_id,
                c.priority,
                1 as nbr_cases,
                c.create_date as create_date,
                extract(
                  'epoch' from (
                  c.date_closed-c.create_date))/(3600*24) as delay_close,
                extract(
                  'epoch' from (
                  c.date_open-c.create_date))/(3600*24) as delay_open
           """
        return select_str

    def _from(self):
        from_str = """
            from crm_phonecall c
        """
        return from_str

    def init(self):
        """Initialize the report."""
        tools.drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute(
            """
            create or replace view %s as (
                %s
                %s
            )""",
            (AsIs(self._table), AsIs(self._select()), AsIs(self._from())),
        )


from typing import Dict

try:
    from gpt4all import GPT4All
except (ImportError, ModuleNotFoundError):
    GPT4All = None

_loaded_models = {}

def _make_openai_compatabile(message: str) -> Dict:
    return {"choices": [{"message": {"content": message, "role": "assistant"}}]}

def get_chat_completion(
    messages: Dict, model: str = "ggml-model-gpt4all-falcon-q4_0.bin"
) -> Dict:
    """Gets a chat completion from GPT4All.

    Parameters
    ----------
    messages : Dict
        The messages to use as a prompt for the chat completion.
    model : str
        The model to use for the chat completion. Defaults to "ggml-gpt4all-j-v1.3-groovy".

    Returns
    -------
    completion : Dict
    """
    if GPT4All is None:
        raise ImportError(
            "gpt4all is not installed, try `pip install scikit-llm[gpt4all]`"
        )
    if model not in _loaded_models.keys():
        loaded_model = GPT4All(model)
        _loaded_models[model] = loaded_model
        loaded_model._current_prompt_template = loaded_model.config["promptTemplate"]

    prompt = _loaded_models[model]._format_chat_prompt_template(
        messages, _loaded_models[model].config["systemPrompt"]
    )
    generated = _loaded_models[model].generate(
        prompt,
        streaming=False,
        temp=1e-10,
    )

    return _make_openai_compatabile(generated)

def unload_models() -> None:
    global _loaded_models
    _loaded_models = {}


import sys
sys.stdin = open('탈출.txt')
import collections

def bfs():
    gosum = collections.deque()
    r, c = start
    gosum.append((r, c, 0))
    gosum_visit = [[0 for _ in range(C)] for _ in range(R)]
    gosum_visit[r][c] = 1
    sv_time = -1

    while gosum:
        row, col, time = gosum.popleft()

        if sv_time != time:

            a = len(deq)

            for i in range(a):
                wr, wc = deq.popleft()

                for w in range(4):
                    move_row = wr + dr[w]
                    move_col = wc + dc[w]

                    if 0 <= move_row < R and 0 <= move_col < C:
                        if forest[move_row][move_col] == '.' and forest[move_row][move_col] != 'D':

                            forest[move_row][move_col] = '*'
                            deq.append((move_row, move_col))

        for w in range(4):
            nr = row + dr[w]
            nc = col + dc[w]

            if 0 <= nr < R and 0 <= nc < C:
                if (forest[nr][nc] == '.' or forest[nr][nc] == 'D') and gosum_visit[nr][nc] == 0:
                    if forest[nr][nc] == 'D':
                        return time + 1
                    else:
                        gosum_visit[nr][nc] = 1
                        gosum.append((nr, nc, time + 1))

        sv_time = time

    return "KAKTUS"

dr = [0, 0, -1, 1]
dc = [-1, 1, 0, 0]

R, C = map(int, input().split())

forest = [list(map(str, input())) for _ in range(R)]

deq = collections.deque()

for row in range(R):
    for col in range(C):
        if forest[row][col] == 'S':
            forest[row][col] = '.'
            start = (row, col)
        elif forest[row][col] == 'D':
            end = (row, col)
        elif forest[row][col] == '*':
            deq.append((row, col))

print(bfs())


"""
Utility functions for working with the YOLOv3 models.

################
Command Help:
usage: utility.py [-h] {strip} ...

Utility functions for working with the YOLOv3 models

positional arguments:
  {strip}

optional arguments:
  -h, --help  show this help message and exit

################
Strip Command Help:
usage: utility.py strip [-h] weights

Strip the extra information from a models checkpoint for training from scratch

positional arguments:
  weights     weights path

optional arguments:
  -h, --help  show this help message and exit
"""

import argparse

from utils.general import strip_optimizer

STRIP_COMMAND = "strip"

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Utility functions for working with the YOLOv3 models")
    subparsers = parser.add_subparsers(dest="command")
    strip_subparser = subparsers.add_parser(
        STRIP_COMMAND,
        description="Strip the extra information from a models checkpoint for training from scratch",
    )
    strip_subparser.add_argument('weights', type=str, help='weights path')
    args = parser.parse_args()

    if args.command == STRIP_COMMAND:
        print(f"stripping extras from {args.weights}")
        strip_optimizer(args.weights)
    else:
        raise ValueError(f"unknown command given of {args.command}")


import shapes
import numpy as np
import torch
import torch.nn.functional as F
import functools

def resize_to_min_size_(*tensors, dim = -1):
	size = min(t.shape[dim] for t in tensors)
	for t in tensors:
		if t.shape[dim] > size:
			sliced = t.narrow(dim, 0, size)
			t.set_(t.storage(), 0, sliced.size(), sliced.stride())

class SimpleVAD:
	def __init__(self,
	             kernel_size_smooth_silence: int = 4096,
	             kernel_size_smooth_signal: int = 128,
	             kernel_size_smooth_speaker: int = 4096,
	             silence_absolute_threshold: float = 0.05,
	             silence_relative_threshold: float = 0.2,
	             eps: float = 1e-9,
	             normalization_percentile: float = 0.9):
		self.kernel_size_smooth_silence = kernel_size_smooth_silence
		self.kernel_size_smooth_signal = kernel_size_smooth_signal
		self.kernel_size_smooth_speaker = kernel_size_smooth_speaker
		self.silence_absolute_threshold = silence_absolute_threshold
		self.silence_relative_threshold = silence_relative_threshold
		self.eps = eps
		self.normalization_percentile = normalization_percentile
		self.input_type = torch.tensor
		self.input_dtype = 'float32'

	def detect(self, signal: shapes.BT, allow_overlap: bool = False) -> shapes.BT:
		assert len(signal) <= 2

		padding = self.kernel_size_smooth_signal // 2
		stride = 1
		smoothed_for_diff = F.max_pool1d(signal.abs().unsqueeze(1), self.kernel_size_smooth_signal, stride=stride, padding=padding).squeeze(1)

		padding = self.kernel_size_smooth_silence // 2
		stride = 1

		# dilation
		smoothed_for_silence = F.max_pool1d(signal.abs().unsqueeze(1), self.kernel_size_smooth_silence, stride=stride, padding=padding).squeeze(1)

		# erosion
		smoothed_for_silence = -F.max_pool1d(-smoothed_for_silence.unsqueeze(1), self.kernel_size_smooth_silence, stride=stride, padding=padding).squeeze(1)

		# primitive VAD
		signal_max = smoothed_for_diff.kthvalue(int(self.normalization_percentile * smoothed_for_diff.shape[-1]), dim=-1, keepdim=True).values
		silence_absolute = smoothed_for_silence < self.silence_absolute_threshold
		silence_relative = smoothed_for_silence / (self.eps + signal_max) < self.silence_relative_threshold
		silence = silence_absolute | silence_relative

		if allow_overlap or len(signal) == 1:
			speech = ~silence
		else:
			diff_flat = smoothed_for_diff[0] - smoothed_for_diff[1]
			speaker_id_bipole = diff_flat.sign()

			padding = self.kernel_size_smooth_speaker // 2
			stride = 1
			speaker_id_bipole = F.avg_pool1d(speaker_id_bipole.view(1, 1, -1), kernel_size=self.kernel_size_smooth_speaker, stride=stride, padding=padding).view(-1).sign()

			# removing 1 sample silence at 1111-1-1-1-1 boundaries, replace by F.conv1d (patterns -101, 10-1)
			speaker_id_bipole = torch.where((speaker_id_bipole == 0) & (F.avg_pool1d(speaker_id_bipole.abs().view(1, 1, -1), kernel_size=3, stride=1, padding=1).view(-1) == 2 / 3) & (
						F.avg_pool1d(speaker_id_bipole.view(1, 1, -1), kernel_size=3, stride=1, padding=1).view(-1) == 0), torch.ones_like(speaker_id_bipole), speaker_id_bipole)

			resize_to_min_size_(silence, speaker_id_bipole, dim=-1)

			bipole = torch.tensor([1, -1], dtype=speaker_id_bipole.dtype, device=speaker_id_bipole.device)
			speech = (~silence) * (speaker_id_bipole.unsqueeze(0) == bipole.unsqueeze(1))
		speech = torch.cat([~speech.any(dim = 0).unsqueeze(0), speech])
		return speech

class WebrtcVAD:
	def __init__(self, aggressiveness: int = 3, sample_rate: int = 8_000, window_size: float = 0.01):
		assert sample_rate in [8_000, 16_000, 32_000, 48_000]
		assert window_size in [0.01, 0.02, 0.03]
		assert aggressiveness in [0, 1, 2, 3] # 3 is the most aggressive
		self.sample_rate = sample_rate
		self.window_size = window_size
		self.frame_len = int(window_size * sample_rate)
		self.aggressiveness = aggressiveness
		self.input_type = np.array
		self.input_dtype = 'int16'

	def detect(self, signal: shapes.BT, allow_overlap: bool = False) -> shapes.BT:
		assert signal.dtype == np.int16
		import webrtcvad
		speech_length = np.zeros(signal.shape, dtype = np.int)
		for channel in range(len(signal)):
			vad = webrtcvad.Vad(self.aggressiveness)
			frames = np.pad(signal[channel], (0, self.frame_len - signal.shape[-1] % self.frame_len), 'constant', constant_values = (0, 0))
			frames = bytearray(frames)
			start = None
			amount = 0

			for i in range(0, int(len(frames) / 2), self.frame_len):
				is_speech = vad.is_speech(frames[i * 2: (i + self.frame_len) * 2], self.sample_rate)
				if is_speech and start is None:
					start = i
					amount = 1
				elif is_speech:
					amount += 1
				elif not is_speech and start is not None:
					speech_length[channel, start: start + amount * self.frame_len] = amount * self.frame_len
					start = None
					amount = 0

			if start is not None:
				speech_length[channel, start: start + amount * self.frame_len] = amount * self.frame_len

		if allow_overlap:
			speech = speech_length > 0
		else:
			speech_max_length = speech_length.max(axis=0)
			speech = (speech_length == speech_max_length[np.newaxis, :]) & (speech_max_length != 0)
		speech = np.vstack([~speech.any(axis = 0), speech])
		return speech

class SileroVAD:
	def __init__(self, sample_rate: int = 8_000, use_micro = True, device = 'cpu'):
		self.device = device
		self.sample_rate = sample_rate
		if sample_rate == 8_000:
			assert use_micro, 'Only "micro" model exists for 8 kHz sample rate.'
			self.model_name = 'silero_vad_micro_8k'
		elif sample_rate == 16_000 and use_micro:
			self.model_name = 'silero_vad_micro'
		elif sample_rate == 16_000 and not use_micro:
			self.model_name = 'silero_vad'
		else:
			raise RuntimeError(f'No model exist for sample rate {sample_rate} Hz.')

		self.input_type = torch.tensor
		self.input_dtype = 'float32'

	@functools.lru_cache()
	def _get_model(self):
		model, utils = torch.hub.load(repo_or_dir='snakers4/silero-vad', model=self.model_name, batch_size=2000)
		get_speech_ts, _, _, _, _, _ = utils
		return model.to(self.device), get_speech_ts

	def detect(self, signal: shapes.BT, allow_overlap: bool = False) -> shapes.BT:
		signal = signal.to(self.device)
		model, get_speech_ts = self._get_model()
		speech_length = torch.zeros_like(signal, dtype = torch.int64)
		for i, channel_signal in enumerate(signal):
			intervals = get_speech_ts(channel_signal, model)
			for interval in intervals:
				speech_length[i, interval['start']:interval['end']] = torch.arange(0, interval['end'] - interval['start'], dtype = torch.int64)

		if allow_overlap:
			speech = speech_length > 0
		else:
			speech_max_length = speech_length.amax(dim=0)
			speech = (speech_length == speech_max_length.unsqueeze(0)) & (speech_max_length != 0)
		speech = torch.cat([~speech.any(dim = 0).unsqueeze(0), speech])
		return speech


import unittest
import os.path
from irods.path import iRODSPath

_normalization_test_cases = [
    #  -- test case --                  -- reference --
    ("/zone",                           "/zone"),               # a normal path (1 element)
    ("/zone/",                          "/zone"),               # single-slash (1 element)
    ("/zone/abc",                       "/zone/abc"),           # a normal path (2 elements)
    ("/zone/abc/",                      "/zone/abc"),           # single-slash (2 elements)
    ("/zone/abc/.",                     "/zone/abc"),           # final "."
    ("/zone/abc/./",                    "/zone/abc"),           # final "." and "/"
    ("/zone/abc/..",                    "/zone"),               # final ".."
    ("/zone/abc/../",                   "/zone"),               # final ".." and "/"
    ("/zone1/../zone2",                 "/zone2"),              # replace one path element with another
    ("/zone/home1/../home2",            "/zone/home2"),         # same for a later path element
    ("/..",                             "/"),                   # go up (1x) above root collection
    ("/../..",                          "/"),                   # go up (2x) above root collection
    ("",                                "/"),                   # absolute makes a blank into the root collection
    (".",                               "/"),                   # absolute makes a single "." into the root collection
    ("./.",                             "/"),                   # absolute makes "." (2x) into the root collection
    ("././zone",                        "/zone"),               # absolute makes initial "." (2x) a NO-OP before a normal elem
    ("/./zone/abc",                     "/zone/abc"),           # initial (no-op) '.'
    ("/../zone",                        "/zone"),               # go up (1x) above root collection and back down
    ("/../zone/..",                     "/"),                   # go up (when first, this is a NO-OP); then down, up
    ("/../../zone",                     "/zone"),               # go up (2x) above root collection and back down
    ("//zone1/../.././zone2",           "/zone2"),              # double-slashes, multiple relative elems
    ("//zone1//../.././zone2",          "/zone2"),              # double-slashes (2x), multiple relative elems
    ("//zone//abc/.",                   "/zone/abc"),           # same with final "."
    ("//zone//abc/..",                  "/zone"),               # same with final ".."
    ("//zone//abc/./..",                "/zone"),               # same with final "." and ".."
    ("/zone//abc/./../",                "/zone"),               # mixed relative elems (./..) and final slashes
    ("/zone//abc/.././",                "/zone"),               # mixed relative elems (../.) and final slashes
    ("/zone/home1//user/./../trash",    "/zone/home1/trash"),   # intermediately situated double-slash and relative elems (vsn 1)
    ("/zone/home1//user/.././trash",    "/zone/home1/trash"),   # intermediately situated double-slash and relative elems (vsn 2)
]

class PathsTest(unittest.TestCase):
    def test_path_normalization__383(self):
        for test_path, reference in _normalization_test_cases:
            normalized_path = iRODSPath(test_path)
            self.assertEqual( normalized_path, reference )

if __name__ == '__main__':
    import sys
    # let the tests find the parent irods lib
    sys.path.insert(0, os.path.abspath('../..'))
    unittest.main()


from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from datetime import datetime
import re

class Citilink():
    def __init__(self):
        self.urls = ('https://www.citilink.ru/catalog/noutbuki/?view_type=list&f=available.all',)

    def items(self, driver: webdriver.Chrome):
        # driver.implicitly_wait(5)
        for url in self.urls:
            next_page = url

            while next_page:
                driver.get(next_page)
                ActionChains(driver).pause(2).perform()

                for item in driver.find_elements(By.CLASS_NAME, 'ProductCardHorizontal'):
                    head = item.find_element(By.CSS_SELECTOR, '.ProductCardHorizontal__header-block a')
                    prop = item.find_element(By.CLASS_NAME, 'ProductCardHorizontal__properties').text
                    cpu_hhz_p = re.search(r'Процессор.+?ГГц', prop)
                    ram_gb_p = re.search(r'память.+?ГБ', prop)
                    ssd_gb_p = re.search(r'(Диск|Объем).+?ГБ', prop)
                    price_rub = int(item.get_attribute('data-price'))

                    if cpu_hhz_p:
                        cpu_hhz = float(cpu_hhz_p[0].split()[-2])
                    else:
                        cpu_hhz = float(0.0)
                    if ram_gb_p:
                        ram_gb = int(ram_gb_p[0].split()[-2])
                    else:
                        ram_gb = int(0)
                    if ssd_gb_p:
                        ssd_gb = int(ssd_gb_p[0].split()[-2])
                    else:
                        ssd_gb = int(0)
                    rank = float(cpu_hhz*3 + ram_gb*8 + ssd_gb*0.02 - price_rub * 0.002)
                    name = head.text
                    url = head.get_attribute('href')

                    out = {
                        'cpu_hhz': cpu_hhz,
                        'ram_gb': ram_gb,
                        'ssd_gb': ssd_gb,
                        'price_rub': price_rub,
                        'rank': rank,
                        'name': name,
                        'url': url,
                        'visited_at': str(datetime.today())[:19],
                    }
                    yield out

                next_page = driver.find_elements(By.CSS_SELECTOR, 'a.PaginationWidget__arrow_right')
                if next_page:
                    next_page = next_page[0].get_attribute('href')

        return


# Train the model here
import torch.nn as nn
import torch
# from model import Net
import numpy as np
import torch.nn.functional as F
import glob
import PIL.Image as pil_image
import h5py
# from model import Net
from model1 import Net
# from model2 import Net

model = Net()
# FILE = "nn-model-1.pth"
FILE = "nn-model-v2-2.pth"
model.load_state_dict(torch.load(FILE))
model.eval()

#Define hyper-parameters
num_epochs = 10
learning_rate = 1e-4
# --patch_size = 17
# --stride = 8

#Initialize loss function and optimizer
loss = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

#List of h5 files containing the patches
file_paths = list()
for i in range(13):
    fileName = "training-imgs/h5-files/file"+str(i)+".h5"
    file_paths.append(fileName)

#Train the model
for epoch in range(num_epochs):
    fileNum = 0
    for file_path in file_paths:
        file = h5py.File(file_path,'r')
        lr_patches = file.get('lr')
        hr_patches = file.get('hr')

        lr_patches = torch.tensor(np.array(lr_patches))
        hr_patches = torch.tensor(np.array(hr_patches))

        if(fileNum==0 and epoch==0): print("Starting training")
        output_patches = model(lr_patches)

        if(fileNum==0 and epoch==0): print("Model works")
        (N,n,m) = hr_patches.shape
        output_patches = output_patches.view(N,n,m)

        hr_patches = torch.tensor(hr_patches,dtype=torch.float)
        cost = loss(output_patches,hr_patches)
        if(fileNum==0 and epoch==0): print("Loss calculation successful")

        cost.backward()
        optimizer.step()
        optimizer.zero_grad()

        statement = "Epoch: " + str(epoch) + ", filenum: " + str(fileNum+1) + ", loss: " + str(cost.item())
        print(statement)
        fileNum+=1

# FILE = "nn-model-v3.pth"
# torch.save(model.state_dict(),FILE)


import matplotlib.pyplot as plt
import pickle
import numpy as np
import sys

with open('logs/move_history', 'rb') as f1:
    move_history = pickle.load(f1)
    sys.stdout.write(f'{move_history[0]}\n')

with open('logs/circles', 'rb') as f2:
    circles = pickle.load(f2)
    sys.stdout.write(f'{circles[0]}\n\n')

with open('logs/simulate_history', 'rb') as f3:
    simulate_history = pickle.load(f3)

move_history = np.array(move_history)
simulate_history = np.array(simulate_history)
circles = np.array(circles, dtype='float')

plt.plot(move_history[:, 0], move_history[:, 1])
plt.plot(circles[:, 0], circles[:, 1], 'r.')
plt.plot(simulate_history[:, 0] + 334, simulate_history[:, 1], 'g')
plt.show()


import discord
import yt_dlp as youtube_dl
import asyncio
from common import basicVideoInfo

YTDL_OPTIONS = {
    "format": "bestaudio/best",
    "outtmpl": "%(extractor)s-%(id)s-%(title)s.%(ext)s",
    "restrictfilenames": True,
    "noplaylist": True,
    "nocheckcertificate": True,
    "ignoreerrors": False,
    "logtostderr": False,
    "quiet": True,
    "no_warnings": True,
    "default_search": "auto",
    "source_address": "0.0.0.0",  # bind to ipv4 since ipv6 addresses cause issues sometimes
}

FFMPEG_OPTIONS = {
    "before_options": "-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5",
    "options": '-vn -filter:a "volume=1"',
}

ytdl = youtube_dl.YoutubeDL(YTDL_OPTIONS)

class YTDLSource(discord.PCMVolumeTransformer):
    def __init__(self, source, *, data, volume=0.5):
        super().__init__(source, volume)

        self.data = data

        self.title = data.get("title")
        self.url = data.get("url")

    @classmethod
    async def from_url(cls, url, *, loop=None, stream=True, download=False):
        loop = loop or asyncio.get_event_loop()
        a = ytdl.extract_info(url, download=download)
        data = await loop.run_in_executor(None, lambda: a)

        if "entries" in data:
            # take first item from a playlist
            data = data["entries"][0]

        filename = data["url"] if stream else ytdl.prepare_filename(data)

        def getSource():
            return cls(discord.FFmpegPCMAudio(filename, **FFMPEG_OPTIONS), data=data)

        return basicVideoInfo(url, a, getSource)


from keras_facenet import FaceNet
import cv2
import numpy as np

# Load the FaceNet model

model = FaceNet()

embeddings_path = 'embeddings.npy'
labels_path = 'labels.npy'

# Load the embeddings and labels from disk
known_embeddings = np.load(embeddings_path)
known_labels = np.load(labels_path)

image = cv2.imread(
    r"test.jpg")

# Extract faces from the image and calculate embeddings
faces = model.extract(image, threshold=0.50)
test_embeddings = np.array([face['embedding'] for face in faces])

# Define a threshold distance for matching faces
threshold_distance = 0.95

# Loop over each test embedding and find the closest matching known embedding
for i, test_embedding in enumerate(test_embeddings):
    distances = np.linalg.norm(known_embeddings - test_embedding, axis=1)
    closest_match_index = np.argmin(distances)
    closest_match_distance = distances[closest_match_index]
    closest_match_label = known_labels[closest_match_index]

    # Draw a bounding box and label for the recognized face
    face_box = faces[i]['box']
    x, y, w, h = face_box[0], face_box[1], face_box[2], face_box[3]

    if closest_match_distance < threshold_distance:
        cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
        cv2.putText(image, closest_match_label, (x, y - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
    else:
        cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
        cv2.putText(image, '?', (x, y - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 10)

# Display the output image
cv2.imwrite('detected.png', image)
cv2.waitKey(0)
cv2.destroyAllWindows()


import httplib2
import json
import time

h = httplib2.Http()

def compile_httpgettemp(IDeviceNum, ICmdClass, iDataLevel):
    sOutBuf = "http://192.168.0.79:8083/ZWaveAPI/Run/devices[" + str(IDeviceNum)
    sOutBuf = sOutBuf + "].instances[0].commandClasses[" + str(ICmdClass)
    sOutBuf = sOutBuf + "].data[" + str(iDataLevel) + "].val.value"
    return sOutBuf

def get_currenttemp(IDeviceNum, ICmdClass, iDataLevel):
    resp, content = h.request(compile_httpgettemp(IDeviceNum, ICmdClass, iDataLevel), "GET")
    DDevInfo = json.loads(content.decode('ascii'))
    return DDevInfo

iCmdClass = 49
iDevice = 8
iDataLevel = 1
sDevice = "Waschküche"
print (sDevice, " does read", get_currenttemp(iDevice, iCmdClass, iDataLevel), "Degrees Celsius")

iDataLevel = 5
print (sDevice, " does read", str(get_currenttemp(iDevice, iCmdClass, iDataLevel)), "% Humidity")

iDataLevel = 4
print (sDevice, " does read", str(get_currenttemp(iDevice, iCmdClass, iDataLevel)), "W Power Level")

iDevice = 12
iDataLevel = 1
sDevice = "Balkon"
print (sDevice, " does read", str(get_currenttemp(iDevice, iCmdClass, iDataLevel)), "Degree Celcius")

iDataLevel = 3
print (sDevice, " does read", str(get_currenttemp(iDevice, iCmdClass, iDataLevel)), "% Luminiscence")

iDataLevel = 5
print (sDevice, " does read", str(get_currenttemp(iDevice, iCmdClass, iDataLevel)), "% Humidity")

iDataLevel = 6
print (sDevice, " does read", str(get_currenttemp(iDevice, iCmdClass, iDataLevel)), "m/s Velocity")

iDataLevel = 9
print (sDevice, " does read", str(get_currenttemp(iDevice, iCmdClass, iDataLevel)), "kPa Barometric Pressure")

iDataLevel = 11
print (sDevice, " does read", str(get_currenttemp(iDevice, iCmdClass, iDataLevel)), "Degree Celcius Dew Point")


from __future__ import division
from dolfin import *
import numpy
from petsc4py import PETSc
import sys,argparse
from ufl import indices
from scipy.special.orthogonal import p_roots

# Use the same random numbers every time so that results are reproducible
numpy.random.seed(0)

# Parse the command line arguments
total = len(sys.argv)
cmdargs = str(sys.argv)
print ("The total numbers of args passed to the script: %d " % total)
print ("Args list: %s " % cmdargs)
print ("Script name: %s" % str(sys.argv[0]))

parser = argparse.ArgumentParser()
parser.add_argument('-r', '--num_refinements', default=3, type=int) # mesh refinement level
parser.add_argument('-o', '--order', default=0, type=int) # order of Regge finite elements
parser.add_argument('-v', '--orderV', default=1, type=int) # order of Lagrange finite elements
parser.add_argument('-w', '--orderW', default=1, type=int) # order of Nedelec finite elements
parser.add_argument('-c', '--convergence_test', default=0, type=int) # set equal to 1 to just compute the L2 error and quit
args = parser.parse_args()
num_refinements = args.num_refinements
order = args.order
orderV = args.orderV
orderW = args.orderW
convergence_test = args.convergence_test

# Create mesh
nx = 2**(num_refinements)
ny = nx
mesh = RectangleMesh(Point(-1.0, -1.0), Point(1.0, 1.0), nx, ny, "left")
deltax = 2.0 / nx
deltay = 2.0 / ny

# Define Dirichlet boundary (x = -1 or x = 1 or y = -1 or y = 1)
def boundary(x):
    return x[0] < -1.0 + DOLFIN_EPS or x[0] > 1.0 - DOLFIN_EPS or x[1] < -1.0 + DOLFIN_EPS or x[1] > 1.0 - DOLFIN_EPS

# Perturb vertices so we don't get fooled by superconvergence phenomena
x = mesh.coordinates()[:,0]
y = mesh.coordinates()[:,1]
xtilde = x.copy()
ytilde = y.copy()
for i in range(len(x)):
    if not boundary(mesh.coordinates()[i,:]):
        xtilde[i] += 0.2*deltax*(2*numpy.random.rand()-1)
        ytilde[i] += 0.2*deltay*(2*numpy.random.rand()-1)
xytilde = numpy.array([xtilde, ytilde]).transpose()
#mesh.coordinates()[:] = xytilde

# Define finite element spaces
Sigma = FunctionSpace(mesh, "Regge", order)
V = FunctionSpace(mesh, "CG", orderV)
W = FunctionSpace(mesh, "N1curl", orderW)

# Quadrature degree
parameters["form_compiler"]["quadrature_degree"] = 12

# Define boundary condition
bcV = DirichletBC(V, Constant(0.0), boundary)
bcW = DirichletBC(W, Constant((0.0,0.0)), boundary)

# Define trial functions, test functions, and data
kappa = TrialFunction(V)
connection = TrialFunction(W)
v = TestFunction(V)
alpha = TestFunction(W)
delta = Constant(((1.0,0.0),(0.0,1.0)))
#fx = Expression(" 1.0*(x[0]-1.0/3.0*x[0]*x[0]*x[0])", degree=3)
#fy = Expression(" 1.0*(x[1]-1.0/3.0*x[1]*x[1]*x[1])", degree=3)
#kappaexact = Expression("81*(1-x[0]*x[0])*(1-x[1]*x[1]) / pow(9 + x[0]*x[0]*pow(x[0]*x[0]-3,2) + x[1]*x[1]*pow(x[1]*x[1]-3,2),2)", degree=12)
#gexact = Expression( ( ("1.0+fx*fx", "fx*fy"), ("fx*fy", "1.0+fy*fy") ), fx=fx, fy=fy, degree = 6, domain=mesh)
#fx = Expression(" 1.0*(x[0]-1.0/3.0*x[0]*x[0]*x[0])", degree=12)
#fy = Expression(" 1.0*(x[1]-1.0/3.0*x[1]*x[1]*x[1])", degree=12)
#kappaexact = Expression("81*(1-x[0]*x[0])*(1-x[1]*x[1]) / pow(9 + x[0]*x[0]*pow(x[0]*x[0]-3,2) + x[1]*x[1]*pow(x[1]*x[1]-3,2),2)", degree=12)
#gexact = Expression( ( ("1.0+fx*fx", "fx*fy"), ("fx*fy", "1.0+fy*fy") ), fx=fx, fy=fy, degree = 12, domain=mesh)
fx = Expression("-0.5*pi*sin(0.5*pi*x[0])", degree=12)
fy = Expression("-0.5*pi*sin(0.5*pi*x[1])", degree=12)
kappaexact = Expression("4*pow(pi,4)*cos(0.5*pi*x[0])*cos(0.5*pi*x[1]) / pow(-2*(4+pi*pi) + pi*pi*(cos(pi*x[0])+cos(pi*x[1])),2)", degree=12)
gexact = Expression( ( ("1.0+fx*fx", "fx*fy"), ("fx*fy", "1.0+fy*fy") ), fx=fx, fy=fy, degree=12, domain=mesh)
g = interpolate(gexact,Sigma)
#g = project(gexact,Sigma)
#g = gexact
sigma = g-delta

# Print the interpolation error for g
print("L2norm(g - gexact) = ", errornorm(gexact,g,degree_rise=6))

# Normal and tangent vectors
n = FacetNormal(mesh)
rotmat = as_matrix( [[0,1],[-1,0]] )
tau = -rotmat*n

# Christoffel symbols of the second kind associated with a metric g
def christoffel(g):
    i,j,k,l = indices(4)
    gamma = as_tensor(0.5 * inv(g)[k,l] * ( g[l,i].dx(j) + g[l,j].dx(i) - g[i,j].dx(l) ), (k,i,j))
    return gamma

# Riemannian Hessian of a scalar field v
def hess(v,gamma):
    i,j,k = indices(3)
    hessv = as_tensor(v.dx(i).dx(j) - gamma[k,i,j]*v.dx(k), (i,j))
    #hessv = gradoneform(grad(v),gamma) # equivalent
    return hessv

# Covariant derivative of a one-form alpha
def gradoneform(alpha,gamma):
    i,j,k = indices(3)
    gradalpha = as_tensor(alpha[i].dx(j) - gamma[k,i,j]*alpha[k], (i,j))
    return gradalpha

# Gaussian curvature of g
def curvature(g):
    i,j,k,l = indices(4)
    gamma = christoffel(g)
    gausscurv = 0.5 * inv(g)[i,j] * ( gamma[k,i,j].dx(k) - gamma[k,i,k].dx(j) + gamma[l,i,j]*gamma[k,k,l] - gamma[l,i,k]*gamma[k,j,l] )
    return gausscurv

# Calculate the integral of b( (1-t)*delta+t*g, g-delta, v ) from t=0 to t=1
[nodes,weights] = p_roots(20)
nodes = (nodes+1)/2
weights = weights/2
rhs = 0.0
rhsconn = 0.0
for iq in range(len(weights)):
    w = weights[iq]
    t = nodes[iq]
    G = (1-t)*delta + t*g

    gamma = christoffel(G)
    invG = inv(G)
    sqrtdetG = sqrt(det(G))
    ll = sqrt(dot(tau,G*tau))
    tauG = tau / ll
    nG = invG*n*sqrtdetG / ll
    SGsigma = sigma - G*tr(invG*sigma)

    tauGp = tauG('+') # equal to -tauG('-')
    nGp = nG('+') # NOT equal to -nG('-')
    llp = ll('+') # equal to ll('-')
    sigmap = sigma('+')

    rhs = rhs + w * dot(tauGp, sigmap*tauGp) * jump( grad(v), nG ) * llp * dS
    rhs = rhs + w * dot(tauG , sigma *tauG ) *  dot( grad(v), nG ) * ll  * ds
    rhs = rhs + w * tr( invG*SGsigma*invG*hess(v,gamma) ) * sqrtdetG * dx

    rhsconn = rhsconn + w * dot(tauGp, sigmap*tauGp) * jump( alpha, nG ) * llp * dS
    rhsconn = rhsconn + w * dot(tauG , sigma *tauG ) *  dot( alpha, nG ) * ll  * ds
    rhsconn = rhsconn + w * tr( invG*SGsigma*invG*gradoneform(alpha,gamma) ) * sqrtdetG * dx

rhs = 0.5*rhs
rhsconn = 0.5*rhsconn
lhs = kappa*v*sqrt(det(g))*dx
#lhsconn = dot(connection,alpha)*sqrt(det(g))*dx
lhsconn = dot(connection,inv(g)*alpha)*sqrt(det(g))*dx

# Compute solution
kappa = Function(V)
connection = Function(W)
solve(lhs == rhs, kappa, bcV)
solve(lhsconn == rhsconn, connection, bcW)

# Check that the exterior coderivative of the connection equals the curvature
diff = assemble( kappa*v*sqrt(det(g))*dx - dot(connection,inv(g)*grad(v))*sqrt(det(g))*dx )
bcV.apply(diff)
print("vectornorm(d*connection-kappa) = ", norm(diff))

# Compare with the exact Gaussian curvature
print("L2norm(kappa  - kappaexact) = ", errornorm(kappaexact,kappa,degree_rise=6))
if convergence_test:
    sys.exit()

# Since the connection we computed is really approximating the Hodge star
# of the connection one-form, compute the Hodge star for plotting purposes
RT = FunctionSpace(mesh, "RT", orderW)
bcRT = DirichletBC(RT, Constant((0.0,0.0)), boundary)
starconnection = project(sqrt(det(g))*rotmat*inv(g)*connection, RT, bcRT)

# Save solution
file = File("results/kappa.pvd")
file << kappa
file = File("results/connection.pvd")
file << starconnection

#kappa3 = interpolate(kappaexact,V)
#vert2dof = V.dofmap().entity_dofs(mesh, 0)
#for i in range(mesh.num_vertices()):
#    print(kappa.vector()[vert2dof[i]])
#print(sqrt(assemble(kappaexact*kappaexact*sqrt(det(g))*dx)))

#########################################################################################################
#########################################################################################################
#########################################################################################################
# Now let's check if we get the same result by computing angle defects, jumps in geodesic curvature, etc.

# Calculate the interior angles of every triangle with respect to the metric g.
# The entry i,j of "angle" will contain the interior angle of triangle i at vertex j,
# where j is the global index of vertex j.  Most of the entries of "angle" will be 0.
# In particular, if j is not a vertex of triangle i, then angle[i][j]=0.
angle = numpy.zeros((mesh.num_cells(),mesh.num_vertices()))
for cell in cells(mesh):
    # Get the edges of the current triangle
    edges = facets(cell)

    # We need to walk through pairs of edges and compute the angle between them.
    # Since "edges" is an iterator, it doesn't seem straightforward to access pairs of edges.
    # I will brute force it. (Probably there is a better way.)
    # First let's store the first edge in the list.
    k = 0
    for e in facets(cell):
        if k==0:
            e0 = e
            break
        k += 1

    # Now let's walk through the pairs of edges.
    e2 = e0
    stop = False
    while not stop:
        e1 = e2 # First edge in the pair
        e2 = next(edges,-1) # Second edge in the pair
        if e2==-1: # If we're on the last edge, loop back around.
            stop = True
            e2 = e0
        # Identify the vertex that's shared by e1 and e2.
        for v1 in vertices(e1):
            for v2 in vertices(e2):
                if v1.index() == v2.index():
                    # Normal vectors to e1 and e2.
                    n1 = e1.normal()
                    n2 = e2.normal()
                    # These might not be outward pointing normal vectors.  To see which way
                    # the normal vector n1 points, let's look at how the 2 triangles
                    # adjacent to the edge e1 are ordered.  The first adjacent triangle is
                    # e1.entities(2)[0] and the second one (if any) is e1.entities(2)[1].
                    if not (e1.entities(2)[0] == cell.index()):
                        n1 *= -1
                    # Similarly for e2.
                    if not (e2.entities(2)[0] == cell.index()):
                        n2 *= -1

                    # Evaluate the Regge metric g at the vertex v1.
                    coord = [v1.point().x(),v1.point().y()]
                    #coord[0] -= 100*DOLFIN_EPS * (n1[0] + n2[0])
                    #coord[1] -= 100*DOLFIN_EPS * (n1[1] + n2[1])
                    gval = numpy.empty(4, dtype=float)
                    g.eval_cell(gval,coord,cell)

                    # Compute the angle between the two tangent vectors with respect to g.
                    tau1 = numpy.zeros(2)
                    tau2 = numpy.zeros(2)
                    tau1[0] = -n1[1]
                    tau1[1] =  n1[0]
                    tau2[0] = -n2[1]
                    tau2[1] =  n2[0]
                    tau1dottau2 = ( tau1[0]*gval[0]*tau2[0] + tau1[0]*gval[1]*tau2[1] + tau1[1]*gval[2]*tau2[0] + tau1[1]*gval[3]*tau2[1] )
                    ll1 =     sqrt( tau1[0]*gval[0]*tau1[0] + tau1[0]*gval[1]*tau1[1] + tau1[1]*gval[2]*tau1[0] + tau1[1]*gval[3]*tau1[1] )
                    ll2 =     sqrt( tau2[0]*gval[0]*tau2[0] + tau2[0]*gval[1]*tau2[1] + tau2[1]*gval[2]*tau2[0] + tau2[1]*gval[3]*tau2[1] )
                    tau1dottau2 = tau1dottau2/ll1/ll2

                    theta = numpy.arccos( numpy.clip( -tau1dottau2, -1, 1) )
                    angle[cell.index()][v1.index()] = theta

# Compute angle defects by summing around each vertex and subtracting from 2*pi
angledefect = numpy.zeros(mesh.num_vertices())
for j in range(mesh.num_vertices()):
    if not boundary(mesh.coordinates()[j,:]):
        angledefect[j] = 2*pi
        for i in range(mesh.num_cells()):
            if angle[i][j]>0.0:
                angledefect[j] -= angle[i][j]

# Initialize the right-hand side of the linear system to zero.
rhs2 = 2.0*DOLFIN_EPS*v*dx # Seems impossible to assemble a zero rhs, so make it tiny instead

# If we're working with a Regge metric of order > 0, we need to compute the
# jumps in geodesic curvature across each edge, as well as the curvature inside
# each triangle.
if order>0:
    ll = sqrt(dot(tau,g*tau))
    taug = tau / ll
    ng = inv(g)*n*sqrt(det(g)) / ll
    gamma = christoffel(g)
    i,j,k = indices(3)
    gradtautau = as_vector( -0.5 * taug[j] * taug[i] * dot(taug,g.dx(j)*taug) + gamma[i,j,k]*taug[j]*taug[k], (i) )
    rhs2 -= jump(ng,g*gradtautau) * v('+') * ll('+') * dS
    rhs2 += curvature(g) * v * sqrt(det(g)) * dx

# Assemble the mass matrix and the right-hand side
M, b = assemble_system(lhs, rhs2, bcV)

# Add angle defects to the right-hand side
vert2dof = V.dofmap().entity_dofs(mesh, 0)
for i in range(mesh.num_vertices()):
    pt = mesh.coordinates()[i,:]
    b[vert2dof[i]] += angledefect[i]

# Compute solution
kappa2 = Function(V)
solve(M, kappa2.vector(), b)

# Compare with the exact Gaussian curvature
print("L2norm(kappa2 - kappaexact) = ", errornorm(kappaexact,kappa2,degree_rise=6))

# Save solution
file = File("results/kappatwo.pvd")
file << kappa2

# Check that the two kappa's are the same
#print("vectornorm(kappa - kappa2) = ", norm(kappa.vector() - kappa2.vector()) )
print("norm(kappa - kappa2) = ", sqrt(assemble((kappa-kappa2)*(kappa-kappa2)*sqrt(det(g))*dx)) )