Index: profile/src/main/java/geniusweb/profile/DefaultPartialOrdering.java
===================================================================
--- profile/src/main/java/geniusweb/profile/DefaultPartialOrdering.java	(revision 52)
+++ profile/src/main/java/geniusweb/profile/DefaultPartialOrdering.java	(revision 53)
@@ -1,7 +1,7 @@
 package geniusweb.profile;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -10,10 +10,53 @@
 import java.util.Set;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
 import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonGetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 import geniusweb.issuevalue.Bid;
 import geniusweb.issuevalue.Domain;
+
+/**
+ * Strange that we need to do this. Bid is completely standard. The complication
+ * that this solves is that the keys are places as STRING in the json code
+ * because json allows only strings as key.
+ */
+@SuppressWarnings("serial")
+class BidDeserializer extends KeyDeserializer {
+	private ObjectMapper jackson = new ObjectMapper();
+
+	@Override
+	public Bid deserializeKey(String key, DeserializationContext ctxt)
+			throws IOException {
+		return jackson.readValue(key, Bid.class);
+	}
+
+}
+
+/**
+ * Serializes a Bid to string. Unfortunately by default jackson uses
+ * key.toString() for serializing (rather than
+ * {@link ObjectMapper#writeValueAsString(Object)}).
+ *
+ */
+class BidSerializer extends JsonSerializer<Bid> {
+	private ObjectMapper jackson = new ObjectMapper();
+
+	@Override
+	public void serialize(Bid bid, JsonGenerator gen,
+			SerializerProvider serializers) throws IOException {
+		gen.writeFieldName(jackson.writeValueAsString(bid));
+	}
+
+}
 
 /**
@@ -26,4 +69,5 @@
  * limited anyway.
  */
+@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
 public class DefaultPartialOrdering extends DefaultProfile
 		implements PartialOrdering {
@@ -33,17 +77,14 @@
 	 * other bid.
 	 */
-	private final Map<Bid, Set<Bid>> isBetter;
+	@JsonDeserialize(keyUsing = BidDeserializer.class)
+	@JsonSerialize(keyUsing = BidSerializer.class)
+	private final Map<Bid, Set<Bid>> better;
 
 	/**
-	 * 
-	 * @param name           name of the profile
-	 * @param domain         the {@link Domain}
-	 * @param reservationbid the reservation bid, this is the minimum acceptable
-	 *                       bid
-	 * @param bids           a list of bids that this ordering contains
-	 *                       comparisons for
-	 * @param isbetterList   a list of tuples [bid1index, bid2index]. It
-	 *                       indicates that bids[bid1index] isbetterthan
-	 *                       bids[bid2index].
+	 * @param name           the name for the profile
+	 * @param domain         the {@link Domain} description
+	 * @param reservationbid the reservation {@link Bid}
+	 * @param better         a map with keys = a better bid and value=a less
+	 *                       good bid.
 	 */
 	@JsonCreator
@@ -51,27 +92,14 @@
 			@JsonProperty("domain") Domain domain,
 			@JsonProperty("reservationBid") Bid reservationbid,
-			@JsonProperty("bids") List<Bid> bids,
-			@JsonProperty("better") List<List<Integer>> isbetterList) {
-		this(name, domain, reservationbid, makeBidMap(bids, isbetterList));
-	}
-
-	/**
-	 * @param name           the name for the profile
-	 * @param domain         the {@link Domain} description
-	 * @param reservationbid the reservation {@link Bid}
-	 * @param isBetterMap    a map with keys = a better bid and value=a less
-	 *                       good bid.
-	 */
-	public DefaultPartialOrdering(String name, Domain domain,
-			Bid reservationbid, Map<Bid, Set<Bid>> isBetterMap) {
+			@JsonProperty("better") Map<Bid, Set<Bid>> better) {
 		super(name, domain, reservationbid);
-		this.isBetter = isBetterMap;
+		this.better = better;
 	}
 
 	@Override
 	public boolean isPreferredOrEqual(Bid bid1, Bid bid2) {
-		if (!isBetter.containsKey(bid1))
+		if (!better.containsKey(bid1))
 			return false;
-		return isBetter.get(bid1).contains(bid2);
+		return better.get(bid1).contains(bid2);
 	}
 
@@ -81,16 +109,14 @@
 	 *         or as worse than another bid
 	 */
