From b53552db3f2542c6d9a34deef1416e12ba9166c8 Mon Sep 17 00:00:00 2001
From: cfrainay <clement.frainay@inrae.fr>
Date: Fri, 18 Mar 2022 17:10:48 +0100
Subject: [PATCH 1/3] [ToolBox] create advanced compound graph creation

---
 .../networkAnalysis/CompoundNet.java          | 180 ++++++++++++++++++
 1 file changed, 180 insertions(+)
 create mode 100644 met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java

diff --git a/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java
new file mode 100644
index 000000000..43ee1635a
--- /dev/null
+++ b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java
@@ -0,0 +1,180 @@
+package fr.inrae.toulouse.metexplore.met4j_toolbox.networkAnalysis;
+
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioMetabolite;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioNetwork;
+import fr.inrae.toulouse.metexplore.met4j_core.biodata.collection.BioCollection;
+import fr.inrae.toulouse.metexplore.met4j_graph.computation.connect.weighting.*;
+import fr.inrae.toulouse.metexplore.met4j_graph.computation.transform.EdgeMerger;
+import fr.inrae.toulouse.metexplore.met4j_graph.computation.transform.VertexContraction;
+import fr.inrae.toulouse.metexplore.met4j_graph.computation.utils.ComputeAdjacencyMatrix;
+import fr.inrae.toulouse.metexplore.met4j_graph.core.WeightingPolicy;
+import fr.inrae.toulouse.metexplore.met4j_graph.core.compound.CompoundGraph;
+import fr.inrae.toulouse.metexplore.met4j_graph.core.compound.ReactionEdge;
+import fr.inrae.toulouse.metexplore.met4j_graph.io.Bionetwork2BioGraph;
+import fr.inrae.toulouse.metexplore.met4j_graph.io.ExportGraph;
+import fr.inrae.toulouse.metexplore.met4j_graph.io.NodeMapping;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.reader.JsbmlReader;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.reader.Met4jSbmlReaderException;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.reader.plugin.FBCParser;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.reader.plugin.GroupPathwayParser;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.reader.plugin.NotesParser;
+import fr.inrae.toulouse.metexplore.met4j_io.jsbml.reader.plugin.PackageParser;
+import fr.inrae.toulouse.metexplore.met4j_mathUtils.matrix.ExportMatrix;
+import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.AbstractMet4jApplication;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+
+public class CompoundNet extends AbstractMet4jApplication {
+
+    @Option(name = "-s", usage = "input SBML file", required = true)
+    public String inputPath = null;
+
+    @Option(name = "-sc", usage = "input Side compound file", required = false)
+    public String inputSide = null;
+
+    @Option(name = "-o", usage = "output Graph file", required = true)
+    public String outputPath = null;
+
+    @Option(name = "-mc", aliases = {"--nocomp"}, usage = "merge compartments (requires unique compound names that are consistent across compartments)", required = false)
+    public boolean mergeComp = false;
+
+    @Option(name = "-me", aliases = {"--simple"}, usage = "merge parallel edges to produce a simple graph", required = false)
+    public boolean mergeEdges = false;
+
+    @Option(name = "-ri", aliases = {"--removeIsolatedNodes"}, usage = "remove isolated nodes", required = false)
+    public boolean removeIsolated = false;
+
+    @Option(name = "-dw", aliases = {"--degreeWeights"}, usage = "penalize traversal of hubs by using degree square weighting", forbids = {"-cw", "-sw"})
+    public Boolean degree = false;
+    @Option(name = "-cw", aliases = {"--customWeights"}, usage = "an optional file containing weights for compound pairs", forbids = {"-dw", "-sw"})
+    public String weightFile = null;
+
+    @Option(name = "-un", aliases = {"--undirected"}, usage = "create as undirected", required = false)
+    public boolean undirected = false;
+
+    @Option(name = "-tp", aliases = {"--transitionproba"}, usage = "set weight as random walk transition probability, normalized by reaction", required = false)
+    public boolean computeWeight = false;
+
+    @Option(name = "-am", aliases = {"--asmatrix"}, usage = "export as matrix (implies simple graph conversion). Default export as GML file", required = false)
+    public boolean asMatrix = false;
+
+    public static void main(String[] args) throws IOException, Met4jSbmlReaderException {
+
+        CompoundNet app = new CompoundNet();
+
+        app.parseArguments(args);
+
+        app.run();
+
+    }
+
+
+    public void run() throws IOException, Met4jSbmlReaderException {
+        System.out.print("Reading SBML...");
+        JsbmlReader reader = new JsbmlReader(this.inputPath, false);
+        ArrayList<PackageParser> pkgs = new ArrayList<>(Arrays.asList(
+                new NotesParser(false), new FBCParser(), new GroupPathwayParser()));
+        BioNetwork network = reader.read(pkgs);
+        System.out.println(" Done.");
+
+
+        System.out.print("Buildinig Network...");
+        Bionetwork2BioGraph builder = new Bionetwork2BioGraph(network);
+        CompoundGraph graph = builder.getCompoundGraph();
+        System.out.println(" Done.");
+
+        //Graph processing: side compound removal [optional]
+        if (inputSide != null) {
+            System.err.println("removing side compounds...");
+            NodeMapping<BioMetabolite, ReactionEdge, CompoundGraph> mapper = new NodeMapping<>(graph).skipIfNotFound();
+            BioCollection<BioMetabolite> sideCpds = mapper.map(inputSide);
+            boolean removed = graph.removeAllVertices(sideCpds);
+            if (removed) System.err.println(sideCpds.size() + " compounds removed.");
+        }
+
+        //Graph processing: set weights [optional]
+        WeightingPolicy<BioMetabolite, ReactionEdge, CompoundGraph> wp = new DefaultWeightPolicy<>();
+        if (weightFile != null) {
+            System.err.println("Setting edge weights...");
+            wp = new WeightsFromFile(weightFile);
+        } else if (degree) {
+            System.err.println("Setting edge weights...");
+            int pow = 2;
+            wp = new DegreeWeightPolicy(pow);
+        }
+        wp.setWeight(graph);
+
+        //invert graph as undirected (copy edge weight to reversed edge)
+       if(undirected){
+           System.out.print("Create Undirected...");
+           graph.asUndirected();
+           System.out.println(" Done.");
+       }
+
+        //merge compartment
+        if(mergeComp){
+            System.out.print("Merging compartments...");
+            VertexContraction vc = new VertexContraction();
+            graph = vc.decompartmentalize(graph, new VertexContraction.MapByName());
+            System.out.println(" Done.");
+        }
+
+        //remove isolated nodes
+        if(removeIsolated){
+            System.out.println("Remove isolated nodes...");
+            HashSet<BioMetabolite> nodes = new HashSet<>(graph.vertexSet());
+            graph.removeIsolatedNodes();
+            nodes.removeAll(graph.vertexSet());
+            for(BioMetabolite n : nodes){
+                System.out.println("\tremoving " + n.getName());
+            }
+            System.out.println(" Done.");
+        }
+
+        //compute transitions probability from weights
+        if(computeWeight) {
+            System.out.print("Compute transition matrix...");
+            ReactionProbabilityWeight wp2 = new ReactionProbabilityWeight();
+            wp2.setWeight(graph);
+            System.out.println(" Done.");
+        }
+
+        //merge parallel edges
+        if(mergeEdges){
+            System.out.print("Merging edges...");
+            EdgeMerger.mergeEdgesWithOverride(graph);
+            System.out.println(" Done.");
+        }
+
+        //export graph
+        System.out.print("Exporting...");
+        if(asMatrix){
+            ComputeAdjacencyMatrix adjBuilder = new ComputeAdjacencyMatrix(graph);
+            if(!computeWeight) adjBuilder.parallelEdgeWeightsHandling((u, v) -> Math.max(u,v));
+            ExportMatrix.toCSV(this.outputPath,adjBuilder.getadjacencyMatrix());
+        }else{
+            ExportGraph.toGmlWithAttributes(graph, this.outputPath, true);
+        }
+        System.out.println(" Done.");
+        return;
+    }
+
+    @Override
+    public String getLabel() {return this.getClass().getSimpleName();}
+
+    @Override
+    public String getLongDescription() {
+        return "Metabolic networks used for quantitative analysis often contain links that are irrelevant for graph-based structural analysis. For example, inclusion of side compounds or modelling artifacts such as 'biomass' nodes.\n" +
+                "While Carbon Skeleton Graph offer a relevant alternative topology for graph-based analysis, it requires compounds' structure information, usually not provided in model, and difficult to retrieve for model with sparse cross-reference annotations.\n" +
+                "In contrary to the SBML2Graph app that performs a raw conversion of the SBML content, the present app propose a fine-tuned creation of compound graph from predefined list of side compounds and degree² weighting to get relevant structure without structural data."+
+                "This app also enable Markov-chain based analysis of metabolic networks by computing reaction-normalized transition probabilities on the network.";
+    }
+
+    @Override
+    public String getShortDescription() {return "Advanced creation of a compound graph representation of a SBML file content";}
+}
+
-- 
GitLab


