def unify_value(value1, value2, source_values): """ Unify two values that exist in the same context. :param value1: :param value2: :param source_values: :return: """ # Variables are negative numbers or None # Naive implementation (no occurs check) if is_variable(value1) and is_variable(value2): if value1 == value2: return value1 elif value1 is None: return value2 if value2 is None: # second one is anonymous return value1 else: # Two named variables: unify their source_values value = unify_value(source_values.get(value1), source_values.get(value2), source_values) if value is None: value = max(value1, value2) if value1 != value: source_values[value1] = value if value2 != value: source_values[value2] = value # Return the one of lowest rank: negative numbers, so max return value elif is_variable(value1): if value1 is None: return value2 else: if value1 in value2.variables(): raise OccursCheck() value = unify_value(source_values.get(value1), value2, source_values) source_values[value1] = value return value elif is_variable(value2): if value2 is None: return value1 else: if value2 in value1.variables(): raise OccursCheck() value = unify_value(source_values.get(value2), value1, source_values) source_values[value2] = value return value elif value1.signature == value2.signature: # Assume Term return value1.with_args(*[unify_value(a1, a2, source_values) for a1, a2 in zip(value1.args, value2.args)]) else: raise UnifyError()
def get_variable_types(self, *literals: Iterable[Term]) -> Dict[TypeName, Set[Term]]: """Get the types of all variables that occur in the given literals. :param literals: literals to extract variables from :type literals: collections.Iterable[Term] :return: dictionary with list of variables for each type :rtype: dict[str, list[Term]] """ result = defaultdict(set) for lit in literals: # type: Term if lit is None: pass else: if lit.is_negated(): lit = -lit if lit.functor == '_recursive': # Don't need to process this, variables will occur somewhere else # because _recursive has mode + on all arguments. continue argument_types = self.get_argument_types(lit.functor, lit.arity) # type: TypeArguments for arg, argtype in zip(lit.args, argument_types): # type: Term, TypeName if is_variable(arg) or arg.is_var(): result[argtype].add(arg) return result
def __getitem__(self, key): if key is None: return None elif key < 0: value = self.numbers.get(key) if value is None: self.num_count -= 1 value = self.num_count self.numbers[key] = value self.translate[value] = key else: value = self.context[key] if value is None or type(value) == int: if value is not None: key = value okey = value else: okey = None value = self.numbers.get(key) if value is None: self.num_count -= 1 value = self.num_count self.numbers[key] = value self.translate[value] = okey if not is_variable(value): value = value.apply(self) return value
def term2list2(term): result = [] while not is_variable(term) and term.functor == '.' and term.arity == 2: result.append(term.args[0]) term = term.args[1] if not term == Term('[]'): raise ValueError('Expected fixed list.') return result
def instantiate(term, context): """Replace variables in Term by values based on context lookup table. :param term: :param context: :return: """ if is_variable(term): return context[term] else: return term.apply(context)
def _unify_call_head_single(source_value, target_value, target_context, source_values): """ Unify a call argument with a clause head argument. :param source_value: value occuring in clause call :type source_value: Term or variable. All variables are represented as negative numbers. :param target_value: value occuring in clause head :type target_value: Term or variable. All variables are represented as positive numbers \ corresponding to positions in target_context. :param target_context: values of the variables in the current context. Output argument. :param source_values: :raise UnifyError: unification failed The values in the target_context contain only variables from the source context \ (i.e. negative numbers) (or Terms). Initially values are set to None. """ if is_variable(target_value): # target_value is variable (integer >= 0) assert type(target_value) == int and target_value >= 0 target_context[target_value] = \ unify_value(source_value, target_context[target_value], source_values) else: # target_value is a Term (which can still contain variables) if is_variable(source_value): # source value is variable (integer < 0) if source_value is None: pass else: assert type(source_value) == int and source_value < 0 # This means that *all* occurrences of source_value should unify with the same value. # We keep track of the value for each source_value, and unify the target_value with the current value. # Note that we unify two values in the same scope. source_values[source_value] = \ unify_value(source_values.get(source_value), target_value, source_values) else: # source value is a Term (which can still contain variables) if target_value.signature == source_value.signature: # When signatures match, recursively unify the arguments. for s_arg, t_arg in zip(source_value.args, target_value.args): _unify_call_head_single(s_arg, t_arg, target_context, source_values) else: raise UnifyError()
def substitute_all(terms, subst, wrapped=False): """ :param terms: :param subst: :return: """ if not wrapped: subst = _SubstitutionWrapper(subst) result = [] for term in terms: if is_variable(term): result.append(subst[term]) else: result.append(term.apply(subst)) return result
def unify_value_different_contexts(value1: Term, value2: Term, source_values, target_values): """ Unify two values that exist in different contexts. Updates the mapping of variables from value1 to values from value2. :param value1: :param value2: :param source_values: mapping of source variable to target value :param target_values: mapping of target variable to TARGET value """ # Variables are negative numbers or None if is_variable(value1) and is_variable(value2): if value1 is None: # value1 is anonyous var pass elif value2 is None: # value2 is anonymous pass else: # Two named variables # Check whether value2 is already linked to another value in the target context sv2 = target_values.get(value2, value2) if sv2 == value2: # no # Check whether value1 is already linked to a value sv1 = source_values.get(value1) if sv1 is None: # no # We can link variable 1 with variable 2 source_values[value1] = value2 else: # yes: we need to unify sv1 and value2 (they both are in target scope) unify_value(sv1, value2, target_values) else: # value2 is already linked to another value # we need to unify value1 with that value unify_value_different_contexts(value1, sv2, source_values, target_values) # only value1 is a variable; value2 is a nonvar term elif is_variable(value1): if value1 is None: pass else: sv1 = source_values.get(value1) if sv1 is None: source_values[value1] = value2 elif is_variable(sv1): if sv1 in value2.variables(): raise OccursCheck() if sv1 != value2: target_values[sv1] = value2 source_values[value1] = value2 else: # unify in same context target_values source_values[value1] = unify_value(source_values[value1], value2, target_values) # only value2 is a variable; value1 is a nonvar term elif is_variable(value2): sv2 = target_values.get(value2) if sv2 is None: target_values[value2] = value1.apply(source_values) elif is_variable(sv2): pass else: unify_value_different_contexts(value1, sv2, source_values, target_values) # value1 and value2 are both not variables # but they are terms with the same signature elif value1.signature == value2.signature: # Assume Term for a1, a2 in zip(value1.args, value2.args): unify_value_different_contexts(a1, a2, source_values, target_values) # they don't have the same signature else: raise UnifyError()
def load(cls, filedata): engine = DefaultEngine() cores_l = [] cores = [] modes = [] types = {} constants = {} constant_modes = [] # Load the cores. cores_str = '\n'.join(filedata.get('CORES', [])) for core in PrologString(cores_str): core_literals = Clause.literals_from_logic(core) cores_l.append(core_literals) # Load the modes. modes_str = '\n'.join(filedata.get('MODES', [])) for mode_term in PrologString(modes_str): if mode_term.is_negated(): mode_term = -mode_term modes.append((False, mode_term)) else: modes.append((True, mode_term)) for i, a in enumerate(mode_term.args): if cls.mode_is_constant(a): constant_modes.append((mode_term.signature, i)) # Load the types. all_types = set() types_str = '\n'.join(filedata.get('TYPES', [])) for type_term in PrologString(types_str): predicate = type_term.signature argtypes = type_term.args for argtype in argtypes: all_types.add(argtype) types[predicate] = argtypes # Load the constants. constants_str = '\n'.join(filedata.get('CONSTANTS', [])) constants_db = engine.prepare(PrologString(constants_str)) for t in all_types: values = [x[0] for x in engine.query(constants_db, t(None))] if values: constants[t] = values # Change variables in cores for core_l in cores_l: core = [] varreplace = {} vars_head = set() vars_body = set() new_vars = 0 for lit in core_l: if lit.is_negated(): negated = True lit = -lit else: negated = False new_args = [] for arg_i, arg_v in enumerate(lit.args): if isinstance(arg_v, Var) or is_variable(arg_v): if arg_v in varreplace: new_var = varreplace[arg_v] new_args.append(new_var) else: if lit.signature not in types: raise InvalidLanguage('No type specified for predicate \'%s\'.' % lit.signature) argtype = types[lit.signature][arg_i] new_var = TypedVar(new_vars, argtype) new_vars += 1 varreplace[arg_v] = new_var new_args.append(new_var) if negated: vars_body.add(new_var) else: vars_head.add(new_var) else: new_args.append(arg_v) new_lit = lit(*new_args) if negated: new_lit = -new_lit core.append(new_lit) for var in vars_head - vars_body: core.insert(0, -Term('#', var.type, var)) # raise InvalidLanguage('Core rule is not range-restricted.') cores.append(Clause.from_list(core)) from problog.logic import Clause as ClauseP background = [] for pred, args in types.items(): pred = pred.rsplit('/', 1)[0] for i, a in enumerate(args): argl = [None] * len(args) argl[i] = 0 background.append(ClauseP(Term('#', a, 0), Term(pred, *argl))) # Verify the extracted information: # - we need at least one core if not cores: raise InvalidLanguage('At least one core is required.') # - we need at least one mode if not modes: raise InvalidLanguage('At least one mode is required.') # - only + and c allowed in positive modes for pn, mode in modes: if pn: for arg in mode.args: if cls.mode_is_add(arg): raise InvalidLanguage('New variables are not allowed in head of clause: \'%s\'.' % mode) # - we need types for all modes missing_types = [] for _, mode in modes: if mode.signature not in types: missing_types.append(mode.signature) if missing_types: raise InvalidLanguage('Types are missing for %s.' % and_join(missing_types)) # - when a 'c' mode is used, we need constants for that type missing_constants = set() for cs, ci in constant_modes: argtype = types[cs][ci] if argtype not in constants: missing_constants.add(argtype) if missing_constants: raise InvalidLanguage('Constants are missing for type %s.' % and_join(missing_constants)) return CModeLanguage(cores, modes, types, constants, background)