.. _comparison: ########## Comparison ########## .. testsetup:: >>> from ragraph.node import Node >>> from ragraph.edge import Edge >>> from ragraph.graph import Graph >>> import ragraph.plot >>> >>> delta_dsm_path_1 = str(outdir / "delta_dsm_path_1.svg") >>> delta_dsm_path_2 = str(outdir / "delta_dsm_path_2.svg") >>> sigma_dsm_path_1 = str(outdir / "sigma_dsm_path_1.svg") >>> sigma_dsm_path_2 = str(outdir / "sigma_dsm_path_2.svg") >>> sigma_dsm_path_3 = str(outdir / "sigma_dsm_path_3.svg") >>> >>> n0 = Node(name="a") >>> n1 = Node(name="b") >>> n2 = Node(name="c") >>> n3 = Node(name="d") >>> n4 = Node(name="e") >>> n5 = Node(name="f") >>> >>> nodes_a = [n0, n1, n2, n3, n4, n5] >>> edges_a = [ ... Edge(n0, n1, labels=["electric", "information", "mechanical"]), ... Edge(n1, n0, labels=["electric", "information", "mechanical"]), ... Edge(n1, n2, labels=["mechanical"]), ... Edge(n2, n1, labels=["mechanical"]), ... Edge(n2, n3, labels=["electric", "information", "mechanical"]), ... Edge(n2, n4, labels=["electric", "information", "mechanical"]), ... Edge(n2, n5, labels=["electric", "mechanical"]), ... Edge(n3, n2, labels=["electric", "information", "mechanical"]), ... Edge(n3, n4, labels=["electric", "information", "mechanical"]), ... Edge(n4, n2, labels=["electric", "information", "mechanical"]), ... Edge(n4, n3, labels=["electric", "information", "mechanical"]), ... Edge(n5, n2, labels=["electric", "mechanical"]), ... ] >>> >>> n6 = Node(name="b") >>> n7 = Node(name="c") >>> n8 = Node(name="d") >>> n9 = Node(name="e") >>> n10 = Node(name="f") >>> n11 = Node(name="g") >>> >>> nodes_b = [n6, n7, n8, n9, n10, n11] >>> edges_b = [ ... Edge(n6, n7, labels=["mechanical"]), ... Edge(n6, n9, labels=["electric", "information", "mechanical"]), ... Edge(n7, n6, labels=["mechanical"]), ... Edge(n7, n8, labels=["electric", "information", "mechanical"]), ... Edge(n7, n9, labels=["mechanical"]), ... Edge(n8, n7, labels=["electric", "information", "mechanical"]), ... Edge(n9, n6, labels=["electric", "information", "mechanical"]), ... Edge(n9, n7, labels=["mechanical"]), ... Edge(n9, n10, labels=["electric", "information"]), ... Edge(n10, n9, labels=["electric", "information"]), ... Edge(n10, n11, labels=["mechanical"]), ... Edge(n11, n10, labels=["mechanical"]), ... ] >>> >>> n12 = Node(name="c") >>> n13 = Node(name="d") >>> n14 = Node(name="e") >>> n15 = Node(name="f") >>> n16 = Node(name="g") >>> n17 = Node(name="h") >>> >>> nodes_c = [n12, n13, n14, n15, n16, n17] >>> edges_c = [ ... Edge(n12, n13, labels=["electric", "information", "mechanical"]), ... Edge(n12, n15, labels=["electric", "information", "mechanical"]), ... Edge(n13, n12, labels=["electric", "information", "mechanical"]), ... Edge(n13, n14, labels=["electric", "information", "mechanical"]), ... Edge(n13, n15, labels=["mechanical"]), ... Edge(n14, n13, labels=["electric", "information", "mechanical"]), ... Edge(n15, n12, labels=["electric", "information", "mechanical"]), ... Edge(n15, n13, labels=["mechanical"]), ... Edge(n15, n16, labels=["electric", "information"]), ... Edge(n16, n15, labels=["electric", "information"]), ... Edge(n16, n17, labels=["electric"]), ... Edge(n17, n16, labels=["electric"]), ... ] >>> >>> graph_a = Graph(nodes=nodes_a, edges=edges_a) >>> graph_b = Graph(nodes=nodes_b, edges=edges_b) >>> graph_c = Graph(nodes=nodes_c, edges=edges_c) >>> >>> fig = ragraph.plot.mdm( ... leafs=graph_a.nodes, ... edges=graph_a.edges, ... style=ragraph.plot.Style( ... piemap={ ... "mode": "relative", ... "display": "labels" ... }, ... ), ... ) >>> fig.write_image(str(outdir / "comparison_dsm_a.svg")) >>> >>> fig = ragraph.plot.mdm( ... leafs=graph_b.nodes, ... edges=graph_b.edges, ... style=ragraph.plot.Style( ... piemap={ ... "mode": "relative", ... "display": "labels" ... }, ... ), ... ) >>> fig.write_image(str(outdir / "comparison_dsm_b.svg")) >>> >>> fig = ragraph.plot.mdm( ... leafs=graph_c.nodes, ... edges=graph_c.edges, ... style=ragraph.plot.Style( ... piemap={ ... "mode": "relative", ... "display": "labels" ... }, ... ), ... ) >>> fig.write_image(str(outdir / "comparison_dsm_c.svg")) In engineering design it is common practice to compare different architectures. For example, to highlight the architectural differences and commonalities between different conceptual designs, different generations of a system, or different products within a portfolio. Hence we have developed the :obj:`~ragraph.analysis.comparison` module which contains the basic analysis classes :obj:`~ragraph.analysis.comparison.DeltaAnalysis` and :obj:`~ragraph.analysis.comparison.SigmaAnalysis`. To illustrate usage of these classes the example dependency structure matrix (DSM) models show in :numref:`comparison_dsm_a`, :numref:`comparison_dsm_b`, :numref:`comparison_dsm_c`. The data behind these models is stored in three :obj:`~ragraph.graph.Graph` objects named ``graph_a``, ``graph_b`` and ``graph_c``. Note that the elements on the the rows and columns of the DSMs, i.e. the nodes of the graphs, and the dependencies between these elements, i.e. the edges of the graphs, partially overlap between the three different models. For example, elements ``c``, ``d``, ``e``, en ``f`` are part of all three DSMs. Similarly, all three DSMs contain a dependency between ``c`` and ``d`` with labels ``electrical``, ``information``, and ``mechanical``. .. figure:: /_static/generated/comparison_dsm_a.svg :alt: Example dependency structure matrix (DSM) A. :name: comparison_dsm_a :align: center Example dependency structure matrix (DSM) A. .. figure:: /_static/generated/comparison_dsm_b.svg :alt: Example dependency structure matrix (DSM) B. :name: comparison_dsm_b :align: center Example dependency structure matrix (DSM) B. .. figure:: /_static/generated/comparison_dsm_c.svg :alt: Example dependency structure matrix (DSM) C. :name: comparison_dsm_c :align: center Example dependency structure matrix (DSM) C. ************** Delta analysis ************** If one is interested in the differences and commonalities between two graphs one can perform a delta analysis using the :obj:`~ragraph.analysis.comparison.DeltaAnalysis` class, which you can easily instantiate using the code below: .. doctest:: >>> from ragraph.analysis.comparison import DeltaAnalysis >>> da = DeltaAnalysis(graph_a=graph_a, graph_b=graph_b) In this case, ``graph_a``, shown in :numref:`comparison_dsm_a`, is compared with ``graph_b``, shown in :numref:`comparison_dsm_b`. Internally, the :obj:`~ragraph.analysis.comparison.DeltaAnalysis` object creates nodes and edges that can reflect the differences and commonalities between the provided graphs. You can visualize these nodes and edges using the regular :obj:`~ragraph.plot.mdm` method: .. doctest:: >>> fig = ragraph.plot.mdm( ... leafs=da.nodes, ... edges=da.edges, ... style=ragraph.plot.Style( ... piemap={ ... "mode": "relative", ... }, ... ), ... ) >>> fig.write_image(delta_dsm_path_1) which yields :numref:`delta_dsm_1`. Note that the rows and columns are divided into three domains by dashed lines. The first domain, containing only node ``a``, contains those nodes that are unique to ``graph_a``. The second domain (rows, cols 2-5) contains those nodes that are part of both graphs. The third domain, containing only node ``g``, contains those nodes that are unique to ``graph_b``. The graph contains three edge kinds. Edges that are unique to ``graph_a`` (orange), edges that are unique to ``graph_b`` (green), and edges that are part of both graphs (blue). In case, there are four edges that are common in both graphs. Note that element ``e`` is part of both graphs, yet none of its edges are common. In both graphs, ``e`` has a dependency with element ``c``. However, the assigned labels differ. .. figure:: /_static/generated/delta_dsm_path_1.svg :alt: Delta DSM. :name: delta_dsm_1 :align: center Delta DSM showing the differences and commonalities between ``graph_a`` and ``graph_b``. Similarly, the differences and commonalities between ``graph_a`` and ``graph_c`` can be visualized: .. doctest:: >>> from ragraph.analysis.comparison import DeltaAnalysis >>> da = DeltaAnalysis(graph_a=graph_a, graph_b=graph_c) >>> fig = ragraph.plot.mdm( ... leafs=da.nodes, ... edges=da.edges, ... style=ragraph.plot.Style( ... piemap={ ... "mode": "relative", ... }, ... ), ... ) >>> fig.write_image(delta_dsm_path_2) which results in :numref:`delta_dsm_2`. In this case, the first domain contains nodes ``a`` and ``b``. The third domain contains nodes ``g`` and ``h``. Nodes ``c``, ``d``, ``e``, and ``f`` are common among both graphs. .. figure:: /_static/generated/delta_dsm_path_2.svg :alt: Delta DSM. :name: delta_dsm_2 :align: center Delta DSM showing the differences and commonalities between ``graph_a`` and ``graph_c``. ************** Sigma analysis ************** When you're interested in identifying the commonalities between :math:`N` graphs the sigma analysis enables you to do so. For example, to find the 'common core' architecture of a group of products within a product portfolio. You can perform a sigma analysis by instantiating the :obj:`~ragraph.analysis.comparison.SigmaAnalysis` with a list of graphs as arguments: .. doctest:: >>> from ragraph.analysis.comparison import SigmaAnalysis >>> sa = SigmaAnalysis(graphs=[graph_a, graph_b, graph_c]) In this case, we perform a sigma analysis on ``graph_a``, ``graph_b``, and ``graph_c``. Internally, the :obj:`~ragraph.analysis.comparison.SigmaAnalysis` object creates nodes and edges that can reflect the commonalities between the provided graphs. You can visualize these nodes and edges using the regular :obj:`~ragraph.plot.mdm` method: .. doctest:: >>> fig = ragraph.plot.mdm( ... leafs=sa.nodes, ... edges=sa.edges, ... style=ragraph.plot.Style( ... piemap={ ... "mode": "relative", ... "display": "weights", ... "fields": ["_count"] ... }, ... legend={"height": 6, "n_ticks": 3}, ... ), ... ) >>> fig.write_image(sigma_dsm_path_1) which results in :numref:`sigma_dsm_1`. This figure displays the ``_count`` edge count as a fraction of the number of provided graphs. A value of 1 implies that the respective edge is present within all of the provided graphs. A value of 0.33 implies that the edge is only present in one-third of the provided graphs. In computing the ``_count`` edge count differences in edge labels are ignored. .. figure:: /_static/generated/sigma_dsm_path_1.svg :alt: Sigma DSM. :name: sigma_dsm_1 :align: center Sigma DSM showing edge count fractions. If you want to visualize the absolute edge counts, you can simply set the :obj:`~ragraph.analysis.comparison.DeltaAnalysis.mode` property to `"absolute"`: .. doctest:: >>> sa.mode = "absolute" and subsequently re-visualize the nodes and edges: .. doctest:: >>> fig = ragraph.plot.mdm( ... leafs=sa.nodes, ... edges=sa.edges, ... style=ragraph.plot.Style( ... piemap={ ... "mode": "relative", ... "display": "weights", ... "fields": ["_count"] ... }, ... legend={"height": 6, "n_ticks": 3}, ... ), ... ) >>> fig.write_image(sigma_dsm_path_2) yielding :numref:`sigma_dsm_2`. This figure displays the ``_count`` edge counts as absolute numbers. .. figure:: /_static/generated/sigma_dsm_path_2.svg :alt: Sigma DSM. :name: sigma_dsm_2 :align: center Sigma DSM showing absolute edge counts. Besides visualizing ``_count`` edge counts, you can visualize edge ``label`` counts as well. You only have change the ``fields`` property within the piemap style options: .. doctest:: >>> fig = ragraph.plot.mdm( ... leafs=sa.nodes, ... edges=sa.edges, ... style=ragraph.plot.Style( ... piemap={ ... "mode": "relative", ... "display": "weights", ... "fields": [ ... "electric", ... "information", ... "mechanical" ... ] ... }, ... legend={"height": 6, "n_ticks": 3}, ... ), ... ) >>> fig.write_image(sigma_dsm_path_3) which yields :numref:`sigma_dsm_3`. This figure shows the absolute edge label counts. The edges between ``c`` and ``e`` are, for example, present within two out of three graphs. In both graphs the edge has label ``mechanical``, yet in only one graph it has labels ``information`` and ``electric``. .. figure:: /_static/generated/sigma_dsm_path_3.svg :alt: Sigma DSM. :name: sigma_dsm_3 :align: center Sigma DSM showing edge label counts.