[hltdi-l3] 2 new revisions pushed by onlysk...@gmail.com on 2014-04-04 06:27 GMT

0 views
Skip to first unread message

hltd...@googlecode.com

unread,
Apr 4, 2014, 2:27:14 AM4/4/14
to hltdi-...@googlegroups.com
2 new revisions:

Revision: 8e78a46a0faf
Branch: default
Author: Michael Gasser <gas...@cs.indiana.edu>
Date: Thu Mar 27 17:19:15 2014 UTC
Log: L3Lite: Groups (MWEs), variables
http://code.google.com/p/hltdi-l3/source/detail?r=8e78a46a0faf

Revision: dffb6356c66f
Branch: default
Author: Michael Gasser <gas...@cs.indiana.edu>
Date: Fri Apr 4 06:26:55 2014 UTC
Log: L3Lite: constraints as before with new ComplexSetConvexity
constraint
http://code.google.com/p/hltdi-l3/source/detail?r=dffb6356c66f

==============================================================================
Revision: 8e78a46a0faf
Branch: default
Author: Michael Gasser <gas...@cs.indiana.edu>
Date: Thu Mar 27 17:19:15 2014 UTC
Log: L3Lite: Groups (MWEs), variables
http://code.google.com/p/hltdi-l3/source/detail?r=8e78a46a0faf

Added:
/l3lite/variable.py
Modified:
/l3lite/__init__.py
/l3lite/entry.py
/l3lite/language.py
/l3lite/ui.py
/lite.py

