Index: opponentmodel/src/main/java/geniusweb/opponentmodel/FrequencyOpponentModel.java
===================================================================
--- opponentmodel/src/main/java/geniusweb/opponentmodel/FrequencyOpponentModel.java	(revision 33)
+++ opponentmodel/src/main/java/geniusweb/opponentmodel/FrequencyOpponentModel.java	(revision 34)
@@ -16,4 +16,5 @@
 import geniusweb.profile.utilityspace.UtilitySpace;
 import geniusweb.progress.Progress;
+import geniusweb.references.Parameters;
 
 /**
@@ -31,6 +32,4 @@
 
 	private static final int DECIMALS = 4; // accuracy of our computations.
-	private static int serial = 1; // counter for auto name generation
-
 	private final Domain domain;
 	private final Map<String, Map<Value, Integer>> bidFrequencies;
@@ -71,13 +70,13 @@
 
 	@Override
-	public FrequencyOpponentModel with(Domain domain, Bid resBid) {
-		if (domain == null) {
+	public FrequencyOpponentModel with(Domain newDomain, Bid newResBid) {
+		if (newDomain == null) {
 			throw new NullPointerException("domain is not initialized");
 		}
 		// FIXME merge already available frequencies?
-		return new FrequencyOpponentModel(domain,
-				domain.getIssues().stream().collect(
+		return new FrequencyOpponentModel(newDomain,
+				newDomain.getIssues().stream().collect(
 						Collectors.toMap(iss -> iss, iss -> new HashMap<>())),
-				BigDecimal.ZERO, resBid);
+				BigDecimal.ZERO, newResBid);
 	}
 
@@ -106,5 +105,5 @@
 			throw new IllegalStateException("domain is not initialized");
 		}
-		return "FreqOppModel" + (serial++) + "For" + domain;
+		return "FreqOppModel" + this.hashCode() + "For" + domain;
 	}
 
@@ -158,4 +157,9 @@
 	}
 
+	@Override
+	public OpponentModel with(Parameters parameters) {
+		return this; // ignore parameters
+	}
+
 	/**
 	 * 
Index: opponentmodel/src/main/java/geniusweb/opponentmodel/OpponentModel.java
===================================================================
--- opponentmodel/src/main/java/geniusweb/opponentmodel/OpponentModel.java	(revision 33)
+++ opponentmodel/src/main/java/geniusweb/opponentmodel/OpponentModel.java	(revision 34)
@@ -7,4 +7,5 @@
 import geniusweb.profile.utilityspace.UtilitySpace;
 import geniusweb.progress.Progress;
+import geniusweb.references.Parameters;
 
 /**
@@ -24,6 +25,6 @@
 	/**
 	 * Initializes the model. This function must be called first after
-	 * constructing an instance. It can also be called later, if there is a
-	 * change in the domain or resBid.
+	 * constructing an instance. It can also be called again later, if there is
+	 * a change in the domain or resBid.
 	 * <p>
 	 * This late-initialization is to support boa models that have late
@@ -37,4 +38,13 @@
 	 */
 	OpponentModel with(Domain domain, Bid resBid);
