Index: geniuswebcore/geniusweb/bidspace/BidsWithUtility.py
===================================================================
--- geniuswebcore/geniusweb/bidspace/BidsWithUtility.py	(revision 81)
+++ geniuswebcore/geniusweb/bidspace/BidsWithUtility.py	(revision 81)
@@ -0,0 +1,209 @@
+
+
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.Domain import Domain
+from geniusweb.issuevalue.Value import Value
+from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
+from tudelft.utilities.immutablelist.AbstractImmutableList import AbstractImmutableList
+from tudelft.utilities.immutablelist.FixedList import FixedList
+from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
+from tudelft.utilities.immutablelist.JoinedList import JoinedList
+from tudelft.utilities.immutablelist.MapList import MapList
+from tudelft.utilities.immutablelist.Tuple import Tuple
+from typing import List, Dict
+from geniusweb.bidspace.IssueInfo import IssueInfo
+from geniusweb.bidspace.Interval import Interval
+from geniusweb.utils import val
+from decimal import Decimal
+
+class BidsWithUtility :
+	'''
+	WARNING DO NOT USE, NOT YET WORKING CORRECTLY
+	
+	Tool class containing functions dealing with utilities of all bids in a given
+	{@link LinearAdditive}. This class caches previously computed values to
+	accelerate the calls and subsequent calls. Re-use the object to keep/reuse
+	the cache.
+	<h2>Rounding</h2> Internally, utilities of bids are rounded to the given
+	precision. This may cause inclusion/exclusion of some bids in the results.
+	See {@link #BidsWithUtility(LinearAdditive, int)} for more details
+	Immutable.
+	'''
+
+
+	def __init__(self, issuesInfo:List[IssueInfo] , precision:int ) :
+		'''
+		@param issuesInfo List of the relevant issues (in order of relevance) and
+		                  all info of each issue.
+		@param precision the number of digits to use for computations. In
+		                 practice, 6 seems a good default value.
+		                 <p>
+		                 All utilities * weight are rounded to this number of
+		                 digits. This value should match the max number of
+		                 (digits used in the weight of an issue + number of
+		                 digits used in the issue utility). To determine the
+		                 optimal value, one may consider the step size of the
+		                 issues, and the range of interest. For instance if the
+		                 utility function has values 1/3 and 2/3, then these have
+		                 an 'infinite' number of relevant digits. But if the goal
+		                 is to search bids between utility 0.1 and 0.2, then
+		                 computing in 2 digits might already be sufficient.
+		                 <p>
+		                 This algorithm has memory and space complexity O(
+		                 |nissues| 10^precision ). For spaces up to 7 issues, 7
+		                 digits should be feasible; for 9 issues, 6 digits may be
+		                 the maximum.
+		'''
+		if issuesInfo == None or len(issuesInfo)==0:
+			raise ValueError("sortedissues list must contain at least 1 element")
+		self._issueInfo = issuesInfo;
+		self._precision = precision;
+		# cache. Key = call arguments for {@link #get(int, Interval)}. Value=return
+	 	# value of that call.
+
+		self._cache:Dict[Tuple[int, Interval], ImmutableList[Bid]]  = {}
+
+	@staticmethod
+	def create(space:LinearAdditive, precision:int=6) -> "BidsWithUtility":
+		'''
+		Support constructor, uses default precision 6. This value seems practical
+		for the common range of issues, utilities and weights. See
+		{@link #BidsWithUtility(LinearAdditive, int)} for more details on the
+		precision.
+		
+		@param space the {@link LinearAdditive} to analyze
+		@param space     the {@link LinearAdditive} to analyze. Optional, defaults to 6
+		'''
+		return BidsWithUtility(BidsWithUtility._getInfo(space, precision), precision);
+
+
+	def getRange(self) ->Interval :
+		'''
+		@return the (rounded) utility {@link Interval} of this space: minimum and
+		        maximum achievable utility.
+		'''
+		return self._getRange(len(self._issueInfo) - 1)
+
+	def getBids(self, range: Interval) -> ImmutableList[Bid] :
+		'''
+		@param range the minimum and maximum utility required of the bids. to be
+		             included (both ends inclusive).
+		@return a list with bids that have a (rounded) utility inside range.
+		        possibly empty.
+		'''
+		return self._get(len(self._issueInfo) - 1, range.round(self._precision));
+
+	def getInfo(self) -> List[IssueInfo] :
+		return self._issueInfo.copy()
+
+	def getExtremeBid(self, isMax:bool) ->Bid :
+		'''
+		@param isMax the extreme bid required
+		@return the extreme bid, either the minimum if isMax=false or maximum if
+		        isMax=true
+		'''
+		map:Dict[str, Value]  = {}
+		for info in self._issueInfo:
+			map[info.getName()] = info.getExtreme(isMax)
+		return Bid(map)
+
+	def _get(self, n:int , goal:Interval) -> ImmutableList[Bid] :
+		'''
+		Create partial BidsWithUtil list considering only issues 0..n, with
+		utilities in given range.
+		
+		@param n    the number of issueRanges to consider, we consider 0..n here.
+		            The recursion decreases n until n=0
+		@param goal the minimum and maximum utility required of the bids. to be
+		            included (both ends inclusive)
+		@return BidsWithUtil list, possibly empty.
+		'''
+		if goal == None:
+			raise ValueError("Interval=null")
+
+		# clamp goal into what is reachable. Avoid caching empty
+		goal = goal.intersect(self._getRange(n))
+		if (goal.isEmpty()):
+			return FixedList([])
+
+		cachetuple = Tuple(n, goal)
+		if (cachetuple in self._cache):
+			return self._cache[cachetuple]
+
+		result = self._checkedGet(n, goal)
+		self._cache[cachetuple]=result
+		return result
+
+	@staticmethod
+	def _getInfo(space2:LinearAdditive , precision:int) -> List[IssueInfo] :
+		dom = space2.getDomain()
+		return [IssueInfo(issue, dom.getValues(issue), \
+				val(space2.getUtilities().get(issue)), \
+				space2.getWeight(issue), precision) \
+				for issue in dom.getIssues()]
+		
+
+	def _checkedGet(self, n:int, goal:Interval ) -> ImmutableList[Bid] :
+		info = self._issueInfo[n]
+		# issue is the first issuesWithRange.
+		issue = info.getName()
+
+		if n == 0:
+			return OneIssueSubset(info, goal)
+
+		# make new list, joining all sub-lists
+		fulllist:ImmutableList[Bid]  = FixedList([])
+		for val in info.getValues():
+			weightedutil = info.getWeightedUtil(val)
+			subgoal = goal.subtract(weightedutil)
+			# recurse: get list of bids for the subspace
+			partialbids = self._get(n - 1, subgoal)
+
+			bid = Bid({issue: val})
+			fullbids = BidsWithUtility.maplist(bid, partialbids)
+			if fullbids.size() != 0:
+				fulllist = JoinedList[Bid]([fullbids, fulllist])
+		return fulllist
+	
+	@staticmethod
+	def maplist(bid: Bid, partialbids: ImmutableList[Bid]) -> ImmutableList[Bid]:
+		'''
+		this is just to force a scope onto bid
+		'''
+		return MapList[Bid, Bid](lambda pbid: pbid.merge(bid), partialbids)
+
+	def _getRange(self,  n:int) ->Interval :
+		'''
+		@param n the maximum issuevalue utility to include. Use n=index of last
+		         issue s= (#issues in the domain - 1) for the full range of this
+		         domain.
+		@return Interval (min, max) of the total weighted utility Interval of
+		        issues 0..n. All weighted utilities have been rounded to the set
+		        {@link #precision}
+		'''
+		value = Interval(Decimal(0),Decimal(0))
+		for i in range(0,n+1): # include end point
+			value = value.add(self._issueInfo[i].getInterval())
+		return value
+
+class OneIssueSubset (AbstractImmutableList[Bid]):
+	'''
+	List of all one-issue bids that have utility inside given interval.
+	'''
+	def __init__(self, info:IssueInfo , interval:Interval ) :
+		'''
+		@param info     the {@link IssueInfo}
+		@param interval a utility interval (weighted)
+		'''
+		self._info = info;
+		self._interval = interval;
+		self._size = info._subsetSize(interval)
+
+	#Override
+	def get(self, index:int) ->Bid :
+		return Bid({self._info.getName():
+				self._info._subset(self._interval)[index]})
+
+	#Override
+	def size(self) ->int:
+		return self._size
Index: geniuswebcore/geniusweb/bidspace/Interval.py
===================================================================
--- geniuswebcore/geniusweb/bidspace/Interval.py	(revision 81)
+++ geniuswebcore/geniusweb/bidspace/Interval.py	(revision 81)
@@ -0,0 +1,110 @@
+from decimal import Decimal
+from math import floor, ceil
+from typing import Optional
+
+class Interval:
+	'''
+	An interval [min, max].
+	'''
+	#ZERO = Interval(0,0) can't be done in Python...
+
+
+	def __init__(self, min:Decimal, max:Decimal):
+		'''
+		AN interval [min,max]. If min &gt; max, then there are no values in this
+		interval.
+		
+		@param min the minimum value of the interval
+		@param max the maximum value of the interval
+		'''
+		if min == None or max == None:
+			raise ValueError("min and max must contain not None")
+		self._min = min
+		self._max = max
+
+	def getMin(self)->Decimal :
+		'''
+		@return the minimum value of the interval
+		'''
+		return self._min
+
+	def getMax(self) ->Decimal :
+		'''
+		@return the maximum value of the interval
+		'''
+		return self._max
+
+	def isEmpty(self)->bool :
+		'''
+	 	@return true iff this range does not contain any element.
+		'''
+		return self._min > self._max
+
+	def contains(self, value:Decimal ) ->bool :
+		'''
+		@param value the value to test
+		@return true iff min &le; value &le; max
+		'''
+		return self._min<=value  and self._max>= value
+
+	def add(self, other:"Interval" ) ->"Interval" :
+		'''
+		@param other {@link Interval} to be added to this
+		@return new interval [ this.min + other.min , this.max + other.max ]
+		'''
+		return Interval(self._min + other._min, self._max+other._max)
+
+	def intersect(self, other:"Interval" ) ->"Interval" :
+		'''
+		@param other another {@link Interval} intersect with
+		@return intersection of this with other. returns null if intersection is
+		        empty.
+		'''
+		return Interval(max(self._min,other._min),min(self._max,other._max))
+
+	def invert(self,  other:"Interval") -> Optional["Interval"] :
+		'''
+		@param other the other minmax to deal with
+		@return the range of values that, when added to a value from other, will
+		        possibly get in our range. effectively, [min-other.max,
+		        max-other.min]. Returns None if the resulting range is empty.
+		'''
+		newmin = self._min-other._max
+		newmax = self._max-other._min
+		if newmin>newmax:
+			return None;
+		return Interval(newmin, newmax);
+
+	def subtract(self,  value:Decimal) ->"Interval" :
+		'''
+		@param value the value to subtract
+		@return Interval with both min and max reduced by value.
+		'''
+		return Interval(self._min-value, self._max-value)
+
+	def multiply(self, weight:Decimal ) -> "Interval" :
+		return Interval(self._min*weight, self._max*weight)
+	
+	#Override
+	def __repr__(self) ->str:
+		return "Interval[" + str(self._min) + "," + str(self._max) + "]"
+
+	def round(self, precision:int)->"Interval" :
+		'''
+		@param precision number of digits required
+		@return this but with modified precision. The interval is rounded so that
+		        the new interval is inside the old one.
+		'''
+		factor=Decimal(10)**precision
+		return Interval( ceil(self._min * factor)/factor,
+						floor(self._max * factor)/factor)	
+
+	#Override
+	def __hash__(self)->int:
+		return hash((self._min, self._max))
+
+	def __eq__(self, other):
+		return isinstance(other, self.__class__) and \
+			self._min == other._min and \
+			self._max == other._max
+
Index: geniuswebcore/geniusweb/bidspace/IssueInfo.py
===================================================================
--- geniuswebcore/geniusweb/bidspace/IssueInfo.py	(revision 81)
+++ geniuswebcore/geniusweb/bidspace/IssueInfo.py	(revision 81)
@@ -0,0 +1,127 @@
+from decimal import Decimal
+from geniusweb.issuevalue.ValueSet import ValueSet
+from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
+from geniusweb.bidspace.Interval import Interval
+from geniusweb.issuevalue.Value import Value
+from typing import List, Dict, Optional
+
+class IssueInfo:
+	'''
+	 Tool class to collect all relevant info about one issue (from LinearAdditive)
+	 in one class. Used for internally grouping data for more efficient
+	 processing. This class may change in the future, not recommended for direct
+	 use.
+	 <p>
+	 immutable
+	'''
+
+	def __init__(self, name:str, values:ValueSet ,  utils:ValueSetUtilities,
+			weight:Decimal , precision:int ) :
+		'''
+		@param name      the issue name
+		@param values    the {@link ValueSet} of the issue
+		@param utils     the {@link ValueSetUtilities} of the issue profile
+		@param weight    the weight of the {@link ValueSetUtilities}
+		@param precision the precision to compute with. Basically the number of
+		         decimal places used for the computations.
+		'''
+		self._name = name;
+		self._values = values;
+		self._weightedUtils = self._computeWeightedUtils(utils, weight, precision);
+		self._interval = self._getRange();
+
+	def getValues(self) ->ValueSet :
+		return self._values
+
+	def getName(self)->str:
+		return self._name;
+
+	def getInterval(self)->Interval:
+		'''
+		@return weighted minimum and maximum utility achievable with this issue,
+		    rounded to the requested precision.
+		'''
+		return self._interval;
+
+	def getExtreme(self, isMax:bool)->Value :
+		'''
+		@param isMax if true the max {@link Value} is returned, else the min is
+		             returned.
+		@return the extreme value, either the minimum if isMax=false or maximum
+		        if isMax=true
+		'''
+		extremeutil:Decimal = None #type:ignore
+		extremeval:Decimal = None #type:ignore
+		for val in self._values:
+			util = self._weightedUtils.get(val)
+			if extremeval == None:
+				extremeutil = self._weightedUtils.get(val) #type:ignore
+				extremeval = val
+			else:
+				if isMax:
+					if util > extremeutil: #type:ignore
+						extremeutil = util #type:ignore
+						extremeval = val
+				else:
+					if util<extremeutil: #type:ignore
+						extremeutil = util #type:ignore
+						extremeval = val
+		return extremeval #type:ignore
+
+	def getWeightedUtil(self, val:Value ) -> Decimal :
+		'''
+		@param val the issue value to be evaluated
+		@return weighted utility of given value, rounded to nearest value with
+		        the requested precision number of digits.
+		'''
+		return self._weightedUtils.get(val) #type:ignore
+
+	def _subset(self,  interval:Interval) -> List[Value]:
+		'''
+		@param interval an {@link Interval} of utility values.
+		@return all values that are inside the interval.
+		'''
+
+		selection:List[Value]  = []
+		for value in self._values:
+			if interval.contains(self.getWeightedUtil(value)):
+				selection.append(value);
+		return selection
+
+	def _subsetSize( self, interval:Interval) ->int:
+		'''
+		Faster way to determine subset size, it does not create a list
+		
+		@param interval an {@link Interval} of utility values.
+		@return size of the subset that you will get from calling subset
+		'''
+		n = 0
+		for value in self._values:
+			if interval.contains(self.getWeightedUtil(value)):
+				n=n+1
+		return n
+
+	def _getRange(self)->Interval :
+		'''
+		@return the {@link Interval} (minimum and maximum) of the utility of the
+		        weighted utility of this issue, properly rounded to the
+		        {@link #precision}/
+
+		'''
+		min = Decimal(1)
+		max = Decimal(0)
+		for value in  self._values:
+			util = self.getWeightedUtil(value)
+			if util < min:
+				min = util
+			if util > max:
+				max = util
+		return Interval(min, max)
+
+	def _computeWeightedUtils(self, utilities:ValueSetUtilities, w:Decimal, prec:int) \
+				-> Dict[Value, Decimal] :
+		map:Dict[Value, Decimal]  = {}
+
+		for val in self._values:
+			map[val] = round(utilities.getUtility(val)* w, prec)
+		return map