From 1e0428ec6e1da4dff162e718a7825322156a4899 Mon Sep 17 00:00:00 2001
From: cfrainay <clement.frainay@inrae.fr>
Date: Fri, 18 Mar 2022 17:17:10 +0100
Subject: [PATCH 2/3] Allow compartment merging from identifier suffix

---
 .../met4j_toolbox/networkAnalysis/CompoundNet.java  | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java
index 43ee1635a..bc0b5d22d 100644
--- a/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java
+++ b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java
@@ -27,6 +27,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.function.Function;
 
 public class CompoundNet extends AbstractMet4jApplication {
 
@@ -39,8 +40,11 @@ public class CompoundNet extends AbstractMet4jApplication {
     @Option(name = "-o", usage = "output Graph file", required = true)
     public String outputPath = null;
 
-    @Option(name = "-mc", aliases = {"--nocomp"}, usage = "merge compartments (requires unique compound names that are consistent across compartments)", required = false)
-    public boolean mergeComp = false;
+    enum strategy {by_name,by_id}
+    @Option(name = "-mc", aliases = {"--mergecomp"}, usage = "merge compartments. " +
+            "Use names if consistent and unambiguous across compartments, or identifiers if compartment suffix is present (id in form \"xxx_y\" with xxx as base identifier and y as compartment label).")
+    public strategy mergingStrat = null;
+    public String idRegex = "^(\\w+)_\\w$";
 
     @Option(name = "-me", aliases = {"--simple"}, usage = "merge parallel edges to produce a simple graph", required = false)
     public boolean mergeEdges = false;
@@ -116,10 +120,11 @@ public class CompoundNet extends AbstractMet4jApplication {
        }
 
         //merge compartment
-        if(mergeComp){
+        if(mergingStrat!=null){
             System.out.print("Merging compartments...");
             VertexContraction vc = new VertexContraction();
-            graph = vc.decompartmentalize(graph, new VertexContraction.MapByName());
+            VertexContraction.Mapper merger = mergingStrat.equals(strategy.by_name) ? new VertexContraction.MapByName() : new VertexContraction.MapByIdSubString(idRegex);
+            graph = vc.decompartmentalize(graph, merger);
             System.out.println(" Done.");
         }
 
-- 
GitLab


From 027b476cc26a5ff8faae74132ed1f94ea25880b3 Mon Sep 17 00:00:00 2001
From: Ludovic Cottret <ludovic.cottret@inra.fr>
Date: Fri, 1 Apr 2022 17:11:23 +0200
Subject: [PATCH 3/3] Update JsbmlReader call

---
 .../metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java
index bc0b5d22d..c09ef4d57 100644
--- a/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java
+++ b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/CompoundNet.java
@@ -79,7 +79,7 @@ public class CompoundNet extends AbstractMet4jApplication {
 
     public void run() throws IOException, Met4jSbmlReaderException {
         System.out.print("Reading SBML...");
-        JsbmlReader reader = new JsbmlReader(this.inputPath, false);
+        JsbmlReader reader = new JsbmlReader(this.inputPath);
         ArrayList<PackageParser> pkgs = new ArrayList<>(Arrays.asList(
                 new NotesParser(false), new FBCParser(), new GroupPathwayParser()));
         BioNetwork network = reader.read(pkgs);
-- 
GitLab