frame.py 8.09 KB
#! /usr/bin/python3
# -*- coding: utf-8 -*-

from collections import defaultdict
import numpy as np

from sqlalchemy import func
from sqlalchemy.orm import aliased
from local_db import Subtree

_general_selprefs_translations = {
    u'LUDZIE': [6047, 7702],
    u'PODMIOTY': [6047, 7702, 228023],
    u'ISTOTY': [6047, 6045, 103330],
    u'CECHA': [323],
    u'KONCEPCJA': [1088, 8137],
    u'KOMUNIKAT': [1088, 3998],
    u'JADŁO': [10738, 5268],
    u'DOBRA': [1648, 2646, 2903, 10738, 5268],
    u'OBIEKTY': [6047, 234224, 1282],
    u'WYTWÓR': [2646, 2903],
    u'MIEJSCE': [4750, 4897, 234224],
    u'OTOCZENIE': [4750, 4897, 234224],
    u'POŁOŻENIE': [4750, 4897, 234224],
    u'CZYNNOŚĆ': [10765, 23278, 146069, 79514],
    u'SYTUACJA': [10765, 6526, 247969, 47401],
    u'CZAS': [366, 50721, 6096, 33638],
    u'KIEDY': [366, 50721, 6096, 33638, 10765, 6526, 247969, 47401],
    u'CZEMU': [6047, 7702, 323, 10765, 6526, 247969, 47401],
    u'ILOŚĆ': [1078, 3960, 102887, 1161, 102892],
}

class LexicalUnit():

    _session = None
    
    def __init__(self, luid):
        self._id = luid

    @classmethod
    def similarity(cls, l1, l2): # (2*ic(lcs(l1, l2)))/(ic(l1) + ic(l2))
        lu1 = l1._id
        lu2 = l2._id
        if lu1 is None or lu2 is None:
            return 1.0
        if lu1 == lu2:
            return 1.0
        if cls._session is None:
            raise LexicalUnitSessionError()
        else:
            session = cls._session

        ST1 = aliased(Subtree)
        ST2 = aliased(Subtree)
        ST3 = aliased(Subtree)
        lcs = session.query(ST1.ancestor, func.count(ST1.ancestor)).join(ST2, ST1.ancestor == ST2.ancestor).filter(ST1.descendant==lu1).filter(ST2.descendant==lu2).join(ST3, ST1.ancestor == ST3.ancestor).group_by(ST1.ancestor).order_by(func.count(ST1.ancestor)).first()
        if lcs is not None:
            lcs_sid, lcs_count = lcs
            ic_lcs = cls.count_ic(lcs_count)
            ic_lu1 = cls.ic(lu1)
            ic_lu2 = cls.ic(lu2)
            return (2 * ic_lcs)/(ic_lu1 + ic_lu2)
        else:
            return 0.0

    # 235383 synsets
    @classmethod
    def ic(cls, lu):
        if cls._session is None:
            raise LexicalUnitSessionError()
        else:
            session = cls._session
        query = session.query(Subtree).filter(Subtree.ancestor==lu).distinct().count()
        return cls.count_ic(query)

    _SYNSET_COUNT = 235383
    
    @classmethod
    def count_ic(cls, count):
        return 1.0 - (np.log(count)/np.log(cls._SYNSET_COUNT))
    
    
        
