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