Index: geniuswebcore/geniusweb/bidspace/pareto/GenericPareto.py
===================================================================
--- geniuswebcore/geniusweb/bidspace/pareto/GenericPareto.py	(revision 84)
+++ geniuswebcore/geniusweb/bidspace/pareto/GenericPareto.py	(revision 84)
@@ -0,0 +1,88 @@
+import threading
+from typing import List, Set, Optional
+
+from geniusweb.bidspace.AllBidsList import AllBidsList
+from geniusweb.bidspace.pareto.ParetoFrontier import ParetoFrontier
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.profile.PartialOrdering import PartialOrdering
+from geniusweb.profile.Profile import Profile
+from geniusweb.utils import val
+
+
+class GenericPareto(ParetoFrontier):
+	'''
+	A set of all pareto points associated with a set of profiles. Generally
+	applicable but slow.
+	'''
+
+	def __init__(self, profiles:List[PartialOrdering]):
+		'''
+		Constructs pareto from a general {@link PartialOrdering}. This is a
+		brute-force algorithm that will inspect (but not store) ALL bids in the
+		space, and therefore may be inapproproate depending on the size of your
+		domain. Complexity O( |bidspace|^2 |profiles| )
+		
+		@param profiles a set of at least 2 {@link PartialOrdering}s. All must be
+		                defined on the same domain.
+		'''
+		if len(profiles) < 2:
+			raise ValueError("at least 2 profiles are needed")
+		
+		if None in profiles:
+			raise ValueError("Profiles must not be None")
+		domain = profiles[0].getDomain()
+		for profile in profiles:
+			if not domain == profile.getDomain():
+				raise ValueError(
+						"All profiles must be on same domain ("
+								+domain.getName() + ") but found " + str(profile));
+		self._profiles = list(profiles)
+		self._paretobids:Optional[Set[Bid]] = None
+		self._lock = threading.Lock()
+
+	# Override
+	def getProfiles(self) -> List[Profile]:
+		return list(self._profiles)
+
+	# Override
+	def getPoints(self) -> Set[Bid]:
+		with self._lock:
+			if self._paretobids == None:
+				self._computePareto()
+			return set(val(self._paretobids))
+
+	# Override
+	def __repr__(self):
+		return "Pareto " + str(self.getPoints())
+
+	def _computePareto(self):
+		'''
+		assigns {@link #paretobids}.
+		'''
+		self._paretobids:Set[Bid] = set()
+
+		for newbid in AllBidsList(self._profiles[0].getDomain()):
+			'''
+			invariant: paretobids contains bids not dominated by other bids
+			in paretobids. That means we need (1) check if new bid is
+			dominated (2) if existing bids are dominated if we add a new bid
+			 '''
+			newBidIsDominated = any(self._isDominatedBy(newbid, paretobid) 
+								for paretobid in self._paretobids)
+
+			# if new bid is not dominated, we add it and we re-check existing
+			# if they are now dominated
+			if not newBidIsDominated:
+				self._paretobids = {paretobid for paretobid in self._paretobids
+					if not self._isDominatedBy(paretobid, newbid)}
+				self._paretobids.add(newbid)
+
+	def _isDominatedBy(self, bid1:Bid , dominantbid: Bid) -> bool:
+		'''
+		@param bid1        the bid to check
+		@param dominantbid the bid that is supposed to dominate bid1
+		@return true iff bid1 is dominated by dominant bid. "Dominated by" means
+		        that the bid is preferred or equal in all profiles.
+		'''
+		return all(profile.isPreferredOrEqual(dominantbid, bid1)
+				for profile in self._profiles)
Index: geniuswebcore/geniusweb/bidspace/pareto/ParetoFrontier.py
===================================================================
--- geniuswebcore/geniusweb/bidspace/pareto/ParetoFrontier.py	(revision 84)
+++ geniuswebcore/geniusweb/bidspace/pareto/ParetoFrontier.py	(revision 84)
@@ -0,0 +1,23 @@
+from typing import List, Set
+from geniusweb.profile.Profile import Profile
+from abc import ABC, abstractmethod
+from geniusweb.issuevalue.Bid import Bid
+
+class ParetoFrontier (ABC):
+	'''
+	The pareto frontier is the set of {@link Bid}s for which there is no other
+	bid that is similar or better for the involved profiles.
+	'''
+
+	@abstractmethod
+	def getProfiles(self) -> List[Profile] :
+		'''
+		@return the profiles that form the basis of this pareto frontier.
+		'''
+
+	@abstractmethod
+	def getPoints(self) -> Set[Bid] :
+		'''
+		@return the set of all {@link Bid}s that are on the pareto frontier.
+		'''
+
Index: geniuswebcore/geniusweb/bidspace/pareto/ParetoLinearAdditive.py
===================================================================
--- geniuswebcore/geniusweb/bidspace/pareto/ParetoLinearAdditive.py	(revision 84)
+++ geniuswebcore/geniusweb/bidspace/pareto/ParetoLinearAdditive.py	(revision 84)
@@ -0,0 +1,51 @@
+from typing import List, Set, Optional
+
+from geniusweb import profile
+from geniusweb.bidspace.pareto.ParetoFrontier import ParetoFrontier
+from geniusweb.bidspace.pareto.PartialPareto import PartialPareto
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.profile.Profile import Profile
+from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
+
+
+class ParetoLinearAdditive(ParetoFrontier):
+	'''
+	pareto frontier implementation for {@link LinearAdditive}. This is a highly
+	optimized pareto method for {@link LinearAdditive} spaces
+	'''
+	
+	def __init__(self, utilSpaces:List[LinearAdditive]):
+		'''
+		@param utilSpaces. Must contain at least two {@link LinearAdditive}s and
+		                   all must be defined on the same donain.
+		'''
+		if utilSpaces == None or len(utilSpaces) < 2:
+			raise ValueError("utilSpaces must contain at least 2 spaces")
+
+		domain = utilSpaces[0].getDomain()
+		for space in utilSpaces:
+			if not space.getDomain() == domain:
+				raise ValueError(
+					"Expected all spaces using domain " + domain.getName()
+								+" but found " + space.getDomain().getName());
+		self._utilSpaces = utilSpaces
+		self._points:Optional[Set[Bid] ] = None
+
+	# Override
+	def getProfiles(self) -> List[Profile]:
+		return list(self._utilSpaces)
+
+	# Override
+	def getPoints(self) -> Set[Bid]:
+		if self._points == None:
+			self._points = self._computePareto()
+		return self._points  # type:ignore
+
+	# Override
+	def toString(self) -> str:
+		return "Pareto " + str(self.getPoints())
+
+	def _computePareto(self) -> Set[Bid]:
+		issues:List[str] = list(self._utilSpaces[0].getDomain().getIssues())
+		partial = PartialPareto.create(self._utilSpaces, issues)
+		return set([ point.getBid() for point in partial.getPoints() ])
Index: geniuswebcore/geniusweb/bidspace/pareto/ParetoPoint.py
===================================================================
--- geniuswebcore/geniusweb/bidspace/pareto/ParetoPoint.py	(revision 84)
+++ geniuswebcore/geniusweb/bidspace/pareto/ParetoPoint.py	(revision 84)
@@ -0,0 +1,65 @@
+from decimal import Decimal
+from typing import List, Dict
+
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.Value import Value
+from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
+
+
+class ParetoPoint:
+	'''
+	A Paretopoint is a Bid together with an N-dimensional utility vector. This is
+	also a caching mechanism to avoid repeated utility computation of good bids.
+	This is a internal utility class to streamline pareto computations.
+	'''
+
+	def __init__(self, utils:List[Decimal] , bid:Bid):
+		'''
+		@param bid    a (possibly partial) {@link Bid}
+		@param utils the utilities of the bid in all spaces, in order
+		'''
+		self._bid = bid;
+		self._utilities = list(utils)
+
+	@staticmethod
+	def create(bid:Bid , spaces:List[LinearAdditive]) -> "ParetoPoint":
+		'''
+		@param bid    a (possibly partial) {@link Bid}
+		@param spaces the {@link LinearAdditive}s to consider
+		'''
+		utilities:List[Decimal] = []
+		for space in spaces:
+			utilities.append(space.getUtility(bid))
+		return ParetoPoint(utilities, bid)
+
+	def merge(self, otherpoint:"ParetoPoint") -> "ParetoPoint":
+		'''
+		Merges the issues from both bids and adds the utilities. This only works
+		correctly if the issues in other point are completely disjoint from our
+		bid issues.
+		
+		@param otherpoint with the utils summed and the issue values merged
+		'''
+		summedutils:List[Decimal] = []
+		for n in range(len(self._utilities)):
+			summedutils.append(self._utilities[n] + otherpoint._utilities[n])
+
+		return ParetoPoint(summedutils, self._bid.merge(otherpoint.getBid()))
+
+	def isDominatedBy(self, other:"ParetoPoint") -> bool:
+		'''
+		@param other
+		@return true if this ParetoPoint is dominated by the other. That means
+		        other has better or equal utilities in ALL dimensions.
+		'''
+		otherutils:List[Decimal] = other.getUtilities()
+		for i  in range(len(self._utilities)):
+			if otherutils[i] < self._utilities[i]:
+				return False
+		return True
+
+	def getUtilities(self) -> List[Decimal]:
+		return self._utilities
+
+	def getBid(self) -> Bid:
+		return self._bid
Index: geniuswebcore/geniusweb/bidspace/pareto/PartialPareto.py
===================================================================
--- geniuswebcore/geniusweb/bidspace/pareto/PartialPareto.py	(revision 84)
+++ geniuswebcore/geniusweb/bidspace/pareto/PartialPareto.py	(revision 84)
@@ -0,0 +1,89 @@
+from typing import List, Dict
+
+from geniusweb.bidspace.pareto.ParetoPoint import ParetoPoint
+from geniusweb.issuevalue.Bid import Bid
+from geniusweb.issuevalue.Value import Value
+from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
+
+
+class PartialPareto:
+	'''
+	 PartialPareto contains a pareto surface of partial bids. Internal-only
+	 utility class for computing LinearAdditive pareto surface. Immutable.
+	'''
+
+	def __init__(self, points:List[ParetoPoint]):
+		'''
+		@param points the points to be contained in this PartialPareto
+'''
+		self._points = points
+
+	@staticmethod
+	def create(utilSpaces:List[LinearAdditive] ,
+			issues: List[str]) -> "PartialPareto":
+		'''
+		Constructor. Avoided the standard 'new' constructor as this is
+		implemented recursively (O(log(#issues))).
+		
+		@param utilSpaces the {@link LinearAdditive}s
+		@param issues     the issues (subset of all issues in the space) to be
+		                  used for this pareto
+		@return the pareto surface (non-dominated bids) when considering only the
+		        given issues.
+		'''
+
+		if len(issues) == 1:
+			issue = issues[0]
+			map:Dict[str, Value] = {}
+			list:List[ParetoPoint] = []
+			for value in utilSpaces[0].getDomain().getValues(issue):
+				map[issue] = value
+				list = PartialPareto._add(list, ParetoPoint.create(Bid(map), utilSpaces))
+			return PartialPareto(list)
+		else:
+			halfway = int(len(issues) / 2)
+			return PartialPareto.create(utilSpaces, issues[0: halfway])._merge(
+					PartialPareto.create(utilSpaces, issues[halfway:]))
+
+	def getPoints(self) -> List[ParetoPoint]:
+		'''
+		@return the ParetoPoints on the (possibly partial) Pareto surface
+		'''
+		return self._points
+
+	def _merge(self, other:"PartialPareto") -> "PartialPareto":
+		'''
+		This combines two partial paretos. Here is the heart of the algorithm.
+		
+		@param other another {@link PartialPareto} to be merged with this. The
+		             other pareto must be composed with non-overlapping range.
+		@return merge of two partial paretos. Checks all partial1*partial2
+		        combinations and only non-dominated points are kept.
+		'''
+		merge:List[ParetoPoint] = []
+		for point in  self._points:
+			for otherpoint in other._points:
+				merge = PartialPareto._add(merge, point.merge(otherpoint))
+		return PartialPareto(merge)
+
+	@staticmethod
+	def _add(list:List[ParetoPoint], candidate:ParetoPoint) -> List[ParetoPoint]:
+		'''
+		Add a new ParetoPoint to a list
+		
+		@param list      the existing list
+		@param candidate a new {@link ParetoPoint} candidate
+		@return a new list that either ignored the candidate, or added it and
+		        removed the old points that are dominated by the candidate
+		'''
+		for existing in list:
+			if candidate.isDominatedBy(existing):
+				return list
+		# if we get here, candidate is not dominated so we add it
+		# and remove existing ones now dominated
+		newlist:List[ParetoPoint] = []
+		newlist.append(candidate)
+		for existing in list:
+			if not existing.isDominatedBy(candidate):
+				newlist.append(existing)
+		return newlist