=======================================
--- /dev/null
+++ /l3lite/variable.py Thu Mar 27 17:19:15 2014 UTC
@@ -0,0 +1,466 @@
+#
+# L3Lite variables and domain stores: required for constraint
satisfaction.
+#
+########################################################################
+#
+# This file is part of the HLTDI L^3 project
+# for parsing, generation, translation, and computer-assisted
+# human translation.
+#
+# Copyright (C) 2014, HLTDI <gas...@cs.indiana.edu>
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# =========================================================================
+
+# 2014.02.14
+# -- Created. Copied from l3xdg/variable.py.
+# 2014.03.26
+# -- One variable class (SVar from l3xdg).
+
+# Maximum number of values for a variable.
+MAX = 200
+# Maximum set of integers
+ALL = set(range(MAX))
+
+class DStore:
+ """Domain store holding domains for variables. (Really the domains are
held in
+ dicts kept by the variables.)"""
+
+ def __init__(self, name='', level=0, problem=None, parent=None):
+ """This store is a strengthening of parent store if there is
one."""
+ self.problem = problem
+ self.parent = parent
+ self.children = []
+ self.name = name
+ self.level = level
+ # Undetermined variables
+ self.undetermined = []
+
+ def __repr__(self):
+ return '<DS {}/{}>'.format(self.name, self.level)
+
+ def clone(self, constraint=None, name='', project=False, verbosity=0):
+ """Create a new dstore by applying the basic constraint
+ to the bindings in this store."""
+ new_store = DStore(name=name or self.name, level=self.level+1,
+ problem=self.problem, parent=self)
+ self.children.append(new_store)
+ new_store.undetermined = self.undetermined[:]
+ constraint.infer(dstore=new_store, verbosity=0, tracevar=[])
+ for var in constraint.variables:
+ # See if the new variable(?s) is now determined
+ var.determined(dstore=new_store, verbosity=0)
+ return new_store
+
+DS0 = DStore(name='top')
+
+class Var:
+
+ # Threshold for "peripheral" variables
+ weight_thresh = .5
+
+ def __init__(self, name,
+ lower_domain=None, upper_domain=None,
+ lower_card=0, upper_card=MAX,
+ problem=None, dstores=None, rootDS=None,
+ # Vars with low weights are "peripheral".
+ weight=1):
+ self.name = name
+ self.problem = problem
+ if problem:
+ self.problem.add_variable(self)
+ self.value = None
+ # Normally initialize with a top-level domain store
+ self.rootDS = rootDS or DS0
+ # Values of this variable in different domain stores
+ self.dstores = dstores or {self.rootDS: {}}
+ # Add the variable to the list of undetermined variables for
+ # the dstore
+ self.rootDS.undetermined.append(self)
+ self.weight = weight
+ if lower_domain != None:
+ self.lower_domain = lower_domain
+ else:
+ self.lower_domain = set()
+ if upper_domain != None:
+ self.upper_domain = upper_domain
+ else:
+ self.upper_domain = ALL.copy()
+ self.init_lower_card = max(lower_card, len(self.lower_domain))
+ self.init_upper_card = min(upper_card, len(self.upper_domain))
+ self.init_values(dstore=self.rootDS)
+
+ def __repr__(self):
+ return '${}'.format(self.name)
+
+ # Initializing bounds
+
+ def init_values(self, dstore=None):
+ self.set_lower(self.lower_domain, dstore=dstore)
+ self.set_upper(self.upper_domain, dstore=dstore)
+ self.set_lower_card(self.init_lower_card, dstore=dstore)
+ self.set_upper_card(self.init_upper_card, dstore=dstore)
+ self.set_value(None, dstore=dstore)
+
+ def set_lower(self, lower, dstore=None):
+ self.set(dstore, 'lower', lower)
+
+ def set_upper(self, upper, dstore=None):
+ self.set(dstore, 'upper', upper)
+
+ def set_lower_card(self, lower_card, dstore=None):
+ self.set(dstore, 'lower_card', lower_card)
+
+ def set_upper_card(self, upper_card, dstore=None):
+ self.set(dstore, 'upper_card', upper_card)
+
+ def get_name(self):
+ '''Function used in sorting lists of variables.'''
+ return self.name
+
+ def get_dstore(self, dstore):
+ """Returns the dictionary of value and domain(s) for dstore."""
+ dstore = dstore or self.rootDS
+ return self.dstores.get(dstore, {})
+
+ def add_dstore(self, dstore):
+ """Adds a domain store to the dstores dict."""
+ self.dstores[dstore] = {}
+
+ def set(self, dstore, feature, value):
+ """Sets feature to be value in dstore, creating a dict for dstore
if one doesn't exist."""
+ dstore = dstore or self.rootDS
+ dsdict = self.dstores.get(dstore, None)
+ if dsdict == None:
+ dsdict = {'value': None}
+ self.dstores[dstore] = dsdict
+ dsdict[feature] = value
+
+ def set_value(self, value, dstore=None):
+ """Sets the value of the variable in dstore."""
+ self.set(dstore, 'value', value)
+
+ def is_determined(self, dstore=None):
+ """Is the variable already determined?"""
+ return self.get_value(dstore=dstore) is not None
+
+ ## How constraints on a variable can fail
+
+ def bound_fail(self, dstore=None):
+ """Fail if the lower bound includes any elements not in the upper
bound."""
+ return self.get_lower(dstore=dstore) -
self.get_upper(dstore=dstore)
+
+ def card_fail(self, dstore=None):
+ """Fail if the lower cardinality bound is greater than the upper
cardinality bound."""
+ return self.get_lower_card(dstore=dstore) >
self.get_upper_card(dstore=dstore)
+
+ def upper_bound_card_fail(self, dstore=None):
+ """Fail if the length of upper bound < lower card."""
+ return len(self.get_upper(dstore=dstore)) <
self.get_lower_card(dstore=dstore)
+
+ def lower_bound_card_fail(self, dstore=None):
+ """Fail if length of lower bound > upper card."""
+ return len(self.get_lower(dstore=dstore)) >
self.get_upper_card(dstore=dstore)
+
+ def fail(self, dstore=None):
+ """Fail in one of three ways."""
+ return self.bound_fail(dstore=dstore) or
self.card_fail(dstore=dstore)
+# or self.bound_card_fail(dstore=dstore)
+
+ ## Getters
+
+ def get(self, dstore, feature, default=None):
+ """Returns a value for feature associated with dstore, recursively
+ checking dstore's parent is nothing is found."""
+ dstore_dict = self.dstores.get(dstore, {})
+ x = dstore_dict.get(feature, None)
+ if x != None:
+ return x
+ parent = dstore.parent
+ if parent:
+ return self.get(parent, feature, default=default)
+ return default
+
+ def get_value(self, dstore=None):
+ """Return the value of the variable in dstore."""
+ dstore = dstore or self.rootDS
+ return self.get(dstore, 'value', None)
+
+ def get_lower(self, dstore=None):
+ dstore = dstore or self.rootDS
+ return self.get(dstore, 'lower')
+
+ def get_upper(self, dstore=None):
+ dstore = dstore or self.rootDS
+ return self.get(dstore, 'upper')
+
+ def get_lower_card(self, dstore=None):
+ dstore = dstore or self.rootDS
+ return self.get(dstore, 'lower_card', 0)
+
+ def get_upper_card(self, dstore=None):
+ dstore = dstore or self.rootDS
+ return self.get(dstore, 'upper_card', MAX)
+
+ def get_undecided(self, dstore=None):
+ """Returns the set of values that may or may not be in the
variable."""
+ dstore = dstore or self.rootDS
+ return self.get_upper(dstore=dstore) -
self.get_lower(dstore=dstore)
+
+ def determined(self, dstore=None, constraint=None, verbosity=0):
+ """Attempt to determine the variable, returning the value if this
is possible,
+ False if it's not."""
+ val = self.get_value(dstore=dstore)
+ if val != None:
+ return val
+ def determined_help(value, dst, verb):
+ value_card = len(value)
+ lower_card = self.get_lower_card(dstore=dst)
+ upper_card = self.get_upper_card(dstore=dst)
+ if value_card < lower_card:
+ s = "{} lowering lower card for {} to {}, less than
previous value {}"
+ raise(VarError(s.format(constraint, self, value_card,
lower_card)))
+ if value_card > upper_card:
+ s = "{} raising upper card for {} to {}, greater than
previous value {}"
+ raise(VarError(s.format(constraint, self, value_card,
upper_card)))
+ self.set_value(value, dstore=dst)
+ self.set_lower(value, dstore=dst)
+ self.set_upper(value, dstore=dst)
+ self.set_lower_card(value_card, dstore=dst)
+ self.set_upper_card(value_card, dstore=dst)
+ if verb > 1:
+ print(' {} is determined at {}'.format(self, value))
+ if dst:
+ dst.undetermined.remove(self)
+ return value
+ lower = self.get_lower(dstore=dstore)
+ upper = self.get_upper(dstore=dstore)
+ if lower == None or upper == None:
+ return False
+ # If upper and lower bounds are equal, determine at either
+ if lower == upper:
+ return determined_help(lower, dstore, verbosity)
+ # Combine cardinality and set bounds to determine
+ # If the length of the upper bound is <= the lower cardinality
bound,
+ # then make the upper bound the value
+ if len(upper) <= self.get_lower_card(dstore=dstore):
+ return determined_help(upper, dstore, verbosity)
+ if len(lower) >= self.get_upper_card(dstore=dstore):
+ return determined_help(lower, dstore, verbosity)
+ return False
+
+ ## Methods that can change the variable's set bounds or cardinality
bounds
+
+ def determine(self, value, dstore=None, constraint=None):
+ """Attempt to determine the variable as value, returning False it
can't be
+ or if it's already determined."""
+ if self.is_determined(dstore=dstore):
+ return False
+ value = value if isinstance(value, set) else {value}
+ orig_upper = self.get_upper(dstore=dstore)
+ orig_lower = self.get_lower(dstore=dstore)
+ upper = self.get_upper(dstore=dstore)
+ if not value.issubset(orig_upper):
+ # Var can't be determined at this value
+ return False
+ if constraint:
+ print(' {} determining {} as {}'.format(constraint, self,
value))
+ val_card = len(value)
+ self.set_lower(value, dstore=dstore)
+ self.set_upper(value, dstore=dstore)
+ self.set_value(value, dstore=dstore)
+ self.set_lower_card(val_card, dstore=dstore)
+ self.set_upper_card(val_card, dstore=dstore)
+ if dstore and self in dstore.undetermined:
+ dstore.undetermined.remove(self)
+ if orig_upper != value or orig_lower != value:
+ return True
+ return False
+
+ def strengthen_upper(self, upper2, dstore=None, constraint=None,
+ reduce=False, det=False):
+ """Strengthens the upper bound by intersecting it with upper2.
+ If det is True, attempt to determine variable.
+ """
+ upper = self.get_upper(dstore=dstore)
+ if not isinstance(upper, set):
+ print("{}'s upper {} is not set".format(self, upper))
+ if not upper.issubset(upper2):
+ new_upper = upper.intersection(upper2)
+ lower_card = self.get_lower_card(dstore=dstore)
+ if new_upper == upper:
+ return False
+ lower = self.get_lower(dstore=dstore)
+ if not lower.issubset(new_upper) and constraint:
+ s = 'Warning: attempting to change upper bound of {} to
{}, which is not a superset of lower bound {}'
+ print(s.format(self, new_upper, lower))
+ if len(new_upper) < lower_card and constraint:
+ s = 'Warning: attempting to change upper bound of {} to
{}, which is smaller than lower card {}'
+ print(s.format(self, new_upper, lower_card))
+ if constraint:
+ s = ' {} strengthening upper bound of {} ({}) with {},
now {}'
+ print(s.format(constraint, self, upper, upper2, new_upper))
+ self.set_upper(new_upper, dstore=dstore)
+ if det:
+ if new_upper == lower:
+# print('Determining', self)
+ val_len = len(lower)
+ self.set_value(lower, dstore=dstore)
+ self.set_lower_card(val_len, dstore=dstore)
+ self.set_upper_card(val_len, dstore=dstore)
+ if dstore and self in dstore.undetermined:
+ dstore.undetermined.remove(self)
+ elif len(new_upper) == lower_card:
+ val_len = lower_card
+ self.set_lower(new_upper, dstore=dstore)
+ self.set_value(new_upper, dstore=dstore)
+ self.set_upper_card(val_len, dstore=dstore)
+ if dstore and self in dstore.undetermined:
+ dstore.undetermined.remove(self)
+ return True
+ return False
+
+ def discard_upper(self, value, dstore=None, constraint=None):
+ """Discard set or element from upper bound."""
+ upper = self.get_upper(dstore=dstore)
+ value = value if isinstance(value, set) else {value}
+ if value & upper:
+ new_upper = upper - value
+ lower = self.get_lower(dstore=dstore)
+ if len(new_upper) < len(lower) and constraint:
+ s = 'Warning: attempting to discard {} from upper bound {}
of {}, making it smaller than lower bound {}'
+ print(s.format(value, upper, self, lower))
+ # If value and upper overlap
+ if constraint:
+ print(' {} discarding {} from {}'.format(constraint,
value, self))
+ self.set_upper(new_upper, dstore=dstore)
+ return True
+ return False
+
+ def strengthen_lower(self, lower2, dstore=None, constraint=None,
det=False):
+ """Strengthens the lower bound by unioning it with lower2."""
+ lower = self.get_lower(dstore=dstore)
+ if not lower.issuperset(lower2):
+ new_lower = lower.union(lower2)
+ upper = self.get_upper(dstore=dstore)
+ upper_card = self.get_upper_card(dstore=dstore)
+ if not new_lower.issubset(upper) and constraint:
+ s = 'Warning: attempting to change lower bound of {} to
{}, which is not a subset of upper bound {}'
+ print(s.format(self, new_lower, upper))
+ if len(new_lower) > upper_card and constraint:
+ s = 'Warning: attempting to change lower bound of {} to
{}, which is greater than upper card {}'
+ print(s.format(self, new_lower, upper_card))
+ if constraint:
+ print(' {} strengthening lower bound of {} with
{}'.format(constraint, self, lower2))
+ self.set_lower(new_lower, dstore=dstore)
+ if det:
+ if new_lower == upper and upper_card ==
self.lower_card(dstore=dstore):
+ self.set_value(upper, dstore=dstore)
+ if dstore and self in dstore.undetermined:
+ dstore.undetermined.remove(self)
+ return True
+ return False
+
+ def strengthen_lower_card(self, lower2, dstore=None, constraint=None,
det=False):
+ """Raises the lower bound on the cardinality of the set."""
+ if lower2 > self.get_lower_card(dstore=dstore):
+ if constraint:
+ print(' {} raising lower cardinality bound of {} to
{}'.format(constraint, self, lower2))
+ self.set_lower_card(lower2, dstore=dstore)
+ if det:
+ upper_card = self.get_upper_card(dstore=dstore)
+ if lower2 == upper_card:
+ upper = self.get_upper(dstore=dstore)
+ if len(upper) == upper_card:
+ # Determine
+ self.set_lower(upper, dstore=dstore)
+ self.set_value(upper, dstore=dstore)
+ if dstore and self in dstore.undetermined:
+ dstore.undetermined.remove(self)
+ return True
+ return False
+
+ def strengthen_upper_card(self, upper2, dstore=None, constraint=None,
det=False):
+ """Lowers the upper bound on the cardinality of the set."""
+ if upper2 < self.get_upper_card(dstore=dstore):
+ if constraint:
+ print(' {} lowering upper cardinality bound of {} to
{}'.format(constraint, self, upper2))
+ self.set_upper_card(upper2, dstore=dstore)
+ if det:
+ lower_card = self.get_lower_card(dstore=dstore)
+ if upper2 == lower_card:
+ lower = self.get_lower(dstore=dstore)
+ if len(lower) == lower_card:
+ # Determine
+ self.set_upper(lower, dstore=dstore)
+ self.set_value(lower, dstore=dstore)
+ if dstore and self in dstore.undetermined:
+ dstore.undetermined.remove(self)
+ return True
+ return False
+
+ ## Printing
+
+ @staticmethod
+ def string_range(lower, upper):
+ s = '{'
+ for i,v in enumerate(upper):
+ if i != 0:
+ s += ','
+ if v not in lower:
+ s += '({})'.format(v)
+ else:
+ s += '{}'.format(v)
+ return s + '}'
+
+ def pprint(self, dstore=None, spaces=0, end='\n'):
+ string = '{0}${1}:{2}|{3},{4}|'.format(spaces*' ',
+ self.name,
+
Var.string_range(self.get_lower(dstore=dstore),
+
self.get_upper(dstore=dstore)),
+
self.get_lower_card(dstore=dstore),
+
self.get_upper_card(dstore=dstore))
+ print(string)
+ #, end=end)
+
+class IVar(Var):
+
+ def __init__(self, name, domain=None,
+ problem=None, dstores=None, rootDS=None,
+ # Vars with low weights are "peripheral".
+ weight=1):
+ Var.__init__(self, name,
+ lower_domain=set(), upper_domain=domain,
+ lower_card=1, upper_card=1,
+ problem=problem, dstores=dstores, rootDS=rootDS,
weight=weight)
+
+ def __repr__(self):
+ return '#{}'.format(self.name)
+
+ def pprint(self, dstore=None, spaces=0, end='\n'):
+ string = '{0}${1}:{2}'.format(spaces*' ',
+ self.name,
+ self.get_upper(dstore=dstore))
+ print(string)
+
+class VarError(Exception):
+ '''Class for errors encountered when attempting to execute an event on
a variable.'''
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
=======================================
--- /l3lite/__init__.py Wed Feb 19 07:17:01 2014 UTC
+++ /l3lite/__init__.py Thu Mar 27 17:19:15 2014 UTC
@@ -1,5 +1,5 @@
"""Do-it-yourself L3. Create simple bilingual lexicons and grammars for
language pairs."""

-__all__ = ['language', 'entry', 'ui']
+__all__ = ['language', 'entry', 'ui', 'variable']

from .ui import *
=======================================
--- /l3lite/entry.py Wed Feb 19 07:17:01 2014 UTC
+++ /l3lite/entry.py Thu Mar 27 17:19:15 2014 UTC
@@ -37,15 +37,23 @@
# -- Class for groups (multi-word expressions).
# 2014.02.18
# -- Cloning of Lex instances (for groups and L3 nodes).
+# 2014.03.18
+# -- Lots of changes and additions to groups.
+# 2014.03.24
+# -- words attribute in Group is a list of [word, feat_dict] pairs.

-import copy
+import copy, itertools
import yaml

+LEXEME_PRE = '%'
+CLASS_PRE = '$'
+
class Entry:
"""The central unit in L3Lite, containing lexical and grammatical
constraints that are instantiated during processing."""

ID = 1
+ dflt_dep = 'dflt'

def __init__(self, name, language, id=0):
"""Initialize name and basic features: language, trans, count,
id."""
@@ -89,11 +97,16 @@
"""Update count on the basis of data from somewhere."""
self.count += count

- ### Translations (word, gram, lexeme entries)
+ ### Translations (word, gram, lexeme, group entries)
+ ###
+ ### Translations are stored in a language-id-keyed dict.
+ ### Values are dicts with target entry repr strings as ids.
+ ### Values are dicts with correspondence ('cor'), count ('cnt'), etc.
+ ### as keys.

- def get_trans(self, language, create=False):
+ def get_translations(self, language, create=True):
"""Get the translation dict for language in word/lexeme/gram entry.
- Create it if it doesn't exit and create is True."""
+ Create it if it doesn't exist and create is True."""
if self.trans is None:
self.trans = {}
if language not in self.trans and create:
@@ -101,18 +114,18 @@
return self.trans.get(language)

def add_trans(self, language, trans, count=1):
- """Add translation to the translation dictionary dictionary for
language,
+ """Add translation to the translation dictionary for language,
initializing its count."""
- transdict = self.get_trans(language, create=True)
- transdict[trans] = {'count': count}
+ transdict = self.get_translations(language, create=True)
+ transdict[trans] = {'c': count}

def update_trans(self, language, trans, count=1):
"""Update the count of translation."""
- transdict = self.get_trans(language)
+ transdict = self.get_translations(language)
if trans not in transdict:
s = "Attempting to update non-existent translation {} for {}"
raise(EntryError(s.format(trans, self.name)))
- transdict[trans][count] += count
+ transdict[trans]['c'] += count

def add_trans_dep(self, language, trans, typ, src_dep, targ_dep):
"""Add a translation dependency specification.
@@ -124,7 +137,7 @@

cloneID = 1

- def __init__(self, name, language, cls=None, id=0):
+ def __init__(self, name, language, cls=None, id=0, group=False):
"""In addition to Entry features, initialize
depsin, depsout, order, agr, gov, grams, and (for word and lexeme)
class."""
Entry.__init__(self, name, language, id=id)
@@ -136,17 +149,19 @@
self.grams = None
self.cls = cls
self.cloneID = 0
+ # Whether entry is part of a group
+ self.group = group

def __repr__(self):
- """Print name."""
+ """Print name and a unique identifier."""
return '<{}:{}{}>'.format(self.name, self.id, ';' +
str(self.cloneID) if self.cloneID else '')

## Cloning
## Needed for groups, which consist of copies of lexes and
## for L3 node entries

- def clone(self):
- copied = Lex(self.name, self.language, cls=self.cls, id=self.id)
+ def clone(self, group=True):
+ copied = Lex(self.name, self.language, cls=self.cls, id=self.id,
group=group)
copied.depsin = self.depsin
copied.depsout = self.depsout
copied.order = self.order
@@ -396,44 +411,40 @@
"""Inherit government constraints from class."""

class Group(Entry):
- """Multi-word expressions. Each group consists of a head
- and a set of dependency/dependent pairs, together with
- an order for the words.
+ """Multi-word expressions. Each group consists of a head and a set of
nodes,
+ possibly connected to other nodes through explicit dependencies and an
explicit
+ order of the nodes.
Variable slots have dedicated names that allow them to be
referenced in translations.
Groups must be created *after* other lexical items.
- For example:
- read_the_riot_act:
- [read, {ord: 0,
- io: [?io, 1],
- do: [act, {ord: 4, nmod: [riot, {ord: 3}], det: [the, {ord:
2}]}]}]
- trans {spa: [cantar_las_cuarenta, [[?io, ?oi]]]}
-
- cantar_las_cuarenta:
- [cantar, {lexeme: cantar, ord: 0,
- oi: [?oi],
- od: [cuarenta, {det: [las]}]}]
+ {index: [word_obj, {dep/position_feats}...}
"""

- def __init__(self, name, language, head, head_feats=None,
dependents=None, head_lexeme=False):
+ def __init__(self, name, language, head, head_feats=None,
head_order=None, head_lexeme=False):
"""name of a Group is something like acabar_de_V.
head is the word that is the syntactic head of the group."""
Entry.__init__(self, name, language)
- # An index->[word, {dep: word...}] dict
- self.words = {}
+ # A list of [word feats] pairs; index in the list is the word's
(node's) ID
+ self.words = []
self.word_id = 0
if head_lexeme:
self.head_lexeme = True
head_type = language.get_lexeme(head)
else:
self.head_lexeme = False
- # There may be more than one of these; for now just use the
first
- head_type = language.get_words(head)[0]
+ head_type = language.get_words(head)
if not head_type:
- s = "No existing lexical entry in {} for head of group {}"
- raise(EntryError(s.format(language, name)))
- self.head = head_type.clone()
- self.words[self.word_id] = [self.head, {}]
+# print("No existing lexical entry in {} for head of group
{}".format(language, name))
+ # SHOULD THIS BE RECORDED IN THE WORD LEXICON?
+ self.head = language.add_word(head, group=True)
+ else:
+ # Use the first one if there's more than one
+ self.head = head_type[0].clone()
+ self.words.append([self.head, {}])
+# self.words[self.word_id] = [self.head, {}]
+ if head_order is not None:
+ self.words[word_id][1]['o'] = head_order
+# self.words[self.word_id][1]['o'] = head_order
self.word_id += 1

def __repr__(self):
@@ -446,26 +457,31 @@
"""Convert the group to a dictionary to be serialized in a yaml
file."""
d = Entry.to_dict(self)
d['head_lexeme'] = self.head_lexeme
- d['words'] = {}
+# d['words'] = {}
+ d['words'] = []
w = d['words']
- for index, lex in self.words.items():
+# for index, lex in self.words.items():
+ for lex in enumerate(self.words):
l = lex[0]
name = l.name
- w[index] = [name]
+ w.append([name])
+# w[index] = [name]
if len(lex) == 2:
- w[index].append(copy.deepcopy(lex[1]))
+ w[-1].append(copy.deepcopy(lex[1]))
+# w[index].append(copy.deepcopy(lex[1]))
return d

@staticmethod
def from_dict(d, language):
"""Convert a dict (loaded from a yaml file) to a Lex object."""
lexeme = d['head_lexeme']
- g = Group(d.get('name'), language, d.get('words').get(0)[0],
head_lexeme=lexeme)
- for id, word in d.get('words').items():
+ g = Group(d.get('name'), language, d.get('words')[0][0],
head_lexeme=lexeme)
+# for id, word in d.get('words').items():
+ for id, word in enumerate(d.get('words')):
if id == 0:
# Just handle the dependencies for this case
deps = word[1]
- g.words[id][1] = copy.deepcopy(deps)
+ g.words[0][1] = copy.deepcopy(deps)
else:
name = word[0]
lex = language.get_words(name)[0]
@@ -474,37 +490,149 @@
lex_info = [lex.clone(), copy.deepcopy(deps)]
else:
lex_info = [lex.clone()]
- g.words[id] = lex_info
+ g.words.append(lex_info)
+# g.words[id] = lex_info
return g

- def add_word(self, word, head_id, dependency):
+ ## Getters
+
+ def get_word(self, index):
+ """The lex and features for a word in the group with ID index."""
+ if index > len(self.words) - 1:
+ s = "No word in {} with internal ID {}"
+ raise(EntryError(s.format(self, index)))
+ return self.words[index]
+
+ def get_word_feats(self, index):
+ word = self.get_word(index)
+ return word[1]
+
+ def get_lex(self, id):
+ """Return the Lex with the given index."""
+ word = self.get_word(id)
+ return word[0]
+
+ def get_daughters(self, word_id, dep=None):
+ """Return the indices of the daughters of word with id word_id
+ of type dep or all daughters if dep is None."""
+ feats = self.get_word_feats(word_id)
+ if 'd' not in feats:
+ return
+ daughters = feats['d']
+ if dep is not None:
+ return daughters.get(dep)
+ else:
+ # Maybe leave as an iterable object?
+ return list(itertools.chain.from_iterable(daughters.values()))
+
+ def get_mother(self, word_id):
+ """Return the type and index of the internal mother of word with
id word_id.
+ If this is the head, return None."""
+ feats = self.get_word_feats(word_id)
+ if 'm' not in feats:
+ return
+ return feats['m']
+
+ def add_word(self, word, head_id=None, dependency=Entry.dflt_dep,
order=None):
"""Add a word to the group, as dependent on dependency from
head."""
# For now, use first word entry
- typ = self.language.get_words(word)[0]
+ typ = self.language.get_words(word)
if not typ:
- s = "No existing lexical entry in {} for word {} in group {}"
- raise(EntryError(s.format(language, word, self)))
- word = typ.clone()
- self.words[self.word_id] = [word]
+# print("No existing lexical entry in {} for head of group
{}".format(self.language, word))
+ # SHOULD THIS BE RECORDED IN THE WORD LEXICON?
+ word = self.language.add_word(word, group=True)
+ else:
+ # Pick the first lexical entry for now
+ word = typ[0].clone()
+ self.words.append([word, {}])
+# self.words[self.word_id] = [word, {}]
+ if head_id is not None:
+ self.add_dep(head_id, self.word_id, dep=dependency)
+ if order is not None:
+ self.words[self.word_id][1]['o'] = order
+ id = self.word_id
self.word_id += 1
- head_list = self.words.get(head_id)
- if not head_list:
+ return id
+
+ def add_dep(self, src, dest, dep=Entry.dflt_dep):
+ """Make a dependency of type dep from word with id src to word
with id dest."""
+ if src >= len(self.words):
s = "No word in {} with internal ID {}"
- raise(EntryError(s.format(self, head_id)))
- if len(head_list) == 1:
- head_list.append({})
- head, deps = head_list
- if dependency not in deps:
- deps[dependency] = []
- deps[dependency].append(self.word_id)
- return word
+ raise(EntryError(s.format(self, src)))
+ if dest >= len(self.words):
+ s = "No word in {} with internal ID {}"
+ raise(EntryError(s.format(self, dest)))
+ daughter_dict = self.get_word_feats(dest)
+ if 'm' in daughter_dict:
+ s = "Word {} in {} already has a mother"
+ raise(EntryError(s.format(dest, self)))
+ daughter_dict['m'] = (dep, src)
+ mother_dict = self.get_word_feats(src)
+ if 'd' not in mother_dict:
+ mother_dict['d'] = {}
+ mother_daughters = mother_dict['d']
+ if dep not in mother_daughters:
+ mother_daughters[dep] = []
+ mother_daughters[dep].append(dest)
+
+ ## Translations
+ ## A translation of a group is a group in another language, with a
mapping or alignment
+ ## between the nodes (words) in the two groups.
+ ## The mapping takes the form of a list of target word indices or None
if the corresponding
+ ## word is unspecified or -1 if there is not corresponding word
(deletion). If there are
+ ## more words/nodes in the target than in the source group, the length
of the list of
+ ## is the number of target nodes.

- def get_lex(self, id):
- """Return the Lex with the given index."""
- if id not in self.words:
- s = "{} has no word with index {}"
- raise(EntryError(s.format(self, id)))
- return self.words[id][0]
+ def add_trans(self, language, trans, count=1):
+ """Add translation to the translation dictionary for language,
+ initializing its count."""
+ Entry.add_trans(self, language, trans, count=count)
+ transdict = self.get_trans(language, trans)
+ transdict['m'] = [None for x in range(len(self.words))]
+
+ def get_trans(self, language, trans, create=True):
+ alltrans = self.get_translations(language, create=create)
+ if not alltrans or trans not in alltrans:
+ s = "Attempting to update non-existent translation {} for {}"
+ raise(EntryError(s.format(trans, self.name)))
+ return alltrans[trans]
+
+ def get_trans_map(self, language, trans):
+ """Get the mapping to nodes in translation."""
+ tdict = self.get_trans(language, trans)
+ return tdict.get('m')
+
+ def get_trans_map1(self, language, trans, src_index):
+ """Get the mapped index of src_index in translation trans."""
+ map = self.get_trans_map(language, trans)
+ if not map:
+ s = "Attempting to access non-existing mapping for translation
{} of {}"
+ raise(EntryError(s.format(trans, self)))
+ return map[src_index]
+
+ def add_trans_map(self, language, trans, src_id, trg_id):
+ """Add a correspondence between source and target nodes in a
translation mapping."""
+ tdict = self.get_trans(language, trans)
+# if 'm' not in tdict:
+# tdict['m'] = []
+# tdict['m'].append((src_id, trg_id))
+ tdict['m'][src_id] = trg_id
+
+ def add_trans_del(self, language, trans, src_id):
+ """Record a node in the source group with nothing corresponding to
it in the target group."""
+ tdict = self.get_trans(language, trans)
+# if 'm' not in tdict:
+# tdict['m'] = []
+# tdict['m'].append((src_id, -1))
+ tdict['m'][src_id] = -1
+
+ def add_trans_ins(self, language, trans, trg_id):
+ """Record a node in the target group with nothing corresponding to
it in the source group."""
+ tdict = self.get_trans(language, trans)
+# if 'm' not in tdict:
+# tdict['m'] = []
+ tdict['m'].append(trg_id)
+# tdict['m'].append((-1, trg_id))

class EntryError(Exception):
'''Class for errors encountered when attempting to update an entry.'''
=======================================
--- /l3lite/language.py Wed Feb 19 07:17:01 2014 UTC
+++ /l3lite/language.py Thu Mar 27 17:19:15 2014 UTC
@@ -30,8 +30,13 @@
# -- Made entries a separate class.
# 2014.02.15
# -- Methods for serializing and deserializing languages (using YAML).
+# 2014.03.24
+# -- Words, lexemes, and classes are all in the same dictionary
(self.words).
+# Lexemes start with %, classes with $.

from .entry import *
+from .variable import *
+
import os, yaml

class Language:
@@ -46,14 +51,17 @@
"""Initialize dictionaries and names."""
self.name = name
self.abbrev = abbrev
+ # Words, lexemes, classes
self.words = words or {}
# Combine with words in a single lexicon?
- self.lexemes = lexemes or {}
+# self.lexemes = lexemes or {}
self.grams = grams or {}
- self.classes = classes or {}
+# self.classes = classes or {}
self.groups = groups or {}
# Record possibilities for dependency labels, feature values,
order constraints
self.possible = {}
+ # Record whether language has changed since last loaded
+ self.changed = False
Language.languages.append(abbrev)

def __repr__(self):
@@ -72,23 +80,28 @@
for k, v in self.grams.items():
grams[k] = v.to_dict()
d['grams'] = grams
- if self.classes:
- classes = {}
- for k, v in self.classes.items():
- classes[k] = v.to_dict()
- d['classes'] = classes
+# if self.classes:
+# classes = {}
+# for k, v in self.classes.items():
+# classes[k] = v.to_dict()
+# d['classes'] = classes
# Lexemes and words should probably be separate dictionaries (and
files).
- if self.lexemes:
- lexemes = {}
- for k, v in self.lexemes.items():
- lexemes[k] = v.to_dict()
- d['lexemes'] = lexemes
+# if self.lexemes:
+# lexemes = {}
+# for k, v in self.lexemes.items():
+# lexemes[k] = v.to_dict()
+# d['lexemes'] = lexemes
if self.words:
words = {}
for k, v in self.words.items():
# Words are lists
words[k] = [lex.to_dict() for lex in v]
d['words'] = words
+ if self.groups:
+ groups = {}
+ for k, v in self.groups.items():
+ groups[k] = [g.to_dict() for g in v]
+ d['groups'] = groups
return d

def write(self, directory, filename=''):
@@ -108,21 +121,26 @@
l.grams = {}
for k, v in grams.items():
l.grams[k] = Entry.from_dict(v, l)
- classes = d.get('classes')
- if classes:
- l.classes = {}
- for k, v in classes.items():
- l.classes[k] = Lex.from_dict(v, l)
- lexemes = d.get('lexemes')
- if lexemes:
- l.lexemes = {}
- for k, v in lexemes.items():
- l.lexemes[k] = Lex.from_dict(v, l)
+# classes = d.get('classes')
+# if classes:
+# l.classes = {}
+# for k, v in classes.items():
+# l.classes[k] = Lex.from_dict(v, l)
+# lexemes = d.get('lexemes')
+# if lexemes:
+# l.lexemes = {}
+# for k, v in lexemes.items():
+# l.lexemes[k] = Lex.from_dict(v, l)
words = d.get('words')
if words:
l.words = {}
for k, v in words.items():
l.words[k] = [Lex.from_dict(lex, l) for lex in v]
+ groups = d.get('groups')
+ if groups:
+ l.groups = {}
+ for k, v in groups.items():
+ l.groups[k] = [Group.from_dict(g, l) for g in v]
return l

@staticmethod
@@ -136,48 +154,55 @@
### Basic setters. Create entries (dicts) for item. For debugging
purposes, include name
### in entry.

- def add_word(self, word, cls=None):
- entry = Lex(word, self, cls=cls)
+ def add_word(self, word, cls=None, group=False):
+ entry = Lex(word, self, cls=cls, group=group)
if word in self.words:
self.words[word].append(entry)
else:
self.words[word] = [entry]
+ self.changed = True
return entry

def add_lexeme(self, lexeme, cls=None):
- entry = Lex(lexeme, self, cls=cls)
- if lexeme in self.lexemes:
+ if lexeme in self.words:
s = "Lexeme {} already in dictionary"
raise(LanguageError(s.format(lexeme)))
- self.lexemes[lexeme] = entry
+ entry = Lex(lexeme, self, cls=cls)
+ # Maybe not a list since there's always only one
+ self.words[lexeme] = [entry]
+ self.changed = True
return entry

def add_class(self, cls):
- entry = Lex(cls, self)
- if cls in self.classes:
+ if cls in self.words:
s = "Class {} already in dictionary"
raise(LanguageError(s.format(cls)))
- self.classes[cls] = entry
+ entry = Lex(cls, self)
+ # Maybe not a list since there's always only one
+ self.words[cls] = [entry]
+ self.changed = True
return entry

- def add_group(self, name, head, head_lexeme=False):
- entry = Group(name, self, head, head_lexeme=head_lexeme)
+ def add_group(self, name, head, head_order=None, head_lexeme=False):
+ entry = Group(name, self, head, head_order=head_order,
head_lexeme=head_lexeme)
if head not in self.groups:
self.groups[head] = []
self.groups[head].append(entry)
+ self.changed = True
return entry

def add_gram(self, gram, feature, count=1):
"""A gram, for example, 'plural', must have a feature, for example,
'number'."""
- entry = Entry(gram, self)
if gram in self.grams:
s = "Grammatical morpheme {} already in dictionary"
raise(LanguageError(s.format(gram)))
+ entry = Entry(gram, self)
self.grams[gram] = entry
entry.feature = feature
self.grams[gram] = entry
self.record_gram(gram, feature, count)
+ self.changed = True
return entry

def record_gram(self, name, feature, count):
@@ -202,7 +227,7 @@

def get_class(self, cls):
"""Returns a single class entry."""
- return self.classes.get(cls)
+ return self.words.get(cls)[0]

def get_gram(self, gram):
"""Returns a single gram feature value entry."""
@@ -210,7 +235,7 @@

def get_lexeme(self, lexeme):
"""Returns a single lexeme entry."""
- return self.lexemes.get(lexeme)
+ return self.words.get(lexeme)[0]

## Dependencies (word, lexeme, class entries)

=======================================
--- /l3lite/ui.py Wed Feb 19 07:17:01 2014 UTC
+++ /l3lite/ui.py Thu Mar 27 17:19:15 2014 UTC
@@ -26,39 +26,183 @@

# 2014.02.15
# -- Created
+# 2014.03.04
+# -- UI class
+# 2014.03.18
+# -- Adding groups

from .language import *
-import os
+import os, sys

LANGUAGE_DIR = os.path.join(os.path.dirname(__file__), 'languages')

-# Later a UI class? Subclass of tkinter Frame?
+class UI:
+ """Normally only one of these so doesn't have to be a class. Later a
subclass
+ of tkinter Frame?"""

-def load_language():
- abbrev = input("Give abbreviation for language.\n>> ")
- path = os.path.join(LANGUAGE_DIR, abbrev + '.lg')
- print(path)
- try:
- return Language.read(path)
- except IOError:
- print("That language doesn't seem to exist.")
- return
+ # Editing the grammar/lexicon
+ edit_mode = 0
+ # Parsing and translating
+ proc_mode = 1
+
+ def __init__(self):
+ self.languages = {}
+ self.mode = UI.edit_mode

-def add_word(language):
- word = input("Write the word to be added to the lexicon.\n>> ")
- if word in language.words:
- response = input("There's already a word with that form in the
lexicon; add another? ")
- if not response or response[0].lower() == 'y':
- return add_word1(word, language)
- return
- else:
- return add_word1(word, language)
+ @staticmethod
+ def yes(response):
+ return not response or response[0].lower() == 'y'

-def add_word1(word, language):
- cls = None
- response = input("Do you want to assign a class to the word? ")
- if not response or response[0].lower() == 'y':
- class_names = list(language.classes.keys())
- cls = input("Choose from these
classes:\n{}\n>> ".format(' '.join(class_names)))
- return language.add_word(word, cls=cls)
+ def load_language(self):
+ abbrev = input("Give abbreviation for language.\n>> ")
+ path = os.path.join(LANGUAGE_DIR, abbrev + '.lg')
+ try:
+ language = Language.read(path)
+ self.languages[abbrev] = language
+ return language
+ except IOError:
+ print("That language doesn't seem to exist.")
+ return

+ def quit(self):
+ """Quit the UI (and L3Lite)."""
+ response = input("Are you sure you want to quit L3Lite? ")
+ if UI.yes(response):
+ self.write_languages()
+ sys.exit()
+
+ def write_languages():
+ """Write the languages the user wants to save."""
+ for language in self.languages.values():
+ if language.changed:
+ response = input("{} has been changed;
save?\n>> ".format(language.name))
+ if UI.yes(response):
+ language.write(LANGUAGE_DIR)
+
+ def add_word(self, language):
+ word = input("Write the word to be added to the lexicon.\n>> ")
+ if word in language.words:
+ response = input("There's already a word with that form in the
lexicon; add another? ")
+ if UI.yes(response):
+ return self.add_word1(word, language)
+ return
+ else:
+ return self.add_word1(word, language)
+
+ def add_word1(self, word, language):
+ cls = None
+ response = input("Do you want to assign a class to the word? ")
+ if UI.yes(response):
+ class_names = list(language.classes.keys())
+ cls = input("Choose from these
classes:\n{}\n>> ".format(' '.join(class_names)))
+ return language.add_word(word, cls=cls)
+
+ def add_class(self, language):
+ name = input("Write the name of the class to be added to the
lexicon.\n>> ")
+ if name in self.language.classes:
+ response = input("There's already a class with that name in
the lexicon; add a class with a different name? ")
+ if UI.yes(response):
+ return self.add_class1(name, language)
+ return
+ else:
+ return self.add_class1(name, language)
+
+ def add_class1(self, name, language):
+ return language.add_class(name)
+
+ def add_group(self, language):
+ """Get the words that will be in the group. make_group() creates
the group."""
+ words = input(
+ """Write the words, lexemes, or classes in the group in their
typical order.
+Precede any lexemes with % and any classes with $.
+>> """)
+ words = words.split()
+ response = input("Are these the words you want in the
group?\n{}\n".format(', '.join(words)))
+ if UI.yes(response):
+ return self.make_group(language, words)
+ else:
+ return self.add_group(language)
+
+ def make_group(self, language, words, word_string=''):
+ if not word_string:
+ word_list = []
+ for i, w in enumerate(words):
+ word_list.append("[{}] {}".format(i+1, w))
+ word_string = '\n'.join(word_list)
+ head_index = input("Give the number of the word or lexeme that is
the head of the group.\n{}\n>> ".format(word_string))
+ if not head_index.isdigit():
+ print("You need to give a number between 1 and
{}".format(len(words)))
+ return self.make_group(language, words,
word_string=word_string)
+ else:
+ head_index = int(head_index)
+ if head_index > len(words):
+ print("You need to give a number between 1 and
{}".format(len(words)))
+ return self.make_group(language, words,
word_string=word_string)
+ else:
+ head_index = head_index - 1
+ head = words[head_index]
+ name = '_'.join(words)
+ print("OK, the head is '{}'".format(head))
+ print("Creating group {} with head {}".format(name, head))
+ group = language.add_group(name, head,
head_lexeme=head.startswith(LEXEME_PRE), head_order=head_index)
+ # A dictionary to associate order of words within the
group with their IDs (indices).
+ order2index = {head_index: 0}
+ for index, word in enumerate(words):
+ if word == head:
+ continue
+ word_id = group.add_word(word, order=index)
+ order2index[index] = word_id
+ response = input("Create dependencies among words?\n")
+ if response:
+ return self.add_group_deps(group, word_string,
order2index=order2index)
+ else:
+ return self.add_group_deps(group, word_string,
first=False, finished=True, order2index=order2index)
+
+ def add_group_deps(self, group, word_string, first=True,
finished=False, order2index=None):
+ if not first:
+ if not finished:
+ response = input("Finished with dependencies? ")
+ if UI.yes(response):
+ finished = True
+ if finished:
+ for index, (lex, feats) in group.words.items():
+ # For each word in the group, make sure it's either
+ # the group head or that it has a mother within the
+ # group.
+ if index != 0 and 'm' not in feats:
+ print("Making word {} a daughter of head with
default dependency".format(feats['o'] + 1))
+ group.add_dep(0, index)
+ return group
+ else:
+ return self.add_group_dep(group, word_string,
order2index=order2index)
+ else:
+ return self.add_group_dep(group, word_string,
order2index=order2index)
+
+ def add_group_dep(self, group, word_string, src_index=None,
dest_index=None, order2index=None):
+ if src_index is None:
+ src_index = input("Give the index of the source word for a
dependency.\n{}\n>> ".format(word_string))
+ if not src_index.isdigit() or int(src_index) >
len(group.words):
+ print("You need to give a number between 1 and
{}".format(len(group.words)))
+ return self.add_group_dep(group, word_string,
order2index=order2index)
+ else:
+ src_index = int(src_index) - 1
+ if dest_index is None:
+ dest_index = input("Give the index of the destination
word for the dependency.\n{}\n>> ".format(word_string))
+ if not dest_index.isdigit() or int(dest_index) >
len(group.words):
+ print("You need to give a number between 1 and
{}".format(len(group.words)))
+ return self.add_group_dep(group, word_string,
src_index=src_index, order2index=order2index)
+ else:
+ dest_index = int(dest_index) - 1
+ dep = input("If you want a particular dependency
type, enter it.\n>> ")
+ if not dep:
+ dep = Entry.dflt_dep
+ response = input("OK to create dependency of type
{} from word {} to word {}?\n".format(dep, src_index + 1, dest_index + 1))
+ if UI.yes(response):
+ s = order2index[src_index]
+ d = order2index[dest_index]
+ # Actually create the dependency
+ group.add_dep(s, d, dep=dep)
+ return self.add_group_deps(group, word_string,
first=False, order2index=order2index)
+
+
+
=======================================
--- /lite.py Wed Feb 19 07:17:01 2014 UTC
+++ /lite.py Thu Mar 27 17:19:15 2014 UTC
@@ -40,44 +40,75 @@
return l3lite.Language(name, abbrev)

def eg():
+ ### Spanish
e = l3lite.Language('español', 'spa')
- mm = e.add_word('mujer', cls='sus')
+ ## Words
+ mm = e.add_word('mujer', cls='$sus')
+ la = e.add_word('la', cls='$det')
+ ## Classes
+ ss = e.add_class('$sus')
+ ss.add_depin('sj', {'min': 0, 'max': 1, 'dflt': 0})
+ ss.add_depin('oj', {'min': 0, 'max': 1, 'dflt': 0})
+ ss.add_depout('det', {'min': 0, 'max': 1, 'dflt': 1})
+ ss.add_order(['det', '^'])
+ ss.set_gram('num', {'sing': 2, 'plur': 1})
+ ss.set_gram('gen', {'masc': 1, 'fem': 1})
+ ss.add_agr('det', 'gen', 'gen')
+ dd = e.add_class('$det')
+ dd.add_depin('det', {'min': 1, 'max': 1})
+ # alguien
+ e.add_class('$alg')
+ # Some grammatical features
e.add_gram('plur', 'num')
e.add_gram('sing', 'num')
e.add_gram('pres', 'tmp')
e.add_gram('pret', 'tmp')
+ ## Translations
mm.add_trans('grn', 'kuña')
mm.add_trans('grn', 'kuñakarai')
mm.set_gram('num', 'sing')
mm.set_gram('gen', 'fem')
- la = e.add_word('la', 'det')
la.set_gram('num', 'sing')
la.set_gram('gen', 'fem')
- ss = e.add_class('sus')
- ss.add_depin('sj', {'min': 0, 'max': 1, 'dflt': 0})
- ss.add_depin('oj', {'min': 0, 'max': 1, 'dflt': 0})
- ss.add_depout('det', {'min': 0, 'max': 1, 'dflt': 1})
- ss.add_order(['det', '^'])
- ss.set_gram('num', {'sing': 2, 'plur': 1})
- ss.set_gram('gen', {'masc': 1, 'fem': 1})
- ss.add_agr('det', 'gen', 'gen')
- dd = e.add_class('det')
- dd.add_depin('det', {'min': 1, 'max': 1})
- g = l3lite.Language('guarani', 'grn')
- kk = g.add_word('kuña')
+ # Inheritance (after class creation)
la.inherit()
mm.inherit()
+ ### Guarani
+ g = l3lite.Language('guarani', 'grn')
+ ## Words
+ kk = g.add_word('kuña')
+ ### English
E = l3lite.Language('English', 'eng')
+ ## Words
rr = E.add_word('read')
tt = E.add_word('the')
- rrr = E.add_word('riot')
- rrr = E.add_word('act')
- rra = E.add_group('read_the_riot_act', 'read')
- rra.add_word('act', 0, 'do')
- rra.add_word('the', 1, 'det')
- rra.add_word('riot', 0, 'nmod')
+# rrr = E.add_word('riot')
+# rrr = E.add_word('act')
+ nn = E.add_class('$sbd')
+ ## Groups
+ # English: read sbd. the riot act
+ rra = E.add_group('read_sbd_the_riot_act', 'read')
+ rra.add_word('$sbd')
+ rra.add_word('the')
+ rra.add_word('riot')
+ rra.add_word('act')
+ # Spanish: cantar (a) alg. las cuarenta
+ clc = e.add_group('cantar_alg_las_cuarenta', '%cantar')
+ clc.add_word('$alg')
+ clc.add_word('las')
+ clc.add_word('cuarenta')
+ # Translation: Eng->Spa
+ rra.add_trans('spa', 'cantar_alg_las_cuarenta')
+ rra.add_trans_map('spa', 'cantar_alg_las_cuarenta', 0, 0)
+ rra.add_trans_map('spa', 'cantar_alg_las_cuarenta', 1, 1)
+ rra.add_trans_del('spa', 'cantar_alg_las_cuarenta', 3)
return e, g, E

+def ui():
+ u = l3lite.UI()
+ e, s = l3lite.Language("English", 'eng'),
l3lite.Language("español", 'spa')
+ return u, e, s
+
if __name__ == "__main__":
print('L^3 Lite, version {}\n'.format(__version__))


==============================================================================
Revision: dffb6356c66f
Branch: default
Author: Michael Gasser <gas...@cs.indiana.edu>
Date: Fri Apr 4 06:26:55 2014 UTC
Log: L3Lite: constraints as before with new ComplexSetConvexity
constraint
http://code.google.com/p/hltdi-l3/source/detail?r=dffb6356c66f

Added:
/l3lite/constraint.py
Modified:
/l3lite/__init__.py
/l3lite/language.py
/l3lite/variable.py
/lite.py

=======================================
--- /dev/null
+++ /l3lite/constraint.py Fri Apr 4 06:26:55 2014 UTC
@@ -0,0 +1,2565 @@
+#
+# L3Lite constraints.
+#
+########################################################################
+#
+# This file is part of the HLTDI L^3 project
+# for parsing, generation, translation, and computer-assisted
+# human translation.
+#
+# Copyright (C) 2014, HLTDI <gas...@cs.indiana.edu>
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# =========================================================================
+
+# 2014.03.27
+# -- Created. Initially just copied from l3xdg/constraint.py.
+# 2014.03.29
+# -- Fixed cant_precede() so it works with IVars (determined and not).
+# 2014.04.03
+# -- Created ComplexSetConvexity
+
+from .variable import *
+import itertools
+
+class Constraint:
+
+ # Constants for outcome of running
+ failed = 0
+ entailed = 1
+ sleeping = 2
+
+ # Constant threshold for lenience
+ lenience = .5
+
+ def __init__(self, variables, problem=None, record=True, weight=1):
+ self.variables = variables
+ self.problem = problem
+ self.weight = weight
+ if record:
+ for var in variables:
+ if isinstance(var, Determined):
+ if problem:
+ if var not in problem.detvarsD:
+ problem.detvarsD[var] = []
+ problem.detvarsD[var].append(self)
+ else:
+ var.constraints.append(self)
+ self.name = ''
+
+ def __repr__(self):
+ return self.name
+
+ def is_lenient(self):
+ return self.weight < Constraint.lenience
+
+ def set_weight(self, weight):
+ self.weight = weight
+
+ def get_var(self):
+ """The single variable for this constraint."""
+ return self.variables[0]
+
+ # Each Constraint type must implement fails(), is_entailed(), and
infer().
+
+ def fails(self, dstore=None):
+ raise NotImplementedError("{} is an abstract
class".format(self.__class__.__name__))
+
+ def is_entailed(self, dstore=None):
+ raise NotImplementedError("{} is an abstract
class".format(self.__class__.__name__))
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ """Should return state and variables that change."""
+ raise NotImplementedError("{} is an abstract
class".format(self.__class__.__name__))
+
+ def determine(self, dstore=None, verbosity=0, tracevar=None):
+ """Try to determine each variable, returning the set if any
determined."""
+ det = set()
+ for variable in self.variables:
+ if not variable.is_determined(dstore=dstore) and \
+ variable.determined(dstore=dstore, constraint=self,
verbosity=verbosity) is not False:
+ if verbosity and variable in tracevar:
+ print(' {} determining {} at {}'.format(self,
variable, variable.get_value(dstore=dstore)))
+ det.add(variable)
+ return det
+
+ def run(self, dstore=None, verbosity=0, tracevar=[]):
+ """Run this constraint during constraint satisfaction."""
+ if verbosity > 1:
+ print(' Running {}'.format(self))
+ determined = self.determine(dstore=dstore, verbosity=verbosity,
tracevar=tracevar)
+ # Try to determine the variables; if any are determined, go to
sleep and return
+ # the set of newly determined variables.
+ if determined:
+ if verbosity > 1:
+ print(' Determined variables', determined)
+ return Constraint.sleeping, determined
+ # Otherwise see if the constraint fails. If it does fail and
return the empty set.
+ if self.fails(dstore=dstore):
+ if verbosity > 1:
+ print(' Failed!')
+ elif verbosity:
+ print('{} failed; weight: {}'.format(self, self.weight))
+ return Constraint.failed, set()
+ # Otherwise see if the constraint is entailed. If it is, succeed
and return the empty set.
+ if self.is_entailed(dstore=dstore):
+ if verbosity > 1:
+ print(' Entailed')
+ return Constraint.entailed, set()
+ # Otherwise try inferring variable values. Either succeed or sleep
and return any changed
+ # variables.
+ return self.infer(dstore=dstore, verbosity=verbosity,
tracevar=tracevar)
+
+ @staticmethod
+ def string_set(s):
+ """Convenient print name for a set."""
+ if len(s) > 10:
+ return '{{{0}...{1}}}'.format(min(s), max(s))
+ else:
+ return '{}'.format(set.__repr__(s))
+
+ def print_vars(self):
+ '''Print out components of constraint variables.'''
+ for v in self.variables:
+ print('{} :: {}'.format(v, v.dstores))
+
+## Primitive basic constraints
+
+# Integer domains
+
+class Member(Constraint):
+
+ def __init__(self, var, domain, problem=None):
+ """
+ var: an IVar
+ domain: a set of ints
+ """
+ Constraint.__init__(self, (var,), problem=problem)
+ self.domain = domain
+ self.name = '{0}<{1}'.format(self.get_var(),
Constraint.string_set(self.domain))
+
+ def fails(self, dstore=None):
+ """Is the constraint domain not a superset of the variable's
domain?"""
+ if not
self.domain.issubset(self.get_var().get_domain(dstore=dstore)):
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Is the variable's domain a subset of the constraint's domain?"""
+ if self.get_var().get_domain(dstore=dstore).issubset(self.domain):
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ """The variable's values are restricted to the intersection of
+ their current values and the constraint's domain."""
+ var = self.get_var()
+ if var.strengthen(self.domain, dstore=dstore,
constraint=(verbosity>1 or var in tracevar) and self):
+ return Constraint.entailed, {var}
+ return Constraint.entailed, set()
+
+# Set domains
+
+class Superset(Constraint):
+ """Set variable is constrained to be a superset of subset."""
+
+ def __init__(self, var, subset, problem=None):
+ """
+ var: a SVar
+ subset: a set of ints
+ """
+ Constraint.__init__(self, (var,), problem=problem)
+ self.subset = subset
+ self.name = '{0} >= {1}'.format(self.get_var(),
Constraint.string_set(self.subset))
+
+ def fails(self, dstore=None):
+ """Is the constraint subset not a subset of the var's upper
bound?"""
+ if not
self.subset.issubset(self.get_var().get_upper(dstore=dstore)):
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Is the variable's lower bound a superset of the constraint's
subset?"""
+ if self.get_var().get_lower(dstore=dstore).issuperset(self.subset):
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ """The variable's values are restricted to be a superset of the
union
+ of the current lower bound and subset."""
+ var = self.get_var()
+ if var.strengthen_lower(self.subset, dstore=dstore,
+ constraint=(verbosity>1 or var in
tracevar) and self):
+ return Constraint.entailed, {var}
+ return Constraint.entailed, set()
+
+class Subset(Constraint):
+ """Set variable is constrained to be a subset of superset."""
+
+ def __init__(self, var, superset, problem=None):
+ """
+ var: a SVar
+ superset: a set of ints
+ """
+ Constraint.__init__(self, (var,), problem=problem)
+ self.superset = superset
+ self.name = '{0} c= {1}'.format(self.get_var(),
Constraint.string_set(self.superset))
+
+ def fails(self, dstore=None):
+ """Is the var's lower bound not a subset of the constraint
superset?"""
+ if not
self.get_var().get_lower(dstore=dstore).issubset(self.superset):
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Is the variable's upper bound a subset of the constraint's
superset?"""
+ if self.get_var().get_upper(dstore=dstore).issubset(self.superset):
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ """The variable's values are restricted to be a subset of the
intersection
+ of the current upper bound and superset."""
+ var = self.get_var()
+ if var.strengthen_upper(self.superset, dstore=dstore,
constraint=(verbosity>1 or var in tracevar) and self):
+ return Constraint.entailed, {var}
+ return Constraint.entailed, set()
+
+# Set cardinality
+
+class CardinalityGEQ(Constraint):
+ """Set variable's cardinality is constrained to be >= lower bound."""
+
+ def __init__(self, var, lower, problem=None):
+ Constraint.__init__(self, (var,), problem=problem)
+ self.lower = lower
+ self.name = '|{0}|>={1}'.format(self.get_var(), self.lower)
+
+ def fails(self, dstore=None):
+ """Is the var's upper cardinality bound < lower?"""
+ if self.get_var().get_upper_card(dstore=dstore) < self.lower:
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Is the variable's lower cardinality bound already >= lower?"""
+ if self.get_var().get_lower_card(dstore=dstore) >= self.lower:
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ """The variable's cardinality is restricted be >= lower: lower
bound
+ is raised if necessary."""
+ var = self.get_var()
+ if var.strengthen_lower_card(self.lower, dstore=dstore,
+ constraint=(verbosity>1 or var in
tracevar) and self):
+ return Constraint.entailed, {var}
+ return Constraint.entailed, set()
+
+class CardinalityLEQ(Constraint):
+ """Set variable's cardinality is constrained to be <= upper bound."""
+
+ def __init__(self, var, upper, problem=None):
+ Constraint.__init__(self, (var,), problem=problem)
+ self.upper = upper
+ self.name = '|{0}| c= {1}'.format(self.get_var(), self.upper)
+
+ def fails(self, dstore=None):
+ """Is the var's lower cardinality bound > upper?"""
+ if self.get_var().get_lower_card(dstore=dstore) > self.upper:
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Is the variable's upper cardinality bound already <= upper?"""
+ if self.get_var().get_upper_card(dstore=dstore) <= self.upper:
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ """The variable's cardinality is restricted to be <= upper:
+ upper bound is lowered if necessary."""
+ var = self.get_var()
+ if var.strengthen_upper_card(self.upper, dstore=dstore,
+ constraint=(verbosity>1 or var in
tracevar) and self):
+ return Constraint.entailed, {var}
+ return Constraint.entailed, set()
+
+### Constraints that propagate
+
+## Primitive propagators
+
+# Integer domain variables only
+
+class LessThan(Constraint):
+ """IVar1 is less than or equal to IVar2."""
+
+ def __init__(self, variables, problem=None, weight=1):
+ Constraint.__init__(self, variables, problem=problem,
+ weight=weight)
+ self.name = '{0} <= {1}'.format(self.get_iv1(), self.get_iv2())
+
+ def get_iv1(self):
+ return self.variables[0]
+
+ def get_iv2(self):
+ return self.variables[1]
+
+ def fails(self, dstore=None):
+ """
+ Fail if min of domain1 > max of domain2.
+ """
+ iv1 = self.get_iv1()
+ iv2 = self.get_iv2()
+ min1 = min(iv1.get_domain(dstore=dstore))
+ max2 = max(iv2.get_domain(dstore=dstore))
+ if min1 > max2:
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Entailed if max of domain1 <= min of domain2."""
+ iv1 = self.get_iv1()
+ iv2 = self.get_iv2()
+ max1 = max(iv1.get_domain(dstore=dstore))
+ min2 = min(iv2.get_domain(dstore=dstore))
+ if max1 <= min2:
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ changed = set()
+ iv1 = self.get_iv1()
+ iv2 = self.get_iv2()
+ d1 = iv1.get_domain(dstore=dstore)
+ d2 = iv2.get_domain(dstore=dstore)
+ # iv2 must be between the min of iv1's domain and the maximum value
+ iv2_values = set(range(min(d1), max(d2) + 1))
+ if iv2.strengthen(iv2_values, dstore=dstore,
+ constraint=(verbosity>1 or iv2 in tracevar) and
self):
+ changed.add(iv2)
+ # iv1 must be between the min of its domain and the max of iv2's
domain
+ # (iv2's domain may have changed)
+ iv1_values = set(range(min(d1), max(iv2.get_domain(dstore=dstore))
+ 1))
+ # Maximum value of sv2's upper bound constrains sv1's upper card
+ if iv1.strengthen(iv1_values, dstore=dstore,
+ constraint=(verbosity>1 or iv1 in tracevar) and
self):
+ changed.add(iv1)
+
+ if verbosity > 1 and changed:
+ print(' Variables {} changed'.format(changed))
+ return Constraint.sleeping, changed
+
+class CardinalityEq(Constraint):
+ """Set variable's cardinality is constrained to be equal to value of
IVar."""
+
+ def __init__(self, variables, problem=None, weight=1):
+ Constraint.__init__(self, variables, problem=problem,
+ weight=weight)
+ self.sv = variables[0]
+ self.iv = variables[1]
+ self.name = '|{0}| = {1}'.format(self.sv, self.iv)
+
+ def fails(self, dstore=None):
+ """Is the sv's lower cardinality bound > max of iv's domain?"""
+ if self.iv.determined(dstore=dstore) is not False and
self.sv.determined(dstore=dstore) is not False:
+# print('Both vars determined: {}, {}'.format(self.iv, self.sv))
+ if self.iv.get_value(dstore=dstore) !=
self.sv.get_upper_card(dstore=dstore):
+ return True
+ if self.sv.get_lower_card(dstore=dstore) >
max(self.iv.get_domain(dstore=dstore)):
+ return True
+ if min(self.iv.get_domain(dstore=dstore)) >
self.sv.get_upper_card(dstore=dstore):
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Is the variable's upper cardinality bound already = iv?"""
+ if self.iv.determined(dstore=dstore) is not False and
self.sv.determined(dstore=dstore) is not False:
+ if self.sv.get_upper_card(dstore=dstore) ==
self.iv.get_value(dstore=dstore):
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ """sv's upper cardinality is restricted to be <= min of iv's
domain.
+ iv's domain is restricted to values >= lower cardinality of sv."""
+ state = Constraint.sleeping
+ changed = set()
+ sv = self.sv
+ iv = self.iv
+ sv_low_card = sv.get_lower_card(dstore=dstore)
+ sv_up_card = sv.get_upper_card(dstore=dstore)
+ if iv.strengthen(set(range(sv_low_card, sv.max)), dstore=dstore,
+ constraint=(verbosity>1 or iv in tracevar) and
self):
+ changed.add(iv)
+ return state, changed
+ if iv.strengthen(set(range(0, sv_up_card + 1)), dstore=dstore,
+ constraint=(verbosity>1 or iv in tracevar) and
self):
+ changed.add(iv)
+ return state, changed
+ iv_dom = iv.get_domain(dstore=dstore)
+ if sv.strengthen_lower_card(min(iv_dom), dstore=dstore,
+ constraint=(verbosity>1 or sv in
tracevar) and self):
+ changed.add(sv)
+ return state, changed
+ if sv.strengthen_upper_card(max(iv_dom), dstore=dstore,
+ constraint=(verbosity>1 or sv in
tracevar) and self):
+ changed.add(sv)
+ return state, changed
+
+# Set domain variables only
+
+class SetConvexity(Constraint):
+ """There must not be any 'holes' in the (single) set variable, which
represents
+ the positions of the descendants of a node as well as that of the node
itself."""
+
+ def __init__(self, var, problem=None, weight=1):
+ """Only one variable, so a special constructor."""
+ Constraint.__init__(self, [var], problem=problem, weight=weight)
+ self.var = self.variables[0]
+ self.name = '{0} <>'.format(self.var)
+
+ def fails(self, dstore=None):
+ """If the variable's upper does not include all values between
+ min and max of lower bound, or if the determined value has gaps,
fail."""
+ if self.var.determined(dstore=dstore, constraint=self) is not
False:
+ val = self.var.get_value(dstore=dstore)
+ # There can't be any holes
+ if val:
+ val_range = set(range(min(val), max(val)+1))
+ if val_range - val:
+ return True
+ lower = self.var.get_lower(dstore=dstore)
+ if not lower:
+ return False
+ upper = self.var.get_upper(dstore=dstore)
+ neces_range = set(range(min(lower), max(lower)+1))
+ if neces_range - upper:
+ # There is some value in necessary range not in upper bound
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """If the variable is determined, or if the lower bound is convex,
+ and the upper only adds a single vowel below or above the lower
bound."""
+ if self.var.determined(dstore=dstore, constraint=self) is not
False:
+ return True
+ lower = self.var.get_lower(dstore=dstore)
+ upper = self.var.get_upper(dstore=dstore)
+ if not lower:
+ return False
+ min_lower = min(lower)
+ max_lower = max(lower)
+ if not set(range(min_lower, max_lower+1)) - lower:
+ if min_lower - min(upper) <= 1 and max(upper) - max_lower <= 1:
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=[]):
+ changed = set()
+ # If the variable's lower bound is non-empty, every value between
+ # the min and max of the lower bound must be in the variable, and
+ # there can't be any gaps in the upper bound either.
+ v = self.var
+ lower = v.get_lower(dstore=dstore)
+ if len(lower) > 0:
+ upper = v.get_upper(dstore=dstore)
+ min_low = min(lower)
+ max_low = max(lower)
+ # Make the lower bound everything between the min and max
+ if v.strengthen_lower(set(range(min_low, max_low+1)),
+ dstore=dstore, constraint=(verbosity>1
or v in tracevar) and self):
+ changed.add(v)
+ return Constraint.sleeping, changed
+
+ # Look for gaps in the upper bound
+ # Starting at the max of the lower bound...
+ max_up = max(upper)
+ x = max_low+1
+ while x in upper and x < max_up:
+ x += 1
+ if x < max_up:
+ if v.discard_upper(set(range(x, max_up+1)),
+ dstore=dstore, constraint=(verbosity>1
or v in tracevar) and self):
+ changed.add(v)
+ return Constraint.sleeping, changed
+ # Starting at the min of the lower bound...
+ min_up = min(upper)
+ x = min_low-1
+ while x in upper and x > min_up:
+ x -= 1
+ if x > min_up + 1:
+ if v.discard_upper(set(range(min_up, x)),
+ dstore=dstore, constraint=(verbosity>1
or v in tracevar) and self):
+ changed.add(v)
+ return Constraint.sleeping, changed
+
+ return Constraint.sleeping, changed
+
+class SupersetIntersection(Constraint):
+ """Set var S1 is superset of intersection of set vars S2 and S3."""
+
+ def __init__(self, variables, problem=None, weight=1):
+ Constraint.__init__(self, variables, problem=problem,
+ weight=weight)
+ self.name = '{0} >= {1} ^ {2}'.format(self.variables[0],
self.variables[1], self.variables[2])
+
+ def fails(self, dstore=None):
+ """Is the intersection of the lower bounds of S2 and S3 not a
subset of
+ the upper bound of S1?"""
+ s1 = self.variables[0]
+ s2 = self.variables[1]
+ s3 = self.variables[2]
+ s2_inters_s3 = s2.get_lower(dstore=dstore) &
s3.get_lower(dstore=dstore)
+ if not s2_inters_s3 <= s1.get_upper(dstore=dstore):
+ return True
+ # Fail on cardinalities
+ if s1.get_upper_card(dstore=dstore) < len(s2_inters_s3):
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Is the intersection of the upper bounds of S2 and S3 already a
subset of
+ the lower bound of S1?"""
+ s1 = self.variables[0]
+ s2 = self.variables[1]
+ s3 = self.variables[2]
+ if s2.get_upper(dstore=dstore) & s3.get_upper(dstore=dstore) <=
s1.get_lower(dstore=dstore):
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ changed = set()
+ # Intersection of lower bound of S2 and S3 is subset of lower
bound of S1.
+ s1 = self.variables[0]
+ s2 = self.variables[1]
+ s3 = self.variables[2]
+ if s1.strengthen_lower(s2.get_lower(dstore=dstore) &
s3.get_lower(dstore=dstore),
+ dstore=dstore, constraint=(verbosity>1 or
s1 in tracevar) and self):
+ changed.add(s1)
+ # Upper bound of S2 and S3 excludes elements which are in the
lower bounds of S3 and S2, respectively,
+ # but not in the upper bound of S1.
+ s1_up = s1.get_upper(dstore=dstore)
+ s2_not_s1 = s2.get_lower(dstore=dstore) - s1_up
+ s3_not_s1 = s3.get_lower(dstore=dstore) - s1_up
+ for x in s3.get_upper(dstore=dstore).copy():
+ if x in s2_not_s1:
+ if s3.discard_upper(x, dstore=dstore,
constraint=(verbosity>1 or s3 in tracevar) and self):
+ changed.add(s3)
+ for x in s2.get_upper(dstore=dstore).copy():
+ if x in s3_not_s1:
+ if s2.discard_upper(x, dstore=dstore,
constraint=(verbosity>1 or s2 in tracevar) and self):
+ changed.add(s2)
+ # Inference based on cardinalities (from Müller, p. 104)
+ s2Us3_card = len(s2.get_upper(dstore=dstore) |
s3.get_upper(dstore=dstore))
+ s1_up_card = s1.get_upper_card(dstore=dstore)
+ s2_low_card = s2.get_lower_card(dstore=dstore)
+ s3_low_card = s3.get_lower_card(dstore=dstore)
+ if s1.strengthen_lower_card(s2_low_card + s3_low_card - s2Us3_card,
+ dstore=dstore, constraint=(verbosity>1
or s1 in tracevar) and self):
+ changed.add(s1)
+ if s2.strengthen_upper_card(s2Us3_card + s1_up_card - s3_low_card,
+ dstore=dstore, constraint=(verbosity>1
or s2 in tracevar) and self):
+ changed.add(s2)
+ if s3.strengthen_upper_card(s2Us3_card + s1_up_card - s2_low_card,
+ dstore=dstore, constraint=(verbosity>1
or s3 in tracevar) and self):
+ changed.add(s3)
+ if verbosity > 1 and changed:
+ print(' Variables {} changed'.format(changed))
+ return Constraint.sleeping, changed
+
+class SubsetUnion(Constraint):
+ """Set var S1 is subset of union of set vars S2 and S3."""
+
+ def __init__(self, variables, problem=None, propagate=True,
+ weight=1):
+ Constraint.__init__(self, variables, problem=problem,
propagate=propagate,
+ weight=weight)
+ self.name = '{0} c= {1} U {2}'.format(self.variables[0],
self.variables[1], self.variables[2])
+
+ def fails(self, dstore=None):
+ """Is the union of the upper bounds of S2 and S3 (the biggest it
can be)
+ not a superset of the lower bound of S1?"""
+ s1 = self.variables[0]
+ s2 = self.variables[1]
+ s3 = self.variables[2]
+ s2_union_s3 = s2.get_upper(dstore=dstore) |
s3.get_upper(dstore=dstore)
+ if not s2_union_s3 >= s1.get_lower(dstore=dstore):
+ return True
+ # Fail on cardinalities
+ if s1.get_lower_card(dstore=dstore) > len(s2_union_s3):
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Is the union of the lower bounds of S2 and S3 already a
superset of
+ the upper bound of S1?"""
+ s1 = self.variables[0]
+ s2 = self.variables[1]
+ s3 = self.variables[2]
+ if s2.get_lower(dstore=dstore) | s3.get_lower(dstore=dstore) >=
s1.get_upper(dstore=dstore):
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ changed = set()
+ # S1 must be a subset of the union of the upper bounds of S2 and S3
+ s1 = self.variables[0]
+ s2 = self.variables[1]
+ s3 = self.variables[2]
+ if s1.strengthen_upper(s2.get_upper(dstore=dstore) |
s3.get_upper(dstore=dstore),
+ dstore=dstore, constraint=(verbosity>1 or
s1 in tracevar) and self):
+ changed.add(s1)
+ # S2's and S3's lower bounds must contain elements that are in the
lower bound of S1 but not
+ # S3 and S2, respectively (note: Müller has *lower* bounds of S3
and S2 (Eq. 11.17, p. 105),
+ # but this seems too strong).
+ s1_not_s2 = s1.get_lower(dstore=dstore) -
s2.get_upper(dstore=dstore)
+ s1_not_s3 = s1.get_lower(dstore=dstore) -
s3.get_upper(dstore=dstore)
+ if s3.strengthen_lower(s1_not_s2, dstore=dstore,
constraint=(verbosity>1 or s3 in tracevar) and self):
+ changed.add(s3)
+ if s2.strengthen_lower(s1_not_s3, dstore=dstore,
constraint=(verbosity>1 or s2 in tracevar) and self):
+ changed.add(s2)
+ # Inference based on cardinalities (from Müller, p. 105, but
there's apparently
+ # a typo; in Eq. 11.19, n1 should be the upper, not the lower
bound of S1)
+ if s1.strengthen_upper_card(s2.get_upper_card(dstore=dstore) +
s3.get_upper_card(dstore=dstore),
+ dstore=dstore, constraint=(verbosity>1
or s1 in tracevar) and self):
+ changed.add(s1)
+ if s2.strengthen_lower_card(s1.get_lower_card(dstore=dstore) -
s3.get_lower_card(dstore=dstore),
+ dstore=dstore, constraint=(verbosity>1
or s2 in tracevar) and self):
+ changed.add(s2)
+ if s3.strengthen_lower_card(s1.get_lower_card(dstore=dstore) -
s2.get_lower_card(dstore=dstore),
+ dstore=dstore, constraint=(verbosity>1
or s3 in tracevar) and self):
+ changed.add(s3)
+ if verbosity > 1 and changed:
+ print(' Variables {} changed'.format(changed))
+ return Constraint.sleeping, changed
+
+class CardinalitySubset(Constraint):
+ """Cardinality of set variable 1 is within set variable 2. This
constraint is not included
+ in Müller, but it is needed for XDG valency.
+ It could be handled with IVMemberSV."""
+
+ def __init__(self, variables, problem=None, weight=1):
+ Constraint.__init__(self, variables, problem=problem,
+ weight=weight)
+ self.name = '|{0}| c= {1}'.format(self.get_sv1(), self.get_sv2())
+
+ def get_sv1(self):
+ return self.variables[0]
+
+ def get_sv2(self):
+ return self.variables[1]
+
+ def fails(self, dstore=None):
+ """Fail if minimum cardinality of SV1 is greater than maximum
possible value of SV2
+ or if maximum cardinality of SV1 is less than the minimum possible
value of SV2.
+ Fixed 2011.12.09: minimum possible value of SV2 is minimum of
*upper* bound, not
+ lower bound."""
+ sv1 = self.get_sv1()
+ sv2 = self.get_sv2()
+ upper2 = sv2.get_upper(dstore=dstore)
+ max2card = max(upper2) if upper2 else 0
+ if sv1.get_lower_card(dstore=dstore) > max2card:
+ return True
+# lower2 = sv2.get_lower(dstore=dstore)
+ min2card = min(upper2) if upper2 else 0
+ # min(lower2) if lower2 else 0
+ if sv1.get_upper_card(dstore=dstore) < min2card:
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Entailed if cardinality of SV1 determined, SV2 determined, and
the former is in the latter."""
+ sv1 = self.get_sv1()
+ sv2 = self.get_sv2()
+ if sv2.determined(dstore=dstore, constraint=self) is not False and
\
+ sv1.get_lower_card(dstore=dstore) ==
sv1.get_upper_card(dstore=dstore) in sv2.get_value(dstore=dstore):
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ changed = set()
+ state = Constraint.sleeping
+ sv1 = self.get_sv1()
+ sv2 = self.get_sv2()
+ sv1_low_card = sv1.get_lower_card(dstore=dstore)
+ sv1_up_card = sv1.get_upper_card(dstore=dstore)
+# if tracevar in self.variables:
+# print(self, 'INFERRING')
+ # If sv1's cardinality is determined, then it must be in sv2
+ if sv1_low_card == sv1_up_card:
+# print('SV1 {} has same upper and lower card {}'.format(sv1,
sv1_low_card))
+ if sv2.strengthen_lower({sv1_low_card}, dstore=dstore,
+ constraint=(verbosity>1 or sv2 in
tracevar) and self):
+# constraint=self):
+# if sv2.determine({sv1_low_card}, dstore=dstore,
+# constraint=(verbosity>1 or sv2 in tracevar)
and self):
+ changed.add(sv2)
+ return state, changed
+
+# if tracevar in self.variables:
+# print(self, 'GOT TO 0')
+
+ sv2_upper = sv2.get_upper(dstore=dstore)
+# sv2_lower = sv2.get_lower(dstore=dstore)
+
+ # Minimum value of sv2 constrains sv1's lower card
+ # Fixed 2011.12.09: minimum value of sv2 is min of *upper* bound,
not lower
+ if sv2_upper:
+ # Could be empty set, in which case no strengthening is
possible
+ if sv1.strengthen_lower_card(min(sv2_upper), dstore=dstore,
+ constraint=(verbosity>1 or sv1 in
tracevar) and self):
+ changed.add(sv1)
+ return state, changed
+
+# if tracevar in self.variables:
+# print(self, 'GOT TO 1')
+ # Maximum value of sv2's upper bound constrains sv1's upper card
+ upcard = max(sv2_upper) if sv2_upper else 0
+ if sv1.strengthen_upper_card(upcard, dstore=dstore,
constraint=(verbosity>1 or sv1 in tracevar) and self):
+ changed.add(sv1)
+ return state, changed
+# if tracevar in self.variables:
+# print(self, 'GOT TO 2')
+
+ if verbosity > 1 and changed:
+ print(' Variables {} changed'.format(changed))
+ return state, changed
+
+class SetPrecedence(Constraint):
+ """All elements of set variable 1 must precede all elements of set
variable 2."""
+
+ def __init__(self, variables, problem=None, weight=1):
+ Constraint.__init__(self, variables, problem=problem,
+ weight=weight)
+ self.name = '{0} << {1}'.format(self.variables[0],
self.variables[1])
+
+ # Also used in PrecedenceSelection
+
+ @staticmethod
+ def must_precede(svar1, svar2, dstore=None):
+ """Is the highest value that can occur in svar1 < the lowest value
that can occur in svar2?"""
+ v1_upper = svar1.get_upper(dstore=dstore)
+ v2_upper = svar2.get_upper(dstore=dstore)
+ return v1_upper and v2_upper and (max(v1_upper) < min(v2_upper))
+
+ @staticmethod
+ def cant_precede(var1, var2, dstore=None):
+ """Is the highest value that must occur in var1 >= the lowest
value that must occur in var2?"""
+ # Lower
+ if isinstance(var1, IVar):
+ v1 = min(var1.get_upper(dstore=dstore))
+ elif not var1.get_lower(dstore=dstore):
+ return False
+ else:
+ v1 = max(var1.get_lower(dstore=dstore))
+ # Upper
+ if isinstance(var2, IVar):
+ v2 = max(var2.get_upper(dstore=dstore))
+ elif not var2.get_lower(dstore=dstore):
+ return False
+ else:
+ v2 = min(var2.get_lower(dstore=dstore))
+ return v1 >= v2
+# return v1_lower and v2_lower and (max(v1_lower) >= min(v2_lower))
+
+ def fails(self, dstore=None):
+ """Fail if any of set1's lower bound > any of set2's lower
bound."""
+ return SetPrecedence.cant_precede(self.variables[0],
self.variables[1], dstore=dstore)
+
+ def is_entailed(self, dstore=None):
+ """Entailed if everything that can be in set1 precedes anything
that can be in set2."""
+ return SetPrecedence.must_precede(self.variables[0],
self.variables[1], dstore=dstore)
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ changed = set()
+ v1 = self.variables[0]
+ v1_low = v1.get_lower(dstore=dstore)
+ v2 = self.variables[1]
+ v2_low = v2.get_lower(dstore=dstore)
+ # If the lower bound on v1 is not empty, v2 must be a subset of
+ # {min(MAX, max(v1 + 1)), ..., MAX}
+ if v1_low:
+ v2_up_new = range(min([v1.max, max(v1_low) + 1]), v2.max+1)
+ if v2.strengthen_upper(v2_up_new, dstore=dstore,
+ constraint=(verbosity>1 or v2 in
tracevar) and self):
+ changed.add(v2)
+ # If the lower bound on v2 is not empty, v1 must be a subset of
+ # {0, ..., max(0, min(v2_low) - 1)}
+ if v2_low:
+ v1_up_new = range(0, max([0, min(v2_low) - 1]) + 1)
+ if v1.strengthen_upper(v1_up_new, dstore=dstore,
+ constraint=(verbosity>1 or v1 in
tracevar) and self):
+ changed.add(v1)
+
+# Integer domain and set domain variables
+
+class IVMemberSV(Constraint):
+ """Integer variable value must be member of set variable value."""
+
+ def __init__(self, variables, problem=None, propagate=True,
+ weight=1):
+ Constraint.__init__(self, variables, problem=problem,
propagate=propagate,
+ weight=weight)
+ self.name = '{0} c {1}'.format(self.get_iv(), self.get_sv())
+
+ def get_iv(self):
+ """The domain variable."""
+ return self.variables[0]
+
+ def get_sv(self):
+ """The set variable."""
+ return self.variables[1]
+
+ def fails(self, dstore=None):
+ """Fail if none of the IV values are in SV upper bound."""
+ iv = self.get_iv()
+ sv = self.get_sv()
+ iv_dom = iv.get_domain(dstore=dstore)
+ sv_up = sv.get_upper(dstore=dstore)
+ if len(iv_dom & sv_up) == 0:
+ return True
+ return False
+
+ def is_entailed(self, dstore=None):
+ """Entailed if IV values are subset of SV lower bound."""
+ iv = self.get_iv()
+ sv = self.get_sv()
+ iv_dom = iv.get_domain(dstore=dstore)
+ sv_low = sv.get_lower(dstore=dstore)
+# if self.pattern:
+# # For patterns, the propagator is entailed if every element
in the domain of iv
+# # unifies with the lower bound of sv
+# if all([unify_fssets({tup}, sv_low) for tup in iv_dom]):
+# return True
+ if iv_dom <= sv_low:
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ changed = set()
+ iv = self.get_iv()
+ sv = self.get_sv()
+ # Constrain the values of IV to be within upper bound of SV
+ if iv.strengthen(sv.get_upper(dstore=dstore), dstore=dstore,
+ constraint=(verbosity>1 or iv in tracevar) and
self):
+ changed.add(iv)
+ # If IV is determined, constrain SV to include it
+ if iv.determined(dstore=dstore, verbosity=verbosity) is not False:
+ if sv.strengthen_lower(iv.get_domain(dstore=dstore),
dstore=dstore,
+ constraint=(verbosity>1 or sv in
tracevar) and self):
+ changed.add(sv)
+ if verbosity > 1 and changed:
+ print(' Variables {} changed'.format(changed))
+ return Constraint.sleeping, changed
+
+# Selection constraint propagators
+
+class Selection(Constraint):
+ """Superclass for most selection constraints.
+
+ mainvar: set domain var or int domain var (set var for primitive
propagators)
+ seqvars: set domain vars, int domain vars, constant sets, or constant
ints
+ (set var for primitive propagators)
+ selvar: set domain var or int domain var (set var for primitive
propagators)
+ """
+
+ def __init__(self, mainvar=None, selvar=None, seqvars=None,
+ problem=None, weight=1):
+ Constraint.__init__(self, [mainvar, selvar] + seqvars,
problem=problem,
+ weight=weight)
+ self.selvar = selvar
+ self.mainvar = mainvar
+ self.seqvars = seqvars
+
+ def is_entailed(self, dstore=None):
+ """Entailed only if all vars are determined.
+ """
+ if self.mainvar.determined(dstore=dstore, constraint=self) is not
False \
+ and self.selvar.determined(dstore=dstore, constraint=self) is
not False \
+ and all([v.determined(dstore=dstore, constraint=self) is not
False for v in self.seqvars]):
+ return True
+ return False
+
+ def infer(self, dstore=None, verbosity=0, tracevar=None):
+ """Some rules are common to all Selection subclasses."""
+
+ changed = set()
+ state = Constraint.sleeping
+ seqvars = self.seqvars
+ selvar = self.selvar
+ mainvar = self.mainvar
+
+ # If there is only one seqvar, then the main var is constrained to
be that value
+ # and the selection var has to be {0} or 0
+ if len(seqvars) == 1:
+ # since there's only one seq var to select, the selection
variable has to
+ # be {0} or 0
+ if selvar.determine(0, dstore=dstore,
+ constraint=(verbosity>1 or selvar in
tracevar) and self):
+ changed.add(selvar)
+ seqvar = seqvars[0]
+ if seqvar.determined(dstore=dstore, verbosity=verbosity,
constraint=self) is not False:
+ if mainvar.determine(seqvar.get_value(dstore=dstore),
dstore=dstore,
+ constraint=(verbosity>1 or mainvar in
tracevar) and self):
+ changed.add(mainvar)
+ state = Constraint.entailed
+ else:
+ if
mainvar.strengthen_lower(seqvar.get_lower(dstore=dstore), dstore=dstore,
+ constraint=(verbosity>1 or
mainvar in tracevar) and self):
+ changed.add(mainvar)
+ if
mainvar.strengthen_upper(seqvar.get_upper(dstore=dstore), dstore=dstore,
+ constraint=(verbosity>1 or
mainvar in tracevar) and self):
+ changed.add(mainvar)
+## if mainvar.determined(dstore=dstore,
verbosity=verbosity) is not False:
+## state = Constraint.entailed
+ if changed:
+ if verbosity > 1:
+ print(' Variables {} changed'.format(changed))
+ return state, changed
+ # If all of the seqvars are equal to one another and determined
and the selection variable must
+ # be non-empty, then the main var can be determined (as long as
the seqvar value is in its domain)
+ if all([v.determined(dstore=dstore, verbosity=verbosity,
constraint=self) is not False for v in seqvars]) and \
+ selvar.get_lower_card(dstore=dstore) > 0 and
seqvars[0].all_equal(seqvars[1:], dstore=dstore):
+ seq0_val = seqvars[0].get_value(dstore=dstore)
+ if mainvar.determine(seq0_val, dstore=dstore,
constraint=(verbosity>1 or mainvar in tracevar) and self):
+ changed.add(mainvar)
+ state = Constraint.entailed
+ if verbosity > 1 and changed:
+ print(' Variables {} changed'.format(changed))
+ return state, changed
+ # If the upper bound of selvar includes values that are greater
than the length of selvars,
+ # then those values can be eliminated from the upper bound.
+# selupper = selvar.get_upper(dstore=dstore)
+# n_seqvars = len(seqvars)
+# to_remove = {index for index in selupper if index >= n_seqvars}
+# if to_remove:
+# if selvar.discard_upper(to_remove, dstore=dstore,
constraint=(verbosity>1 or mainvar in tracevar) and self):
+# changed.add(selvar)
+# if changed:
+# if verbosity > 1:
+# print(' Variables {} changed'.format(changed))
+# return state, changed
+
+ return False
+
+ @staticmethod
+ def format_seq(seq):
+ string = '< '
+ for i, elem in enumerate(seq):
+ if i != 0:
+ string += ', '
+ if elem == set():
+ string += '{}'
+ else:
+ string += elem.__repr__()
+ return string + ' >'
+
+ @staticmethod
+ def format_list(seq):
+ string = '['
+ for i, elem in enumerate(seq):
+ if i != 0:
+ string += ', '
+ if elem == set():
+ string += '{}'
+ else:
+ string += elem.__repr__()
+ return string + ']'
+
+class IntSelection(Selection):
+ """Selection constraint with integer variable as selection variable.
+ Müller treats this as derived from UnionSelection, but it is more
efficient
+ to treat it as a primitive, at least in this program.
+ """
+
+ def __init__(self, mainvar=None, selvar=None, seqvars=None,
problem=None, weight=1,
+ maxset=None):
+ Selection.__init__(self, mainvar=mainvar, selvar=selvar,
seqvars=seqvars,
+ problem=problem, weight=weight)
+ self.maxset = maxset or ALL
+ self.name = '{0} = {1} [{2}]'.format(self.mainvar,
self.format_seq(self.seqvars), self.selvar)
+
+ def fails(self, dstore=None):
+ """Fail if the domain of sel var includes only indices that are
beyond the
+ length of seq vars, of if the domain of sel var is empty."""
+ selvar = self.selvar
+ sel_domain = selvar.get_domain(dstore=dstore)
***The diff for this file has been truncated for email.***
=======================================
--- /l3lite/__init__.py Thu Mar 27 17:19:15 2014 UTC
+++ /l3lite/__init__.py Fri Apr 4 06:26:55 2014 UTC
@@ -1,5 +1,5 @@
"""Do-it-yourself L3. Create simple bilingual lexicons and grammars for
language pairs."""

-__all__ = ['language', 'entry', 'ui', 'variable']
+__all__ = ['language', 'entry', 'ui', 'constraint', 'variable']

from .ui import *
=======================================
--- /l3lite/language.py Thu Mar 27 17:19:15 2014 UTC
+++ /l3lite/language.py Fri Apr 4 06:26:55 2014 UTC
@@ -35,7 +35,7 @@
# Lexemes start with %, classes with $.

from .entry import *
-from .variable import *
+from .constraint import *

import os, yaml

=======================================
--- /l3lite/variable.py Thu Mar 27 17:19:15 2014 UTC
+++ /l3lite/variable.py Fri Apr 4 06:26:55 2014 UTC
@@ -75,12 +75,14 @@
lower_domain=None, upper_domain=None,
lower_card=0, upper_card=MAX,
problem=None, dstores=None, rootDS=None,
+ constraint=None,
# Vars with low weights are "peripheral".
weight=1):
self.name = name
self.problem = problem
if problem:
self.problem.add_variable(self)
+ self.constraints = [constraint] if constraint else []
self.value = None
# Normally initialize with a top-level domain store
self.rootDS = rootDS or DS0
@@ -156,6 +158,25 @@
"""Is the variable already determined?"""
return self.get_value(dstore=dstore) is not None

+ def all_equal(self, variables, dstore=None):
+ """Do all of these variables have the same value as this in
dstore?"""
+ return all([self.equals(var, dstore=dstore) for var in variables])
+
+ def equals(self, var, dstore=None):
+ """Does this variable have the same value as var in dstore?
+ """
+ value = self.get_value(dstore=dstore)
+ if value != None:
+ var_val = var.get_value(dstore=dstore)
+ if var_val == None:
+ return False
+ if var.get_lower_card(dstore=dstore) > 1:
+ return False
+# var_val = list(var_val)[0] if var_val else ()
+ if value == var_val:
+ return True
+ return False
+
## How constraints on a variable can fail

def bound_fail(self, dstore=None):
@@ -425,15 +446,16 @@
s += '{}'.format(v)
return s + '}'

+ def pretty_string(self, dstore=None, spaces=0, end='\n'):
+ return '{0}${1}:{2}|{3},{4}|'.format(spaces*' ',
+ self.name,
+
Var.string_range(self.get_lower(dstore=dstore),
+
self.get_upper(dstore=dstore)),
+
self.get_lower_card(dstore=dstore),
+
self.get_upper_card(dstore=dstore))
+
def pprint(self, dstore=None, spaces=0, end='\n'):
- string = '{0}${1}:{2}|{3},{4}|'.format(spaces*' ',
- self.name,
-
Var.string_range(self.get_lower(dstore=dstore),
-
self.get_upper(dstore=dstore)),
-
self.get_lower_card(dstore=dstore),
-
self.get_upper_card(dstore=dstore))
- print(string)
- #, end=end)
+ print(self.pretty_string(dstore=dstore, spaces=spaces, end=end))

class IVar(Var):

@@ -449,11 +471,136 @@
def __repr__(self):
return '#{}'.format(self.name)

- def pprint(self, dstore=None, spaces=0, end='\n'):
- string = '{0}${1}:{2}'.format(spaces*' ',
- self.name,
- self.get_upper(dstore=dstore))
- print(string)
+ def equals(self, var, dstore=None, pattern=False):
+ """Does this variable have the same value as var in dstore?
+ var could be an IVar."""
+ value = self.get_value(dstore=dstore)
+ var_val = var.get_value(dstore=dstore)
+ is_ivar = isinstance(var, IVar)
+ if value != None and var_val != None:
+# if is_ivar:
+# print('var {}, value {}'.format(var, var_val))
+# var_val = {var_val} if var_val else set()
+ if value == var_val:
+ return True
+ return False
+
+ def determined(self, dstore=None, constraint=None, verbosity=0):
+ """Attempt to determine the variable, returning the value if this
is possible,
+ False if it's not."""
+ val = self.get_value(dstore=dstore)
+ if val != None:
+ return val
+ upper = self.get_upper(dstore=dstore)
+ if len(upper) == 1:
+ self.set_value(upper, dstore=dstore)
+ self.set_lower(upper, dstore=dstore)
+ if verbosity > 1:
+ print(' {} is determined at {}'.format(self, upper))
+ if dstore:
+ dstore.undetermined.remove(self)
+ return upper
+ return False
+
+ def pretty_string(self, dstore=None, spaces=0, end='\n'):
+ return '{0}#{1}:{2}'.format(spaces*' ',
+ self.name,
+ self.get_upper(dstore=dstore))
+
+### Variables that are pre-determined.
+
+class Determined(Var):
+ """Pre-determined variable. If DStore is not specified in constructor,
+ the variable is determined in all DStores. Should not be modified."""
+
+ def __init__(self, name, value, dstore=None):
+ Var.__init__(self, name, rootDS=dstore)
+ self.dstore = dstore
+ if self.dstore:
+ self.determine(value, dstore=dstore)
+ else:
+ self.value = value
+ self.lower_domain = value
+ self.upper_domain = value
+ self.set_cards(value)
+
+ def __repr__(self):
+ return '$!{}:{}'.format(self.name, self.value)
+
+ def set_cards(self, value):
+ self.init_upper_card = len(value)
+ self.init_lower_card = len(value)
+
+ def init_values(self, dstore=None):
+ # Don't do anything
+ pass
+
+ def set(self, dstore, feature, value):
+ """Override set in Variable to prevent changes."""
+ # This message should print out under some verbosity conditions.
+ s = 'Warning: attempting to change pre-determined variable {},
feature: {}, value: {}, orig value: {}'
+ print(s.format(self, feature, value, self.get(dstore, feature)))
+ return False
+
+ def is_determined(self, dstore=None):
+ return True
+
+ def pretty_string(self, dstore=None, spaces=0, end='\n'):
+ return '{0}$!{1}:{2}'.format(spaces*' ', self.name,
self.get(dstore, 'value'))
+
+ def cost(self, dstore=None):
+ return 0
+
+ def determined(self, dstore=None, verbosity=0, constraint=None):
+ if self.dstore:
+ return Var.determined(self, dstore=dstore,
verbosity=verbosity, constraint=constraint)
+ return self.value
+
+ def get(self, dstore, feature, default=None):
+ if self.dstore:
+ return Var.get(self, dstore, feature, default=default)
+ if feature in {'value', 'lower', 'upper'}:
+ return self.value
+ if feature in {'lower_card', 'upper_card'}:
+ return len(self.value)
+
+class DetIVar(Determined, IVar):
+
+ def __init__(self, name='', value=0, dstore=None):
+ IVar.__init__(self, name, rootDS=dstore)
+ # value could be the empty set
+ if not isinstance(value, set):
+ value = {value}
+ Determined.__init__(self, name, value, dstore)
+ self.init_domain = value
+ self.default_value = value
+
+ def __repr__(self):
+ return '#!{}:{}'.format(self.name, list(self.value)[0])
+
+ def init_values(self, dstore=None):
+ # Don't do anything
+ pass
+
+ def set_cards(self, value):
+ self.init_upper_card = 1
+ self.init_lower_card = 1
+
+ def pretty_string(self, dstore=None, spaces=0, end='\n'):
+ return '{0}#!{1}:{2}'.format(spaces*' ', self.name,
self.get(dstore, 'value'))
+
+ def get(self, dstore, feature, default=None):
+ if self.dstore:
+ return IVar.get(self, dstore, feature, default=default)
+ if feature == 'value':
+ return self.value
+ if feature in ('dom', 'upper', 'lower'):
+ if isinstance(self.value, set):
+ return self.value
+ else:
+ return {self.value}
+ if feature in ('lower_card', 'upper_card'):
+ return 1

class VarError(Exception):
'''Class for errors encountered when attempting to execute an event on
a variable.'''
=======================================
--- /lite.py Thu Mar 27 17:19:15 2014 UTC
+++ /lite.py Fri Apr 4 06:26:55 2014 UTC
@@ -109,6 +109,130 @@
e, s = l3lite.Language("English", 'eng'),
l3lite.Language("español", 'spa')
return u, e, s

+def gtest():
+ # broke the window
+ g0_nodes = {0, 1, 2}
+ # the boy
+ g1_nodes = {3, 4}
+ # the window
+ g2_nodes = {5, 6}
+ # broke
+ g3_nodes = {7}
+ # .
+ g4_nodes = {8}
+ # the window broke
+ g5_nodes = {9, 10, 11}
+ # the boy broke the window .
+ w = {0, 1, 2, 3, 4, 5}
+ g = {0, 1, 2, 3, 4, 5}
+ wvars = [l3lite.IVar('the0', {1, 3, 5, 9}), # {3}
+ l3lite.IVar('boy', {4}),
+ l3lite.IVar('broke', {0, 7, 11}), # {0, 7}
+ l3lite.IVar('the3', {1, 3, 5, 9}), # {1, 5}
+ l3lite.IVar('window', {2, 6, 10}), # {2, 6}
+ l3lite.IVar('.', {8})]
+ groups = l3lite.Var('groups', set(), g, 2, 6)
+ gdetvars = [l3lite.Determined('g0', {0, 1, 2}),
+ l3lite.Determined('g1', {3, 4}),
+ l3lite.Determined('g2', {5, 6}),
+ l3lite.Determined('g3', {7}),
+ l3lite.Determined('g4', {8}),
+ l3lite.Determined('g5', {9, 10, 11})]
+ nodes = l3lite.Determined('nodes', w)
+ ## Union selection on the gnodes of all words
+ # Union of all group nodes used
+ gnodeU = l3lite.Var('gnodeU', set(),
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, 6, 6)
+ w_usel = l3lite.UnionSelection(gnodeU, nodes, wvars)
+ ## Union selection on the gnodes of all groups
+ g_usel = l3lite.UnionSelection(gnodeU, groups, gdetvars)
+ ## Position constraints
+ # Positions (word indices) of gnodes
+ # Group 0
+ gno0 = l3lite.DetIVar('gno0', 2) #broke
+ gno1 = l3lite.IVar('gno1', {0, 3}) #the {3}
+ gno2 = l3lite.DetIVar('gno2', 4) #window
+ # Group 1
+ gno3 = l3lite.IVar('gno3', {0, 3}) #the {0}
+ gno4 = l3lite.DetIVar('gno4', 1) #boy
+ # Group 2
+ gno5 = l3lite.IVar('gno5', {0, 3}) #the {3}
+ gno6 = l3lite.DetIVar('gno6', 4) #window
+ # Group 3
+ gno7 = l3lite.DetIVar('gno7', 2) #broke
+ # Group 4
+ gno8 = l3lite.DetIVar('gno8', 5) #.
+ # Group 5 (impossible)
+ gno9 = l3lite.IVar('gno9', {0, 3}) #the
+ gno10 = l3lite.DetIVar('gno10', 4) #window
+ gno11 = l3lite.DetIVar('gno11', 2) #broke
+ # Position pair constraints
+ gposcons = l3lite.Var('gposcons', set(),
+ {(0, 1), (1, 2), (0, 2), (3, 4), (5, 6),
+ (9, 10), (10, 11), (9, 11)})
+ # Precedence selection
+ precsel = l3lite.PrecedenceSelection(gposcons,
+ [gno0, gno1, gno2, gno3, gno4,
gno5,
+ gno6, gno7, gno8, gno9, gno10,
gno11])
+ # Union selection on position pair constraints
+ gp0 = l3lite.Determined('gp0', {(0, 1), (1, 2), (0, 2)})
+ gp1 = l3lite.Determined('gp1', {(3, 4)})
+ gp2 = l3lite.Determined('gp2', {(5, 6)})
+ gp3 = l3lite.Determined('gp3', set())
+ gp4 = l3lite.Determined('gp4', set())
+ gp5 = l3lite.Determined('gp5', {(9, 10), (10, 11), (9, 11)})
+ gpos = [gp0, gp1, gp2, gp3, gp4, gp5]
+ gp_usel = l3lite.UnionSelection(gposcons, groups, gpos)
+ ## Projectivity
+ # Positions of gnodes in each group
+ g0snode = l3lite.Var('g0snode', set(), {0, 1, 2, 3, 4}, 3, 3) # {2,
3, 4}
+ g1snode = l3lite.Var('g1snode', set(), {0, 1, 2, 3, 4}, 2, 2) # {0, 1}
+ g2snode = l3lite.Var('g2snode', set(), {0, 1, 2, 3, 4}, 2, 2) # {3, 4}
+ g3snode = l3lite.DetIVar('g3snode', 2) # {2}
+ g4snode = l3lite.DetIVar('g4snode', 5) # {5}
+ g5snode = l3lite.Var('g5snode', set(), {0, 1, 2, 3, 4}, 3, 3) #
impossible
+## # Constrain gnode positions by individual gnode variables
+## gsnodeU0 = l3lite.UnionSelection(g0snode,
l3lite.Determined('gnpossel0', {0, 1, 2}),
+## [gno0, gno1, gno2])
+## gsnodeU1 = l3lite.UnionSelection(g1snode,
l3lite.Determined('gnpossel1', {0, 1}),
+## [gno3, gno4])
+## gsnodeU2 = l3lite.UnionSelection(g2snode,
l3lite.Determined('gnpossel2', {0, 1}),
+## [gno5, gno6])
+### gsnodeU5 = l3lite.UnionSelection(g5snode,
l3lite.Determined('gnpossel5', {0, 1, 2}),
+### [gno9, gno10, gno11])
+## # Set convexity constraint for each of these
+## setcon0 = l3lite.SetConvexity(g0snode)
+## setcon1 = l3lite.SetConvexity(g1snode)
+## setcon2 = l3lite.SetConvexity(g2snode)
+### setcon5 = l3lite.SetConvexity(g5snode)
+ setconv = l3lite.ComplexSetConvexity(groups, [g0snode, g1snode,
g2snode,
+ g3snode, g4snode,
g5snode])
+### return nodes, groups, wvars, gdetvars, usel
+ ## Union selection on individual groups: snodes -> gnodes
+ ## The union of possible gnodes associated with the snodes of a group
is a *superset*
+ ## of the known gnodes for that group
+ s2g_sup0 = l3lite.Var('s2g_sup0', {0, 1, 2}, {0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11})
+ s2g_sup1 = l3lite.Var('s2g_sup1', {3, 4}, {0, 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11})
+ s2g_sup2 = l3lite.Var('s2g_sup2', {5, 6}, {0, 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11})
+# s2g_sup5 =
+ s2gU0 = l3lite.UnionSelection(s2g_sup0, g0snode, wvars)
+ s2gU1 = l3lite.UnionSelection(s2g_sup1, g1snode, wvars)
+ s2gU2 = l3lite.UnionSelection(s2g_sup2, g2snode, wvars)
+# s2gU5 = l3lite.UnionSelection(gdetvars[5], g5snode, wvars)
+ return w_usel, g_usel, precsel, gp_usel, setconv, s2gU0, s2gU1 #, s2gU2
+# setcon0, setcon1, setcon2, \
+# gsnodeU0, gsnodeU1, gsnodeU2, s2gU0, s2gU1, s2gU2
+#, s2gU5, setcon5, gsnodeU5
+
+def run_constraints(constraints, times=10):
+ for t in range(times):
+ print("\nITERATION {}".format(t))
+ for c in constraints:
+ state, changed = c.run()
+ if changed:
+ print("Running {}".format(c))
+ print(" Changed:
{}".format(list(changed)[0].pretty_string()))
+
if __name__ == "__main__":
print('L^3 Lite, version {}\n'.format(__version__))

Reply all
Reply to author
Forward
0 new messages