+
+	/**
+	 * 
+	 * @param parameters Opponent-model specific {@link Parameters}
+	 * @return an updated OpponentMode, with parameters used. Each
+	 *         implementation of OpponentModel is free to use parameters as it
+	 *         likes. For instance to set learning speed.
+	 */
+	OpponentModel with(Parameters parameters);
 
 	/**
Index: opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/BayesianOpponentModel.java
===================================================================
--- opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/BayesianOpponentModel.java	(revision 34)
+++ opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/BayesianOpponentModel.java	(revision 34)
@@ -0,0 +1,554 @@
+package geniusweb.opponentmodel.bayesian;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.Offer;
+import geniusweb.issuevalue.Bid;
+import geniusweb.issuevalue.DiscreteValue;
+import geniusweb.issuevalue.DiscreteValueSet;
+import geniusweb.issuevalue.Domain;
+import geniusweb.issuevalue.NumberValueSet;
+import geniusweb.issuevalue.Value;
+import geniusweb.issuevalue.ValueSet;
+import geniusweb.opponentmodel.FrequencyOpponentModel;
+import geniusweb.opponentmodel.OpponentModel;
+import geniusweb.profile.utilityspace.DiscreteValueSetUtilities;
+import geniusweb.profile.utilityspace.UtilitySpace;
+import geniusweb.profile.utilityspace.ValueSetUtilities;
+import geniusweb.progress.Progress;
+import geniusweb.references.Parameters;
+import tudelft.utilities.immutablelist.Range;
+
+/**
+ * <b>Warning</b>. This model is available for research and testing, and as
+ * example. Use of this is not recommended. We recommend using the
+ * {@link FrequencyOpponentModel} instead.
+ * <p>
+ * Implementation of the Bayesian Opponent Model. This model uses a set of
+ * {@link TriangleValueSetUtilities} (several for each issue) as hypotheses.
+ * Each hypothesis has a probability. Bayesian inference is used with each
+ * received bids to update these probabilities.
+ * <p>
+ * See: Opponent Modelling in Automated Multi-Issue Negotiation Using Bayesian
+ * Learning, Koen Hindriks, Dmytro Tykhonov, Proc. of 7th Int. Conf. on
+ * Autonomous Agentsand Multiagent Systems (AAMAS 2008), Padgham, Parkes,
+ * Müller and Parsons (eds.), May, 12-16., 2008, Estoril, Portugal, pp.
+ * 331-338.
+ * <h2>Parameters</h2>
+ * <ul>
+ * <li>sigma: see {@link #getSigma()}
+ * <li>decay: see {@link #getDecay()}
+ * </ul>
+ */
+public class BayesianOpponentModel implements UtilitySpace, OpponentModel {
+	private static final double DEFAULT_DECAY = 0.003;
+	private static final double DEFAULT_SIGMA = 0.25;
+	private static final int NWEIGHTS = 11; /* MAGIC NR OF WEIGHTS. */
+	private static final int NEVALUATORS = 6; // magic #numbervaluesetevaluators
+
+	/** Hypotheses are normalized PER ISSUE, so not in total. */
+	private final Map<String, List<WeightHypothesis>> weightHypoMap = new HashMap<>();
+	private final Map<String, List<EvaluatorHypothesis>> evaluatorHypoMap = new HashMap<>();
+	private final List<Bid> bidHistory = new LinkedList<>();
+	private final Domain domain;
+	private final double sigma;
+	private final double decay;
+	/**
+	 * Cached per-issue weights, computed from {@link #weightHypoMap} and scaled
+	 * to sum to 1
+	 */
+	private final transient Map<String, Double> normalizedWeightsMap = new HashMap<>();
+
+	/**
+	 * Empty constructor. If used, {@link #with(Domain, Bid)} must be called
+	 * before further use
+	 */
+	public BayesianOpponentModel() {
+		domain = null;
+		sigma = DEFAULT_SIGMA;
+		decay = DEFAULT_DECAY;
+	}
+
+	/**
+	 * Creates initial settings with default settings (sigma=0.25, decay=0.003).
+	 * 
+	 * @param domain the {@link Domain}
+	 */
+	public BayesianOpponentModel(final Domain domain) {
+		this(domain, initialEvaluators(domain), initialWeightHyps(domain),
+				Collections.emptyList(), DEFAULT_SIGMA, DEFAULT_DECAY);
+	}
+
+	/**
+	 * 
+	 * @param dom            the {@link Domain}
+	 * @param evaluatorHypos the hashmap with the evaluator hypotheses for all
+	 *                       issues. For each issue, the sum of the hypothesis P
+	 *                       values must be =1
+	 * @param weightHypos    the hashmap with the weight hypotheses for all
+	 *                       issues. For each issue, the sum of the hypothesis P
+	 *                       values must be =1
+	 * @param history        List with all previous bids, first bid is the
+	 *                       oldest. Used to check if bid is repeated and to
+	 *                       estimate bid utility
+	 * @param sigma          The spread σ of the conditional distribution. See
+	 *                       {@link #getSigma()} and {@link #DEFAULT_SIGMA}.
+	 *                       Must be in [0.00001, 100000] but these extremes
+	 *                       seem unusable in practice.
+	 * @param decay          The expected concession rate of the opponent
+	 *                       against time. see {@link #getDecay()} and
+	 *                       {@link #DEFAULT_DECAY}. Must be in [0,1] where 0
+	 *                       means no concession and 1 full concession in 1
+	 *                       timestep.
+	 */
+	public BayesianOpponentModel(final Domain dom,
+			final Map<String, List<EvaluatorHypothesis>> evaluatorHypos,
+			final Map<String, List<WeightHypothesis>> weightHypos,
+			final List<Bid> history, double sigma, double decay) {
+		if (dom == null || evaluatorHypos == null || weightHypos == null
+				|| history == null)
+			throw new IllegalArgumentException("Parameters must not be null");
+		checkNormalized(evaluatorHypos);
+		checkNormalized(weightHypos);
+		if (sigma < 0.00001 || sigma > 100000)
+			throw new IllegalArgumentException(
+					"sigma must be in [0.00001, 100000], got " + sigma);
+		if (decay < 0 || decay > 1)
+			throw new IllegalArgumentException(
+					"decay must be in [0,1] but got " + decay);
+
+		this.domain = dom;
+		this.bidHistory.addAll(history);
+		this.weightHypoMap.putAll(weightHypos);
+		this.evaluatorHypoMap.putAll(evaluatorHypos);
+		this.normalizedWeightsMap.putAll(getNormalizedWeights(weightHypos));
+		this.sigma = sigma;
+		this.decay = decay;
+	}
+
+	@Override
+	public String getName() {
+		if (domain == null)
+			throw new IllegalStateException("model is not initialized");
+
+		return "FreqOppModel" + this.hashCode() + "For" + domain;
+	}
+
+	@Override
+	public Domain getDomain() {
+		return domain;
+	}
+
+	@Override
+	public Bid getReservationBid() {
+		return null;
+	}
+
+	@Override
+	public BayesianOpponentModel with(Domain newDomain, Bid newResBid) {
+		if (newDomain == null) {
+			throw new NullPointerException("domain must not be null");
+		}
+		return new BayesianOpponentModel(newDomain);
+	}
+
+	@Override
+	public BayesianOpponentModel with(final Action action,
+			final Progress progress) {
+		if (!(action instanceof Offer))
+			return this;
+		Bid bid = ((Offer) action).getBid();
+		if (bidHistory.contains(bid))
+			return this;
+		List<Bid> newHistory = new LinkedList<>(bidHistory);
+		newHistory.add(bid);
+
+		Map<String, List<EvaluatorHypothesis>> newEvaluatorHyps = updatedEvaluationFns(
+				bid);
+		Map<String, List<WeightHypothesis>> newWeightHypos = weightHypoMap;
+		if (!(bidHistory.isEmpty()))
+			newWeightHypos = updatedWeights(bid);
+
+		return new BayesianOpponentModel(domain, newEvaluatorHyps,
+				newWeightHypos, newHistory, sigma, decay);
+	}
+
+	@Override
+	public BigDecimal getUtility(final Bid bid) {
+		return BigDecimal.valueOf(getPartialUtility(bid, null));
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((bidHistory == null) ? 0 : bidHistory.hashCode());
+		result = prime * result + ((domain == null) ? 0 : domain.hashCode());
+		result = prime * result + ((evaluatorHypoMap == null) ? 0
+				: evaluatorHypoMap.hashCode());
+		result = prime * result
+				+ ((weightHypoMap == null) ? 0 : weightHypoMap.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		BayesianOpponentModel other = (BayesianOpponentModel) obj;
+		if (bidHistory == null) {
+			if (other.bidHistory != null)
+				return false;
+		} else if (!bidHistory.equals(other.bidHistory))
+			return false;
+		if (domain == null) {
+			if (other.domain != null)
+				return false;
+		} else if (!domain.equals(other.domain))
+			return false;
+		if (evaluatorHypoMap == null) {
+			if (other.evaluatorHypoMap != null)
+				return false;
+		} else if (!evaluatorHypoMap.equals(other.evaluatorHypoMap))
+			return false;
+		if (weightHypoMap == null) {
+			if (other.weightHypoMap != null)
+				return false;
+		} else if (!weightHypoMap.equals(other.weightHypoMap))
+			return false;
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		return "BayesianOpponentModel[" + domain + "," + evaluatorHypoMap + ","
+				+ weightHypoMap + "]";
+	}
+
+	@Override
+	public OpponentModel with(Parameters parameters) {
+		double newsigma = parameters.getDouble("sigma", DEFAULT_SIGMA, 0.00001,
+				100000d);
+		double newdecay = parameters.getDouble("decay", DEFAULT_DECAY, 0d, 1d);
+		return new BayesianOpponentModel(domain, evaluatorHypoMap,
+				weightHypoMap, bidHistory, newsigma, newdecay);
+	}
+
+	/**
+	 * 
+	 * @return map with the estimated weights (values) of all issues (keys). The
+	 *         weights sum approximately to 1 (there may be rounding errors
+	 *         here).
+	 */
+	public Map<String, Double> getWeights() {
+		return Collections.unmodifiableMap(normalizedWeightsMap);
+	}
+
+	/**
+	 * 
+	 * @return list with {@link Bid}s received so far. Duplicates are excluded.
+	 */
+	public List<Bid> getBidHistory() {
+		return Collections.unmodifiableList(bidHistory);
+	}
+
+	/**
+	 * 
+	 * @return The spread σ of the conditional distribution. This defines the
+	 *         certainty of the agent about its opponent’s negotiation tactics.
+	 *         If an agent is certain about the utility of an opponent’s bid bt
+	 *         then σ can be set to a low value. A higher level of certainty
+	 *         increases the learning speed, since hypotheses predicting an
+	 *         incorrect utility value of a bid in that case would get assigned
+	 *         an increasingly lower probability, and vice versa. Overestimating
+	 *         the level of certainty, however, may lead to incorrect results,
+	 *         and some care should to be taken to assign the right value to σ.
+	 *         Value 0.25 is recommended. Must be in [0.00001, 100000] but these
+	 *         extremes seem unusable in practice.
+	 */
+	public double getSigma() {
+		return sigma;
+	}
+
+	/**
+	 * @return the decay value which is the expected concession rate of the
+	 *         opponent against time.. We use linear functions to estimate the
+	 *         predicted utility value: u’(bt) = 1- decay·t. This assumption
+	 *         allows us to compute the conditional probability P(bt|hj)
+	 *         representing the probability of a bid bt at time t.
+	 */
+	public double getDecay() {
+		return decay;
+	}
+
+	/*************** private *****************/
+	/**
+	 * @return initial weight hypotheses for each issue. weights are set to
+	 *         linearly increase from 0 for issue 1 to 1 for last issue.
+	 */
+	private static Map<String, List<WeightHypothesis>> initialWeightHyps(
+			final Domain domain) {
+
+		List<WeightHypothesis> WeightHyps = new ArrayList<>();
+		for (int j = 0; j < NWEIGHTS; j++) {
+			double weight = (double) j / (NWEIGHTS - 1);
+			WeightHypothesis lHyp = new WeightHypothesis(weight,
+					Math.exp(-0.4 * j));
+			WeightHyps.add(lHyp);
+		}
+		final List<WeightHypothesis> normalizedW = normalize(WeightHyps);
+
+		return domain.getIssues().stream()
+				.collect(Collectors.toMap(is -> is, is -> normalizedW));
+	}
+
+	/**
+	 * @param domain the {@link Domain}
+	 * @return the initial evaluators for the domain. This amounts to a set of
+	 *         triangle-shaped utility functions for each issue.
+	 */
+	protected static Map<String, List<EvaluatorHypothesis>> initialEvaluators(
+			final Domain domain) {
+		final List<String> issues = new ArrayList<String>(domain.getIssues());
+		final Map<String, List<EvaluatorHypothesis>> evaluatorHyposMap = new HashMap<>();
+
+		for (String issue : issues) {
+			List<EvaluatorHypothesis> hypos = new ArrayList<>();
+			ValueSet vals = domain.getValues(issue);
+
+			if (vals instanceof NumberValueSet) {
+				Range range = ((NumberValueSet) vals).getRange();
+				double P = 1d / NEVALUATORS;
+
+				double low = range.getLow().doubleValue();
+				double step = (range.getHigh().doubleValue()
+						- range.getLow().doubleValue()) / (NEVALUATORS - 1);
+				for (int k = 0; k < NEVALUATORS; k++) {
+					double midvalue;
+					if (k == NEVALUATORS - 1) // avoid round error
+						midvalue = range.getHigh().doubleValue();
+					else
+						midvalue = low + k * step;
+					ValueSetUtilities lHypEval = new TriangleValueSetUtilities(
+							range.getLow(), BigDecimal.ZERO,
+							BigDecimal.valueOf(midvalue), BigDecimal.ONE,
+							range.getHigh(), BigDecimal.ZERO);
+					hypos.add(new EvaluatorHypothesis(lHypEval, P));
+				}
+
+			} else if (vals instanceof DiscreteValueSet) {
+				int nvals = vals.size().intValue();
+				double P = 1d / nvals;
+
+				// add one triangle for each issuevalue, with peak at value
+				Map<DiscreteValue, BigDecimal> valueUtils = new HashMap<>();
+				for (int peakpos = 0; peakpos < nvals; peakpos++) {
+					for (int j = 0; j < nvals; j++) {
+						DiscreteValue val = ((DiscreteValueSet) vals).get(j);
+						if (peakpos != 0 && j <= peakpos) {
+							valueUtils.put(val,
+									BigDecimal.valueOf((double) j / peakpos));
+						} else {
+							valueUtils.put(val, BigDecimal.valueOf(
+									(nvals - j - 1) / (nvals - peakpos - 1)));
+						}
+					}
+					hypos.add(new EvaluatorHypothesis(
+							new DiscreteValueSetUtilities(valueUtils), P));
+				}
+			}
+
+			evaluatorHyposMap.put(issue, normalize(hypos));
+		}
+		return evaluatorHyposMap;
+	}
+
+	/**
+	 * @param util     utility of current bid
+	 * @param prevUtil utility of last received bid
+	 * @return a value in &lt; 0, 1 ]. Higher means that |util-prevUtil| is
+	 *         smaller.
+	 *         <p>
+	 *         NOTE: the paper calls for dividing this value by (sigma sqrt(2
+	 *         pi)). This just scales the distance to have a peak value of 1.59,
+	 *         which leads to P values >1 in the rest of the computation, Also
+	 *         the values are scaled down anyway after that by a normalization,
+	 *         which makes the entire scaling just a problem. This scale factor
+	 *         was therefore removed.
+	 */
+	private double distance(double util, double prevUtil) {
+		double x = (prevUtil - util) / prevUtil;
+		return Math.exp(-(x * x) / (2.0 * sigma * sigma));
+	}
+
+	/**
+	 * @param issue the name of the issue to test
+	 * @param value the value to evaluate. Should be a valid value for issue.
+	 * @return the current hypothezized utility of issues
+	 * @throws NullPointerException if value is not valid for issue
+	 */
+	private Double getHypoUtility(String issue, Value value) {
+		return evaluatorHypoMap.get(issue).stream()
+				.map(hypo -> hypo.getProbability()
+						* hypo.getEvaluator().getUtility(value).doubleValue())
+				.mapToDouble(f -> f).sum();
+	}
+
+	/**
+	 * 
+	 * @return map, with key=issue and value=expected weight of issue = sum of
+	 *         weightHyps[pIssueNumber].probability * weight/sum where sum is
+	 *         the sum of raw weights, such that sum of weights is 1.
+	 */
+	private static Map<String, Double> getNormalizedWeights(
+			Map<String, List<WeightHypothesis>> weightHypoMap) {
+		Map<String, Double> wmap = new HashMap<>();
+
+		for (String issue : weightHypoMap.keySet()) {
+			double w = weightHypoMap.get(issue).stream()
+					.map(hypo -> hypo.getProbability() * hypo.getWeight())
+					.mapToDouble(f -> f).sum();
+
+			wmap.put(issue, w);
+		}
+		// normalize the map.
+		double sum = wmap.values().stream().mapToDouble(f -> f).sum();
+		if (sum != 0) {
+			wmap = wmap.entrySet().stream().collect(Collectors.toMap(
+					entry -> entry.getKey(), entry -> entry.getValue() / sum));
+		}
+
+		return wmap;
+	}
+
+	/**
+	 * 
+	 * @param bid         the {@link Bid} to evaluate
+	 * @param ignoreIssue the issue in the bid to ignore. If null or issue is
+	 *                    not part of the domain, a full utility is computed.
+	 * @return the estimated utility of issue pIssueIndex, according to the
+	 *         standing hypotheses, but without issue.
+	 */
+	private double getPartialUtility(Bid bid, String ignoreIssue) {
+		// calculate partial utility w/o issue pIssueIndex
+		double u = 0;
+		for (String issue : bid.getIssues()) {
+			if (issue.equals(ignoreIssue))
+				continue;
+			u = u + normalizedWeightsMap.get(issue)
+					* getHypoUtility(issue, bid.getValue(issue));
+		}
+		return u;
+	}
+
+	/**
+	 * @param bid the bid that came in
+	 * @return Map of updated WeightHypotheses for all issues
+	 */
+	protected Map<String, List<WeightHypothesis>> updatedWeights(Bid bid) {
+		Map<String, List<WeightHypothesis>> newHypoMap = new HashMap<>();
+
+		for (String issue : domain.getIssues()) {
+			ArrayList<WeightHypothesis> weighHypos = new ArrayList<>();
+
+			for (WeightHypothesis hypo : weightHypoMap.get(issue)) {
+				double util = hypo.getWeight()
+						* getHypoUtility(issue, bid.getValue(issue))
+						+ getPartialUtility(bid, issue);
+
+				double p = hypo.getProbability()
+						* distance(util, getPrevBidUtility());
+				WeightHypothesis lHyp = hypo.with(p);
+				weighHypos.add(lHyp);
+			}
+
+			newHypoMap.put(issue, normalize(weighHypos));
+		}
+		return newHypoMap;
+	}
+
+	/**
+	 * @param lBid the bid that came in
+	 * @return a set of new {@link EvaluatorHypothesis}
+	 */
+	protected Map<String, List<EvaluatorHypothesis>> updatedEvaluationFns(
+			Bid lBid) {
+		Map<String, List<EvaluatorHypothesis>> evalHypoMap = new HashMap<>();
+
+		for (String issue : domain.getIssues()) {
+			List<EvaluatorHypothesis> hypos = new ArrayList<EvaluatorHypothesis>();
+
+			for (EvaluatorHypothesis lHyp : evaluatorHypoMap.get(issue)) {
+				// update P. This is the heart of the algorithm.
+				double p = (lHyp.getProbability()
+						* distance(
+								getPartialUtility(lBid, issue)
+										+ normalizedWeightsMap.get(issue) * lHyp
+												.getEvaluator()
+												.getUtility(
+														lBid.getValue(issue))
+												.doubleValue(),
+								getPrevBidUtility()));
+				hypos.add(lHyp.with(p));
+			}
+
+			evalHypoMap.put(issue, normalize(hypos));
+		}
+		return evalHypoMap;
+	}
+
+	/**
+	 * @param hypos the list of {@link Hypothesis} to be normalized (sum of P =
+	 *              1).
+	 * @return same list of hypotheses, but with all P scaled with same factor
+	 *         so that the sum of all {@link Hypothesis#getProbability()} = 1.
+	 */
+	@SuppressWarnings("unchecked")
+	private static <T extends Hypothesis> List<T> normalize(List<T> hypos) {
+		final double sum = hypos.stream().map(hypo -> hypo.getProbability())
+				.mapToDouble(f -> f).sum();
+		return hypos.stream()
+				.map(hypo -> (T) hypo.with(hypo.getProbability() / sum))
+				.collect(Collectors.toList());
+	}
+
+	/**
+	 * Check that the P's sum to 1 (with some allowance for round errors).
+	 * 
+	 * @param <T>     the type of {@link Hypothesis} in the map.
+	 * @param hypomap a map of {@link Hypothesis}
+	 */
+	private static <T extends Hypothesis> void checkNormalized(
+			Map<String, List<T>> hypomap) {
+		for (List<T> hypos : hypomap.values()) {
+			double sum = hypos.stream().map(hypo -> hypo.getProbability())
+					.mapToDouble(f -> f).sum();
+			if (Math.abs(sum - 1d) > 0.0000001)
+				throw new IllegalArgumentException(
+						"Sum of P's is not 1 " + hypos);
+		}
+	}
+
+	/**
+	 * @return estimated opponent utility of previous bid.. Assumes linear
+	 *         decreasing utility. See paper.
+	 */
+	private double getPrevBidUtility() {
+		// Notice the decay constant is mush maller than in the paper.
+		return Math.max(0d, 1d - decay * (bidHistory.size() - 1));
+	}
+
+}
Index: opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/EvaluatorHypothesis.java
===================================================================
--- opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/EvaluatorHypothesis.java	(revision 34)
+++ opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/EvaluatorHypothesis.java	(revision 34)
@@ -0,0 +1,60 @@
+package geniusweb.opponentmodel.bayesian;
+
+import geniusweb.profile.utilityspace.ValueSetUtilities;
+
+/**
+ * EXPERIMENTAL. DO NOT USE THIS.
+ *
+ */
+class EvaluatorHypothesis extends Hypothesis {
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = super.hashCode();
+		result = prime * result + ((utils == null) ? 0 : utils.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (!super.equals(obj))
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		EvaluatorHypothesis other = (EvaluatorHypothesis) obj;
+		if (utils == null) {
+			if (other.utils != null)
+				return false;
+		} else if (!utils.equals(other.utils))
+			return false;
+		return true;
+	}
+
+	private final ValueSetUtilities utils;
+
+	/**
+	 * 
+	 * @param pEval the ValueSetUtilities utility function
+	 */
+	public EvaluatorHypothesis(ValueSetUtilities pEval, double p) {
+		super(p);
+		utils = pEval;
+	}
+
+	public ValueSetUtilities getEvaluator() {
+		return utils;
+	}
+
+	@Override
+	public EvaluatorHypothesis with(double p) {
+		return new EvaluatorHypothesis(utils, p);
+	}
+
+	@Override
+	public String toString() {
+		return "P(" + utils + ")=" + getProbability();
+	}
+
+}
Index: opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/Hypothesis.java
===================================================================
--- opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/Hypothesis.java	(revision 34)
+++ opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/Hypothesis.java	(revision 34)
@@ -0,0 +1,63 @@
+package geniusweb.opponentmodel.bayesian;
+
+/**
+ * A hypothesis is a value with a probability attached to it.
+ * <p>
+ * DO NOT USE THIS. Private use for {@link BayesianOpponentModel}.
+ */
+
+abstract class Hypothesis {
+	final private double p;
+
+	/**
+	 * 
+	 * @param p the probability of the item
+	 */
+	public Hypothesis(double p) {
+		if (p < 0 || p > 1)
+			throw new IllegalArgumentException("p must be in [0,1], got " + p);
+		this.p = p;
+	}
+
+	/**
+	 * 
+	 * @return the probability that this hypothesis holds
+	 */
+	public double getProbability() {
+		return p;
+	}
+
+	/**
+	 * Subclasses implement this. We can not implement it here because
+	 * subclasses return a more specific class than Hypothesis.
+	 * 
+	 * @param p the new probability value.
+	 * @return new Hypothesis with p set to this value (and the rest unchanged).
+	 */
+	public abstract Hypothesis with(double p);
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		long temp;
+		temp = Double.doubleToLongBits(p);
+		result = prime * result + (int) (temp ^ (temp >>> 32));
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Hypothesis other = (Hypothesis) obj;
+		if (Double.doubleToLongBits(p) != Double.doubleToLongBits(other.p))
+			return false;
+		return true;
+	}
+
+}
Index: opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/TriangleValueSetUtilities.java
===================================================================
--- opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/TriangleValueSetUtilities.java	(revision 34)
+++ opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/TriangleValueSetUtilities.java	(revision 34)
@@ -0,0 +1,153 @@
+package geniusweb.opponentmodel.bayesian;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import geniusweb.issuevalue.NumberValue;
+import geniusweb.issuevalue.Value;
+import geniusweb.profile.utilityspace.NumberValueSetUtilities;
+import tudelft.utilities.immutablelist.Range;
+
+/**
+ * Similar to {@link NumberValueSetUtilities} but contains an extra mid point
+ * between low and high value. Utilities are linearly interpolated within the
+ * triangle and 0 outside. This is used by the {@link BayesianOpponentModel} and
+ * not json-serializable.
+ * <p>
+ * DO NOT USE THIS. Private use for {@link BayesianOpponentModel}.
+ */
+class TriangleValueSetUtilities extends NumberValueSetUtilities {
+
+	/**
+	 * The value and utility at mid point.
+	 */
+	private final BigDecimal midValue;
+	private final BigDecimal midUtility;
+
+	/**
+	 * 
+	 * @param lowValue    the low value of the {@link Range}
+	 * @param lowUtility  the utility of the {@link #lowValue}. must be &ge; 0
+	 * @param midValue    the mid value of the {@link Range}. must be
+	 *                    &ge;lowValue and &le;highValue.
+	 * @param midUtility  the utility of the {@link #midValue}.
+	 * @param highValue   the high value of the {@link Range}. Must be @le;1
+	 * @param highUtility the utility of the {@link #highValue}
+	 */
+	public TriangleValueSetUtilities(BigDecimal lowValue, BigDecimal lowUtility,
+			BigDecimal midValue, BigDecimal midUtility, BigDecimal highValue,
+			BigDecimal highUtility) {
+		super(lowValue, lowUtility, highValue, highUtility);
+		if (midValue == null || midUtility == null) {
+			throw new NullPointerException(
+					"arguments midValue, midUtility must be non-null");
+		}
+		// ensure 0 <= lowvalue <=midvalue <= highvalue <=1
+		if (midValue.compareTo(lowValue) < 0
+				|| midValue.compareTo(highValue) > 0)
+			throw new IllegalArgumentException(
+					"midvalue must be between lowValue and highValue");
+
+		this.midValue = midValue;
+		this.midUtility = midUtility;
+	}
+
+	/**
+	 * {@inheritDoc} <
+	 * <h2>Details</h2> Any value &ge; {@link #midValue} will be interpolated
+	 * from the mid-high interval; any value below the {@link #midValue} with
+	 * the low-mid interval. Any value outside low-high interval returns 0.
+	 * {@link #midUtility} is returned if value =midValue.
+	 */
+	@Override
+	public BigDecimal getUtility(Value value) {
+		if (!(value instanceof NumberValue)) {
+			return BigDecimal.ZERO;
+		}
+		BigDecimal x = ((NumberValue) value).getValue();
+		if (x.compareTo(getLowValue()) < 0 || x.compareTo(getHighValue()) > 0)
+			return BigDecimal.ZERO;
+
+		BigDecimal lowValue, highValue, lowUtility, highUtility;
+
+		if (x.compareTo(midValue) < 0) {
+			lowValue = getLowValue();
+			lowUtility = getLowUtility();
+			highValue = midValue;
+			highUtility = midUtility;
+		} else {
+			lowValue = midValue;
+			lowUtility = midUtility;
+			highValue = getHighValue();
+			highUtility = getHighUtility();
+		}
+		if (x.compareTo(midValue) == 0)
+			return midUtility;
+
+		// we need to be careful to avoid round errors from divides.
+		// so we return lowU + deltaU * (x-lowV) /deltaV
+		BigDecimal deltaU = highUtility.subtract(lowUtility);
+		BigDecimal deltaV = highValue.subtract(lowValue);
+		// deltaV can't be 0 because value!=midValue.
+
+		return lowUtility.add(deltaU.multiply(
+				x.subtract(lowValue).divide(deltaV, 8, RoundingMode.HALF_UP)));
+	}
+
+	/**
+	 * 
+	 * @return the mid value
+	 */
+	public BigDecimal getMidValue() {
+		return midValue;
+	}
+
+	/**
+	 * 
+	 * @return the utility of the mid value.
+	 */
+	public BigDecimal getMidUtility() {
+		return midUtility;
+	}
+
+	@Override
+	public String toString() {
+		return "TriangleValueSetUtilities(" + getLowValue() + "->"
+				+ getLowUtility() + "," + midValue + "->" + midUtility + ","
+				+ getHighValue() + "->" + getHighUtility() + ")";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = super.hashCode();
+		result = prime * result
+				+ ((midUtility == null) ? 0 : midUtility.hashCode());
+		result = prime * result
+				+ ((midValue == null) ? 0 : midValue.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (!super.equals(obj))
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		TriangleValueSetUtilities other = (TriangleValueSetUtilities) obj;
+		if (midUtility == null) {
+			if (other.midUtility != null)
+				return false;
+		} else if (!midUtility.equals(other.midUtility))
+			return false;
+		if (midValue == null) {
+			if (other.midValue != null)
+				return false;
+		} else if (!midValue.equals(other.midValue))
+			return false;
+		return true;
+	}
+
+}
Index: opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/WeightHypothesis.java
===================================================================
--- opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/WeightHypothesis.java	(revision 34)
+++ opponentmodel/src/main/java/geniusweb/opponentmodel/bayesian/WeightHypothesis.java	(revision 34)
@@ -0,0 +1,58 @@
+package geniusweb.opponentmodel.bayesian;
+
+/**
+ * Hypothesis on the weight of an issue.
+ * <p>
+ * DO NOT USE THIS. Private use for {@link BayesianOpponentModel}.
+ */
+class WeightHypothesis extends Hypothesis {
+	private final double weight;
+
+	public WeightHypothesis(double weight, double p) {
+		super(p);
+		this.weight = weight;
+	}
+
+	public WeightHypothesis withWeight(double value) {
+		return new WeightHypothesis(value, getProbability());
+	}
+
+	public double getWeight() {
+		return weight;
+	}
+
+	@Override
+	public String toString() {
+		return "P(" + weight + ")=" + getProbability();
+	}
+
+	@Override
+	public WeightHypothesis with(double p) {
+		return new WeightHypothesis(weight, p);
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = super.hashCode();
+		long temp;
+		temp = Double.doubleToLongBits(weight);
+		result = prime * result + (int) (temp ^ (temp >>> 32));
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (!super.equals(obj))
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		WeightHypothesis other = (WeightHypothesis) obj;
+		if (Double.doubleToLongBits(weight) != Double
+				.doubleToLongBits(other.weight))
+			return false;
+		return true;
+	}
+}
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"
+			}
+		}
+	}
+}
