diff --git a/exam_template/python/Graph.py b/exam_template/python/Graph.py index 8e57edf..43afd7b 100644 --- a/exam_template/python/Graph.py +++ b/exam_template/python/Graph.py @@ -2,7 +2,7 @@ from sys import argv from pathlib import Path from math import sin, cos, pi -# from run import printred +from common import printc class Matrix: """ Adjacency matrix which supports 0 and 1 """ @@ -59,12 +59,12 @@ class Matrix: class Graph: - def __init__(self, nodes, edges): + def __init__(self, nodes, edges): # Nodes = str, Edges = (str,str) self.nodes = nodes self.edges = edges def __str__(self): - print(self.isUndirected()) + printc('Is undirected: ' + str(self.isUndirected())) return \ f'Nodes: {" ".join(self.nodes)}\n' \ + f'Edges: {" ".join(x+y for x,y in (self.undirectedEdgeSet() if self.isUndirected() else self.edges))}' @@ -117,6 +117,34 @@ class Graph: edges = sorted(list(set((x,y) if x < y else (y,x) for x,y in edges))) return edges + # def latexifyEulerPath(self): + # degrees = [sum(line) for line in self.toMatrix()] + + # def latexifyEulerCircuit(): + # pass + + def getKruskalsSpanningTree(self, weigths): # weights = dict[str, int] + edges = [] + connected_subgraphs = [set(v) for v in self.nodes] + + def find_subgraph_containing(v): + return [x for x in connected_subgraphs if v in x][0] + + def merge_subgraphs_that_contains(u, v): + subgraph_v = find_subgraph_containing(v) + new_connected_subgraphs = [x for x in connected_subgraphs if v not in x] + new_connected_subgraphs = [subgraph_v.union(x) if u in x else x for x in new_connected_subgraphs] + return new_connected_subgraphs + + sorted_edges = [ e for e in sorted(self.edges, key=lambda e: weigths[str(e)]) ] + + for u,v in sorted_edges: + if find_subgraph_containing(u) != find_subgraph_containing(v): + edges += [(u, v)] + connected_subgraphs = merge_subgraphs_that_contains(u, v) + + return Graph(self.nodes, edges) + def toLaTeX(self): zippedNodes = zip(self.nodes, generateNodeCoords(len(self.nodes))) nodeString = '\n'.join(f'\\node ({name}) at ({x},{y}) {{${name}$}};' for name,(x,y) in zippedNodes) diff --git a/exam_template/python/Hasse.py b/exam_template/python/Hasse.py index 01960bf..d98bdfe 100644 --- a/exam_template/python/Hasse.py +++ b/exam_template/python/Hasse.py @@ -1,7 +1,7 @@ from sys import argv from pathlib import Path -from run import printred +from common import printc # Increase if the diagram becomes too clobbered HEIGHT_SEPARATOR = 1 @@ -57,10 +57,10 @@ def latex_hasse(hasse): output.append(f'\\draw ({x}) -- ({y});') - printred(f"Minimal elements: $\{{ {', '.join(str(e) for e in min_el)} \}}$ \\\\") + printc(f"Minimal elements: $\{{ {', '.join(str(e) for e in min_el)} \}}$ \\\\") max_el = set(v for k,v in hasse if v not in (x for x,_ in hasse)) - printred(f"Maximal elements: $\{{ {', '.join(str(e) for e in max_el)} \}}$" ) + printc(f"Maximal elements: $\{{ {', '.join(str(e) for e in max_el)} \}}$" ) return '\n'.join(output) diff --git a/exam_template/python/Powerset.py b/exam_template/python/Powerset.py new file mode 100644 index 0000000..baecb06 --- /dev/null +++ b/exam_template/python/Powerset.py @@ -0,0 +1,51 @@ +from itertools import combinations + +class Set: + def __init__(self, elements): + self.elements = set(elements) + + def __str__(self): + if len(self) == 0: return "\\emptyset" + return f"\\{{ {', '.join(sorted(self.elements))} \\}}" + + def __iter__(self): + return list(sorted(self.elements)) + + def __len__(self): + return len(self.elements) + + def __gt__(self, value): + if len(self) != len(value): + return len(self) > len(value) + return self.elements > value.elements + + def __ge__(self, value): + return self > value + + def __lt__(self, value): + return not self > value + + def __le__(self, value): + return self < value + + def cardinality(self): + return len(self) + + def powerset(self): + powerset = [] + for i in range(len(self) + 1): + for subset in combinations(self.elements, i): + powerset.append(Set(list(subset))) + return Set(powerset) + + def to_vertical_latex(self): + column = [] + for e in sorted(self.elements): + column.append(str(e) + ' \\\\') + return '\\{\n' + '\n'.join(column) + '\n\\}' + +#TODO: make process input func + +if __name__ == "__main__": + print(Set(['a', 'b', 'c']).powerset().to_vertical_latex()) + # print(a for a in Set(['A', 'B', 'C']).powerset()) \ No newline at end of file diff --git a/exam_template/python/Python.py b/exam_template/python/Python.py new file mode 100644 index 0000000..e69de29 diff --git a/exam_template/python/Relations.py b/exam_template/python/Relations.py new file mode 100644 index 0000000..9272cfe --- /dev/null +++ b/exam_template/python/Relations.py @@ -0,0 +1,182 @@ +from sys import argv +from pathlib import Path + +from common import printc, printerr +from Graph import Graph + +# Increase if hasse diagram becomes too clobbered +HEIGHT_SEPARATOR = 1 + +def pairToString(pair): + if str(pair[0]).isdigit() or str(pair[1]).isdigit(): + return f'({pair[0]},{pair[1]})' + else: + return pair[0] + pair[1] + +def stringToPair(string): + if string[0] == '(': + return tuple(string.split(',')[0][1:], string.split(',')[0][:-1]) + else: + return tuple(list(string)) + + +class Relation: + def __init__(self, pairs): # pair = (str,str) + self.pairs = set(pairs) + + def __str__(self): + return f'\\{{ {", ".join(x + y for x,y in self.pairs)} \}}' + + @classmethod + def fromString(cls, string): + relations = (stringToPair(x) for x in relations.split(' ')) + return cls(relations) + + @classmethod + def fromDivisibilityPosetTo(cls, n): + pairs = set() + for dst in range(n): + pairs.add((dst, dst)) + for src in range(1, dst): + if dst % src == 0: + pairs.add((src, dst)) + return cls(pairs) + + + def isReflexive(self): + elements = set(list(sum(self.pairs, ()))) + result = all((x,x) in self.pairs for x in elements) + + if not result: + printc('Not reflexive, missing following pairs:', color='green') + missingPairs = [(x,x) for x in elements if (x,x) not in self.pairs] + print(f'\\[ {", ".join(pairToString(p) for p in missingPairs)} \\]') + + return result + + def isSymmetric(self): + result = all((y,x) in self.pairs for x,y in self.pairs) + + if not result: + printc('Not symmetric, missing following pairs:', color='green') + missingPairs = [(x,y) for x,y in self.pairs if (y,x) not in self.pairs] + print(f'\\[ {", ".join(pairToString(p) for p in missingPairs)} \\]') + + return result + + def isAntiSymmetric(self): + result = not any((y,x) in self.pairs and y != x for x,y in self.pairs) + + if not result: + printc('Not antisymmetric, following pairs are symmetric:', color='green') + symmetricPairs = [((x,y), (y,x)) for x,y in self.pairs if (y,x) in self.pairs and y > x] + print(f'\\[ {", ".join(f"{pairToString(p)} and {pairToString(q)}" for p,q in symmetricPairs)} \\]') + + return result + + def isTransitive(self): + result = True + nonTransitivePairs = [] + + for x,y in self.pairs: + for z,w in self.pairs: + if not (y != z or ((x,w) in self.pairs)): + nonTransitivePairs.append(((x,y), (z,w))) + result = False + + if not result: + printc('Not transitive, following pairs are missing its transitive counterpart:', color='green') + print(f'\\[ {", ".join(f"{pairToString(p)} and {pairToString(q)} without {pairToString((p[0], q[1]))}" for p,q in nonTransitivePairs)} \\]') + + return result + + def isEquivalenceRelation(self): + return self.isReflexive() and self.isSymmetric() and self.isTransitive() + + def isPartialOrder(self): + return self.isReflexive() and self.isAntiSymmetric() and self.isTransitive() + + def getHassePairs(self): + if not self.isPartialOrder(): + printerr('This is not a partial order') + return + hassePairs = set() + for x1, y1 in self.pairs: + for x2, y2 in self.pairs: + if y1 == x2: + hassePairs.add((x1, y2)) + return self.pairs - hassePairs + + def latexifyHasseDiagram(self): + hasse = self.getHassePairs() + min_el = set(a for a,b in hasse if a not in list(zip(*hasse))[1]) + keys = set(item for tup in hasse for item in tup) + y_pos = dict.fromkeys(keys, 0) + + i = 0 + while len(next_row := [val for key,val in hasse if key in [x for x,y in y_pos.items() if y == i] ]) != 0: + for item in next_row: + y_pos[item] = i + 1 + i += 1 + + inv_ypos = dict() + for key in set(y_pos.values()): + inv_ypos[key] = [x for x,y in y_pos.items() if y == key] + + output = [] + + for y in inv_ypos.keys(): + for i, n in enumerate(inv_ypos[y]): + output.append(f'\\node ({n}) at ({i - len(inv_ypos[y])/2}, {y * HEIGHT_SEPARATOR}) {{${n}$}};') + + output.append('') + + for x,y in hasse: + output.append(f'\\draw ({x}) -- ({y});') + + + printc(f"Minimal elements: $\{{ {', '.join(str(e) for e in min_el)} \}}$ \\\\") + + max_el = set(v for k,v in hasse if v not in (x for x,_ in hasse)) + printc(f"Maximal elements: $\{{ {', '.join(str(e) for e in max_el)} \}}$" ) + + return '\n'.join(output) + + def latexifyGraph(self): + if self.isEquivalenceRelation(): + graphType = 'undirected' + pairs = [(x,y) for x,y in self.pairs if x != y] + else: + graphType = 'directed' + pairs = self.pairs + + pass + + +def processFileContent(raw, template): + outputType, inp = raw.split('\n\n') + + if inp.startsWith('divisibilityPosetTo'): + n = int(inp.split(' ')[1]) + relation = Relation.fromDivisibilityPosetTo(n) + else: + relation = Relation.fromString(inp) + + if outputType == 'proveEquivalence': + content = relation.isEquivalenceRelation() + elif outputType == 'provePoset': + content = relation.isPartialOrder() + elif outputType == 'hasseDiagram': + content = relation.latexifyHasseDiagram() + elif outputType == 'graph': + content = relation.latexifyGraph() + + content = Relation.fromString(raw).latexifyHasseDiagram() + return template.replace("%CONTENT", content) + +if __name__ == '__main__': + inp = 'AA BB CC DD AB AC AD BC BD CD' + relations = [stringToPair(p) for p in inp.split(' ')] + # print(Relation(relations).isEquivalenceRelation()) + print(Relation(relations).isPartialOrder()) + print(Relation.fromDivisibilityPosetTo(30).isPartialOrder()) diff --git a/exam_template/python/Truthtable.py b/exam_template/python/Truthtable.py index fe7f7d5..f5e101e 100644 --- a/exam_template/python/Truthtable.py +++ b/exam_template/python/Truthtable.py @@ -1,9 +1,12 @@ from sys import argv from pathlib import Path + +from common import printerr + try: from tabulate import tabulate except: - print('Couldn\'t find tabulate. Do you have it installed?') + printerr('Couldn\'t find tabulate. Do you have it installed?') def parseExpressionList(inputData): @@ -111,11 +114,8 @@ if __name__ == '__main__': exps = parseExpressionList(file.read()) truthtable = generateTruthTable(exps) - # try: from tabulate import tabulate printTruthtable(exps, generateTruthTable(exps)) - # except e: - # print(e) content = latexify(exps, truthtable) diff --git a/exam_template/python/common.py b/exam_template/python/common.py new file mode 100644 index 0000000..974d7ef --- /dev/null +++ b/exam_template/python/common.py @@ -0,0 +1,16 @@ + +clear = '\033[0m' +colors = { + 'red': '\033[31m', + 'green': '\033[32m', + 'yellow': '\033[33m', + 'blue': '\033[34m', + 'purple': '\033[35m', + 'cyan': '\033[36m', +} + +def printc(text, color='blue'): + print(f'{colors[color]}{text}{clear}') + +def printerr(text): + print(f'\033[31;5m[ERROR] {text}{clear}') \ No newline at end of file diff --git a/exam_template/python/run.py b/exam_template/python/run.py index 7d7083d..77ac917 100644 --- a/exam_template/python/run.py +++ b/exam_template/python/run.py @@ -5,9 +5,8 @@ import FSA import Graph import Hasse import Truthtable - -def printred(text): - print(f'\033[31m{text}\033[0m') +import Relations +from common import printerr def fetchContentType(content): new_content = content.split('\n') @@ -25,10 +24,12 @@ def processContent(content): result = Graph.processFileContent('toGraph\n\n' + content, template.read()) elif contentType == 'Matrix': result = Graph.processFileContent('toMatrix\n\n' + content, template.read()) + elif contentType == 'Relations': + result = Relations.processFileContent(content, template.read()) elif contentType == 'Truthtable': result = Truthtable.processFileContent(content, template.read()) else: - print('DIDN\'T RECOGNIZE FILE TYPE') + printerr('DIDN\'T RECOGNIZE FILE TYPE') exit(1) return result diff --git a/exam_template_graphics/graphics/src/equivalenceDiagram.txt b/exam_template_graphics/graphics/src/equivalenceDiagram.txt new file mode 100644 index 0000000..506cb1a --- /dev/null +++ b/exam_template_graphics/graphics/src/equivalenceDiagram.txt @@ -0,0 +1,4 @@ +# Relations +graph + +AA BB CC DD EE AC CA AE EA CE EC BD DB \ No newline at end of file diff --git a/exam_template_graphics/graphics/src/hasse.txt b/exam_template_graphics/graphics/src/hasse.txt index 2d7cea0..07d095d 100644 --- a/exam_template_graphics/graphics/src/hasse.txt +++ b/exam_template_graphics/graphics/src/hasse.txt @@ -1,2 +1,4 @@ -# Hasse +# Relations +hasseDiagram + ab ac ad ae bc ed ec \ No newline at end of file diff --git a/exam_template_graphics/graphics/src/proveEquivalence.txt b/exam_template_graphics/graphics/src/proveEquivalence.txt new file mode 100644 index 0000000..35ce3b3 --- /dev/null +++ b/exam_template_graphics/graphics/src/proveEquivalence.txt @@ -0,0 +1,4 @@ +# Relations +proveEquivalence + +AA BB CC DD EE AC CA AE EA CE EC BD DB \ No newline at end of file diff --git a/exam_template_graphics/graphics/src/provePoset.txt b/exam_template_graphics/graphics/src/provePoset.txt new file mode 100644 index 0000000..1895ad9 --- /dev/null +++ b/exam_template_graphics/graphics/src/provePoset.txt @@ -0,0 +1,4 @@ +# Relations +provePoset + +AA BB CC DD AB AC AD BC CD BD \ No newline at end of file