diff --git a/docs/aba_examples.rst b/docs/aba_examples.rst index 6f85b61..9f8dc96 100644 --- a/docs/aba_examples.rst +++ b/docs/aba_examples.rst @@ -34,5 +34,5 @@ Toni, Francesca. A tutorial on assumption-based argumentation. *Argument & Compu aba_framework = ABAF(assumptions, rules, language, contraries) # Get preferred extensions - extensions = get_preferred_extensions.apply(aba_framework) + extensions = get_preferred_extensions.get_preferred_extensions(aba_framework) print(extensions) diff --git a/pyproject.toml b/pyproject.toml index 0833e54..5ed691f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-argumentation" -version = "0.0.1" +version = "2.0.0" authors = [ { name="Daphne Odekerken", email="d.odekerken@uu.nl" }, { name="AnneMarie Borg", email="a.borg@uu.nl" }, diff --git a/src/py_arg/aba_classes/aba_framework.py b/src/py_arg/aba_classes/aba_framework.py index 94b2e62..1a9c093 100644 --- a/src/py_arg/aba_classes/aba_framework.py +++ b/src/py_arg/aba_classes/aba_framework.py @@ -2,7 +2,8 @@ from py_arg.aba_classes.instantiated_argument import InstantiatedArgument from py_arg.aba_classes.rule import Rule -from py_arg.abstract_argumentation_classes.abstract_argumentation_framework import AbstractArgumentationFramework +from py_arg.abstract_argumentation_classes.abstract_argumentation_framework import \ + AbstractArgumentationFramework from py_arg.abstract_argumentation_classes.defeat import Defeat diff --git a/src/py_arg/aba_classes/instantiated_argument.py b/src/py_arg/aba_classes/instantiated_argument.py index f7525e6..9e53313 100644 --- a/src/py_arg/aba_classes/instantiated_argument.py +++ b/src/py_arg/aba_classes/instantiated_argument.py @@ -25,4 +25,3 @@ def __hash__(self): def __lt__(self, other): return self.arg_hash < other.arg_hash - diff --git a/src/py_arg/aba_classes/semantics/get_admissible_extensions.py b/src/py_arg/aba_classes/semantics/get_admissible_extensions.py index 72cc375..bc0709c 100644 --- a/src/py_arg/aba_classes/semantics/get_admissible_extensions.py +++ b/src/py_arg/aba_classes/semantics/get_admissible_extensions.py @@ -5,16 +5,16 @@ import py_arg.algorithms.canonical_constructions.aux_operators as aux -def apply(abaf: ABAF) -> Set[FrozenSet[str]]: - af = abaf.generate_af() +def get_admissible_extensions(aba_framework: ABAF) -> Set[FrozenSet[str]]: + af = aba_framework.generate_af() - abaf_extensions = set() - for ext in aux.powerset(abaf.assumptions): + aba_framework_extensions = set() + for ext in aux.powerset(aba_framework.assumptions): af_ext = set() for arg in af.arguments: if arg.premise.issubset(ext): af_ext.add(arg) if is_admissible_af.is_admissible(af_ext, af): - abaf_extensions.add(ext) + aba_framework_extensions.add(ext) - return abaf_extensions + return aba_framework_extensions diff --git a/src/py_arg/aba_classes/semantics/get_complete_extensions.py b/src/py_arg/aba_classes/semantics/get_complete_extensions.py index a9d35a4..c497edd 100644 --- a/src/py_arg/aba_classes/semantics/get_complete_extensions.py +++ b/src/py_arg/aba_classes/semantics/get_complete_extensions.py @@ -4,15 +4,15 @@ import py_arg.algorithms.semantics.get_complete_extensions as get_complete_extensions_af -def apply(abaf: ABAF) -> Set[FrozenSet[str]]: - af = abaf.generate_af() +def get_complete_extensions(aba_framework: ABAF) -> Set[FrozenSet[str]]: + af = aba_framework.generate_af() af_extensions = get_complete_extensions_af.get_complete_extensions(af) - abaf_extensions = set() + aba_framework_extensions = set() for af_ext in af_extensions: aba_ext = set() for arg in af_ext: - if arg.conclusion in abaf.assumptions: + if arg.conclusion in aba_framework.assumptions: aba_ext.add(arg.conclusion) - abaf_extensions.add(frozenset(aba_ext)) + aba_framework_extensions.add(frozenset(aba_ext)) - return abaf_extensions + return aba_framework_extensions diff --git a/src/py_arg/aba_classes/semantics/get_conflict_free_extensions.py b/src/py_arg/aba_classes/semantics/get_conflict_free_extensions.py index a181af3..5a9b623 100644 --- a/src/py_arg/aba_classes/semantics/get_conflict_free_extensions.py +++ b/src/py_arg/aba_classes/semantics/get_conflict_free_extensions.py @@ -4,17 +4,17 @@ import py_arg.algorithms.canonical_constructions.aux_operators as aux -def apply(abaf: ABAF) -> Set[FrozenSet[str]]: - abaf_extensions = set() - af = abaf.generate_af() - for ext in aux.powerset(abaf.assumptions): +def get_conflict_free_extensions(aba_framework: ABAF) -> Set[FrozenSet[str]]: + aba_framework_extensions = set() + af = aba_framework.generate_af() + for ext in aux.powerset(aba_framework.assumptions): cf = True for arg in af.arguments: if arg.premise.issubset(ext): for asm in ext: - if abaf.contraries[asm] == arg.conclusion: + if aba_framework.contraries[asm] == arg.conclusion: cf = False if cf: - abaf_extensions.add(ext) + aba_framework_extensions.add(ext) - return abaf_extensions + return aba_framework_extensions diff --git a/src/py_arg/aba_classes/semantics/get_ground_extensions.py b/src/py_arg/aba_classes/semantics/get_ground_extensions.py deleted file mode 100644 index 4ff2537..0000000 --- a/src/py_arg/aba_classes/semantics/get_ground_extensions.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Set, FrozenSet - -from py_arg.aba_classes.aba_framework import ABAF -import py_arg.algorithms.semantics.get_grounded_extension as get_grounded_extensions_af - - -def apply(abaf: ABAF) -> Set[FrozenSet[str]]: - af = abaf.generate_af() - af_extension = get_grounded_extensions_af.get_grounded_extension(af) - abaf_ext = set() - for arg in af_extension: - if arg.conclusion in abaf.assumptions: - abaf_ext.add(arg.conclusion) - - return {frozenset(abaf_ext)} diff --git a/src/py_arg/aba_classes/semantics/get_grounded_extensions.py b/src/py_arg/aba_classes/semantics/get_grounded_extensions.py new file mode 100644 index 0000000..304e157 --- /dev/null +++ b/src/py_arg/aba_classes/semantics/get_grounded_extensions.py @@ -0,0 +1,15 @@ +from typing import Set, FrozenSet + +from py_arg.aba_classes.aba_framework import ABAF +import py_arg.algorithms.semantics.get_grounded_extension as get_grounded_extensions_af + + +def get_preferred_extensions(aba_framework: ABAF) -> Set[FrozenSet[str]]: + af = aba_framework.generate_af() + af_extension = get_grounded_extensions_af.get_grounded_extension(af) + aba_framework_extensions = set() + for arg in af_extension: + if arg.conclusion in aba_framework.assumptions: + aba_framework_extensions.add(arg.conclusion) + + return {frozenset(aba_framework_extensions)} diff --git a/src/py_arg/aba_classes/semantics/get_naive_extensions.py b/src/py_arg/aba_classes/semantics/get_naive_extensions.py index 62e71ba..8d1910c 100644 --- a/src/py_arg/aba_classes/semantics/get_naive_extensions.py +++ b/src/py_arg/aba_classes/semantics/get_naive_extensions.py @@ -4,8 +4,8 @@ import py_arg.aba_classes.semantics.get_conflict_free_extensions as get_conflict_free_extensions -def apply(abaf: ABAF) -> Set[FrozenSet[str]]: - cf_ext = get_conflict_free_extensions.apply(abaf) +def get_naive_extensions(aba_framework: ABAF) -> Set[FrozenSet[str]]: + cf_ext = get_conflict_free_extensions.get_conflict_free_extensions(aba_framework) rm = set() for ext1 in cf_ext: for ext2 in cf_ext: diff --git a/src/py_arg/aba_classes/semantics/get_preferred_extensions.py b/src/py_arg/aba_classes/semantics/get_preferred_extensions.py index 0f273af..5f2efa6 100644 --- a/src/py_arg/aba_classes/semantics/get_preferred_extensions.py +++ b/src/py_arg/aba_classes/semantics/get_preferred_extensions.py @@ -4,14 +4,14 @@ import py_arg.algorithms.semantics.get_preferred_extensions as get_preferred_extensions_af -def apply(abaf: ABAF) -> Set[FrozenSet[str]]: - af = abaf.generate_af() +def get_preferred_extensions(aba_framework: ABAF) -> Set[FrozenSet[str]]: + af = aba_framework.generate_af() af_extensions = get_preferred_extensions_af.get_preferred_extensions(af) abaf_extensions = set() for af_ext in af_extensions: aba_ext = set() for arg in af_ext: - if arg.conclusion in abaf.assumptions: + if arg.conclusion in aba_framework.assumptions: aba_ext.add(arg.conclusion) abaf_extensions.add(frozenset(aba_ext)) diff --git a/src/py_arg/aba_classes/semantics/get_semi_stable_extensions.py b/src/py_arg/aba_classes/semantics/get_semi_stable_extensions.py index 4fe91de..fb69a26 100644 --- a/src/py_arg/aba_classes/semantics/get_semi_stable_extensions.py +++ b/src/py_arg/aba_classes/semantics/get_semi_stable_extensions.py @@ -7,25 +7,24 @@ # We thank Anh Kiet Nguyen for pointing out that semi-stable assumption extensions # cannot in general be attained from the instantiated af. # cf. 'ON THE DIFFERENCE BETWEEN ASSUMPTION-BASED ARGUMENTATION AND ABSTRACT ARGUMENTATION' -def apply(abaf: ABAF) -> Set[FrozenSet[str]]: - af = abaf.generate_af() - com_ext = get_complete_extensions.apply(abaf) +def get_semi_stable_extensions(aba_framework: ABAF) -> Set[FrozenSet[str]]: + af = aba_framework.generate_af() + com_ext = get_complete_extensions.get_complete_extensions(aba_framework) extension_reach = {} for ext in com_ext: extension_reach[ext] = set(ext.copy()) for arg in af.arguments: - if arg.premise.issubset(ext): - if arg.conclusion not in abaf.assumptions: - extension_reach[ext].add( - list(abaf.contraries.keys())[list(abaf.contraries.values()).index(arg.conclusion)]) + if arg.premise.issubset(ext) and arg.conclusion not in aba_framework.assumptions: + conclusion_index = list(aba_framework.contraries.values()).index(arg.conclusion) + extension_reach[ext].add(list(aba_framework.contraries.keys())[conclusion_index]) ss_ext = set() for ext1 in com_ext: - is_semistable = True + is_semi_stable = True for ext2 in com_ext: if extension_reach[ext1].issubset(extension_reach[ext2]) and \ not extension_reach[ext2].issubset(extension_reach[ext1]): - is_semistable = False - if is_semistable: + is_semi_stable = False + if is_semi_stable: ss_ext.add(ext1) return ss_ext diff --git a/src/py_arg/aba_classes/semantics/get_stable_extensions.py b/src/py_arg/aba_classes/semantics/get_stable_extensions.py index fffde02..1a7f36b 100644 --- a/src/py_arg/aba_classes/semantics/get_stable_extensions.py +++ b/src/py_arg/aba_classes/semantics/get_stable_extensions.py @@ -4,15 +4,15 @@ import py_arg.algorithms.semantics.get_stable_extensions as get_stable_extensions_af -def apply(abaf: ABAF) -> Set[FrozenSet[str]]: - af = abaf.generate_af() +def get_stable_extensions(aba_framework: ABAF) -> Set[FrozenSet[str]]: + af = aba_framework.generate_af() af_extensions = get_stable_extensions_af.get_stable_extensions(af) - abaf_extensions = set() + aba_framework_extensions = set() for af_ext in af_extensions: aba_ext = set() for arg in af_ext: - if arg.conclusion in abaf.assumptions: + if arg.conclusion in aba_framework.assumptions: aba_ext.add(arg.conclusion) - abaf_extensions.add(frozenset(aba_ext)) + aba_framework_extensions.add(frozenset(aba_ext)) - return abaf_extensions + return aba_framework_extensions diff --git a/src/py_arg/abstract_argumentation_classes/abstract_argumentation_framework.py b/src/py_arg/abstract_argumentation_classes/abstract_argumentation_framework.py index 7802ebb..e91bfca 100644 --- a/src/py_arg/abstract_argumentation_classes/abstract_argumentation_framework.py +++ b/src/py_arg/abstract_argumentation_classes/abstract_argumentation_framework.py @@ -24,6 +24,16 @@ def __init__(self, name: str = '', defeat.from_argument.add_outgoing_defeat(defeat.to_argument) defeat.to_argument.add_ingoing_defeat(defeat.from_argument) + def __repr__(self): + return '( [' + ', '.join(argument.name for argument in self.arguments) + \ + '], [' + ', '.join(defeat.__repr__() for defeat in self.defeats) + '] )' + + def __eq__(self, other): + return isinstance(other, AbstractArgumentationFramework) and \ + self.name == other.name and \ + self.arguments == other.arguments and \ + self.defeats == other.defeats + def get_incoming_defeat_arguments(self, argument: Argument) -> List[Argument]: """ Get a list of arguments that defeat this argument. diff --git a/src/py_arg/abstract_argumentation_classes/defeat.py b/src/py_arg/abstract_argumentation_classes/defeat.py index c1f3ce3..b5cf4db 100644 --- a/src/py_arg/abstract_argumentation_classes/defeat.py +++ b/src/py_arg/abstract_argumentation_classes/defeat.py @@ -9,6 +9,13 @@ def __init__(self, from_argument: Argument, to_argument: Argument): def __str__(self): return str(self.from_argument) + ' defeats ' + str(self.to_argument) + def __lt__(self, other): + return self.from_argument < other.from_argument or self.from_argument == other.from_argument and \ + self.to_argument < other.to_argument + + def __repr__(self): + return '(' + str(self.from_argument) + ', ' + str(self.to_argument) + ')' + def __eq__(self, other): return self.from_argument == other.from_argument and self.to_argument == other.to_argument diff --git a/src/py_arg/experiments/experiment_generate_data_set_from_layered_argsys.py b/src/py_arg/experiments/experiment_generate_data_set_from_layered_argsys.py index 8c57989..1f2c33d 100644 --- a/src/py_arg/experiments/experiment_generate_data_set_from_layered_argsys.py +++ b/src/py_arg/experiments/experiment_generate_data_set_from_layered_argsys.py @@ -44,8 +44,7 @@ def instantiate_incomplete_argumentation_theory_generator(nr_of_literals, nr_of_ ), topics -# literal_sizes = [50, 100, 150, 200, 250, 500, 1000, 2500] -literal_sizes = [60, 70, 80, 90] +literal_sizes = [20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150] rule_literal_ratios = [0.5, 1, 1.5] for literal_size in literal_sizes: diff --git a/src/py_arg/experiments/experiment_generate_incomplete_argumentation_theory.py b/src/py_arg/experiments/experiment_generate_incomplete_argumentation_theory.py index ffebb5e..33838f5 100644 --- a/src/py_arg/experiments/experiment_generate_incomplete_argumentation_theory.py +++ b/src/py_arg/experiments/experiment_generate_incomplete_argumentation_theory.py @@ -4,9 +4,7 @@ IncompleteArgumentationTheoryGenerator -def instantiate_incomplete_argumentation_theory_generator(): - nr_of_literals = 10000 - nr_of_rules = 15000 +def instantiate_incomplete_argumentation_theory_generator(nr_of_literals, nr_of_rules): rule_antecedent_distribution = {1: int(nr_of_rules / 3), 2: int(nr_of_rules / 3), 3: int(nr_of_rules / 9), @@ -42,7 +40,9 @@ def instantiate_incomplete_argumentation_theory_generator(): ) -generator = instantiate_incomplete_argumentation_theory_generator() -iaf = generator.generate() -i = 0 - +if __name__ == "__main__": + for nr_of_literals in [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150]: + for nr_of_rules in [nr_of_literals / 2, nr_of_literals, 3 * nr_of_literals / 2]: + generator = instantiate_incomplete_argumentation_theory_generator(nr_of_literals, nr_of_rules) + for instance in range(50): + iaf = generator.generate() diff --git a/src/py_arg/import_export/incomplete_argumentation_theory_from_lp_file_reader.py b/src/py_arg/import_export/incomplete_argumentation_theory_from_lp_file_reader.py index 40d4b74..b9cb83e 100644 --- a/src/py_arg/import_export/incomplete_argumentation_theory_from_lp_file_reader.py +++ b/src/py_arg/import_export/incomplete_argumentation_theory_from_lp_file_reader.py @@ -3,6 +3,7 @@ from py_arg.aspic_classes.argumentation_system import ArgumentationSystem from py_arg.aspic_classes.defeasible_rule import DefeasibleRule from py_arg.aspic_classes.literal import Literal +from py_arg.aspic_classes.orderings.preference_preorder import PreferencePreorder from py_arg.incomplete_aspic_classes.incomplete_argumentation_theory import IncompleteArgumentationTheory @@ -20,6 +21,7 @@ def read_from_lp_file(file_path: str) -> IncompleteArgumentationTheory: defeasible_rule_bodies = [(r[0], r[1]) for r in parse.findall('body({}, {})', lines)] defeasible_rule_heads = [(r[0], r[1]) for r in parse.findall('head({}, {})', lines)] contradiction_pairs = [(r[0], r[1]) for r in parse.findall('neg({}, {})', lines)] + preferred_pairs = [(r[0], r[1]) for r in parse.findall('preferred({}, {})', lines)] all_positive_literals = set(positive_queryable_strs) for axiom in axiom_strs: @@ -48,11 +50,22 @@ def read_from_lp_file(file_path: str) -> IncompleteArgumentationTheory: language[rule_head]) defeasible_rules.append(defeasible_rule) - argumentation_system = ArgumentationSystem(language, contraries_and_contradictories, [], defeasible_rules, - add_defeasible_rule_literals=False) + def_rules_lookup = {defeasible_rule.id: defeasible_rule for defeasible_rule in defeasible_rules} + preference_preorder = PreferencePreorder( + [(def_rules_lookup[rule_a], def_rules_lookup[rule_b]) + for rule_a, rule_b in preferred_pairs]) + + argumentation_system = ArgumentationSystem( + language=language, contraries_and_contradictories=contraries_and_contradictories, + strict_rules=[], defeasible_rules=defeasible_rules, + defeasible_rule_preferences=preference_preorder, add_defeasible_rule_literals=False) negative_queryable_strs = ['-' + queryable for queryable in positive_queryable_strs] queryables = [language[queryable] for queryable in positive_queryable_strs + negative_queryable_strs] knowledge_base_axioms = [language[axiom_str] for axiom_str in axiom_strs] - return IncompleteArgumentationTheory(argumentation_system, queryables, knowledge_base_axioms, []) + return IncompleteArgumentationTheory( + argumentation_system=argumentation_system, queryables=queryables, + knowledge_base_axioms=knowledge_base_axioms, knowledge_base_ordinary_premises=[], + ordinary_premise_preferences=None + ) diff --git a/src/py_arg/incomplete_argumentation_classes/argument_incomplete_argumentation_framework.py b/src/py_arg/incomplete_argumentation_classes/argument_incomplete_argumentation_framework.py deleted file mode 100644 index 5980d04..0000000 --- a/src/py_arg/incomplete_argumentation_classes/argument_incomplete_argumentation_framework.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import Optional, List, Dict - -from py_arg.abstract_argumentation_classes.abstract_argumentation_framework import AbstractArgumentationFramework -from py_arg.abstract_argumentation_classes.argument import Argument -from py_arg.abstract_argumentation_classes.defeat import Defeat - - -class ArgumentIncompleteArgumentationFramework: - # TODO: Docstrings and tests. - - def __init__(self, name: str = '', - arguments: Optional[List[Argument]] = None, - uncertain_arguments: Optional[List[Argument]] = None, - defeats: Optional[List[Defeat]] = None, - uncertain_defeats: Optional[List[Defeat]] = None): - self.name = name - - if arguments is None: - self._arguments = {} - else: - self._arguments = {argument.name: argument for argument in arguments} - - if uncertain_arguments is None: - self._uncertain_arguments = {} - else: - if any(uncertain_argument in arguments for uncertain_argument in uncertain_arguments): - raise ValueError('Argument cannot be both certain and uncertain.') - self._uncertain_arguments = {argument.name: argument for argument in uncertain_arguments} - - if defeats is None: - self._defeats = [] - else: - self._defeats = defeats - - if uncertain_defeats is None: - self._uncertain_defeats = [] - else: - self._uncertain_defeats = defeats - - for defeat in self._defeats + self._uncertain_defeats: - if defeat.from_argument.name in self._arguments.keys(): - defeat_from_argument = self._arguments[defeat.from_argument.name] - else: - defeat_from_argument = self._uncertain_arguments[defeat.from_argument.name] - if defeat.to_argument.name in self._arguments.keys(): - defeat_to_argument = self._arguments[defeat.to_argument.name] - else: - defeat_to_argument = self._uncertain_arguments[defeat.to_argument.name] - defeat_from_argument.add_outgoing_defeat(defeat.to_argument) - defeat_to_argument.add_ingoing_defeat(defeat.from_argument) - - @property - def arguments(self) -> Dict[str, Argument]: - return self._arguments - - @property - def uncertain_arguments(self) -> Dict[str, Argument]: - return self._uncertain_arguments - - @property - def certain_projection(self) -> AbstractArgumentationFramework: - arguments = list(self._arguments.values()) - return AbstractArgumentationFramework(arguments=arguments, - defeats=[defeat for defeat in self._defeats - if defeat.to_argument in arguments and - defeat.to_argument in arguments]) - - def _get_direct_specifications(self): - if self._uncertain_arguments: - first_uncertain_argument = sorted(self._uncertain_arguments.keys())[0] - new_uncertain_arguments = [value for key, value in self._uncertain_arguments.items() - if key != first_uncertain_argument] - return [ - ArgumentIncompleteArgumentationFramework( - arguments=list(self.arguments.values()), uncertain_arguments=new_uncertain_arguments, - defeats=self._defeats, uncertain_defeats=self._uncertain_defeats), - ArgumentIncompleteArgumentationFramework( - arguments=list(self.arguments.values()) + [self.arguments[first_uncertain_argument]], - uncertain_arguments=new_uncertain_arguments, - defeats=self._defeats, uncertain_defeats=self._uncertain_defeats), - ] - elif self._uncertain_defeats: - first_uncertain_defeat = self._defeats[0] - new_uncertain_defeats = self._defeats[1:] - return [ - ArgumentIncompleteArgumentationFramework( - arguments=list(self.arguments.values()), - uncertain_arguments=list(self.uncertain_arguments.values()), - defeats=self._defeats, uncertain_defeats=new_uncertain_defeats - ), - ArgumentIncompleteArgumentationFramework( - arguments=list(self.arguments.values()), - uncertain_arguments=list(self.uncertain_arguments.values()), - defeats=self._defeats + [first_uncertain_defeat], uncertain_defeats=new_uncertain_defeats - ) - ] diff --git a/src/py_arg/incomplete_argumentation_classes/incomplete_argumentation_framework.py b/src/py_arg/incomplete_argumentation_classes/incomplete_argumentation_framework.py new file mode 100644 index 0000000..c1d1515 --- /dev/null +++ b/src/py_arg/incomplete_argumentation_classes/incomplete_argumentation_framework.py @@ -0,0 +1,192 @@ +import itertools +from typing import Optional, List, Dict + +from py_arg.abstract_argumentation_classes.abstract_argumentation_framework import AbstractArgumentationFramework +from py_arg.abstract_argumentation_classes.argument import Argument +from py_arg.abstract_argumentation_classes.defeat import Defeat + + +class IncompleteArgumentationFramework: + """ + An incomplete argumentation framework (IAF) is an extension to abstract argumentation frameworks + in which the sets of arguments and defeats are each split in two disjoint parts: a certain part + and an uncertain part. + """ + def __init__(self, name: str = '', + arguments: Optional[List[Argument]] = None, + uncertain_arguments: Optional[List[Argument]] = None, + defeats: Optional[List[Defeat]] = None, + uncertain_defeats: Optional[List[Defeat]] = None): + self.name = name + + if arguments is None: + self._arguments = {} + else: + self._arguments = {argument.name: argument for argument in arguments} + + if uncertain_arguments is None: + self._uncertain_arguments = {} + else: + if any(uncertain_argument in arguments for uncertain_argument in uncertain_arguments): + raise ValueError('Argument cannot be both certain and uncertain.') + self._uncertain_arguments = {argument.name: argument for argument in uncertain_arguments} + + if defeats is None: + self._defeats = [] + else: + self._defeats = defeats + + if uncertain_defeats is None: + self._uncertain_defeats = [] + else: + self._uncertain_defeats = uncertain_defeats + + for defeat in self._defeats + self._uncertain_defeats: + if defeat.from_argument.name in self._arguments.keys(): + defeat_from_argument = self._arguments[defeat.from_argument.name] + else: + defeat_from_argument = self._uncertain_arguments[defeat.from_argument.name] + if defeat.to_argument.name in self._arguments.keys(): + defeat_to_argument = self._arguments[defeat.to_argument.name] + else: + defeat_to_argument = self._uncertain_arguments[defeat.to_argument.name] + defeat_from_argument.add_outgoing_defeat(defeat.to_argument) + defeat_to_argument.add_ingoing_defeat(defeat.from_argument) + + def __eq__(self, other): + return isinstance(other, IncompleteArgumentationFramework) and \ + self.arguments == other.arguments and \ + self.uncertain_arguments == other.uncertain_arguments and \ + self.defeats == other.defeats and \ + self.uncertain_defeats == other.uncertain_defeats + + def __repr__(self): + return '( [' + ', '.join(argument_key for argument_key in self.arguments.keys()) + \ + '], [' + ', '.join(argument_key for argument_key in self.uncertain_arguments.keys()) + \ + '], [' + ', '.join(defeat.__repr__() for defeat in self.defeats) + \ + '], [' + ', '.join(defeat.__repr__() for defeat in self.uncertain_defeats) + '] )' + + @property + def arguments(self) -> Dict[str, Argument]: + """ + Obtain the (certain) arguments of the IAF. + """ + return self._arguments + + @property + def uncertain_arguments(self) -> Dict[str, Argument]: + """ + Obtain the uncertain arguments of the IAF. + """ + return self._uncertain_arguments + + @property + def defeats(self) -> List[Defeat]: + """ + Obtain the (certain) defeats of the IAF. + """ + return self._defeats + + @property + def uncertain_defeats(self) -> List[Defeat]: + """ + Obtain the uncertain defeats of the IAF. + """ + return self._uncertain_defeats + + @property + def certain_projection(self) -> AbstractArgumentationFramework: + """ + The certain projection consists of all certain arguments and all certain defeats between two + certain arguments. + """ + arguments = list(self._arguments.values()) + return AbstractArgumentationFramework(arguments=arguments, + defeats=[defeat for defeat in self._defeats + if defeat.from_argument in arguments and + defeat.to_argument in arguments]) + + def get_all_completions(self) -> List[AbstractArgumentationFramework]: + """ + The set of completions consists of all abstract AFs that can be obtained by resolving the + uncertainties of the IAF. + """ + + def powerset(s): + return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s) + 1)) + + remaining_uncertain_argument_options = powerset(self.uncertain_arguments.values()) + remaining_uncertain_defeat_options = powerset(self.uncertain_defeats) + combined_options = itertools.product(remaining_uncertain_argument_options, remaining_uncertain_defeat_options) + + result = [] + arguments = list(self.arguments.values()) + for uncertain_arguments, uncertain_defeats in combined_options: + all_arguments = sorted(arguments + list(uncertain_arguments)) + all_defeats = sorted(self.defeats + list(uncertain_defeats)) + completion = AbstractArgumentationFramework( + arguments=all_arguments, + defeats=[defeat for defeat in all_defeats + if defeat.to_argument in all_arguments and defeat.from_argument in all_arguments] + ) + if completion not in result: + result.append(completion) + + return result + + def get_all_partial_completions(self) -> List['IncompleteArgumentationFramework']: + """ + The set of partial completions consists of all IAFs that can be obtained by resolving some of the + uncertainties of the IAF, as well as the original IAF itself. + """ + + def powerset(s): + return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s) + 1)) + + # Remember the old values. + old_certain_arguments = list(self.arguments.values()) + old_certain_defeats = self.defeats + old_uncertain_arguments = self.uncertain_arguments.values() + old_uncertain_defeats = self.uncertain_defeats + + # Construct all possible combinations of uncertain elements. + remaining_uncertain_argument_options = powerset(old_uncertain_arguments) + remaining_uncertain_defeat_options = powerset(old_uncertain_defeats) + combined_options = itertools.product(remaining_uncertain_argument_options, remaining_uncertain_defeat_options) + + result = [] + for uncertain_arguments, uncertain_defeats in combined_options: + # This is a particular guess for the uncertain elements that will remain (as either certain or uncertain + # arguments) in the partial completion. + + # Now construct all possible combinations of these remaining elements. + new_certain_argument_options = powerset(uncertain_arguments) + new_certain_defeat_options = powerset(uncertain_defeats) + new_combined_options = itertools.product(new_certain_argument_options, new_certain_defeat_options) + + for new_certain_arguments, new_certain_defeats in new_combined_options: + # This is a particular guess for the elements that will become the new certain elements in the partial + # completion. + all_new_certain_arguments = sorted(old_certain_arguments + list(new_certain_arguments)) + all_new_certain_defeats = sorted(old_certain_defeats + list(new_certain_defeats)) + new_uncertain_arguments = sorted([arg for arg in uncertain_arguments + if arg not in new_certain_arguments]) + new_uncertain_defeats = sorted([defeat for defeat in uncertain_defeats + if defeat not in new_certain_defeats]) + + all_new_certain_or_uncertain_arguments = all_new_certain_arguments + new_uncertain_arguments + + partial_completion = IncompleteArgumentationFramework( + arguments=all_new_certain_arguments, + uncertain_arguments=new_uncertain_arguments, + defeats=[defeat for defeat in all_new_certain_defeats + if defeat.to_argument in all_new_certain_or_uncertain_arguments and + defeat.from_argument in all_new_certain_or_uncertain_arguments], + uncertain_defeats=[defeat for defeat in new_uncertain_defeats + if defeat.to_argument in all_new_certain_or_uncertain_arguments and + defeat.from_argument in all_new_certain_or_uncertain_arguments] + ) + if partial_completion not in result: + result.append(partial_completion) + + return result diff --git a/src/py_arg_tests/test_aba_canonical_constructions.py b/src/py_arg_tests/test_aba_canonical_constructions.py index 212e876..5ea50ac 100644 --- a/src/py_arg_tests/test_aba_canonical_constructions.py +++ b/src/py_arg_tests/test_aba_canonical_constructions.py @@ -161,7 +161,7 @@ def test_canonical_stb(self): self.assertEqual(canonical_aba1.language, {a, b, c, a_c, b_c, c_c}) self.assertEqual(canonical_aba1.rules, {Rule('', {a, b}, c_c), Rule('', {a, c}, b_c), Rule('', {b, c}, a_c)}) self.assertEqual(canonical_aba1.contraries, {a: a_c, b: b_c, c: c_c}) - self.assertEqual(get_stable_extensions.apply(canonical_aba1), es1) + self.assertEqual(get_stable_extensions.get_stable_extensions(canonical_aba1), es1) es2 = {set_a, set_b, set_c} canonical_aba2 = canonical_st.apply(es2) @@ -171,7 +171,7 @@ def test_canonical_stb(self): Rule('', {b}, a_c), Rule('', {b}, c_c), Rule('', {c}, a_c), Rule('', {c}, b_c)}) self.assertEqual(canonical_aba2.contraries, {a: a_c, b: b_c, c: c_c}) - self.assertEqual(get_stable_extensions.apply(canonical_aba2), es2) + self.assertEqual(get_stable_extensions.get_stable_extensions(canonical_aba2), es2) def test_canonical_cf(self): a = 'a' @@ -198,12 +198,12 @@ def test_canonical_cf(self): es1 = {set_empty, set_a, set_b, set_c, set_ab} aba = construct_abaf_cf.apply(es1) - es_n = get_conflict_free_extensions.apply(aba) + es_n = get_conflict_free_extensions.get_conflict_free_extensions(aba) self.assertEqual(es1, es_n) es2 = {set_empty, set_a, set_b, set_c, set_ab, set_ac, set_bc} aba = construct_abaf_cf.apply(es2) - es_n = get_conflict_free_extensions.apply(aba) + es_n = get_conflict_free_extensions.get_conflict_free_extensions(aba) self.assertEqual(es2, es_n) def test_canonical_adm(self): @@ -232,14 +232,14 @@ def test_canonical_adm(self): es1 = {set_empty, set_c, set_ab, set_abc} canonical_aba1 = construct_abaf_adm.apply(es1) canonical_aba1.reduce() - es_n = get_admissible_extensions.apply(canonical_aba1) + es_n = get_admissible_extensions.get_admissible_extensions(canonical_aba1) self.assertEqual(es1, es_n) es2 = {set_empty, set_ab, set_ac, set_bc} canonical_aba2 = construct_abaf_adm.apply(es2) canonical_aba2.reduce() - es_n = get_admissible_extensions.apply(canonical_aba2) + es_n = get_admissible_extensions.get_admissible_extensions(canonical_aba2) self.assertEqual(es2, es_n) @@ -284,7 +284,7 @@ def test_canonical_com(self): self.assertTrue(check_intersection_in.apply(es1)) canonical_aba1 = construct_abaf_com.apply(es1) canonical_aba1.reduce() - es_n = get_complete_extensions.apply(canonical_aba1) + es_n = get_complete_extensions.get_complete_extensions(canonical_aba1) self.assertEqual(es1, es_n) @@ -294,7 +294,7 @@ def test_canonical_com(self): self.assertTrue(check_intersection_in.apply(es2)) canonical_aba2 = construct_abaf_com.apply(es2) canonical_aba2.reduce() - es_n = get_complete_extensions.apply(canonical_aba2) + es_n = get_complete_extensions.get_complete_extensions(canonical_aba2) self.assertEqual(es2, es_n) @@ -304,6 +304,6 @@ def test_canonical_com(self): self.assertTrue(check_intersection_in.apply(es3)) canonical_aba3 = construct_abaf_com.apply(es3) canonical_aba3.reduce() - es_n = get_complete_extensions.apply(canonical_aba3) + es_n = get_complete_extensions.get_complete_extensions(canonical_aba3) self.assertEqual(es3, es_n) diff --git a/src/py_arg_tests/test_aba_lasagne_example.py b/src/py_arg_tests/test_aba_lasagne_example.py index dc8546c..7feb67a 100644 --- a/src/py_arg_tests/test_aba_lasagne_example.py +++ b/src/py_arg_tests/test_aba_lasagne_example.py @@ -25,5 +25,5 @@ def test_aba_lasagne_example(self): aba_framework = ABAF(assumptions, rules, language, contraries) # Get preferred extensions - extensions = get_preferred_extensions.apply(aba_framework) + extensions = get_preferred_extensions.get_preferred_extensions(aba_framework) self.assertSetEqual(extensions, {frozenset({'dirty_hands', 'no_fork'})}) \ No newline at end of file diff --git a/src/py_arg_tests/test_incomplete_argumentation_framework.py b/src/py_arg_tests/test_incomplete_argumentation_framework.py new file mode 100644 index 0000000..ba9e89f --- /dev/null +++ b/src/py_arg_tests/test_incomplete_argumentation_framework.py @@ -0,0 +1,123 @@ +import unittest + +from py_arg.abstract_argumentation_classes.abstract_argumentation_framework import AbstractArgumentationFramework +from py_arg.abstract_argumentation_classes.argument import Argument +from py_arg.abstract_argumentation_classes.defeat import Defeat +from py_arg.incomplete_argumentation_classes.incomplete_argumentation_framework import IncompleteArgumentationFramework + + +a = Argument('a') +b = Argument('b') +c = Argument('c') +d = Argument('d') +e = Argument('e') +ab = Defeat(a, b) +bc = Defeat(b, c) +cb = Defeat(c, b) +dc = Defeat(d, c) +de = Defeat(d, e) +ed = Defeat(e, d) +iaf = IncompleteArgumentationFramework( + arguments=[b, c, e], + uncertain_arguments=[a, d], + defeats=[ab, bc, dc, de, ed], + uncertain_defeats=[cb] +) + + +class TestCertainProjection(unittest.TestCase): + def test_ac_example(self): + certain_projection = iaf.certain_projection + self.assertEqual(certain_projection, + AbstractArgumentationFramework( + arguments=[b, c, e], + defeats=[bc] + )) + + +class TestCompletions(unittest.TestCase): + def test_ac_example_4(self): + all_completions = iaf.get_all_completions() + self.assertEqual(len(all_completions), 8) + af1 = AbstractArgumentationFramework( + arguments=[a, b, c, d, e], + defeats=[ab, bc, cb, dc, de, ed] + ) + self.assertIn(af1, all_completions) + af2 = AbstractArgumentationFramework( + arguments=[a, b, c, e], + defeats=[ab, bc, cb] + ) + self.assertIn(af2, all_completions) + af3 = AbstractArgumentationFramework( + arguments=[a, b, c, d, e], + defeats=[ab, bc, dc, de, ed] + ) + self.assertIn(af3, all_completions) + af4 = AbstractArgumentationFramework( + arguments=[a, b, c, e], + defeats=[ab, bc] + ) + self.assertIn(af4, all_completions) + af5 = AbstractArgumentationFramework( + arguments=[b, c, d, e], + defeats=[bc, cb, dc, de, ed] + ) + self.assertIn(af5, all_completions) + af6 = AbstractArgumentationFramework( + arguments=[b, c, e], + defeats=[bc, cb] + ) + self.assertIn(af6, all_completions) + af7 = AbstractArgumentationFramework( + arguments=[b, c, d, e], + defeats=[bc, dc, de, ed] + ) + self.assertIn(af7, all_completions) + af8 = AbstractArgumentationFramework( + arguments=[b, c, e], + defeats=[bc] + ) + self.assertIn(af8, all_completions) + + +class TestPartialCompletions(unittest.TestCase): + def test_ac_example_9(self): + all_partial_completions = iaf.get_all_partial_completions() + + self.assertEqual(len(all_partial_completions), 27) + self.assertIn(iaf, all_partial_completions) + iaf1 = IncompleteArgumentationFramework( + arguments=[a, b, c, d, e], + uncertain_arguments=[], + defeats=[ab, bc, cb, dc, de, ed], + uncertain_defeats=[] + ) + self.assertIn(iaf1, all_partial_completions) + + cert_iaf1 = iaf1.certain_projection + self.assertEqual(cert_iaf1, AbstractArgumentationFramework(arguments=[a, b, c, d, e], + defeats=[ab, bc, cb, dc, de, ed])) + + iaf2 = IncompleteArgumentationFramework( + arguments=[b, c, e], + uncertain_arguments=[], + defeats=[bc], + uncertain_defeats=[] + ) + self.assertIn(iaf2, all_partial_completions) + + cert_iaf2 = iaf2.certain_projection + self.assertEqual(cert_iaf2, AbstractArgumentationFramework(arguments=[b, c, e], + defeats=[bc])) + + iaf3 = IncompleteArgumentationFramework( + arguments=[a, b, c, e], + uncertain_arguments=[d], + defeats=[ab, bc, dc, de, ed], + uncertain_defeats=[cb] + ) + self.assertIn(iaf3, all_partial_completions) + cert_iaf3 = iaf3.certain_projection + self.assertEqual(cert_iaf3, AbstractArgumentationFramework(arguments=[a, b, c, e], + defeats=[ab, bc])) diff --git a/src/py_arg_tests/test_incomplete_argumentation_theory_generator.py b/src/py_arg_tests/test_incomplete_argumentation_theory_generator.py index f752583..5d3a886 100644 --- a/src/py_arg_tests/test_incomplete_argumentation_theory_generator.py +++ b/src/py_arg_tests/test_incomplete_argumentation_theory_generator.py @@ -1,8 +1,10 @@ import pathlib import unittest -from py_arg.experiments.experiment_generate_incomplete_argumentation_theory import \ - instantiate_incomplete_argumentation_theory_generator +from py_arg.generators.argumentation_system_generators.layered_argumentation_system_generator import \ + LayeredArgumentationSystemGenerator +from py_arg.generators.incomplete_argumentation_theory_generators.incomplete_argumentation_theory_generator import \ + IncompleteArgumentationTheoryGenerator from py_arg.import_export.argumentation_system_from_json_reader import ArgumentationSystemFromJsonReader from py_arg.import_export.argumentation_system_to_json_writer import ArgumentationSystemToJSONWriter from py_arg.import_export.incomplete_argumentation_theory_from_json_reader import \ @@ -15,9 +17,47 @@ IncompleteArgumentationTheoryToLPFileWriter +def instantiate_incomplete_argumentation_theory_generator(): + nr_of_literals = 100 + nr_of_rules = 150 + rule_antecedent_distribution = {1: int(nr_of_rules / 3), + 2: int(nr_of_rules / 3), + 3: int(nr_of_rules / 9), + 4: int(nr_of_rules / 9)} + rules_left = nr_of_rules - sum(rule_antecedent_distribution.values()) + rule_antecedent_distribution[5] = rules_left + + literal_layer_distribution = {0: 2 * nr_of_literals / 3, + 1: nr_of_literals / 10, + 2: nr_of_literals / 10, + 3: nr_of_literals / 10} + literals_left = nr_of_literals - sum(literal_layer_distribution.values()) + literal_layer_distribution[4] = literals_left + + layered_argumentation_system_generator = \ + LayeredArgumentationSystemGenerator(nr_of_literals=nr_of_literals, + nr_of_rules=nr_of_rules, + rule_antecedent_distribution=rule_antecedent_distribution, + literal_layer_distribution=literal_layer_distribution, + strict_rule_ratio=0) + + # Generate the argumentation system, and keep the "layers" of literals. + arg_sys, layered_language = layered_argumentation_system_generator.generate(return_layered_language=True) + + # Generate an incomplete argumentation theory, where only literals on the first layer can be queryable. + positive_queryable_candidates = {arg_sys.language[str(literal).replace('-', '')] for literal in layered_language[0]} + return IncompleteArgumentationTheoryGenerator( + argumentation_system=arg_sys, + positive_queryable_candidates=list(positive_queryable_candidates), + queryable_literal_ratio=0.5, + knowledge_queryable_ratio=0.5, + axiom_knowledge_ratio=1 + ) + + class TestGenerator(unittest.TestCase): def test_generator_returns_consistent_knowledge_base_without_duplicates(self): - for _ in range(100): + for _ in range(10): iat_generator = instantiate_incomplete_argumentation_theory_generator() iat = iat_generator.generate() axioms = iat.knowledge_base_axioms diff --git a/src/py_arg_tests/test_toast_api.py b/src/py_arg_tests/test_toast_api.py new file mode 100644 index 0000000..5c9ddef --- /dev/null +++ b/src/py_arg_tests/test_toast_api.py @@ -0,0 +1,24 @@ +import json +import unittest + +import requests + + +class TestToastAPI(unittest.TestCase): + def test_toast_api(self): + input_dict = { + 'premises': ['p', 'q'], + 'kbPrefs': ['p < q'], + 'rules': ['[r1] p => s', '[r2] q => t'], + 'rulePrefs': ['[r1]<[r2]'], + 'contrariness': ['s-t'], + 'link': 'weakest', + 'semantics': 'preferred', + 'assumptions': [], + 'axioms': [] + } + json_str = json.dumps(input_dict) + response = requests.post('http://toast.arg-tech.org/api/evaluate', json_str) + result = response.json() + self.assertEqual(len(result['arguments']), 4) + self.assertListEqual(result['acceptableConclusions']['0'], ['p', 'q', 't']) diff --git a/src/py_arg_visualisation/app.py b/src/py_arg_visualisation/app.py index ccd71cb..5675c11 100644 --- a/src/py_arg_visualisation/app.py +++ b/src/py_arg_visualisation/app.py @@ -81,4 +81,4 @@ # Running the app. if __name__ == '__main__': - app.run_server(debug=True) + app.run_server(debug=False) diff --git a/src/py_arg_visualisation/functions/extensions_functions/get_abaf_extensions.py b/src/py_arg_visualisation/functions/extensions_functions/get_abaf_extensions.py index 3376996..d522af0 100644 --- a/src/py_arg_visualisation/functions/extensions_functions/get_abaf_extensions.py +++ b/src/py_arg_visualisation/functions/extensions_functions/get_abaf_extensions.py @@ -5,24 +5,24 @@ import py_arg.aba_classes.semantics.get_admissible_extensions as get_admissible_extensions import py_arg.aba_classes.semantics.get_preferred_extensions as get_preferred_extensions import py_arg.aba_classes.semantics.get_semi_stable_extensions as get_semi_stable_extensions -import py_arg.aba_classes.semantics.get_ground_extensions as get_ground_extensions +import py_arg.aba_classes.semantics.get_grounded_extensions as get_ground_extensions import py_arg.aba_classes.semantics.get_naive_extensions as get_naive_extensions def apply(abaf: ABAF, semantics_specification: str): if semantics_specification == 'Stable': - return get_stable_extensions.apply(abaf) + return get_stable_extensions.get_stable_extensions(abaf) if semantics_specification == 'Preferred': - return get_preferred_extensions.apply(abaf) + return get_preferred_extensions.get_preferred_extensions(abaf) if semantics_specification == 'Conflict-Free': - return get_conflict_free_extensions.apply(abaf) + return get_conflict_free_extensions.get_conflict_free_extensions(abaf) if semantics_specification == 'Naive': - return get_naive_extensions.apply(abaf) + return get_naive_extensions.get_naive_extensions(abaf) if semantics_specification == 'Admissible': - return get_admissible_extensions.apply(abaf) + return get_admissible_extensions.get_admissible_extensions(abaf) if semantics_specification == 'Complete': - return get_complete_extensions.apply(abaf) + return get_complete_extensions.get_complete_extensions(abaf) if semantics_specification == 'SemiStable': - return get_semi_stable_extensions.apply(abaf) + return get_semi_stable_extensions.get_semi_stable_extensions(abaf) if semantics_specification == 'Grounded': - return get_ground_extensions.apply(abaf) + return get_ground_extensions.get_preferred_extensions(abaf) diff --git a/src/py_arg_visualisation/pages/90_pyarg.py b/src/py_arg_visualisation/pages/90_pyarg.py index 7e069a5..33ab9c8 100644 --- a/src/py_arg_visualisation/pages/90_pyarg.py +++ b/src/py_arg_visualisation/pages/90_pyarg.py @@ -5,7 +5,7 @@ layout = html.Div(children=[ html.H1('Welcome to PyArg!'), - html.P('This is a Python package and web interface for solving various problems in' + html.P('This is a Python package and web interface for solving various problems in ' 'computational argumentation.'), html.P(['If you have any questions, feedback or ambitions to contribute, feel free to contact ', html.A('Daphne Odekerken', href='mailto:d.odekerken@uu.nl?subject=PyArg'),