def test_graph_degree_code(self): g1 = Graph([ (0, 1), (0, 2), (0, 3), (0, 5), (2, 3), (3, 4), (2, 5), (3, 5), (4, 6), (3, 6), ]) g2 = Graph([ (0, 1), (0, 4), (0, 2), (0, 6), (0, 3), (1, 4), (2, 6), (2, 5), (3, 6), (2, 3), ]) self.assertTrue(g1.is_isomorphic(g2)) self.assertEqual(2057732, g1.degree_code()) self.assertEqual(g1.degree_code(), g2.degree_code()) for code, edges in DEGREE_CODE_TABLE: self.assertEqual(code, Graph(edges).degree_code())
def test_graph_duplicate(self): g1 = Graph([(0, 1), (1, 2), (2, 3), (0, 3)]) g2 = g1.duplicate([2, 3], 1) self.assertEqual(set(g2.edges), { (0, 1), (1, 2), (4, 5), (1, 4), (2, 3), (0, 5), (0, 3), })
def from_profile(self, params: Dict[str, Any]) -> None: """Simple load by dict object.""" # Customize points and multiple joints g = Graph(params['graph']) expression: str = params['expression'] pos_list = parse_pos(expression) cus: Dict[int, int] = params['cus'] same: Dict[int, int] = params['same'] self.cus = cus self.same = same for node, ref in sorted(self.same.items()): pos_list.insert(node, pos_list[ref]) self.set_graph(g, {i: (x, y) for i, (x, y) in enumerate(pos_list)}) # Grounded setting for row in self.grounded_detect(set(params['placement']), g, self.same): self.set_grounded(row) # Driver setting input_list: List[Tuple[Tuple[int, int], Tuple[float, float]]] = params['input'] self.driver.clear() self.driver.update(b for (b, _), _ in input_list) # Target setting target: Dict[int, Sequence[_Coord]] = params['target'] self.target.clear() self.target.update(target) self.update()
def __edges2atlas(self) -> None: """Turn the text files into a atlas image. This operation will load all edges to list widget first. """ file_names = self.input_from_multiple("edges data", ["Text file (*.txt)"]) if not file_names: return read_data = [] for file_name in file_names: with open(file_name, 'r', encoding='utf-8') as f: for line in f: read_data.append(line) answer = [] for edges in read_data: try: g = Graph(eval(edges)) except (SyntaxError, TypeError): QMessageBox.warning(self, "Wrong format", "Please check text format.") else: answer.append(g) if not answer: QMessageBox.information(self, "No data", "The graph data is empty.") return self.answer = answer self.__set_time_count(0, len(answer)) self.__reload_atlas() self.__save_atlas()
def __from_mechanism(self) -> None: """From a generalized mechanism of main canvas.""" if self.vpoints and self.vlinks: graph, _, _, _, _, _ = self.get_graph() else: graph = Graph([]) if graph.edges: self.edges_text.setText(str(list(graph.edges))) else: self.edges_text.setText("") keep_dof_checked = self.keep_dof.isChecked() self.keep_dof.setChecked(False) self.nl_input.setValue(len(graph.vertices)) self.nj_input.setValue(len(graph.edges)) self.keep_dof.setChecked(keep_dof_checked) # Show attributes QMessageBox.information( self, "Generalization", f"Link assortment:\n{link_assortment(graph)}\n" f"Contracted link assortment:\n{contracted_link_assortment(graph)}" if graph.edges else "Is a empty graph." )
def test_graph_loop(self): g1 = Graph([ (0, 1), (4, 10), (1, 3), (2, 9), (5, 6), (4, 5), (5, 7), (8, 10), (1, 8), (9, 11), (3, 6), (0, 4), (3, 7), (2, 5), (0, 2), (4, 11), ]) pos = external_loop_layout(g1, True) self.assertEqual(set(g1.vertices), set(pos))
def test_graph_planar(self): g1 = Graph([ (0, 1), (2, 7), (1, 5), (1, 6), (3, 6), (0, 4), (3, 7), (2, 5), (3, 4), (0, 2), ]) self.assertTrue(is_planar(g1)) self.assertEqual([4, 4], link_assortment(g1)) self.assertEqual([4, 0, 0, 0], contracted_link_assortment(g1))
def add_collection(self, edges: Iterable[Tuple[int, int]], *, reload: bool = True) -> None: """Add collection by in put edges.""" error = self.__is_valid_graph(edges) if error: QMessageBox.warning(self, "Add Collection Error", f"Error: {error}") return self.collections.append(Graph(edges)) self.project_no_save() if reload: self.__reload_atlas()
def clear(self) -> None: """Clear the attributes.""" self.graph = Graph([]) self.cus.clear() self.same.clear() self.pos.clear() self.status.clear() self.grounded = -1 self.driver.clear() self.target.clear() self.update()
def __init__(self, parent: QWidget): """Input parameters and attributes.""" super(PreviewCanvas, self).__init__(parent) self.graph = Graph([]) self.cus = {} self.same = {} self.pos = {} self.status = {} # Additional attributes self.grounded = -1 self.driver = set() self.target = set() self.clear()
def __is_valid_graph(self, edges: Iterable[Tuple[int, int]]) -> str: """Test graph and return True if it is valid.""" try: g = Graph(edges) except (TypeError, ValueError): return "wrong format" if not g.edges: return "is an empty graph" if not g.is_connected(): return "is not a close chain" if not is_planar(g): return "is not a planar chain" if g.has_cut_link(): return "has cut link" try: external_loop_layout(g, True) except ValueError as error: return str(error) for h in self.collections: if g.is_isomorphic(h): return f"is isomorphic with: {h.edges}" return ""
def __from_profile(self) -> None: """Show up the dialog to load structure data.""" dlg = CollectionsDialog( self.collections, self.get_configure, self.project_no_save, self.prefer.tick_mark_option, self.configure_canvas.monochrome, self ) dlg.show() if not dlg.exec_(): dlg.deleteLater() return # Add customize joints params = dlg.params graph = Graph(params['graph']) expression: str = params['expression'] pos_list = parse_pos(expression) cus: Dict[int, int] = params['cus'] same: Dict[int, int] = params['same'] for node, ref in sorted(same.items()): pos_list.insert(node, pos_list[ref]) if not self.set_graph(graph, {i: (x, y) for i, (x, y) in enumerate(pos_list)}): dlg.deleteLater() return self.profile_name.setText(dlg.name) dlg.deleteLater() del dlg self.configure_canvas.cus = cus self.configure_canvas.same = same # Grounded setting for row in PreviewCanvas.grounded_detect(set(params['placement']), graph, same): self.__set_grounded(row) # Driver, Target input_list: List[Tuple[Tuple[int, int], Tuple[float, float]]] = params['input'] self.driver_list.addItems(f"(P{b}, P{d})" for (b, d), _ in input_list) self.configure_canvas.set_driver([d for d, _ in input_list]) _set_warning(self.driver_label, self.driver_list.count() == 0) target_list: Dict[int, Sequence[_Coord]] = params['target'] self.configure_canvas.set_target(sorted(target_list)) self.target_list.addItems(f"P{n}" for n in target_list) _set_warning(self.target_label, self.target_list.count() == 0) # Expression self.expr_show.setText(params['expression'])
def __init__(self, parent: QWidget): """Input parameters and attributes. + Origin graph + Customize points: Dict[str, int] + Multiple joints: Dict[int, int] + Positions: Dict[int, Tuple[float, float]] + Joint status: Dict[int, bool] + Name dict: Dict['P0', 'A'] """ super(PreviewCanvas, self).__init__(parent) self.graph = Graph([]) self.cus: Dict[int, int] = {} self.same: Dict[int, int] = {} self.pos: Dict[int, _Coord] = {} self.status: Dict[int, bool] = {} # Additional attributes. self.grounded = -1 self.driver: Set[int] = set() self.target: Set[int] = set() self.clear()
def test_graph_degenerate(self): g1 = Graph([(0, 1), (0, 2), (2, 1), (0, 3), (3, 4), (4, 5), (5, 1)]) self.assertTrue(g1.is_degenerate())
def get_graph(self) -> Tuple[ Graph, List[int], List[Tuple[int, int]], Dict[int, Tuple[float, float]], Dict[int, int], Dict[int, int] ]: """Generalization Algorithm Return edges data, grounded list, variable list and multiple joints. VLinks will become graph vertices. """ link_names = [vlink.name for vlink in self.vlink_list] input_pair = set() for b, d, _ in self.inputs_widget.input_pairs(): input_pair.update({b, d}) # links name for RP joint k = len(self.vlink_list) graph = Graph([]) grounded_list = [] pos = {} same = {} used_point: Set[int] = set() mapping = {} # Link names will change to index number for i, vlink in enumerate(self.vlink_list): for p in vlink.points: if p in used_point: continue vpoint = self.vpoint_list[p] base_num = len(graph.edges) mapping[p] = base_num pos[base_num] = (vpoint.x, vpoint.y) for link_name in vpoint.links: if vlink.name == link_name: continue m = link_names.index(link_name) grounded = VLink.FRAME in {vlink.name, link_name} ref_num = len(graph.edges) if ref_num != base_num: pos[ref_num] = (vpoint.x, vpoint.y) if vpoint.type == VJoint.RP: graph.add_edge(i, k) if grounded: grounded_list.append(len(graph.edges)) graph.add_edge(k, m) k += 1 else: if ref_num != base_num: same[ref_num] = base_num graph.add_edge(i, m) if grounded and ref_num not in same: grounded_list.append(ref_num) used_point.add(p) counter = len(graph.edges) cus = {} for vpoint in self.vpoint_list: if len(vpoint.links) == 1: cus[counter] = link_names.index(vpoint.links[0]) counter += 1 return ( graph, grounded_list, [(mapping[b], mapping[d]) for b, d, _ in self.inputs_widget.input_pairs()], pos, cus, same, )
def graph2icon(g: Graph, width: int, node_mode: bool, show_label: bool, monochrome: bool, *, except_node: Optional[int] = None, engine: str = "", pos: Optional[_Pos] = None) -> QIcon: """Draw a generalized chain graph.""" if engine: pos = engine_picker(g, engine, node_mode) if pos is None: raise ValueError("no engine selected") if not pos: pixmap = QPixmap(width, width) pixmap.fill(Qt.transparent) return QIcon(pixmap) width_bound = -float('inf') for x, y in pos.values(): if abs(x) > width_bound: width_bound = x if abs(y) > width_bound: width_bound = y width_bound *= 2.5 image = QImage(QSize(int(width_bound), int(width_bound)), QImage.Format_ARGB32_Premultiplied) image.fill(Qt.transparent) painter = QPainter(image) painter.translate(image.width() / 2, image.height() / 2) pen = QPen() r = int(width_bound / 50) pen.setWidth(r) painter.setPen(pen) _font.setPixelSize(r * 6) painter.setFont(_font) # Draw edges if node_mode: for l1, l2 in g.edges: if except_node in {l1, l2}: pen.setColor(Qt.gray) else: pen.setColor(Qt.black) painter.setPen(pen) painter.drawLine(QPointF(pos[l1][0], -pos[l1][1]), QPointF(pos[l2][0], -pos[l2][1])) else: color = color_qt('dark-gray') if monochrome else LINK_COLOR color.setAlpha(150) painter.setBrush(QBrush(color)) for link in g.vertices: if link == except_node: pen.setColor(Qt.gray) else: pen.setColor(Qt.black) painter.setPen(pen) painter.drawPolygon(*convex_hull([(pos[n][0], -pos[n][1]) for n, edge in edges_view(g) if link in edge], as_qpoint=True)) # Draw vertices for k, (x, y) in pos.items(): if node_mode: color = color_num(len(list(g.neighbors(k))) - 1) if k == except_node: color.setAlpha(150) else: if monochrome: color = Qt.black elif except_node in dict(edges_view(g))[k]: color = color_qt('green') else: color = color_qt('blue') pen.setColor(color) painter.setPen(pen) painter.setBrush(QBrush(color)) point = QPointF(x, -y) painter.drawEllipse(point, r, r) if show_label: pen.setColor(Qt.darkMagenta) painter.setPen(pen) painter.drawText(point, str(k)) painter.end() return QIcon(QPixmap.fromImage(image).scaledToWidth(width))
def test_graph_basic(self): """Test 'graph' libraries.""" g1 = Graph([(0, 1), (0, 4), (1, 5), (2, 3), (2, 4), (3, 5), (4, 5)]) self.assertFalse(g1.is_degenerate()) self.assertTrue(g1.is_connected()) self.assertEqual(1, g1.dof())
def test_graph_isomorphic(self): g1 = Graph([(0, 1), (0, 4), (1, 5), (2, 3), (2, 4), (3, 5), (4, 5)]) g2 = Graph([(0, 2), (0, 4), (1, 3), (1, 4), (2, 5), (3, 5), (4, 5)]) g3 = Graph([(0, 1), (0, 2), (1, 4), (2, 5), (3, 4), (3, 5), (4, 5)]) self.assertTrue(g1.is_isomorphic(g2)) self.assertFalse(g1.is_isomorphic(g3))