def __init__(self, bcmdict, wanteddict, elemdict, vendict): self.vendorlist = list() # contains the active list of vendors. # All matrix keys match this list. self.elementlist = list() # contains the list of elements. # All matrix keys match this list. self.wanted = None # numpy array self.prices = None # numpy array self.stock = None # numpy array self.bcmdict = bcmdict self.__initialize_lists(self.bcmdict) self.wantdict = wanteddict self.elemdict = elemdict self.vendict = vendict self.wanted = self.__buildwantedarray(self.wantdict) self.stock, self.prices = self.__buildvendorarrays() self.__vs = VendorStats(self) self.__update() self.__need_rebuild = False
class BCMData(): """ Contains the core data of pybcm. It contains several constant references, as well as mutable data, that is used by other classes in the system. Attributes: vendorsortdict(): returns a dictionary that defines sorting removevendor( vendorid ): remove a vendor from the vendorlist removevendors( vendorindices ): remove the list of vendor indices from the vendorlsit replacevendorlist(): replace the vendor with avgprices( ): return an array of average element prices """ def __init__(self, bcmdict, wanteddict, elemdict, vendict): self.vendorlist = list() # contains the active list of vendors. # All matrix keys match this list. self.elementlist = list() # contains the list of elements. # All matrix keys match this list. self.wanted = None # numpy array self.prices = None # numpy array self.stock = None # numpy array self.bcmdict = bcmdict self.__initialize_lists(self.bcmdict) self.wantdict = wanteddict self.elemdict = elemdict self.vendict = vendict self.wanted = self.__buildwantedarray(self.wantdict) self.stock, self.prices = self.__buildvendorarrays() self.__vs = VendorStats(self) self.__update() self.__need_rebuild = False #self. = None #self.VENDSORTLIST = self.__createvendsortinglist(self.BCMDICT) def __update(self): """Update the various arrays.""" self.__sortlists() self.__updatearrays() self.__vs.update(self) #self.AVGPRICES = self.avgprices(stockweighted=True) def __initialize_lists(self, bcmdict): """ Build the initial elementlist and vendorlist based on bcmdict.""" logging.info("Building BCMData lists") #k = [ keytuple for keytuple in bcmdict.keys() ] for keytuple in list(bcmdict.keys()): (elementid, vendorid) = keytuple addtolist(self.vendorlist, vendorid) addtolist(self.elementlist, elementid) def __updatearrays(self): """Build the various arrays.""" logging.info("Forcing array update...") self.wanted = self.__buildwantedarray(self.wantdict) self.__updatevendorarrays() self.__need_rebuild = False def __buildvendorarrays(self): """ Iterate over element, vendor in the elementlist and vendorlist to create the numpy arrays. Get the data from the bcmdict. """ shape = (len(self.elementlist), len(self.vendorlist)) pricearray = ma.masked_array(np.zeros(shape, dtype='int')) stockarray = ma.masked_array(np.zeros(shape, dtype='int')) #wanted doesnt change for eindex, element in enumerate(self.elementlist): for vindex, vendor in enumerate(self.vendorlist): if (element, vendor) in self.bcmdict: stockarray[eindex, vindex] = int(self.bcmdict[element, vendor][0]) pricearray[eindex, vindex] = int(self.bcmdict[element, vendor][1] * 100) # clip the max value of stock to the wanted quantity stockarray = np.minimum(stockarray, self.wanted.reshape(len(self.elementlist), 1)) mask = stockarray <= 0 stockarray.mask = mask pricearray.mask = mask return stockarray, pricearray def __updatevendorarrays(self): """ Create new arrays in case elementlist and vendorlits have changed size """ stockarray, pricearray = self.__buildvendorarrays() self.prices = pricearray self.stock = stockarray return stockarray, pricearray def __buildwantedarray(self, wanteddict): # returns numpy array of WANTED items """ Create a numpy array of wanted quantities """ logging.info("Building WANTED array...") m = len(self.elementlist) # ensure the size of the array is consistent with the others wantedarray = np.ndarray(shape=(m,), dtype=np.int) for eidx, elementid in enumerate(self.elementlist): wantedarray[eidx] = wanteddict[elementid] return wantedarray def __sortlists(self): self.__elementsort() self.__vendorsort() def __elementsort(self, sortweights=None): logging.info("Sorting Element List...") if sortweights: weights = sortweights else: weights = self.elementweights() #resort the elementlist using these weights self.elementlist = [y for (x, y) in sorted(zip(weights, self.elementlist), reverse=True)] def __vendorsort(self, sortby='uniqueitems'): logging.info("Sorting Vendor List...") if sortby == 'uniqueitems': weights = self.__vs.itemspervendor elif sortby == 'totalitems': weights = self.__vs.totalvendor else: return # nothing sorted self.vendorlist = [y for (x, y) in sorted(zip(weights, self.vendorlist), reverse=True)] def __createvendsortinglist(self): """ Return list of tuples (vendor index, element index, price) The element index is the element of the highest weight that the vendor offers in sufficient qty """ factor = 1.0 k = list() for vidx, vcol in enumerate(self.stock.T): for eidx, stock in enumerate(vcol): if stock > self.wanted[eidx] * factor: price = self.prices[eidx, vidx] stock = self.stock[eidx, vidx] k.append((vidx, eidx, price, stock)) break # now sort this list on eidx=>ascending, price=>descending, stock->satisfies(eidx) k = sorted(k, key=itemgetter(1, 2)) return k def vendorsortdict(self): """Return a dictionary describing how to sort vendors k[vendor index] = (elementid to sort on, price, qty) Important: It's assumed that the arrays are already sorted on the element weights, meaning the most costly elements are first. This algorithim uses the first element the vendor stocks in sufficient quantity """ #size = len(self.vendorlist) #k = np.zeros(shape=(size), dtype=(int, float)) k = dict() for vidx, vcol in enumerate(self.prices.T): # iterate over columns in the price matrix for eidx, price in enumerate(vcol): if price > 0: qty = self.stock[eidx, vidx] k[vidx] = (eidx, price, qty) break return k def __sufficientqty(self, eidx, vidx, factor=0.5): return self.stock[eidx, vidx] >= self.wanted[eidx] * factor def removevendor(self, vendorid): #doesn't remove it from the .data, only from the list of vendors assert vendorid in self.vendorlist, "Vendor %r does not exist in vendorlist" % vendorid self.vendorlist.remove(vendorid) self.__need_rebuild = True def removevendors(self, vendorindices): #Create a new vendorlist that doesn't include the list of id's passed via vendorindices logging.info("Trying to remove " + str(len(vendorindices)) + " vendors.") before = len(self.vendorlist) newlist = [vendor for vendor in self.vendorlist if self.vendorlist.index(vendor) not in vendorindices] self.replacevendorlist(newlist) after = len(self.vendorlist) logging.info("Removed: " + str(before - after) + " vendors.") self.__update() return newlist def replacevendorlist(self, newvendors): self.vendorlist = newvendors #remove all items that contain these vendors from the dictionaries? self.__update() def avgprices(self, stockweighted=False): """Return a masked array of the average price by element""" p = ma.array(self.prices, mask=self.prices <= 0) if stockweighted: s = ma.array(self.stock, mask=self.stock <= 0) avgprices = ma.average(p, weights=s, axis=1) else: #avgprices = p.sum(axis=1)/(p > 0).sum(axis=1) #denominator sums the non-zero values avgprices = ma.average(p, axis=1) return avgprices def elementweights(self): #generate a weight for each element - basically the avg price for that element * WANTED qty, normalized weights = self.wanted * self.avgprices() return weights def __itemspervendor(self): s = self.stock itemspervendor = np.ndarray(s > 0).sum(0) return itemspervendor def partcount(self): """count the number of unique parts and the total number of parts""" return len(self.wanted), self.wanted.sum()