def update_semantics(self, semantics): parent = None node, index = semantics, self.index while index > 0: if not isinstance(node, l.LambdaExpression): raise ValueError( "semantics provided do not have sufficient arity to extract argument at position %i" % self.index) parent = node node = node.term # Convert target variable to a uniquely named function variable. target_variable = node.variable new_target_var = make_unique_variable(node.term, "function") # Create an application expression applying the raised target var to some # extracted argument. extracted_arg = make_unique_variable(semantics, allow_clash=[target_variable]) application_expr = l.ApplicationExpression( l.FunctionVariableExpression(new_target_var), l.IndividualVariableExpression(extracted_arg)) node.term = node.term.replace(target_variable, application_expr) node.variable = new_target_var if parent is not None: parent.term = node return l.LambdaExpression(extracted_arg, semantics)
def applyto(self, arg): """ self = (\\x.(walk x), (subj -o f)) arg = (john , subj) returns ((walk john), f) """ if self.indices.intersection( arg.indices): # if the sets are NOT disjoint raise linearlogic.LinearLogicApplicationError, '%s applied to %s. Indices are not disjoint.' % ( self, arg) else: # if the sets ARE disjoint return_indices = self.indices.union(arg.indices) try: return_glue = linearlogic.ApplicationExpression( self.glue, arg.glue, arg.indices) except linearlogic.LinearLogicApplicationError: raise linearlogic.LinearLogicApplicationError, '%s applied to %s' % ( self, arg) arg_meaning_abstracted = arg.meaning if return_indices: for dep in self.glue.simplify( ).first.second.dependencies[:: -1]: # if self.glue is (A -o B), dep is in A.dependencies arg_meaning_abstracted = logic.LambdaExpression( logic.Variable('v%s' % dep), arg_meaning_abstracted) return_meaning = logic.ApplicationExpression(self.meaning, arg_meaning_abstracted) return self.__class__(return_meaning, return_glue, return_indices)
def lambda_abstract(self, other): assert isinstance(other, GlueFormula) assert isinstance(other.meaning, logic.VariableExpression) return self.__class__(logic.LambdaExpression(other.meaning.variable, self.meaning), \ linearlogic.ApplicationExpression( linearlogic.ApplicationExpression( linearlogic.Operator(linearlogic.Parser.IMPLIES), other.glue), self.glue ) )
def extract_lambda(expr): """ Extract `LambdaExpression` arguments to the top of a semantic form. This makes them compatible with the CCG parsing setup, which needs top-level lambdas in order to perform function application during parsing. """ variables = [] def process_lambda(lambda_expr): # Create a new unique variable and substitute. unique = l.unique_variable() unique.type = lambda_expr.variable.type new_expr = lambda_expr.term.replace( lambda_expr.variable, l.IndividualVariableExpression(unique)) return unique, new_expr # Traverse the LF and replace lambda expressions wherever necessary. def inner(node): if isinstance(node, l.ApplicationExpression): new_args = [] for arg in node.args: if isinstance(arg, l.LambdaExpression): new_var, new_arg = process_lambda(arg) variables.append(new_var) new_args.append(new_arg) else: new_args.append(inner(arg)) return make_application(node.pred.variable.name, new_args) else: return node expr = inner(expr) wrappings = [] for variable_ordering in itertools.permutations(variables): wrapping = expr for variable in variable_ordering: wrapping = l.LambdaExpression(variable, wrapping) wrappings.append(wrapping.normalize()) return wrappings
def _iter_expressions_inner(self, max_depth, bound_vars, type_request=None, function_weights=None): """ Enumerate all legal expressions. Arguments: max_depth: Maximum tree depth to traverse. bound_vars: Bound variables (and their types) in the parent context. The returned expressions may reference these variables. List of `(name, type)` tuples. type_request: Optional requested type of the expression. This helps greatly restrict the space of enumerations when the type system is strong. function_weights: Override for function weights to determine the order in which we consider proposing function application expressions. """ if max_depth == 0: return elif max_depth == 1 and not bound_vars: # require some bound variables to generate a valid lexical entry # semantics return for expr_type in self.EXPR_TYPES: if expr_type == l.ApplicationExpression: # Loop over functions according to their weights. # from pprint import pprint # pprint(sorted([(fn.weight, fn.name) for fn in self.functions], key=lambda x: x[0])) fn_weight_key = (lambda fn: function_weights[fn.name]) if function_weights is not None \ else (lambda fn: fn.weight) fns_sorted = sorted(self.functions_dict.values(), key=fn_weight_key, reverse=True) if max_depth > 1: for fn in fns_sorted: # If there is a present type request, only consider functions with # the correct return type. # print("\t" * (6 - max_depth), fn.name, fn.return_type, " // request: ", type_request, bound_vars) if type_request is not None and fn.return_type != type_request: continue # Special case: yield fast event queries without recursion. if fn.arity == 1 and fn.arg_types[ 0] == self.types.EVENT_TYPE: yield make_application( fn.name, (l.ConstantExpression(l.Variable("e")), )) elif fn.arity == 0: # 0-arity functions are represented in the logic as # `ConstantExpression`s. # print("\t" * (6 - max_depth + 1), "yielding const ", fn.name) yield l.ConstantExpression(l.Variable(fn.name)) else: # print("\t" * (6 - max_depth), fn, fn.arg_types) sub_args = [] for i, arg_type_request in enumerate(fn.arg_types): # print("\t" * (6 - max_depth + 1), "ARGUMENT %i %s (max_depth %i)" % (i, arg_type_request, max_depth - 1)) sub_args.append( self._iter_expressions_inner( max_depth=max_depth - 1, bound_vars=bound_vars, type_request=arg_type_request, function_weights=function_weights)) for arg_combs in itertools.product(*sub_args): candidate = make_application( fn.name, arg_combs) valid = self._valid_application_expr(candidate) # print("\t" * (6 - max_depth + 1), "valid %s? %s" % (candidate, valid)) if valid: yield candidate elif expr_type == l.LambdaExpression and max_depth > 1: for bound_var_type in self.observed_argument_types: bound_var = next_bound_var(bound_vars, bound_var_type) subexpr_bound_vars = bound_vars + (bound_var, ) subexpr_type_requests = [] if type_request is None: subexpr_type_requests = [None] else: # Build new type requests using the flat structure. type_request_flat = type_request.flat subexpr_type_requests.append(type_request_flat) # Basic case: the variable is used as one of the existing # arguments of a target subexpression. subexpr_type_requests.extend([ type_request_flat[:i] + type_request_flat[i + 1:] for i, type_i in enumerate(type_request_flat) if type_i == bound_var_type ]) # # The subexpression might take this variable as an additional # # argument in any position. We have to enumerate all possibilities # # -- yikes! # for insertion_point in range(len(type_request)): # subexpr_type_requests.append(type_request[:insertion_point] + (bound_var_type,) # + type_request[insertion_point:]) # print("\t" * (6-max_depth), "λ %s :: %s" % (bound_var, bound_var_type), type_request, subexpr_type_requests) # print("\t" * (6-max_depth), "Now recursing with max_depth=%i" % (max_depth - 1)) for subexpr_type_request in subexpr_type_requests: if isinstance(subexpr_type_request, tuple): if not subexpr_type_request: continue else: subexpr_type_request = self.types.make_function_type( subexpr_type_request) results = self._iter_expressions_inner( max_depth=max_depth - 1, bound_vars=subexpr_bound_vars, type_request=subexpr_type_request, function_weights=function_weights) for expr in results: candidate = l.LambdaExpression(bound_var, expr) valid = self._valid_lambda_expr( candidate, bound_vars) # print("\t" * (6 - max_depth), "valid lambda %s? %s" % (candidate, valid)) if self._valid_lambda_expr(candidate, bound_vars): # Assign variable types before returning. extra_types = { bound_var.name: bound_var.type for bound_var in subexpr_bound_vars } try: # TODO make sure variable names are unique before this happens self.typecheck(candidate, extra_types) except l.InconsistentTypeHierarchyException: pass else: yield candidate elif expr_type == l.IndividualVariableExpression: for bound_var in bound_vars: if type_request and not bound_var.type.matches( type_request): continue # print("\t" * (6-max_depth), "var %s" % bound_var) yield l.IndividualVariableExpression(bound_var) elif expr_type == l.ConstantExpression: for constant in self.constants: if type_request is not None and not constant.type.matches( type_request): continue yield l.ConstantExpression(constant) elif expr_type == l.FunctionVariableExpression: # NB we don't support enumerating bound variables with function types # right now -- the following only considers yielding fixed functions # from the ontology. for function in self.functions: # Be a little strict here to avoid excessive enumeration -- only # consider emitting functions when the type request specifically # demands a function, not e.g. AnyType if type_request is None or type_request == self.types.ANY_TYPE \ or not function.type.matches(type_request): continue yield l.FunctionVariableExpression( l.Variable(function.name, function.type))
def read_ec_sexpr(sexpr): """ Parse an EC-style S-expression into an untyped NLTK representation. """ tokens = re.split(r"([()\s])", sexpr) bound_vars = set() bound_var_stack = [] is_call = False stack = [(None, None, [])] for token in tokens: token = token.strip() if not token: continue if token == "(": if is_call: # Second consecutive left-paren -- this means we have a complex # function expression. stack.append((l.ApplicationExpression, None, [])) is_call = True elif token == "lambda": is_call = False variable = next_bound_var(bound_vars, l.ANY_TYPE) bound_vars.add(variable) bound_var_stack.append(variable) stack.append((l.LambdaExpression, None, [])) elif is_call: head = token if head.startswith("$"): bruijn_index = int(head[1:]) # Bound variable is the head of an application expression. # First replace with a function-looking variable, then update parser # state. var_idx = -1 - bruijn_index var = bound_var_stack[var_idx] bound_vars.remove(var) new_var = l.Variable(var.name.upper()) bound_var_stack[var_idx] = new_var bound_vars.add(new_var) head = l.FunctionVariableExpression(new_var) stack.append((l.ApplicationExpression, head, [])) is_call = False elif token == ")": stack_top = stack.pop() if stack_top[0] == l.ApplicationExpression: _, pred, args = stack_top result = make_application(pred, args) elif stack_top[0] == l.LambdaExpression: _, _, term = stack_top variable = bound_var_stack.pop() result = l.LambdaExpression(variable, term[0]) else: raise RuntimeError("unknown element on stack", stack_top) stack_parent = stack[-1] if stack_parent[ 0] == l.ApplicationExpression and stack_parent[1] is None: # We have just finished reading the head of an application expression. expr, _, args = stack_parent stack[-1] = (expr, result, args) else: # Add to children of parent node. stack_parent[2].append(result) elif token.startswith("$"): bruijn_index = int(token[1:]) stack[-1][2].append( l.IndividualVariableExpression(bound_var_stack[-1 - bruijn_index])) else: stack[-1][2].append(l.ConstantExpression(l.Variable(token))) assert len(stack) == 1 assert len(stack[0][2]) == 1 return stack[0][2][0], bound_vars