class SelectionalPreference():

    _session = None
    
    def __init__(self, id, content=(True, [])):
        self._id = id
        self._content = content

    def __str__(self):
        return str(self._id) + "[" + str(self._content) + "]"

    def not_all(self):
        return not self._content[0]
    
    @classmethod
    def from_slowal(cls, selprefs, argument_id):
        if selprefs == None:
            return cls(argument_id, (True, []))
        else:
            synsets = []
            all_synsets = False
            for general in selprefs.generals.all():
                if general.name == u'ALL':
                    all_synsets = True
                else:
                    synsets += _general_selprefs_translations[general.name]
            for synset in selprefs.synsets.all():
                if SelectionalPreference._humanoid(synset.id):
                    synsets += _general_selprefs_translations[u'LUDZIE']
                else:
                    synsets.append(synset.id)
            for relation in selprefs.relations.all():
                all_synsets = True

            return cls(argument_id, (all_synsets, synsets))

    @classmethod
    def _humanoid(cls, sid):
        if cls._session is None:
            raise SelectionalPreferenceSessionError()
        else:
            session = cls._session
        test = session.query(Subtree).filter(Subtree.ancestor==6047).filter(Subtree.descendant==sid).count()
        if test > 0:
            return True
        else:
            return False
        
    @classmethod
    def similarity(cls, selprefs1, selprefs2, table):
        if (selprefs1._id, selprefs2._id) in table:
            return table[(selprefs1._id, selprefs2._id)]
        a1, s1 = selprefs1._content
        a2, s2 = selprefs2._content
        if a1 or a2 or len(s1) == 0 or len(s2) == 0:
            table[(selprefs1._id, selprefs2._id)] = 1.0
            table[(selprefs2._id, selprefs1._id)] = 1.0
            return 1.0
        else:
            if cls._session is None:
                raise SelectionalPreferenceSessionError()
            else:
                session = cls._session
            
            ST1 = aliased(Subtree)
            ST2 = aliased(Subtree)
            count_sp1 = session.query(ST1.descendant).filter(ST1.ancestor.in_(s1)).distinct().count()
            count_sp2 = session.query(ST2.descendant).filter(ST2.ancestor.in_(s2)).distinct().count()
            count_common = session.query(ST1.descendant).join(ST2, ST1.descendant == ST2.descendant).filter(ST1.ancestor.in_(tuple(s1))).filter(ST2.ancestor.in_(tuple(s2))).distinct().count()

            x = count_common + 1.0
            y = count_sp1 + 1.0
            z = count_sp2 + 1.0
            if y == 1.0:
                # may only happen if someone added a non-wordnet selectional preference, e.g. kupić-A
                y += 1.0
            if z == 1.0:
                # may only happen if someone added a non-wordnet selectional preference, e.g. kupić-A
                z += 1.0                
            sim = (np.log(x)/np.log(y) + np.log(x)/np.log(z))/2
            table[(selprefs1._id, selprefs2._id)] = sim
            table[(selprefs2._id, selprefs1._id)] = sim
            return sim
    
class Frame():

    _WORDNET_MIN_SIMILARITY = 0.8
    
    def __init__(self, fid = None, name=None):
        self._arguments = {}
        self._name = name
        self._id = fid
        
    def add_argument(self, label, preference):
        if label not in self._arguments:
            self._arguments[label] = []
        self._arguments[label].append(preference)
        
    def get_arguments(self, label=None):
        if label == None:
            return self._arguments
        elif label in self._arguments:
            return self._arguments[label]
        else:
            return []

    def get_role_labels(self):
        return list(self._arguments.keys())

    def __str__(self):
        parts = []
        for label in sorted(self._arguments):
            sps = []
            for sp in self._arguments[label]:
                sps.append(str(sp._content))
            parts.append(label + ': ' + ','.join(sps))
        return str(self._id) + ' --> ' + '; '.join(parts)

    @classmethod
    def _get_pseudo_role(cls, complement):
        return complement.roles.exclude(role='Foreground').exclude(role='Background').exclude(role='Source').exclude(role='Goal')[0].role
    
    @classmethod
    def from_slowal(cls, sf):
        synsets = [LexicalUnit(lu.synset.id) for lu in sf.lexical_units.all() if lu.luid != -1]
        frame = cls(fid = sf.id, name = synsets)
        complements = sf.complements.all()
        for complement in complements:
            role = cls._get_pseudo_role(complement)
            i = complement.id
            if role != 'Lemma':
                sp = SelectionalPreference.from_slowal(complement.selective_preference, i)
                frame.add_argument(role, sp)
        return frame
    
    def lexical_closeness(self, frame, table):
        if (self._id, frame._id) not in table:
            sim_set = False
            sim = 0.0
            for l1 in self._name:
                for l2 in frame._name:
                      local_sim = LexicalUnit.similarity(l1, l2)
                      sim = max(sim, local_sim)
                      sim_set = True
            if not sim_set:
                sim = 1.0
            table[(self._id, frame._id)] = sim
            table[(frame._id, self._id)] = sim
        return table[(self._id, frame._id)]

    @classmethod
    def far(cls, id1, id2, table):
        return (table[(id1, id2)] < cls._WORDNET_MIN_SIMILARITY)
    
if __name__ == '__main__':
    pass