Index: opponentmodel/src/test/java/geniusweb/opponentmodel/FrequencyOppModelTest.java
===================================================================
--- opponentmodel/src/test/java/geniusweb/opponentmodel/FrequencyOppModelTest.java	(revision 33)
+++ opponentmodel/src/test/java/geniusweb/opponentmodel/FrequencyOppModelTest.java	(revision 34)
@@ -2,4 +2,5 @@
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
@@ -239,3 +240,10 @@
 
 	}
+
+	@Test
+	public void testStableName() {
+		String name = oppModel1.getName();
+		assertNotNull(name);
+		assertEquals(name, oppModel1.getName());
+	}
 }
Index: opponentmodel/src/test/java/geniusweb/opponentmodel/bayesian/BayesianOppModelTest.java
===================================================================
--- opponentmodel/src/test/java/geniusweb/opponentmodel/bayesian/BayesianOppModelTest.java	(revision 34)
+++ opponentmodel/src/test/java/geniusweb/opponentmodel/bayesian/BayesianOppModelTest.java	(revision 34)
@@ -0,0 +1,350 @@
+package geniusweb.opponentmodel.bayesian;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.issuevalue.Bid;
+import geniusweb.issuevalue.DiscreteValue;
+import geniusweb.issuevalue.DiscreteValueSet;
+import geniusweb.issuevalue.Domain;
+import geniusweb.issuevalue.NumberValue;
+import geniusweb.issuevalue.NumberValueSet;
+import geniusweb.issuevalue.Value;
+import geniusweb.issuevalue.ValueSet;
+import geniusweb.profile.utilityspace.UtilitySpace;
+import geniusweb.progress.Progress;
+import tudelft.utilities.immutablelist.ImmutableList;
+import tudelft.utilities.immutablelist.Outer;
+import tudelft.utilities.junit.GeneralTests;
+
+public class BayesianOppModelTest extends GeneralTests<BayesianOpponentModel> {
+	private final static String ISS1 = "issue1";
+	private final static String ISS2 = "issue2";
+	private static final DiscreteValue I1V1 = new DiscreteValue("i1v1");
+	private static final DiscreteValue I1V2 = new DiscreteValue("i1v2");
+	private static final DiscreteValue I2V1 = new DiscreteValue("i2v1");
+	private static final DiscreteValue I2V2 = new DiscreteValue("i2v2");
+	private static final DiscreteValue I1V2b = new DiscreteValue("i1v2b");
+	private static final Progress progress = mock(Progress.class);
+	private static final PartyId other = new PartyId("other");
+	private static final Random rnd = new Random();
+
+	private static Domain domain, domain2, domain3;
+	private static BayesianOpponentModel oppModel1, oppModel1b, oppModel2,
+			oppModel3;
+
+	private final static List<List<Bid>> list = new LinkedList<>();
+
+	private final static Bid bid1, bid2, bid3;
+	private static final BigDecimal HALF = new BigDecimal("0.5");
+
+	static {
+		Map<String, ValueSet> issues = new HashMap<>();
+		Collection<DiscreteValue> discretevalues1 = new LinkedList<>();
+		discretevalues1.add(I1V1);
+		discretevalues1.add(I1V2);
+		DiscreteValueSet values1 = new DiscreteValueSet(discretevalues1);
+		issues.put(ISS1, values1);
+		NumberValueSet values2 = new NumberValueSet(BigDecimal.ZERO,
+				BigDecimal.TEN, new BigDecimal("0.3"));
+		issues.put(ISS2, values2);
+		domain = new Domain("test", issues);
+		domain2 = new Domain("test2", issues);
+
+		// slightly different issue1
+		issues = new HashMap<>();
+		discretevalues1 = new LinkedList<>();
+		discretevalues1.add(I1V1);
+		discretevalues1.add(I1V2b);
+		values1 = new DiscreteValueSet(discretevalues1);
+		issues.put(ISS1, values1);
+		values2 = new NumberValueSet(BigDecimal.ZERO, BigDecimal.TEN,
+				new BigDecimal("0.3"));
+		issues.put(ISS2, values2);
+		domain3 = new Domain("test", issues);
+
+		// all bids are for domain
+		Map<String, Value> issuevalues = new HashMap<>();
+		issuevalues.put(ISS1, I1V1);
+		issuevalues.put(ISS2, new NumberValue(new BigDecimal("1.2")));
+		bid1 = new Bid(issuevalues);
+
+		issuevalues.put(ISS1, I1V1);
+		issuevalues.put(ISS2, new NumberValue(new BigDecimal("1.5")));
+		bid2 = new Bid(issuevalues);
+
+		issuevalues.put(ISS1, I1V2);
+		issuevalues.put(ISS2, new NumberValue(new BigDecimal("1.5")));
+		bid3 = new Bid(issuevalues);
+
+		oppModel1 = new BayesianOpponentModel(domain);
+		oppModel1b = new BayesianOpponentModel(domain);
+		oppModel2 = new BayesianOpponentModel(domain)
+				.with(new Offer(other, bid1), progress);
+		oppModel3 = new BayesianOpponentModel(domain)
+				.with(new Offer(other, bid2), progress);
+
+	}
+
+	private ObjectMapper jackson = new ObjectMapper();
+
+	@Override
+	public List<List<BayesianOpponentModel>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(oppModel1, oppModel1b),
+				Arrays.asList(oppModel2), Arrays.asList(oppModel3));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		// not much we can test. The internals are too detailed and situation
+		// specific
+		return Arrays.asList("BayesianOpponentModel.*",
+				"BayesianOpponentModel.*", "BayesianOpponentModel.*");
+	}
+
+	@Test(expected = NullPointerException.class)
+	public void smokeTestNull() {
+		new BayesianOpponentModel().with((Domain) null, null);
+	}
+
+	@Test
+	public void testNormalization() {
+		System.out.println(oppModel1.getWeights());
+	}
+
+	@SuppressWarnings("unused")
+	@Test
+	public void smokeTest() {
+		new BayesianOpponentModel(domain);
+	}
+
+	@Test
+	public void testEmptyModel() {
+		BayesianOpponentModel oppModel = new BayesianOpponentModel()
+				.with(domain, null);
+		// somewhere 0.4-0.6 should be fine.
+		// exact value varies with the exact hypotheses
+		assertEquals(0.5, oppModel.getUtility(bid1).doubleValue(), 0.1);
+		assertEquals(0.5, oppModel.getUtility(bid2).doubleValue(), 0.1);
+	}
+
+	@Test
+	public void testEmptyBid() {
+		Bid bid = new Bid(Collections.emptyMap());
+		assertEquals(0d, oppModel1.getUtility(bid).doubleValue(), 0.000001);
+	}
+
+	@Test
+	public void testUpdate() {
+
+		BayesianOpponentModel newOppModel = oppModel1
+				.with(new Offer(other, bid1), progress);
+		assertTrue(oppModel1.getUtility(bid1).doubleValue() < newOppModel
+				.getUtility(bid1).doubleValue());
+		assertTrue(newOppModel.getBidHistory().size() == 1);
+
+		// and if we continue with next bid, the first should still hold
+		// and the next bid should also improve
+		BayesianOpponentModel newOppModel2 = newOppModel
+				.with(new Offer(other, bid2), progress);
+		assertTrue(oppModel1.getUtility(bid1).doubleValue() < newOppModel2
+				.getUtility(bid1).doubleValue());
+		assertTrue(oppModel1.getUtility(bid2).doubleValue() < newOppModel2
+				.getUtility(bid2).doubleValue());
+
+		assertTrue(newOppModel2.getBidHistory().size() == 2);
+
+		// bid1= (i1v1, 1.2), bid2= (i1v1, 1.5)
+		// so bidding keeps ISS1 but changes ISS2.
+		// check that ISS1 increases weight and ISS2 decreases
+		assertTrue(oppModel1.getWeights().get(ISS1).doubleValue() < newOppModel2
+				.getWeights().get(ISS1).doubleValue());
+
+		// it may not work like this, due to renormalization steps.
+//		assertTrue(oppModel1.getWeights().get(ISS2).doubleValue() < newOppModel2
+//				.getWeights().get(ISS2).doubleValue());
+
+	}
+
+	@Test
+	public void testPartialBidUpdate() {
+		BayesianOpponentModel oppModel = oppModel1.with(new Offer(other, bid1),
+				progress);
+		Bid partialbid = new Bid(ISS1, I1V1);
+		oppModel.with(new Offer(other, partialbid), progress);
+	}
+
+	@Test
+	public void testStableName() {
+		String name = oppModel1.getName();
+		assertNotNull(name);
+		assertEquals(name, oppModel1.getName());
+	}
+
+	@Test
+	public void testWeightsNormalized() {
+		assertEquals(1d, oppModel1.getWeights().values().stream()
+				.mapToDouble(f -> f).sum(), 0.000001);
+	}
+
+	/**
+	 * duration/stress test with random bids
+	 */
+	@Test
+	public void testRepeatedRandomBidding() throws IOException {
+		// use japantrip as it has numbervalue as well
+		String profile = new String(
+				Files.readAllBytes(
+						Paths.get("src/test/resources/japantrip.json")),
+				StandardCharsets.UTF_8);
+		Domain dom = jackson.readValue(profile, Domain.class);
+		BayesianOpponentModel model = new BayesianOpponentModel(dom);
+
+		for (int time = 1; time < 100; time++) {
+			Bid bid = getRandomBid(dom, null, null);
+			double util = model.getUtility(bid).doubleValue();
+			System.out.println(util);
+			assertTrue(util > 0d && util < 1d);
+			model = model.with(new Offer(other, bid), null);
+		}
+	}
+
+	/**
+	 * duration/stress test with similar bids
+	 */
+
+	@Test
+	public void testRepeateSimilarBidding() throws IOException {
+		// use japantrip as it has numbervalue as well
+		String profile = new String(
+				Files.readAllBytes(
+						Paths.get("src/test/resources/japantrip.json")),
+				StandardCharsets.UTF_8);
+		Domain dom = jackson.readValue(profile, Domain.class);
+		BayesianOpponentModel model = new BayesianOpponentModel(dom);
+
+		for (int time = 1; time < 100; time++) {
+			Bid bid = getRandomBid(dom, "purpose",
+					new DiscreteValue("sightseeing"));
+			double util = model.getUtility(bid).doubleValue();
+			// System.out.println(util);
+			assertTrue(util > 0d && util < 1d);
+			model = model.with(new Offer(other, bid), null);
+		}
+	}
+
+	/**
+	 * 
+	 * @param dom          the {@link Domain}
+	 * @param specialissue an issue name. If not null, value is used for this
+	 *                     issue.
+	 * @param value        the value to use for specialissue.
+	 * 
+	 * @return random bid from domain. Notice that bidspace depends on profile
+	 *         so we should not be using bidspace. This makes this a bit clumsy
+	 */
+	private Bid getRandomBid(Domain dom, String specialissue, Value value) {
+		Map<String, Value> map = new HashMap<>();
+		for (String issue : dom.getIssues()) {
+			Value val = value;
+			if (!issue.equals(specialissue)) {
+				ValueSet values = dom.getValues(issue);
+				val = values.get(rnd.nextInt(values.size().intValue()));
+			}
+			map.put(issue, val);
+		}
+		return new Bid(map);
+	}
+
+	/**
+	 * Generate all bids from a space, sort to order, and pump them in that
+	 * order into Bayesian model. In the end the model should "match" with the
+	 * original space.
+	 * 
+	 * @return
+	 * @throws IOException
+	 */
+	@Ignore
+	@Test
+	public void testPumpBidsInOrder() throws IOException {
+		String profile = new String(
+				Files.readAllBytes(
+						Paths.get("src/test/resources/japantrip1.json")),
+				StandardCharsets.UTF_8);
+		UtilitySpace japantrip = jackson.readValue(profile, UtilitySpace.class);
+		BayesianOpponentModel model = new BayesianOpponentModel(
+				japantrip.getDomain());
+
+		Map<Double, Bid> sorted = getAllBids(japantrip);
+		for (Double val : sorted.keySet()) {
+			model = model.with(new Offer(other, sorted.get(val)), null);
+		}
+		System.out.println(model);
+
+		Map<String, Double> weights = model.getWeights();
+		System.out.println(weights);
+
+		// you would expect this result but in practice it doesn't
+		assertEquals(weights.get("place").doubleValue(), 0.57, 0.1);
+		assertEquals(weights.get("budget").doubleValue(), 0.09, 0.1);
+		assertEquals(weights.get("days").doubleValue(), 0.17, 0.1);
+		assertEquals(weights.get("purpose").doubleValue(), 0.17, 0.1);
+
+	}
+
+	/**
+	 * 
+	 * @param utllspace the {@link UtilitySpace} to be compiled
+	 * @return all bids, sorted from high to low utility. Notice, normally you
+	 *         would use AllBidsList but we can not import that module here.
+	 *         Warning, enumerating and sorting all bids in a space this way is
+	 *         too expensive for use in real time negotiation.
+	 */
+	private Map<Double, Bid> getAllBids(UtilitySpace utllspace) {
+		Domain dom = utllspace.getDomain();
+		List<String> issues = new ArrayList<String>(
+				utllspace.getDomain().getIssues());
+		List<ImmutableList<Value>> values = issues.stream()
+				.map(issue -> dom.getValues(issue))
+				.collect(Collectors.toList());
+
+		Outer<Value> allBidValues = new Outer<Value>(values);
+		Map<Double, Bid> bidmap = new TreeMap<>(Collections.reverseOrder());
+		for (ImmutableList<Value> bidvals : allBidValues) {
+			Map<String, Value> vals = new HashMap<>();
+			for (int n = 0; n < issues.size(); n++) {
+				vals.put(issues.get(n), bidvals.get(n));
+			}
+			Bid bid = new Bid(vals);
+			bidmap.put(utllspace.getUtility(bid).doubleValue(), bid);
+		}
+
+		return bidmap;
+	}
+}
Index: opponentmodel/src/test/java/geniusweb/opponentmodel/bayesian/TriangleValueSetUtilTest.java
===================================================================
--- opponentmodel/src/test/java/geniusweb/opponentmodel/bayesian/TriangleValueSetUtilTest.java	(revision 34)
+++ opponentmodel/src/test/java/geniusweb/opponentmodel/bayesian/TriangleValueSetUtilTest.java	(revision 34)
@@ -0,0 +1,100 @@
+package geniusweb.opponentmodel.bayesian;
+
+import static org.junit.Assert.assertEquals;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import geniusweb.issuevalue.NumberValue;
+import tudelft.utilities.junit.GeneralTests;
+
+public class TriangleValueSetUtilTest
+		extends GeneralTests<TriangleValueSetUtilities> {
+
+	private static final double MAXE = 0.00000001;
+	private static final BigDecimal N0 = BigDecimal.ZERO;
+	private static final BigDecimal N01 = new BigDecimal("0.1");
+	private static final BigDecimal N02 = new BigDecimal("0.2");
+	private static final BigDecimal N03 = new BigDecimal("0.3");
+	private static final BigDecimal N04 = new BigDecimal("0.4");
+	private static final BigDecimal N05 = new BigDecimal("0.5");
+	private static final BigDecimal N07 = new BigDecimal("0.7");
+	private static final BigDecimal N09 = new BigDecimal("0.9");
+	private static final BigDecimal N1 = BigDecimal.ONE;
+
+	private TriangleValueSetUtilities vset1, vset1a, vset2, vset3;
+
+	@Before
+	public void before() {
+		vset1 = new TriangleValueSetUtilities(N01, N02, N04, N05, N07, N09);
+		vset1a = new TriangleValueSetUtilities(N01, N02, N04, N05, N07, N09);
+		vset2 = new TriangleValueSetUtilities(N01, N02, N01, N05, N07, N09);
+		vset3 = new TriangleValueSetUtilities(N01, N02, N07, N05, N07, N09);
+	}
+
+	@Override
+	public List<List<TriangleValueSetUtilities>> getGeneralTestData() {
+		return Arrays.asList(Arrays.asList(vset1, vset1a), Arrays.asList(vset2),
+				Arrays.asList(vset3));
+	}
+
+	@Override
+	public List<String> getGeneralTestStrings() {
+		return Arrays.asList(
+				"TriangleValueSetUtilities.0.1->0.2,0.4->0.5,0.7->0.9.",
+				"TriangleValueSetUtilities.0.1->0.2,0.1->0.5,0.7->0.9.",
+				"TriangleValueSetUtilities.0.1->0.2,0.7->0.5,0.7->0.9.");
+	}
+
+	@Test
+	public void testInterpolation() {
+		assertEquals(0, vset1.getUtility(new NumberValue(N0)).doubleValue(),
+				MAXE);
+		assertEquals(0.2, vset1.getUtility(new NumberValue(N01)).doubleValue(),
+				MAXE);
+		assertEquals(0.3 /* 2 + 1/2*3 */,
+				vset1.getUtility(new NumberValue(N02)).doubleValue(), MAXE);
+		assertEquals(0.5, vset1.getUtility(new NumberValue(N04)).doubleValue(),
+				MAXE);
+		assertEquals(0.9, vset1.getUtility(new NumberValue(N07)).doubleValue(),
+				MAXE);
+		assertEquals(0, vset1.getUtility(new NumberValue(N09)).doubleValue(),
+				MAXE);
+
+	}
+
+	@Test
+	public void testMidEqualsLow() {
+		assertEquals(0, vset2.getUtility(new NumberValue(N0)).doubleValue(),
+				MAXE);
+		assertEquals(0.5, vset2.getUtility(new NumberValue(N01)).doubleValue(),
+				MAXE);
+		assertEquals(0.5 + 0.4 / 6,
+				vset2.getUtility(new NumberValue(N02)).doubleValue(), MAXE);
+		assertEquals(0.9, vset2.getUtility(new NumberValue(N07)).doubleValue(),
+				MAXE);
+		assertEquals(0, vset2.getUtility(new NumberValue(N09)).doubleValue(),
+				MAXE);
+
+	}
+
+	@Test
+	public void testMidEqualsHigh() {
+		assertEquals(0, vset3.getUtility(new NumberValue(N0)).doubleValue(),
+				MAXE);
+		assertEquals(0.2, vset3.getUtility(new NumberValue(N01)).doubleValue(),
+				MAXE);
+		assertEquals(0.2 + 2 * 0.3 / 6,
+				vset3.getUtility(new NumberValue(N03)).doubleValue(), MAXE);
+		assertEquals(0.5, vset3.getUtility(new NumberValue(N07)).doubleValue(),
+				MAXE);
+		assertEquals(0, vset3.getUtility(new NumberValue(N09)).doubleValue(),
+				MAXE);
+
+	}
+
+}
Index: opponentmodel/src/test/resources/japantrip.json
===================================================================
--- opponentmodel/src/test/resources/japantrip.json	(revision 34)
+++ opponentmodel/src/test/resources/japantrip.json	(revision 34)
@@ -0,0 +1,32 @@
+{
+	"name": "japantrip",
+	"issuesValues": {
+		"place": {
+			"values": [
+				"Tokyo",
+				"Osaka",
+				"Negoya",
+				"Hokkaido"
+			]
+		},
+		"budget": {
+			"range":  { "low":900,"high":1800,"step":300 } 
+		},
+		"days": {
+			"values": [
+				"3",
+				"4",
+				"5",
+				"6"
+			]
+		},
+		"purpose": {
+			"values": [
+				"Sightseeing",
+				"Activity",
+				"Shopping"
+			]
+		}
+	}
+}
+	
Index: opponentmodel/src/test/resources/japantrip1.json
===================================================================
--- opponentmodel/src/test/resources/japantrip1.json	(revision 34)
+++ opponentmodel/src/test/resources/japantrip1.json	(revision 34)
@@ -0,0 +1,93 @@
+{
+	"LinearAdditiveUtilitySpace": {
+		"issueUtilities": {
+			"place": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"Tokyo": 1.0,
+						"Osaka": 0.2,
+						"Negoya": 0.2,
+						"Hokkaido": 0.2
+					}
+				}
+			},
+			"budget": {
+				"NumberValueSetUtilities": {
+					"lowValue": 900,
+					"lowUtility": 1.0,
+					"highValue": 1800,
+					"highUtility": 0.15
+				}
+			},
+			"days": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"3": 1.0,
+						"4": 0.2,
+						"5": 0.2,
+						"6": 0.1
+					}
+				}
+			},
+			"purpose": {
+				"DiscreteValueSetUtilities": {
+					"valueUtilities": {
+						"Sightseeing": 0.6,
+						"Activity": 1.0,
+						"Shopping": 0.3
+					}
+				}
+			}
+		},
+		"issueWeights": {
+			"place": 0.57,
+			"budget": 0.09,
+			"days": 0.17,
+			"purpose": 0.17
+		},
+		"domain": {
+			"name": "japantrip",
+			"issuesValues": {
+				"place": {
+					"values": [
+						"Tokyo",
+						"Osaka",
+						"Negoya",
+						"Hokkaido"
+					]
+				},
+				"budget": {
+					"range": {
+						"low": 900,
+						"high": 1800,
+						"step": 300
+					}
+				},
+				"days": {
+					"values": [
+						"3",
+						"4",
+						"5",
+						"6"
+					]
+				},
+				"purpose": {
+					"values": [
+						"Sightseeing",
+						"Activity",
+						"Shopping"
+					]
+				}
+			}
+		},
+		"name": "japantrip1",
+		"reservationBid": {
+			"issuevalues": {
+				"place": "Osaka",
+				"budget": 1800,
+				"days": "6",
+				"purpose": "Shopping"
+			}
+		}
+	}
+}