-	@JsonGetter
 	public List<Bid> getBids() {
 		// FIXME the iteration order may not be guaranteed!
 		Set<Bid> bids = new HashSet<>();
-		for (Bid bid : isBetter.keySet()) {
+		for (Bid bid : better.keySet()) {
 			bids.add(bid);
-			bids.addAll(isBetter.get(bid));
+			bids.addAll(better.get(bid));
 		}
 		return new ArrayList<Bid>(bids);
 	}
 
-	@JsonGetter
 	/**
 	 * 
@@ -103,6 +129,6 @@
 
 		for (Bid bid : bidslist) {
-			if (isBetter.containsKey(bid)) {
-				for (Bid worsebid : isBetter.get(bid)) {
+			if (better.containsKey(bid)) {
+				for (Bid worsebid : better.get(bid)) {
 					betterlist.add(Arrays.asList(bidslist.indexOf(bid),
 							bidslist.indexOf(worsebid)));
@@ -116,27 +142,6 @@
 	@Override
 	public String toString() {
-		return "DefaultPartialOrdering[" + getValuesString() + "," + isBetter
+		return "DefaultPartialOrdering[" + getValuesString() + "," + better
 				+ "]";
-	}
-
-	private static Map<Bid, Set<Bid>> makeBidMap(List<Bid> bids,
-			List<List<Integer>> isBetterList) {
-		Map<Bid, Set<Bid>> betterMap = new HashMap<>();
-
-		for (List<Integer> tuple : isBetterList) {
-			if (tuple.size() != 2) {
-				throw new IllegalArgumentException("Expected tuple but found "
-						+ tuple + "in " + isBetterList);
-			}
-			Bid betterbid = bids.get(tuple.get(0));
-			Set<Bid> map = betterMap.get(betterbid);
-			if (map == null) {
-				map = new HashSet<>();
-				betterMap.put(betterbid, map);
-			}
-			map.add(bids.get(tuple.get(1)));
-		}
-
-		return betterMap;
 	}
 
@@ -145,6 +150,5 @@
 		final int prime = 31;
 		int result = super.hashCode();
-		result = prime * result
-				+ ((isBetter == null) ? 0 : isBetter.hashCode());
+		result = prime * result + ((better == null) ? 0 : better.hashCode());
 		return result;
 	}
@@ -159,8 +163,8 @@
 			return false;
 		DefaultPartialOrdering other = (DefaultPartialOrdering) obj;
-		if (isBetter == null) {
-			if (other.isBetter != null)
+		if (better == null) {
+			if (other.better != null)
 				return false;
-		} else if (!isBetter.equals(other.isBetter))
+		} else if (!better.equals(other.better))
 			return false;
 		return true;
Index: profile/src/main/java/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.java
===================================================================
--- profile/src/main/java/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.java	(revision 52)
+++ profile/src/main/java/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.java	(revision 53)
@@ -49,12 +49,12 @@
 	 * @param domain  the {@link Domain} in which this profile is defined.
 	 * @param name    the name of this profile. Must be simple name (a-Z, 0-9)
-	 * @param utils   a map with key: issue names (String) and value: the values
+	 * @param issueUtilities   a map with key: issue names (String) and value: the values
 	 *                for that issue. There MUST NOT be a null issue. All values
 	 *                MUST NOT be null.
-	 * @param weights the weight of each issue in the computation of the
+	 * @param issueWeights the weight of each issue in the computation of the
 	 *                weighted sum. The issues must be the same as those in the
 	 *                utils map. All weights MUST NOT be null. The weights MUST
 	 *                sum to 1.
-	 * @param resBid  the reservation bid. Only bids that are
+	 * @param reservationBid  the reservation bid. Only bids that are
 	 *                {@link #isPreferredOrEqual(Bid, Bid)} should be accepted.
 	 *                Can be null, meaning that there is no reservation bid and
@@ -66,31 +66,26 @@
 	public LinearAdditiveUtilitySpace(@JsonProperty("domain") Domain domain,
 			@JsonProperty("name") String name,
-			@JsonProperty("issueUtilities") Map<String, ValueSetUtilities> utils,
-			@JsonProperty("issueWeights") Map<String, BigDecimal> weights,
-			@JsonProperty("reservationBid") Bid resBid) {
-		this.domain = domain;
-		this.name = name;
-		this.reservationBid = resBid;
-		this.issueUtilities.putAll(utils);
-		this.issueWeights.putAll(weights);
+			@JsonProperty("issueUtilities") Map<String, ValueSetUtilities> issueUtilities,
+			@JsonProperty("issueWeights") Map<String, BigDecimal> issueWeights,
+			@JsonProperty("reservationBid") Bid reservationBid) {
 
 		if (domain == null) {
 			throw new NullPointerException("domain=null");
 		}
-		if (utils == null) {
+		if (issueUtilities == null) {
 			throw new NullPointerException("utils=null");
 		}
-		if (weights == null) {
+		if (issueWeights == null) {
 			throw new NullPointerException("weights=null");
 		}
-		if (utils.values().contains(null)) {
+		if (issueUtilities.values().contains(null)) {
 			throw new NullPointerException(
 					"One of the ValueSetUtilities in issueUtilitiesis null:"
-							+ utils);
-		}
-		if (weights.values().contains(null)) {
+							+ issueUtilities);
+		}
+		if (issueWeights.values().contains(null)) {
 			throw new NullPointerException("One of the weights is null");
 		}
-		if (utils.keySet().contains(null)) {
+		if (issueUtilities.keySet().contains(null)) {
 			throw new NullPointerException("One of the issue names is null");
 		}
@@ -99,14 +94,14 @@
 					"Name must be simple (a-Z, 0-9) but got " + name);
 		}
-		if (!(utils.keySet().equals(domain.getIssues()))) {
+		if (!(issueUtilities.keySet().equals(domain.getIssues()))) {
 			throw new IllegalArgumentException(
 					"The issues in  utilityspace and domain do not match: utilityspace has issues "
-							+ utils.keySet() + " but domain contains "
+							+ issueUtilities.keySet() + " but domain contains "
 							+ domain.getIssues());
 		}
-		if (!(weights.keySet().equals(domain.getIssues()))) {
+		if (!(issueWeights.keySet().equals(domain.getIssues()))) {
 			throw new IllegalArgumentException(
 					"The issues in weights and domain do not match: weights has "
-							+ weights.keySet() + " but domain contains "
+							+ issueWeights.keySet() + " but domain contains "
 							+ domain.getIssues());
 		}
@@ -118,17 +113,24 @@
 		}
 
-		BigDecimal sum = weights.values().stream().reduce(BigDecimal.ZERO,
+		BigDecimal sum = issueWeights.values().stream().reduce(BigDecimal.ZERO,
 				BigDecimal::add);
 		if (BigDecimal.ONE.compareTo(sum) != 0) { // equals does NOT work for
 													// comparing BigDecimals!!
 			throw new IllegalArgumentException("The sum of the weights ("
-					+ weights.values() + ") must be 1");
-		}
-		if (resBid != null) {
-			String message = domain.isFitting(resBid);
+					+ issueWeights.values() + ") must be 1");
+		}
+		if (reservationBid != null) {
+			String message = domain.isFitting(reservationBid);
 			if (message != null)
 				throw new IllegalArgumentException(
 						"reservationbid is not fitting domain: " + message);
 		}
+
+		this.domain = domain;
+		this.name = name;
+		this.reservationBid = reservationBid;
+		this.issueUtilities.putAll(issueUtilities);
+		this.issueWeights.putAll(issueWeights);
+
 	}
 
Index: profile/src/main/java/geniusweb/profile/utilityspace/PartsUtilities.java
===================================================================
--- profile/src/main/java/geniusweb/profile/utilityspace/PartsUtilities.java	(revision 52)
+++ profile/src/main/java/geniusweb/profile/utilityspace/PartsUtilities.java	(revision 53)
@@ -7,4 +7,5 @@
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
@@ -13,6 +14,9 @@
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import geniusweb.issuevalue.Domain;
 import geniusweb.issuevalue.Value;
 import geniusweb.issuevalue.ValueSet;
+import tudelft.utilities.immutablelist.ImmutableList;
+import tudelft.utilities.immutablelist.Outer;
 
 /**
@@ -41,14 +45,15 @@
 	 * 
 	 * @param issues list of issues
-	 * @param utils  with keys: list of values and value: utility value for that
-	 *               list of values. All list-of-values missing from the map are
-	 *               assumed to have utility 0. This includes partial bids where
-	 *               list-of-values contain null objects.
+	 * @param utils  with keys: list of values, one for each issue in issues (in
+	 *               the order of the list of issues) and value: utility value
+	 *               for that list of values. All list-of-values missing from
+	 *               the map are assumed to have utility 0. This includes
+	 *               partial bids where list-of-values contain null objects.
 	 */
 	public PartsUtilities(List<String> issues,
 			Map<ProductOfValue, BigDecimal> utils) {
-		if (issues == null || utils == null) {
+		if (issues == null || utils == null || issues.isEmpty()) {
 			throw new IllegalArgumentException(
-					"issues and utils must be not null");
+					"issues and utils must be not null or empty");
 		}
 
@@ -149,4 +154,25 @@
 	}
 
+	/**
+	 * Check that all needed values have been set to a utility.
+	 * 
+	 * @param domain the domain for which this should be parts. We need this to
+	 *               check that the values used are actually from the dmoain
+	 * @throws IllegalArgumentException if a problem is found
+	 */
+	public void checkComplete(Domain domain) {
+
+		final List<ImmutableList<Value>> valuesofissues = issues.stream()
+				.map(iss -> domain.getValues(iss)).collect(Collectors.toList());
+
+		// check that all possible value combinations are handled
+		for (ImmutableList<Value> vals : new Outer<Value>(valuesofissues)) {
+			ProductOfValue expected = ProductOfValue.create(vals);
+			if (!utilities.containsKey(expected))
+				throw new IllegalArgumentException(
+						"Values " + vals + " must be assigned a utility");
+		}
+	}
+
 	@Override
 	public boolean equals(Object obj) {
@@ -176,10 +202,15 @@
 	}
 
+	/**
+	 * Check that the set of utilities is complete and no weird utility values
+	 * were used
+	 * 
+	 */
 	private void checkUtilities() {
-		if (utilities.values().stream()
-				.anyMatch(v -> v == null || v.compareTo(BigDecimal.ZERO) < 0
-						|| v.compareTo(BigDecimal.ONE) > 0)) {
-			throw new IllegalArgumentException(
-					"part weights must all be in [0,1]");
+		for (BigDecimal value : utilities.values()) {
+			if (value == null || value.compareTo(BigDecimal.ZERO) < 0
+					|| value.compareTo(BigDecimal.ONE) > 0)
+				throw new IllegalArgumentException(
+						"part weights must all be in [0,1] but found " + value);
 		}
 	}
@@ -196,5 +227,4 @@
 
 	/**
-	 * 
 	 * @return the max utility of all values contained here.
 	 */
Index: profile/src/main/java/geniusweb/profile/utilityspace/ProductOfValue.java
===================================================================
--- profile/src/main/java/geniusweb/profile/utilityspace/ProductOfValue.java	(revision 52)
+++ profile/src/main/java/geniusweb/profile/utilityspace/ProductOfValue.java	(revision 53)
@@ -2,4 +2,5 @@
 
 import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 
@@ -10,7 +11,8 @@
 
 import geniusweb.issuevalue.Value;
+import tudelft.utilities.immutablelist.ImmutableList;
 
 /**
- * A {@link Value} that is the product of some existing values
+ * A {@link Value} that is a list of a number of values in the space
  */
 @JsonDeserialize(using = JsonDeserializer.None.class)
@@ -24,4 +26,12 @@
 	public ProductOfValue(List<Value> newvals) {
 		values.addAll(newvals);
+	}
+
+	public static ProductOfValue create(ImmutableList<Value> vals) {
+		List<Value> list = new LinkedList<>();
+		for (Value val : vals) {
+			list.add(val);
+		}
+		return new ProductOfValue(list);
 	}
 
Index: profile/src/main/java/geniusweb/profile/utilityspace/SumOfGroupsUtilitySpace.java
===================================================================
--- profile/src/main/java/geniusweb/profile/utilityspace/SumOfGroupsUtilitySpace.java	(revision 52)
+++ profile/src/main/java/geniusweb/profile/utilityspace/SumOfGroupsUtilitySpace.java	(revision 53)
@@ -3,4 +3,5 @@
 import java.math.BigDecimal;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -51,11 +52,14 @@
 	 * @param partutils a map with key: part names (String) and value: the
 	 *                  {@link PartsUtilities} for that part. There MUST NOT be
-	 *                  a null part name. All values MUST NOT be null. The
-	 *                  PartsUtilities must match the
+	 *                  a null part name. The PartsUtilities must be complete:
+	 *                  all possible combinations of all parts must cover all
+	 *                  combinations of issue values and assign a proper utility
+	 *                  in [0,1].
 	 * @param resBid    the reservation bid. Only bids that are
 	 *                  {@link #isPreferredOrEqual(Bid, Bid)} should be
 	 *                  accepted. Can be null, meaning that there is no
 	 *                  reservation bid and any agreement is better than no
-	 *                  agreement.
+	 *                  agreement. Read the note about partial bids in
+	 *                  {@link #getUtility(Bid)}.
 	 * @throws NullPointerException     if values are incorrectly null.
 	 * @throws IllegalArgumentException if preconditions not met.
@@ -69,34 +73,47 @@
 		this.partUtilities.putAll(partutils);
 
-		String err = checkParts();
-		if (err != null) {
-			throw new IllegalArgumentException(err);
-		}
-	}
-
-	/**
-	 * Copy settings in las. This will have the exact same utilities as the las
-	 * but this then gives you the power to change it into a non-linear space by
-	 * grouping.
+		checkParts();
+	}
+
+	/**
+	 * Copy settings in las. This will have the exact same utilities as the
+	 * {@link LinearAdditive} space but this then gives you the power to change
+	 * it into a non-linear space by grouping.
 	 * 
 	 * @param las the {@link LinearAdditive} to be converted/copied.
 	 * 
 	 */
-	public SumOfGroupsUtilitySpace(LinearAdditive las) {
-		this(las.getDomain(), las.getName(), las2parts(las),
-				las.getReservationBid());
-	}
-
+	public static SumOfGroupsUtilitySpace create(LinearAdditive las) {
+		return new SumOfGroupsUtilitySpace(las.getDomain(), las.getName(),
+				las2parts(las), las.getReservationBid());
+	}
+
+	/**
+	 * 
+	 * @return all partsutilities
+	 */
+	public Map<String, PartsUtilities> getPartsUtilities() {
+		return Collections.unmodifiableMap(partUtilities);
+	}
+
+	/**
+	 * 
+	 * Note: If bid is partial for a group, the utility of that group can not be
+	 * evaluated and is set to 0.
+	 * 
+	 */
 	@Override
 	public BigDecimal getUtility(Bid bid) {
-		return partUtilities.keySet().stream()
-				.map(partname -> util(partname, bid))
-				.reduce(BigDecimal.ZERO, BigDecimal::add);
+		BigDecimal sum = BigDecimal.ZERO;
+		for (String partname : partUtilities.keySet()) {
+			sum = sum.add(util(partname, bid));
+		}
+		return sum;
 	}
 
 	@Override
 	public String toString() {
-		return "PartialAdditive[" + partUtilities + "," + getReservationBid()
-				+ "]";
+		return "SumOfGroupsUtilitySpace[" + getName() + "," + partUtilities
+				+ "," + getReservationBid() + "]";
 	}
 
@@ -235,5 +252,5 @@
 	 * @return error string, or null if no error (all parts seem fine)
 	 */
-	private String checkParts() {
+	private void checkParts() throws IllegalArgumentException {
 
 		Set<String> collectedIssues = new HashSet<>();
@@ -241,5 +258,6 @@
 			PartsUtilities part = partUtilities.get(partname);
 			if (part == null) {
-				return "partUtilities " + partname + " contains null value";
+				throw new IllegalArgumentException(
+						"partUtilities " + partname + " contains null value");
 			}
 			List<String> issues = part.getIssues();
@@ -247,17 +265,21 @@
 			intersection.retainAll(issues);
 			if (!intersection.isEmpty()) {
-				return "issues " + intersection + " occur multiple times";
-			}
+				throw new IllegalArgumentException(
+						"issues " + intersection + " occur multiple times");
+			}
+			part.checkComplete(getDomain());
 			collectedIssues.addAll(issues);
 		}
 
 		if (!collectedIssues.equals(getDomain().getIssues())) {
-			return "parts must cover the domain issues "
-					+ getDomain().getIssues() + " but cover " + collectedIssues;
+			throw new IllegalArgumentException(
+					"parts must cover the domain issues "
+							+ getDomain().getIssues() + " but cover "
+							+ collectedIssues);
 		}
 		if (getMaxUtility().compareTo(BigDecimal.ONE) > 0) {
-			return "Max utility of the space exceedds 1";
-		}
-		return null;
+			throw new IllegalArgumentException(
+					"Max utility of the space exceedds 1");
+		}
 
 	}
