diff --git a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/computation/transform/VertexContraction.java b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/computation/transform/VertexContraction.java index 4413ca4978d4cbabb25a37218d6cb2e61c952b23..481a4d8124e8f20ba27f293e07044396d04df0f0 100644 --- a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/computation/transform/VertexContraction.java +++ b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/computation/transform/VertexContraction.java @@ -40,6 +40,8 @@ import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioMetabolite; import fr.inrae.toulouse.metexplore.met4j_graph.core.BioGraph; import fr.inrae.toulouse.metexplore.met4j_graph.core.Edge; import fr.inrae.toulouse.metexplore.met4j_graph.core.compound.CompoundGraph; +import fr.inrae.toulouse.metexplore.met4j_graph.core.meta.MetaGraph; +import fr.inrae.toulouse.metexplore.met4j_graph.core.meta.MetaNode; import java.util.*; import java.util.function.Function; @@ -103,7 +105,7 @@ public class VertexContraction<V extends BioEntity,E extends Edge<V>, G extends */ public static <V extends BioEntity, E extends Edge<V>, G extends BioGraph<V,E>> G contractBy(G g, Function<V,String> groupingFunction, Function<List<V>,V> pickFunction){ G g2 = (G) g.clone(); - Map<String, List<V>> groupedNodes = g.vertexSet().stream().collect(Collectors.groupingBy(groupingFunction)); + Map<String, List<V>> groupedNodes = g.vertexSet().stream().filter(x->(groupingFunction.apply(x)!=null)).collect(Collectors.groupingBy(groupingFunction)); for(List<V> toContract : groupedNodes.values()){ V v = pickFunction.apply(toContract); toContract.remove(v); @@ -112,6 +114,33 @@ public class VertexContraction<V extends BioEntity,E extends Edge<V>, G extends return g2; } + /** + * Contract all nodes in graph from an aggregation function, which provide a common group id (key) for each member of a set to contract + * creates a MetaGraph, where each node contains the set of contracted nodes sharing the same group id + * @param g the graph + * @param groupingFunction the aggregation function + * @param <V> the node class + * @param <E> the edge class + * @param <G> the graph class + * @return a meta-graph with super-vertices referencing the contracted nodes + */ + public static <V extends BioEntity, E extends Edge<V>, G extends BioGraph<V,E>> MetaGraph<V,E> contractByAsMeta(G g, Function<V,String> groupingFunction){ + MetaGraph<V,E> g2 = new MetaGraph<V,E>(); + Map<String, List<V>> groupedNodes = g.vertexSet().stream().filter(x->(groupingFunction.apply(x)!=null)).collect(Collectors.groupingBy(groupingFunction)); + for(Map.Entry<String, List<V>> toContract : groupedNodes.entrySet()) { + g2.addVertex(new MetaNode<>(toContract.getKey(), toContract.getValue())); + } + for(E edge : g.edgeSet()){ + try{ + g2.addEdgesFromSubVertices(edge); + }catch (IllegalArgumentException e){ + System.err.println("Edge "+edge.getV1().getId()+"->"+edge.getV2().getId()+" contains node ignored during vertex contraction " + + " (this may happen if some node as undefined arguments)"); + } + } + return g2; + } + /** * Remove compartment in a compound graph by contracting all nodes sharing a given attribute provided by the mapper * @param g the graph to decompartmentalize diff --git a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/BioGraph.java b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/BioGraph.java index 5068f5eff25fc43e698bcaef4fc5e51133f878ac..eb18662c47162bc25a2ae4fdce8de13b4e47d9c2 100644 --- a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/BioGraph.java +++ b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/BioGraph.java @@ -417,7 +417,7 @@ public abstract class BioGraph<V extends BioEntity, E extends Edge<V>> extends D */ /** {@inheritDoc} */ @Override - public final boolean removeEdge(E e) { + public boolean removeEdge(E e) { return super.removeEdge(e); } @@ -426,7 +426,7 @@ public abstract class BioGraph<V extends BioEntity, E extends Edge<V>> extends D */ /** {@inheritDoc} */ @Override - public final boolean removeVertex(V arg0) { + public boolean removeVertex(V arg0) { return super.removeVertex(arg0); } diff --git a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/meta/MetaGraph.java b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/meta/MetaGraph.java new file mode 100644 index 0000000000000000000000000000000000000000..143bfed1c57c7cc3e5e59b14e67a26ffb162142f --- /dev/null +++ b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/meta/MetaGraph.java @@ -0,0 +1,115 @@ +package fr.inrae.toulouse.metexplore.met4j_graph.core.meta; + +import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEntity; +import fr.inrae.toulouse.metexplore.met4j_graph.core.Edge; +import fr.inrae.toulouse.metexplore.met4j_graph.core.parallel.MergedGraph; +import fr.inrae.toulouse.metexplore.met4j_graph.core.parallel.MetaEdge; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; + +public class MetaGraph<V extends BioEntity, E extends Edge<V>> extends MergedGraph<MetaNode<V>,E> { + + public MetaNodeFactory getNodeFactory() { + return factory; + } + public void setNodeFactory(MetaNodeFactory<V> factory) { + this.factory = factory; + } + private HashMap<V,Collection<MetaNode<V>>> nodeMap = new HashMap<>(); + + private MetaNodeFactory<V> factory = new MetaNodeFactory<V>().useRandomId(); + + public boolean addMetaVertex(Collection<V> content) { + return this.addVertex(factory.createMetaNode(content)); + } + + public Collection<MetaNode<V>> getMetaVerticiesOf(V vertex){ + return nodeMap.get(vertex); + } + + @Override + public boolean addVertex(MetaNode<V> vertex){ + boolean res = super.addVertex(vertex); + for(V node : vertex.content){ + Collection<MetaNode<V>> matchingMeta = nodeMap.get(node); + if (matchingMeta==null){ + matchingMeta = new HashSet<>(); + nodeMap.put(node,matchingMeta); + } + matchingMeta.add(vertex); + } + return res; + } + + public void addSubVertex(MetaNode<V> metaNode, V ... nodes){ + if(!this.vertexSet().contains(metaNode)){ + throw new IllegalArgumentException("meta-node not present in graph"); + }else{ + for (V node : nodes){ + metaNode.content.add(node); + Collection<MetaNode<V>> matchingMeta = nodeMap.get(node); + if (matchingMeta==null){ + matchingMeta = new HashSet<>(); + nodeMap.put(node,matchingMeta); + } + matchingMeta.add(metaNode); + } + } + } + + @Override + public boolean removeVertex(MetaNode<V> vertex){ + boolean res = super.removeVertex(vertex); + if(res){ + for(Collection<MetaNode<V>> list : nodeMap.values()){ + list.remove(vertex); + } + } + return res; + } + + public Collection<MetaEdge<MetaNode<V>,E>> addEdgesFromSubVertices(E edge){ + return addEdgesFromSubVertices(edge.getV1(),edge.getV2(),edge); + } + private Collection<MetaEdge<MetaNode<V>,E>> addEdgesFromSubVertices(V arg0, V arg1) { + return addEdgesFromSubVertices(arg0,arg1,null); + } + + + private Collection<MetaEdge<MetaNode<V>,E>> addEdgesFromSubVertices(V arg0, V arg1, E edge) { + if(!nodeMap.containsKey(arg0)) throw new IllegalArgumentException("first vertex not present in any meta node"); + if(!nodeMap.containsKey(arg1)) throw new IllegalArgumentException("first vertex not present in any meta node"); + + ArrayList<MetaEdge<MetaNode<V>,E>> newlyAddedEdges = new ArrayList<>(); + for(MetaNode<V> v1 : nodeMap.get(arg0)){ + for(MetaNode<V> v2 : nodeMap.get(arg1)){ + if(v1!=v2){ + MetaEdge<MetaNode<V>,E> e = this.getEdge(v1,v2); + if(e==null){ + e = new MetaEdge<>(v1,v2); + if(edge!=null) e.addEdge(edge); + boolean res=this.addEdge(e); + if(res) newlyAddedEdges.add(e); + }else{ + if(edge!=null) e.addEdge(edge); + } + } + } + } + return newlyAddedEdges; + } + + + @Override + public MetaNode<V> createVertex(String id) { + return new MetaNode<>(id); + } + + @Override + public void setEdgeWeight(MetaNode<V> sourceVertex, MetaNode<V> targetVertex, double weight) { + super.setEdgeWeight(sourceVertex, targetVertex, weight); + } +} diff --git a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/meta/MetaNode.java b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/meta/MetaNode.java new file mode 100644 index 0000000000000000000000000000000000000000..273fd65606cf7370e99aa227937761547f81b82e --- /dev/null +++ b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/meta/MetaNode.java @@ -0,0 +1,30 @@ +package fr.inrae.toulouse.metexplore.met4j_graph.core.meta; + +import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEntity; +import fr.inrae.toulouse.metexplore.met4j_core.biodata.collection.BioCollection; + +import java.util.Collection; + + +public class MetaNode<V extends BioEntity> extends BioEntity { + + protected BioCollection<V> content=new BioCollection<>(); + public MetaNode(String id, Collection<V> content){ + super(id); + this.content.addAll(content); + } + + public MetaNode(String id){ + super(id); + } + + public int getSize(){ + return content.size(); + } + + public BioCollection<V> getContentView() { + return new BioCollection<>(content); + } + + +} diff --git a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/meta/MetaNodeFactory.java b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/meta/MetaNodeFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..5676c0a7605345b6e33e8a0ceb53473b46d3121a --- /dev/null +++ b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/meta/MetaNodeFactory.java @@ -0,0 +1,32 @@ +package fr.inrae.toulouse.metexplore.met4j_graph.core.meta; + +import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEntity; + +import java.util.Collection; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class MetaNodeFactory<V extends BioEntity> { + + private Function<Collection<V>, String> idGenerator; + private final Function<Collection<V>, String> randomIdGenerator = (v -> UUID.randomUUID().toString()); + private final Function<Collection<V>, String> concatIdGenerator = (v -> v.stream().map(BioEntity::getId).collect(Collectors.joining())); + + public MetaNodeFactory<V> useRandomId(){ + this.idGenerator=randomIdGenerator; + return this; + } + public MetaNodeFactory<V> useConcatenedId(){ + this.idGenerator=concatIdGenerator; + return this; + } + public MetaNodeFactory<V> setIdGenerator(Function<Collection<V>, String> idGenerator){ + this.idGenerator=idGenerator; + return this; + } + + public MetaNode<V> createMetaNode(Collection<V> content){ + return new MetaNode<>(idGenerator.apply(content),content); + } +} diff --git a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/parallel/MergedGraph.java b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/parallel/MergedGraph.java index d1d47534cf469dc8245e2e60b34586f053645ab9..48af45b09901f380475a16307fcd49424a816761 100644 --- a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/parallel/MergedGraph.java +++ b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/parallel/MergedGraph.java @@ -38,7 +38,6 @@ package fr.inrae.toulouse.metexplore.met4j_graph.core.parallel; import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEntity; import fr.inrae.toulouse.metexplore.met4j_graph.core.BioGraph; import fr.inrae.toulouse.metexplore.met4j_graph.core.Edge; -import fr.inrae.toulouse.metexplore.met4j_graph.core.GraphFactory; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -54,7 +53,7 @@ import java.util.HashSet; * @author lcottret * @version $Id: $Id */ -public class MergedGraph<V extends BioEntity, E extends Edge<V>> extends BioGraph<V,MetaEdge<V,E>> { +public class MergedGraph<V extends BioEntity, E extends Edge<?>> extends BioGraph<V,MetaEdge<V,E>> { /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1L; @@ -66,30 +65,14 @@ public class MergedGraph<V extends BioEntity, E extends Edge<V>> extends BioGrap public MergedGraph() { super(); } - - - /** - * Gets the factory. - * - * @return the factory - */ - public GraphFactory<V,MetaEdge<V,E>,MergedGraph<V,E>> getFactory(){ - return new GraphFactory<>() { - @Override - public MergedGraph<V, E> createGraph() { - return new MergedGraph<>(); - } - }; - } - /* (non-Javadoc) * @see parsebionet.computation.graphe.BioGraph#copyEdge(parsebionet.computation.graphe.Edge) */ /** {@inheritDoc} */ @Override - public MetaEdge<V, E> copyEdge(MetaEdge<V, E> edge) { - MetaEdge<V, E> newEdge = new MetaEdge<>(edge.getV1(), edge.getV2(), edge.getEdgeList()); + public MetaEdge<V,E> copyEdge(MetaEdge<V,E> edge) { + MetaEdge<V,E> newEdge = new MetaEdge<>(edge.getV1(), edge.getV2(), edge.getEdgeList()); return newEdge; } @@ -107,20 +90,26 @@ public class MergedGraph<V extends BioEntity, E extends Edge<V>> extends BioGrap } @Override - public MetaEdge<V, E> createEdge(V v1, V v2) { + public MetaEdge<V,E> createEdge(V v1, V v2) { return new MetaEdge<>(v1, v2, new HashSet<>()); } + public void setNumberOfSubEdgesAsWeight(){ + for(MetaEdge<V,E> edge : this.edgeSet()){ + this.setEdgeWeight(edge,edge.getEdgeList().size()); + } + } + /** {@inheritDoc} */ @Override - public MetaEdge<V, E> reverseEdge(MetaEdge<V, E> edge) { - MetaEdge<V, E> reversed = new MetaEdge<>(edge.getV2(), edge.getV1(), edge.getEdgeList()); + public MetaEdge<V,E> reverseEdge(MetaEdge<V,E> edge) { + MetaEdge<V,E> reversed = new MetaEdge<>(edge.getV2(), edge.getV1(), edge.getEdgeList()); return reversed; } @Override - public MetaEdge<V, E> createEdgeFromModel(V v1, V v2, MetaEdge<V, E> edge){ - return new MetaEdge<V, E>(v1, v2, edge.getEdgeList()); + public MetaEdge<V,E> createEdgeFromModel(V v1, V v2, MetaEdge<V,E> edge){ + return new MetaEdge<V,E>(v1, v2, edge.getEdgeList()); } } diff --git a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/parallel/MetaEdge.java b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/parallel/MetaEdge.java index 73e88661b66850d309d501d8e415d02d22313c17..b0492cc1f621abcecf1cca59c3de6bd433c097d7 100644 --- a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/parallel/MetaEdge.java +++ b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/core/parallel/MetaEdge.java @@ -35,13 +35,13 @@ */ package fr.inrae.toulouse.metexplore.met4j_graph.core.parallel; +import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEntity; +import fr.inrae.toulouse.metexplore.met4j_graph.core.Edge; + import java.util.Collection; import java.util.HashSet; import java.util.Set; -import fr.inrae.toulouse.metexplore.met4j_graph.core.Edge; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEntity; - /** * Edges that represent a set of sub-edges * It can be used to store the merging of edges sharing same source and target @@ -51,7 +51,7 @@ import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEntity; * @param <E> the sub-edges Type * @version $Id: $Id */ -public class MetaEdge<V extends BioEntity,E extends Edge<V>> extends Edge<V> { +public class MetaEdge<V extends BioEntity,E extends Edge<?>> extends Edge<V> { /** The Constant serialVersionUID. */ private static final long serialVersionUID = -4274083588408838186L; diff --git a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/io/ExportGraph.java b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/io/ExportGraph.java index 06b296ce4c8bfcd36d4d232cf27a3f104e218763..415ecd1269145c150e65b5e12819da38b97f76a2 100644 --- a/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/io/ExportGraph.java +++ b/met4j-graph/src/main/java/fr/inrae/toulouse/metexplore/met4j_graph/io/ExportGraph.java @@ -35,32 +35,25 @@ */ package fr.inrae.toulouse.metexplore.met4j_graph.io; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.HashMap; -import java.util.Map; - import fr.inrae.toulouse.metexplore.met4j_core.biodata.*; +import fr.inrae.toulouse.metexplore.met4j_core.biodata.collection.BioCollection; +import fr.inrae.toulouse.metexplore.met4j_core.biodata.collection.BioCollections; import fr.inrae.toulouse.metexplore.met4j_graph.core.BioGraph; -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.core.Edge; import fr.inrae.toulouse.metexplore.met4j_graph.core.bipartite.BipartiteEdge; import fr.inrae.toulouse.metexplore.met4j_graph.core.bipartite.BipartiteGraph; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.collection.BioCollection; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.collection.BioCollections; -import fr.inrae.toulouse.metexplore.met4j_graph.core.pathway.PathwayGraph; -import fr.inrae.toulouse.metexplore.met4j_graph.core.pathway.PathwayGraphEdge; +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.core.reaction.CompoundEdge; import fr.inrae.toulouse.metexplore.met4j_graph.core.reaction.ReactionGraph; import org.jgrapht.nio.Attribute; import org.jgrapht.nio.DefaultAttribute; import org.jgrapht.nio.gml.GmlExporter; +import java.io.*; +import java.util.HashMap; +import java.util.Map; + /** * Export informations from graphs generated from {@link Bionetwork2BioGraph} into Cytoscape-readable files * @author clement @@ -592,5 +585,18 @@ public class ExportGraph { e1.printStackTrace(); } } - + + public static <V extends BioEntity, E extends Edge<V>, G extends BioGraph<V,E>> void toAdjacencyList(G graph, String outputPath){ + try { + BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath, true)); + bw.newLine(); + for(E e:graph.edgeSet()){ + bw.write(e.getV1()+"\t"+e.getV2()); + bw.newLine(); + } + bw.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } } diff --git a/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestEdgeMerger.java b/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestEdgeMerger.java index 3fd51575573e5a1be69ba085c04813eb1c0020f0..05a28e916c10a44201a7eafc69cd03fe0c9394a4 100644 --- a/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestEdgeMerger.java +++ b/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestEdgeMerger.java @@ -39,11 +39,8 @@ */ package fr.inrae.toulouse.metexplore.met4j_graph; -import static org.junit.Assert.*; - +import fr.inrae.toulouse.metexplore.met4j_core.biodata.*; import fr.inrae.toulouse.metexplore.met4j_graph.computation.transform.EdgeMerger; -import fr.inrae.toulouse.metexplore.met4j_graph.core.Edge; -import fr.inrae.toulouse.metexplore.met4j_graph.core.GraphFactory; 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.core.parallel.MergedGraph; @@ -52,15 +49,10 @@ import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioEnzyme; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioReaction; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioCompartment; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioNetwork; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioMetabolite; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioProtein; - import java.util.Comparator; +import static org.junit.Assert.assertTrue; + public class TestEdgeMerger { public static CompoundGraph g; diff --git a/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestMergedGraph.java b/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestMergedGraph.java index 950485b0bd0f30501002f419343e24e00434f13a..338118a712f4f68adf6d6600628a89d4b0b5bb0a 100644 --- a/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestMergedGraph.java +++ b/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestMergedGraph.java @@ -36,10 +36,10 @@ package fr.inrae.toulouse.metexplore.met4j_graph; -import static org.junit.Assert.*; - -import java.util.HashSet; - +import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioCompartment; +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.BioReaction; 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.core.parallel.MergedGraph; @@ -47,10 +47,10 @@ import fr.inrae.toulouse.metexplore.met4j_graph.core.parallel.MetaEdge; import org.junit.BeforeClass; import org.junit.Test; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioCompartment; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioNetwork; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioReaction; -import fr.inrae.toulouse.metexplore.met4j_core.biodata.BioMetabolite; +import java.util.HashSet; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class TestMergedGraph { @@ -125,7 +125,7 @@ public class TestMergedGraph { @Test public void testAddEdge(){ - MergedGraph<BioMetabolite, ReactionEdge> cg3 + MergedGraph<BioMetabolite, ReactionEdge> cg3 = (MergedGraph<BioMetabolite, ReactionEdge>) cg2.clone(); cg3.addEdge(v2, v1); assertEquals(2, cg3.edgeSet().size()); diff --git a/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestVertexContraction.java b/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestVertexContraction.java index 6e72798d0277ff4ba8a3b550af4ba2648d2e973f..6fae48afb931ee6998a3b6329aa1c4e020258888 100644 --- a/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestVertexContraction.java +++ b/met4j-graph/src/test/java/fr/inrae/toulouse/metexplore/met4j_graph/TestVertexContraction.java @@ -39,10 +39,15 @@ import fr.inrae.toulouse.metexplore.met4j_core.biodata.*; import fr.inrae.toulouse.metexplore.met4j_graph.computation.transform.VertexContraction; 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.core.meta.MetaGraph; +import fr.inrae.toulouse.metexplore.met4j_graph.core.meta.MetaNode; import org.junit.BeforeClass; import org.junit.Test; -import static org.junit.Assert.*; +import java.util.Iterator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class TestVertexContraction { public static CompoundGraph g; @@ -66,21 +71,25 @@ public class TestVertexContraction { bn.affectToCompartment(comp1, a1); a1.setName("A"); a1.setInchi("InChI=1S/C4H6O4/c1-2(3(5)6)4(7)8/h2H,1H3,(H,5,6)(H,7,8)"); + a1.setChemicalFormula("C4H6O4"); g.addVertex(a1); b1 = new BioMetabolite("M_b_1"); bn.add(b1); bn.affectToCompartment(comp1, b1); b1.setName("B"); b1.setInchi("InChI=1S/C4H10NO6P/c5-3(4(6)7)1-2-11-12(8,9)10/h3H,1-2,5H2,(H,6,7)(H2,8,9,10)/t3-/m0/s1"); + b1.setChemicalFormula("C4H10NO6P"); g.addVertex(b1); a2 = new BioMetabolite("M_a_2"); bn.add(a2); bn.affectToCompartment(comp2, a2); a2.setName("A"); a2.setInchi("InChI=1S/C4H6O4/c1-2(3(5)6)4(7)8/h2H,1H3,(H,5,6)(H,7,8)"); + a2.setChemicalFormula("C4H6O4"); g.addVertex(a2); b2 = new BioMetabolite("M_b_2"); bn.add(b2); bn.affectToCompartment(comp2, b2); b2.setName("B"); b2.setInchi("InChI=1S/C4H10NO6P/c5-3(4(6)7)1-2-11-12(8,9)10/h3H,1-2,5H2,(H,6,7)(H2,8,9,10)/t3-/m0/s1"); + b2.setChemicalFormula("C4H10NO6P"); g.addVertex(b2); a3 = new BioMetabolite("M_a_3"); bn.add(a3); bn.affectToCompartment(comp3, a3); @@ -188,6 +197,25 @@ public class TestVertexContraction { assertEquals("Wrong final number of edges", 6, g2.edgeSet().size()); } + @Test + public void testFormulaNetwork() { + MetaGraph<BioMetabolite,ReactionEdge> g2= VertexContraction.contractByAsMeta(g,BioMetabolite::getChemicalFormula); + g2.setNumberOfSubEdgesAsWeight(); + assertEquals("Error in the initial graph", 5, g.vertexSet().size()); + assertEquals("Error in the initial graph", 12, g.edgeSet().size()); + assertEquals("Wrong final number of nodes", 2, g2.vertexSet().size()); + assertEquals("Wrong final number of edges", 2, g2.edgeSet().size()); +// assertEquals("Wrong final number of edges", 6, g2.edgeSet().iterator().next().getEdgeList().size()); + Iterator<MetaNode<BioMetabolite>> i = g2.vertexSet().iterator(); + assertEquals("Wrong final number of edges", 2, i.next().getSize()); + assertEquals("Wrong final number of edges", 2, i.next().getSize()); + assertEquals(2,g2.getVertex("C4H6O4").getContentView().size()); + assertTrue(g2.getVertex("C4H6O4").getContentView().contains(a1)); + assertTrue(g2.getVertex("C4H6O4").getContentView().contains(a2)); + assertTrue(g2.getVertex("C4H10NO6P").getContentView().contains(b1)); + assertTrue(g2.getVertex("C4H10NO6P").getContentView().contains(b2)); + } + /* @Test public void testMergeCompartmentFromId() { CompoundGraph g2 = Merger.mergeCompartmentFromId(g, "^(.+)_\\w$"); diff --git a/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/FormulaNet.java b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/FormulaNet.java new file mode 100644 index 0000000000000000000000000000000000000000..b00fdd3cca482728589d13c75a12b5007f1a35b0 --- /dev/null +++ b/met4j-toolbox/src/main/java/fr/inrae/toulouse/metexplore/met4j_toolbox/networkAnalysis/FormulaNet.java @@ -0,0 +1,175 @@ +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.transform.VertexContraction; +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.core.meta.MetaGraph; +import fr.inrae.toulouse.metexplore.met4j_graph.core.meta.MetaNode; +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_toolbox.generic.AbstractMet4jApplication; +import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.EnumFormats; +import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.EnumParameterTypes; +import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.Format; +import fr.inrae.toulouse.metexplore.met4j_toolbox.generic.annotations.ParameterType; +import org.kohsuke.args4j.Option; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.stream.Collectors; + +public class FormulaNet extends AbstractMet4jApplication { + + @Format(name= EnumFormats.Sbml) + @ParameterType(name= EnumParameterTypes.InputFile) + @Option(name = "-s", usage = "input SBML file", required = true) + public String inputPath = null; + + @ParameterType(name= EnumParameterTypes.InputFile) + @Format(name= EnumFormats.Txt) + @Option(name = "-sc", usage = "input Side compound file", required = false) + public String inputSide = null; + + @ParameterType(name= EnumParameterTypes.OutputFile) + @Format(name= EnumFormats.Gml) + @Option(name = "-o", usage = "output Graph file", required = true) + public String outputPath = null; + + @Option(name = "-ri", aliases = {"--removeIsolatedNodes"}, usage = "remove isolated nodes", required = false) + public boolean removeIsolated = false; + +// @ParameterType(name=EnumParameterTypes.InputFile) +// @Format(name=EnumFormats.Tsv) +// @Option(name = "-cw", aliases = {"--customWeights"}, usage = "an optional file containing weights for compound pairs", forbids = {"-dw"}) +// public String weightFile = null; + + @Option(name = "-un", aliases = {"--undirected"}, usage = "create as undirected", required = false) + public boolean undirected = false; + + public static void main(String[] args) { + + FormulaNet app = new FormulaNet(); + + app.parseArguments(args); + + app.run(); + + } + + + public void run() { + System.out.print("Reading SBML..."); + JsbmlReader reader = new JsbmlReader(this.inputPath); + ArrayList<PackageParser> pkgs = new ArrayList<>(Arrays.asList( + new NotesParser(false), new FBCParser(), new GroupPathwayParser())); + + BioNetwork network = null; + + try { + network = reader.read(pkgs); + } catch (Met4jSbmlReaderException e) { + System.err.println("Error while reading the SBML file"); + System.err.println(e.getMessage()); + System.exit(1); + } + 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 = null; + try { + sideCpds = mapper.map(inputSide); + } catch (IOException e) { + System.err.println("Error while reading the side compound file"); + System.err.println(e.getMessage()); + System.exit(1); + } + 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 UnweightedPolicy<>(); +// if (weightFile != null) { +// System.err.println("Setting edge weights..."); +// wp = new WeightsFromFile(weightFile); +// } else if (degree && !undirected) { +// System.err.println("Setting edge weights..."); +// int pow = 2; +// wp = new DegreeWeightPolicy(pow); +// } +// wp.setWeight(graph); +// System.out.println(" Done."); + + //invert graph as undirected (copy edge weight to reversed edge) + if(undirected){ + System.out.print("Create Undirected..."); + graph.asUndirected(); + 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."); + } + + //create formula net + MetaGraph<BioMetabolite,ReactionEdge> meta= VertexContraction.contractByAsMeta(graph,BioMetabolite::getChemicalFormula); + meta.setNumberOfSubEdgesAsWeight(); + + //export graph + System.out.print("Exporting..."); + HashMap<MetaNode<BioMetabolite>,Integer> att = meta.vertexSet().stream() + .collect(Collectors.toMap( + v -> v, + v -> v.getContentView().size(), + (existing, replacement) -> existing, + HashMap::new + )); + ExportGraph.toGmlWithAttributes(meta, this.outputPath,att,"node_size",true); + System.out.println(" Done."); + return; + } + + @Override + public String getLabel() {return this.getClass().getSimpleName();} + + @Override + public String getLongDescription() { + return "This application redefines metabolic compound graphs at the chemical formula level. It connects chemical" + + " formulas based on the existence of reactions where a reactant with the first formula is converted into a" + + " product with the second formula. This approach is particularly useful for metabolomics data with low-level " + + "annotation, where the exact compounds are unknown but their chemical formulas are available."; + } + + @Override + public String getShortDescription() {return "Redefines metabolic compound graphs at the chemical formula level, connecting formulas based on reactions. Useful for mapping metabolomics data with low level annotations.";} +} \ No newline at end of file