diff --git a/jastadd-mquat-base/src/main/jastadd/solvers/Helpers.jadd b/jastadd-mquat-base/src/main/jastadd/solvers/Helpers.jadd
index 6294668e9b8ef58b9a2fbfe811ff1e9dccb923c8..c5653a728f575d08c87b1f26f4807d96dde43695 100644
--- a/jastadd-mquat-base/src/main/jastadd/solvers/Helpers.jadd
+++ b/jastadd-mquat-base/src/main/jastadd/solvers/Helpers.jadd
@@ -15,6 +15,21 @@ aspect Helpers {
     return assignment;
   }
 
+  uncache Clause.simpleClusterAssignment(Request request, ResourceClass resource);
+  syn Assignment Clause.simpleClusterAssignment(Request request, ResourceClass resourceClass) {
+    Assignment assignment = new Assignment();
+    assignment.setRequest(request);
+    Implementation impl = containingImplementation();
+    if (impl != null) {
+      assignment.setImplementation(impl);
+
+      ResourceClassMapping mapping = new ResourceClassMapping(impl.getResourceRequirement().getInstance(0),new List<>(),resourceClass);
+      // Resource.populateResourceMapping(mapping,impl.getResourceRequirement(),resource);
+      assignment.setResourceMapping(mapping);
+    }
+    return assignment;
+  }
+
 
 
   public Solution Root.createRandomSolution(java.util.Random random) {
diff --git a/jastadd-mquat-base/src/main/jastadd/solvers/ai/AbstractEval.jrag b/jastadd-mquat-base/src/main/jastadd/solvers/ai/AbstractEval.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..a8db7a5e8456d0b73b7897bc771b8f2a32d0d266
--- /dev/null
+++ b/jastadd-mquat-base/src/main/jastadd/solvers/ai/AbstractEval.jrag
@@ -0,0 +1,10 @@
+aspect eval {
+
+
+  uncache Clause.checkUsing(Request request, ResourceClass resourceClass);
+  syn boolean Clause.checkUsing(Request request, ResourceClass resourceClass) = checkUsing(simpleClusterAssignment(request, resourceClass));
+
+  uncache Clause.evalUsing(Request request, ResourceClass target);
+  syn double Clause.evalUsing(Request request, ResourceClass target) = evalUsing(simpleClusterAssignment(request, target));
+
+}
diff --git a/jastadd-mquat-base/src/main/jastadd/solvers/ai/ai.jrag b/jastadd-mquat-base/src/main/jastadd/solvers/ai/ai.jrag
index 9fd4c96c5f628bc9441303a40d8e35f8cf36d2f8..1beae7881ba65c5ce851ba0aafb8ad687f662bb2 100644
--- a/jastadd-mquat-base/src/main/jastadd/solvers/ai/ai.jrag
+++ b/jastadd-mquat-base/src/main/jastadd/solvers/ai/ai.jrag
@@ -6,8 +6,160 @@ aspect AI {
   syn int Resource.amount() = 1;
   eq MultiResource.amount() = getAmount();
 
+  syn int ResourceClass.amount() {
+    int amount = 0;
+    for (ResourceRef ref : getResourceRefList()) {
+      amount += ref.getRef().amount();
+    }
+    return amount;
+  }
+
+  /**
+   * the non-terminal attribute to compute the abstract ILP subtree
+   */
   syn ILP Root.getAbstractILP() {
-    return null;
+      de.tudresden.inf.st.mquat.utils.StopWatch stopWatch = de.tudresden.inf.st.mquat.utils.StopWatch.start();
+
+      long timeoutValue = (long) de.tudresden.inf.st.mquat.utils.StaticSettings.get(ILP_TIMEOUT_VALUE);
+      java.util.concurrent.TimeUnit timeoutUnit = (java.util.concurrent.TimeUnit) de.tudresden.inf.st.mquat.utils.StaticSettings.get(ILP_TIMEOUT_UNIT);
+      long timeoutNanos = timeoutUnit.toNanos(timeoutValue);
+
+      ILP result = new ILP();
+      IlpVarInfo info = new IlpVarInfo();
+
+      IlpObjective objective = new IlpObjective();
+      objective.setKind(IlpObjectiveKind.MINIMIZE);
+      IlpLeftHandSide olhs = new IlpLeftHandSide();
+      objective.setIlpLeftHandSide(olhs);
+      result.setIlpObjective(objective);
+      for (Request request : this.getRequestList()) {
+        for (Component comp : request.relevantComponents()) {
+          IlpLeftHandSide oneCompLhs = new IlpLeftHandSide();
+          for (Implementation impl : comp.getImplementationList()) {
+            if (stopWatch.time() > timeoutNanos) {
+              return ilpTimeout("Timeout in implementation " + impl.name());
+            }
+            oneCompLhs.addIlpTerm(new IlpTerm(1, info.getIlpVariable(request, impl)));
+            IlpLeftHandSide oneImplLhs = new IlpLeftHandSide();
+
+            // #1 Objective function
+            for (ResourceClass resourceClass : this.getHardwareModel().getResourceClassList()) {
+              // r1#c2#i3#hw4
+              IlpTerm term = new IlpTerm();
+              IlpVariable var = info.getIlpVariable(request, impl, resourceClass);
+              term.setRef(var);
+              Optional<Clause> providingObjectiveClause = impl.findFirstProvidingClause(getObjective().getPropertyRef().getRef());
+              if (providingObjectiveClause.isPresent()) {
+                term.setValue(providingObjectiveClause.get().evalUsing(request, resourceClass));
+              } else {
+                term.setValue(0);
+              }
+              olhs.addIlpTerm(term);
+              oneImplLhs.addIlpTerm(new IlpTerm(1, var));
+            }
+            for (Clause reqClause : impl.requirementClauses()) {
+              if (stopWatch.time() > timeoutNanos) {
+                return ilpTimeout("Timeout in NFP-Negotiation");
+              }
+              Designator designator = reqClause.getDesignator();
+              IlpLeftHandSide reqLhs = new IlpLeftHandSide();
+              if (designator.isSoftwareDesignator()) {
+                for (Tuple<Implementation, Clause> tuple : reqClause.providingClausesOfRequiredComponent()) {
+                  Implementation providingImpl = tuple.getKey();
+                  Clause providingClause = tuple.getValue();
+                  for (ResourceClass resourceClass : this.getHardwareModel().getResourceClassList()) {
+                    reqLhs.addIlpTerm(new IlpTerm(providingClause.evalUsing(request, resourceClass),
+                        info.getIlpVariable(request, providingImpl, resourceClass)));
+                  }
+                }
+                for (ResourceClass resourceClass : this.getHardwareModel().getResourceClassList()) {
+                  // we always use negative eval-value to get the required value on the right side (literally)
+                  reqLhs.addIlpTerm(new IlpTerm(makeNegative(reqClause.evalUsing(request, resourceClass)),
+                      info.getIlpVariable(request, impl, resourceClass)));
+                }
+                // 2b. non-functional requirements of required components
+                result.addIlpConstraint(new IlpConstraint(
+                    request.getIlpName() + "_" + impl.getIlpName() + "_reqs_" +
+                        designator.asSoftwareDesignator().getPropertyRef().getRef().getIlpName() + "_from_" +
+                        designator.asSoftwareDesignator().getInstanceRef().getRef().referringComponent().getIlpName(),
+                    reqLhs, reqClause.getClauseComparator(), 0));
+              } else {
+                for (ResourceClass resourceClass : this.getHardwareModel().getResourceClassList()) {
+                  // check if constraint is fulfilled, otherwise remember this illegal combination
+                  if (!reqClause.checkUsing(request, resourceClass)) {
+                    info.getIlpVariable(request, impl, resourceClass).setIllegal(true);
+                  }
+                }
+              }
+            }
+
+            oneImplLhs.addIlpTerm(new IlpTerm(-1, info.getIlpVariable(request, impl)));
+            // implementation variable definition
+            result.addIlpConstraint(new IlpConstraint(request.getIlpName() + "_single_" + impl.getIlpName(),
+                oneImplLhs, ClauseComparator.EQ, 0));
+            for (ComponentRequirement req : impl.getComponentRequirementList()) {
+              IlpLeftHandSide reqImplLhs = new IlpLeftHandSide();
+              for (Implementation reqImpl : req.getComponentRef().getRef().getImplementationList()) {
+                reqImplLhs.addIlpTerm(new IlpTerm(1, info.getIlpVariable(request, reqImpl)));
+              }
+              reqImplLhs.addIlpTerm(new IlpTerm(-1, info.getIlpVariable(request, impl)));
+              // 1c. required components
+              result.addIlpConstraint(new IlpConstraint(request.getIlpName() + "_" + impl.getIlpName() +
+                  "_req_" + req.getComponentRef().getRef().getIlpName(),
+                  reqImplLhs, ClauseComparator.GE, 0));
+            }
+          }
+          // 1b.
+          result.addIlpConstraint(new IlpConstraint(request.getIlpName() + "_opc_" + comp.getIlpName(),
+              oneCompLhs, ClauseComparator.LE, 1));
+        }
+        IlpLeftHandSide targetLhs = new IlpLeftHandSide();
+        for (Implementation impl : request.getTarget().getRef().getImplementationList()) {
+          IlpVariable var = info.getIlpVariable(request, impl);
+          targetLhs.addIlpTerm(new IlpTerm(1, var));
+        }
+        // 1a. functional requirement of the request
+        result.addIlpConstraint(new IlpConstraint(request.getIlpName() + "_target", targetLhs, ClauseComparator.EQ, 1));
+        for (Clause requiredClause : request.getConstraintList()) {
+          IlpLeftHandSide reqLhs = new IlpLeftHandSide();
+          Property requiredProperty = requiredClause.getDesignator().asSoftwareDesignator().getPropertyRef().getRef();
+          for(Implementation impl : request.getTarget().getRef().getImplementationList()) {
+            for (ResourceClass resourceClass : this.getHardwareModel().getResourceClassList()) {
+              Optional<Clause> providingClause = impl.findFirstProvidingClause(requiredProperty);
+              if (providingClause.isPresent()) {
+                IlpVariable var = info.getIlpVariable(request, impl, resourceClass);
+                reqLhs.addIlpTerm(new IlpTerm(providingClause.get().evalUsing(request, resourceClass), var));
+              }
+            }
+          }
+          // 2a. non-functional requirement of the request
+          result.addIlpConstraint(new IlpConstraint(request.getIlpName() + "_req_" + requiredProperty.getIlpName(),
+              reqLhs, requiredClause.getClauseComparator(),
+              requiredClause.evalUsing(request, (ResourceClass) null)));
+        }
+      }
+      if (stopWatch.time() > timeoutNanos) {
+        return ilpTimeout("Timeout after constraint creation");
+      }
+      /*
+      #2 Constraints
+      #2.1 Request constraints (requiredNFPs)
+      #2.2 Architecture constraints (One SW on one HW, Only one mode/configuration per impl and per SW)
+      #2.3 NFP-negotiation (Satisfy requirements from SW to both SW and HW)
+      */
+
+      // 2.2 Architecture constraints: Only one config per hardware resource
+      for (IlpConstraint constraint : info.resourceConstraints.values()) {
+        // 1d.
+        result.addIlpConstraint(constraint);
+      }
+
+      // variables
+      for (IlpVariable var : info.vars.values()) {
+        result.addIlpVariable(var);
+      }
+
+      return result;
   }
 
 }
\ No newline at end of file
diff --git a/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.ast b/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.ast
index fc06d7cdc801f2c9df118794b1e6bae890832813..7384d30650b85946e076626dcc6d309c36373621 100644
--- a/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.ast
+++ b/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.ast
@@ -9,6 +9,7 @@ IlpConstraint ::= <Name:String> IlpLeftHandSide <ClauseComparator:ClauseComparat
 abstract IlpVariable ::= <Name:String> <Request:Request> <Impl:Implementation> <Illegal:boolean>;
 IlpAllResourcesVariable:IlpVariable ;
 IlpMappingVariable:IlpVariable ::= <Resource:Resource> ;
+IlpClassMappingVariable:IlpVariable ::= <ResourceClass:ResourceClass> ;
 
 // sum of terms
 IlpLeftHandSide ::= IlpTerm* ;
diff --git a/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.jadd b/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.jadd
index e0fb837955c81af237a0731565b9192d14efc22c..eb19a31f697b56813e36a3b7cdc1c50c50620d02 100644
--- a/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.jadd
+++ b/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.jadd
@@ -42,10 +42,12 @@ aspect ILP {
   public class IlpVarInfo {
     public java.util.Map<String, IlpVariable> vars;
     public java.util.Map<Resource, IlpConstraint> resourceConstraints;
+    public java.util.Map<ResourceClass, IlpConstraint> resourceClassConstraints;
 
     public IlpVarInfo() {
       vars = new java.util.TreeMap<>();
       resourceConstraints = new java.util.HashMap<>();
+      resourceClassConstraints = new java.util.HashMap<>();
     }
 
     public IlpVariable getIlpVariable(Request request, Implementation impl, Resource resource) {
@@ -65,6 +67,23 @@ aspect ILP {
       return result;
     }
 
+    public IlpVariable getIlpVariable(Request request, Implementation impl, ResourceClass resourceClass) {
+      String varName = request.getIlpName() + "#" + impl.getIlpName() + "#" + resourceClass.getIlpName();
+      IlpVariable result = vars.get(varName);
+      IlpConstraint resourceClassConstraint = resourceClassConstraints.get(resourceClass);
+      if (resourceClassConstraint == null) {
+        resourceClassConstraint = new IlpConstraint("one_on_" + resourceClass.getIlpName(), new IlpLeftHandSide(),
+            ClauseComparator.LE, resourceClass.amount());
+        resourceClassConstraints.put(resourceClass, resourceClassConstraint);
+      }
+      if (result == null) {
+        result = new IlpClassMappingVariable(varName, request, impl, false, resourceClass);
+        vars.put(varName, result);
+        resourceClassConstraint.getIlpLeftHandSide().addIlpTerm(new IlpTerm(1, result));
+      }
+      return result;
+    }
+
     public IlpVariable getIlpVariable(Request request, Implementation impl) {
       String varName = request.getIlpName() + "#" + impl.getIlpName();
       IlpVariable result = vars.get(varName);
diff --git a/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.jrag b/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.jrag
index f8d8c9898ac066aca6cdb3f4511f307956ff839a..951aa253efbd97cce1bfe1a2c9183fd0d0e2156b 100644
--- a/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.jrag
+++ b/jastadd-mquat-base/src/main/jastadd/solvers/ilp/ILP.jrag
@@ -159,7 +159,7 @@ aspect ILP {
         // 2a. non-functional requirement of the request
         result.addIlpConstraint(new IlpConstraint(request.getIlpName() + "_req_" + requiredProperty.getIlpName(),
             reqLhs, requiredClause.getClauseComparator(),
-            requiredClause.evalUsing(request, null)));
+            requiredClause.evalUsing(request, (Resource) null)));
       }
     }
     if (stopWatch.time() > timeoutNanos) {
diff --git a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/clustering/HardwareComponent.java b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/clustering/HardwareComponent.java
index 5f334aafebf7769a311fffff28899a9e13ab04a9..4e9e2e52773a0b8e05242b65baa855ed74da3d1c 100644
--- a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/clustering/HardwareComponent.java
+++ b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/clustering/HardwareComponent.java
@@ -9,27 +9,27 @@ import weka.core.Instance;
  */
 public class HardwareComponent {
 
-    private Resource resource;
-    private Instance instance;
+  private Resource resource;
+  private Instance instance;
 
-    public HardwareComponent(Resource resource, int attributes_number) {
-        this.resource = resource;
-        this.instance = new DenseInstance(attributes_number);
-    }
+  public HardwareComponent(Resource resource, int attributes_number) {
+    this.resource = resource;
+    this.instance = new DenseInstance(attributes_number);
+  }
 
-    public Resource getResource() {
-        return resource;
-    }
+  public Resource getResource() {
+    return resource;
+  }
 
-    public void setResource(Resource resource) {
-        this.resource = resource;
-    }
+  public void setResource(Resource resource) {
+    this.resource = resource;
+  }
 
-    public Instance getInstance() {
-        return instance;
-    }
+  public Instance getInstance() {
+    return instance;
+  }
 
-    public void setInstance(Instance instance) {
-        this.instance = instance;
-    }
+  public void setInstance(Instance instance) {
+    this.instance = instance;
+  }
 }
diff --git a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/clustering/KMeans.java b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/clustering/KMeans.java
index 04ea64d311d9ef28b6a7bb9e4017636ecf6c6a59..59a7a8c74d683b9f4a6ea018f07202f2d208f737 100644
--- a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/clustering/KMeans.java
+++ b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/clustering/KMeans.java
@@ -1,8 +1,11 @@
 package de.tudresden.inf.st.mquat.solving.clustering;
 
 import de.tudresden.inf.st.mquat.jastadd.model.Resource;
+import de.tudresden.inf.st.mquat.jastadd.model.ResourceType;
 import de.tudresden.inf.st.mquat.jastadd.model.Root;
-import weka.clusterers.*;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import weka.clusterers.SimpleKMeans;
 import weka.core.Attribute;
 import weka.core.Instances;
 
@@ -20,87 +23,120 @@ import java.util.Map;
  * @author Dmytro Pukhkaiev
  * @version 0.1
  */
-public class KMeans{
-    // dataset
-    private Instances resources;
-    // number of clusters
-    private int k;
-    // list of resources
-    private ArrayList<HardwareComponent> hardwareComponentList = new ArrayList<>();
-
-    public KMeans(Root model) {
-        // create attributes for instances
-        Attribute cpu = new Attribute("cpu");
-        Attribute ram = new Attribute("ram");
-        Attribute disk = new Attribute("disk");
-        Attribute network = new Attribute("network");
-        ArrayList<Attribute> attributeList = new ArrayList<Attribute>();
-        attributeList.add(cpu);
-        attributeList.add(ram);
-        attributeList.add(disk);
-        attributeList.add(network);
-
-        // create a dataset
-        resources = new Instances("resources", attributeList, 0);
-
-        //instantiate items in the dataset
-        for (Resource resource : model.getHardwareModel().getResourceList()) {
-            if (resource.getType().getName().getName().equals("ComputeNode")) {
-                HardwareComponent hardwareComponent = new HardwareComponent(resource, resource.getNumSubResource());
-                hardwareComponent.getInstance().setDataset(resources);
-
-                for (Resource sub : resource.getSubResourceList()) {
-                    String typeName = sub.getType().getName().getName();
-                    switch (typeName) {
-                        case "CPU":
-                            hardwareComponent.getInstance().setValue(cpu, sub.getCurrentValueByPropertyName("frequency"));
-                            break;
-                        case "RAM":
-                            hardwareComponent.getInstance().setValue(ram, sub.getCurrentValueByPropertyName("total"));
-                            break;
-                        case "DISK":
-                            hardwareComponent.getInstance().setValue(disk, sub.getCurrentValueByPropertyName("total"));
-                            break;
-                        case "NETWORK":
-                            hardwareComponent.getInstance().setValue(network, sub.getCurrentValueByPropertyName("throughput"));
-                            break;
-                        default:
-                            throw new RuntimeException("Unable to transform unknown resource of type" + typeName);
-                    }
-                }
-
-                hardwareComponentList.add(hardwareComponent);
-            }
+public class KMeans {
+  private static final Logger logger = LogManager.getLogger(KMeans.class);
+
+  // dataset
+  private Instances resources;
+
+  // the model
+  private Root model;
+
+  // list of resources
+  private ArrayList<HardwareComponent> hardwareComponentList = new ArrayList<>();
+
+  public KMeans(Root model) {
+
+    this.model = model;
+
+    // create attributes for instances
+    Attribute cpu = new Attribute("cpu");
+    Attribute ram = new Attribute("ram");
+    Attribute disk = new Attribute("disk");
+    Attribute network = new Attribute("network");
+
+    ArrayList<Attribute> attributeList = new ArrayList<>();
+
+    // we expect one top-level resource type and consider at most four sub-resources of the following names
+    for (ResourceType resourceType : model.getHardwareModel().getResourceType(0).getSubTypeList()) {
+      switch (resourceType.name().toLowerCase()) {
+        case "cpu":
+          attributeList.add(cpu);
+          break;
+        case "ram":
+          attributeList.add(ram);
+          break;
+        case "disk":
+          attributeList.add(disk);
+          break;
+        case "network":
+          attributeList.add(network);
+      }
+    }
+
+    // create a dataset
+    resources = new Instances("resources", attributeList, 0);
+
+    //instantiate items in the dataset
+    for (Resource resource : model.getHardwareModel().getResourceList()) {
+      if (resource.getType().getName().getName().equals("ComputeNode")) {
+        HardwareComponent hardwareComponent = new HardwareComponent(resource, resource.getNumSubResource());
+        hardwareComponent.getInstance().setDataset(resources);
+
+        for (Resource sub : resource.getSubResourceList()) {
+          String typeName = sub.getType().getName().getName();
+          switch (typeName) {
+            case "CPU":
+              hardwareComponent.getInstance().setValue(cpu, sub.getCurrentValueByPropertyName("frequency"));
+              break;
+            case "RAM":
+              hardwareComponent.getInstance().setValue(ram, sub.getCurrentValueByPropertyName("total"));
+              break;
+            case "DISK":
+              hardwareComponent.getInstance().setValue(disk, sub.getCurrentValueByPropertyName("total"));
+              break;
+            case "NETWORK":
+              hardwareComponent.getInstance().setValue(network, sub.getCurrentValueByPropertyName("throughput"));
+              break;
+            default:
+              throw new RuntimeException("Unable to transform unknown resource of type" + typeName);
+          }
         }
 
-        for (HardwareComponent hwc : hardwareComponentList)
-            resources.add(hwc.getInstance());
+        hardwareComponentList.add(hardwareComponent);
+      }
+    }
+
+    for (HardwareComponent hwc : hardwareComponentList)
+      resources.add(hwc.getInstance());
+
+  }
 
-        k = (int) (0.2 * model.getHardwareModel().getResourceList().asJavaCollection().size());
+  public Map<Integer, ArrayList<Resource>> buildClusters(int numClusters) {
 
+    if (numClusters < 1) {
+      logger.error("Unable to cluster the model in less than one cluster (numClusters was " + numClusters + ")");
+      throw new RuntimeException();
     }
 
-    public Map<Integer, ArrayList<Resource>> buildClusters(){
+    logger.info("Clustering " + model.getHardwareModel().getNumResource() + " resources in " + numClusters + " clusters");
 
-        SimpleKMeans algorithm = new SimpleKMeans();
-        Map<Integer, ArrayList<Resource>> Clusters = new HashMap<>();
+    SimpleKMeans algorithm = new SimpleKMeans();
+    Map<Integer, ArrayList<Resource>> Clusters = new HashMap<>();
 
-        for (int i = 0; i < k; i ++)
-            Clusters.put(i, new ArrayList<>());
-        try {
-            algorithm.setNumClusters(k);
-            algorithm.setPreserveInstancesOrder(true);
-            algorithm.buildClusterer(resources);
+    for (int i = 0; i < numClusters; i++)
+      Clusters.put(i, new ArrayList<>());
+    try {
+      algorithm.setNumClusters(numClusters);
+      algorithm.setPreserveInstancesOrder(true);
+      algorithm.buildClusterer(resources);
 
-            int[] assignments = algorithm.getAssignments();
+      int[] assignments = algorithm.getAssignments();
 
-            for (int i = 0; i < assignments.length; i++){
-                Clusters.get(assignments[i]).add(hardwareComponentList.get(i).getResource());
-            }
+      for (int i = 0; i < assignments.length; i++) {
+        Clusters.get(assignments[i]).add(hardwareComponentList.get(i).getResource());
+      }
 
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        return Clusters;
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+
+    // print cluster statistics
+    logger.info("Clusters:");
+    for (int c = 0; c < numClusters; c++) {
+      logger.info("Cluster " + c + ": " + Clusters.get(c).size() + " resources");
     }
+
+    return Clusters;
+  }
 }
diff --git a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/AbstractILPSolver.java b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/AbstractILPSolver.java
index 59cb830d2a2f5a6f23064c40c7a89e030243e4ee..b7d0e40b132c627aae0d3686573b74855dcb0741 100644
--- a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/AbstractILPSolver.java
+++ b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/AbstractILPSolver.java
@@ -61,6 +61,8 @@ public abstract class AbstractILPSolver implements BenchmarkableSolver {
   public synchronized Solution solve(Root model) throws SolvingException {
     StopWatch watch = StopWatch.start();
 
+    transformer.prepareModel(model);
+
     reset();
     if (model.getNumRequest() == 0) {
       return Solution.emptySolutionOf(model);
diff --git a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/AbstractILPTransformer.java b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/AbstractILPTransformer.java
index 46049ad706d86c213b09774842a086b1986803f0..08d99277def6195a90d45599a52d7be10148de37 100644
--- a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/AbstractILPTransformer.java
+++ b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/AbstractILPTransformer.java
@@ -1,7 +1,10 @@
 package de.tudresden.inf.st.mquat.solving.ilp;
 
-import de.tudresden.inf.st.mquat.jastadd.model.ILP;
-import de.tudresden.inf.st.mquat.jastadd.model.Root;
+import de.tudresden.inf.st.mquat.jastadd.model.*;
+import de.tudresden.inf.st.mquat.solving.clustering.KMeans;
+
+import java.util.ArrayList;
+import java.util.Map;
 
 public class AbstractILPTransformer implements ILPTransformer {
 
@@ -15,5 +18,24 @@ public class AbstractILPTransformer implements ILPTransformer {
     return "-abstract";
   }
 
+  @Override
+  public void prepareModel(Root model) {
+    int numClusters = Math.max(2,model.getHardwareModel().getNumResource()/5);
+    KMeans kmeans = new KMeans(model);
+
+    Map<Integer, ArrayList<Resource>> clusters = kmeans.buildClusters(numClusters);
+
+    for (int clusterId : clusters.keySet()) {
+
+      ResourceClass resourceClass = new ResourceClass();
+      resourceClass.setName(new Name("class_" + clusterId));
+
+      for (Resource resource : clusters.get(clusterId)) {
+        resource.setResourceClassRef(resourceClass.createRef());
+        resourceClass.addResourceRef(resource.createRef());
+      }
+    }
+  }
+
 
 }
diff --git a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ConcreteILPTransformer.java b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ConcreteILPTransformer.java
index 0ba3cfc5c983d6483c97926e640d877cf29aa5f4..eacb97c2006d5caba745896b89299d3deae3c95f 100644
--- a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ConcreteILPTransformer.java
+++ b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ConcreteILPTransformer.java
@@ -14,4 +14,9 @@ public class ConcreteILPTransformer implements ILPTransformer {
   public String getSuffix() {
     return "";
   }
+
+  @Override
+  public void prepareModel(Root model) {
+    // do nothing
+  }
 }
diff --git a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ILPMain.java b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ILPMain.java
index cb99848e7a313b5abc14df936495e46eb3fbac74..70e621a8542ac03b574e662cedec02e6075528ab 100644
--- a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ILPMain.java
+++ b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ILPMain.java
@@ -91,7 +91,7 @@ public class ILPMain {
     ScenarioGenerator gen = new ScenarioGenerator(new ScenarioDescription(1, 2, 0, 0, 0, 2, 2, 2.5, 3, 1, 0));
     Root model = gen.generate();
     KMeans kMeans = new KMeans(model);
-    Map<Integer, ArrayList<Resource>> clusters = kMeans.buildClusters();
+    Map<Integer, ArrayList<Resource>> clusters = kMeans.buildClusters(model.getHardwareModel().getNumResource()/5);
     Solver external = new GLPKSolver().setDeleteFilesOnExit(false);
     Solution solution = external.solve(model);
     logger.info(model.print(new MquatWriteSettings(" ")));
diff --git a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ILPTransformer.java b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ILPTransformer.java
index 8c75aeedcb4ff25d02a527dc049678bf26c5caf3..da5a1a703ccac6a6df6dcac2edf933de8fd8c1a1 100644
--- a/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ILPTransformer.java
+++ b/jastadd-mquat-solver-ilp/src/main/java/de/tudresden/inf/st/mquat/solving/ilp/ILPTransformer.java
@@ -7,4 +7,6 @@ public interface ILPTransformer {
   ILP getILP(Root model);
 
   String getSuffix();
+
+  void prepareModel(Root model);
 }