Commit 5e11e463 authored by Arnaud Fontaine's avatar Arnaud Fontaine

ERP5: Remove Capacity/GLPK: never used and broken since glpk Python module is...

ERP5: Remove Capacity/GLPK: never used and broken since glpk Python module is not in ERP5 Software Release neither.
parent c68ed1b8
##############################################################################
#
# Copyright (c) 2003 Nexedi SARL and Contributors. All Rights Reserved.
# Yoshinori Okuji <yo@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from numpy import shape, array
MODEL_HEAD = """
/* The number of samples. */
param n, integer, > 0;
/* The number of resources. */
param d, integer, > 0;
/* The set of samples. */
set S := 1..n;
/* The set of resources. */
set R := 1..d;
/* The query. */
param q{i in R};
/* The samples. */
param s{j in S, i in R};
/* The normal vector of a hyperplane. */
var z{i in R};
/* The origin of a hyperplane. */
var z0;
#display q;
#display s;
/* The objective. */
maximize obj: sum {i in R} (z[i] * q[i]) - z0;
/* Constraints. */
subject to c{j in S}: sum{i in R} (z[i] * s[j,i]) - z0, <= 0;
subject to c0: sum {i in R} (z[i] * q[i]) - z0, <= 1;
data;
"""
MODEL_TAIL="""
end;
"""
def writeModelFile(file, matrix, point):
"""
Write an LP problem in MathProg.
"""
n = shape(matrix)[0]
d = shape(matrix)[1]
file.write(MODEL_HEAD)
file.write("param n := %d;\n" % n)
file.write("param d := %d;\n" % d)
file.write("param s\n:\t")
def insertTab(x,y): return str(x)+"\t"+str(y)
file.write(reduce(insertTab, range(1,d+1)))
file.write("\t:=\n")
for i in range(n):
file.write(repr(i+1))
file.write(reduce(insertTab, matrix[i], ""))
file.write("\n")
file.write(";\n")
file.write("param q := ")
def insertComma(x,y): return str(x)+','+str(y)
def flatten(x): return str(x[0])+' '+str(x[1])
file.write(reduce(insertComma,
map(flatten, map(None, range(1,d+1), point))))
file.write(";\n")
file.write(MODEL_TAIL)
def getOptimalValue(file):
"""
Solve an LP problem described in MathProg language, and return
the result of its objective function.
This version uses GNU Linear Programming Kit.
"""
import glpk
lp = glpk.glp_lpx_read_model(file, None, None)
try:
glpk.glp_lpx_set_int_parm(lp, glpk.LPX_K_PRICE, 1)
glpk.glp_lpx_set_int_parm(lp, glpk.LPX_K_PRESOL, 1)
glpk.glp_lpx_set_int_parm(lp, glpk.LPX_K_BRANCH, 2)
glpk.glp_lpx_set_int_parm(lp, glpk.LPX_K_BTRACK, 2)
glpk.glp_lpx_set_real_parm(lp, glpk.LPX_K_TMLIM, 2000) # XXX
ret = glpk.glp_lpx_simplex(lp)
if ret != glpk.LPX_E_OK:
raise RuntimeError, "The simplex method of GLPK failed"
return glpk.glp_lpx_get_obj_val(lp)
finally:
glpk.glp_lpx_delete_prob(lp)
def solve(matrix, point):
"""
Check if a point is inside a convex hull specified by a matrix.
"""
import tempfile
import os
if shape(point)[0] != shape(matrix)[1]:
raise TypeError, "The argument 'point' has a different number of dimensions from the capacity"
mod_name = tempfile.mktemp(suffix='.mod')
mod = file(mod_name, 'w')
try:
writeModelFile(mod, matrix, point)
mod.close()
obj = getOptimalValue(mod_name)
finally:
os.remove(mod_name)
return obj <= 0
# This is a test.
if __name__ == '__main__':
m = array([[ 0, 1, 2, 3, 4, 5],
[10,11,12,13,14,15],
[20,21,22,23,24,25],
[30,31,32,33,34,35],
[40,41,42,43,44,45],
[50,51,52,53,54,55],
[60,61,62,63,64,65],
[70,71,72,73,74,75]])
print m
p = ([1,2,3,4,5,6])
print p
print solve(m, p)
class Node:
pass
def testAmount1(self):
node = Node()
node._capacity_item_list = [
[['a', 2]],
[['b', 5]],
]
amount_list = [
['b', 1],
['a', 1],
]
return repr(self.portal_simulation.isAmountListInsideCapacity(node, amount_list))
def getCategory(self, relative_url):
# return self.portal_categories.resolveCategory(relative_url)
return self.portal_categories.restrictedTraverse(relative_url)
def testAmount3(self):
node = Node()
node._capacity_item_list = [
[[getCategory(self, 'skill/Assistant/Bebe'), 10]],
[[getCategory(self, 'skill/Assistant/Enfant'), 10]],
]
amount_list = [
[getCategory(self, 'skill/Assistant'), 8]
]
return repr(self.portal_simulation.isAmountListInsideCapacity(node, amount_list, getCategory(self, 'skill'), 1))
def testAmount4(self):
node = Node()
node._capacity_item_list = [
[[getCategory(self, 'skill/Assistant'), 10]],
]
amount_list = [
[getCategory(self, 'skill/Assistant/Bebe'), 10],
[getCategory(self, 'skill/Assistant/Enfant'), 10],
]
return repr(self.portal_simulation.isAmountListInsideCapacity(node, amount_list, getCategory(self, 'skill'), 1))
......@@ -37,8 +37,6 @@ from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
from zLOG import LOG, PROBLEM, WARNING, INFO
from Products.ERP5.Capacity.GLPK import solve
from numpy import zeros, resize
from DateTime import DateTime
from Products.PythonScripts.Utility import allow_class
......@@ -2250,181 +2248,6 @@ class SimulationTool(BaseTool):
# and store the resulting new capacity in node
node._capacity_item_list = capacity_item_list
security.declareProtected( Permissions.ModifyPortalContent, 'isAmountListInsideCapacity' )
def isAmountListInsideCapacity(self, node, amount_list,
resource_aggregation_base_category=None, resource_aggregation_depth=None):
"""
Purpose: decide if a list of amounts is consistent with the capacity of a node
If any resource in amount_list is missing in the capacity of the node, resource
aggregation is performed, based on resource_aggregation_base_category. If the
base category is not specified, it is an error (should guess instead?). The resource
aggregation is done at the level of resource_aggregation_depth in the tree
of categories. If resource_aggregation_depth is not specified, it's an error.
Assumptions: amount_list is an association list, like ((R1 V1) (R2 V2)).
node has an attribute '_capacity_item_list' which is a list of association lists.
resource_aggregation_base_category is a Base Category object or a list of Base
Category objects or None.
resource_aggregation_depth is a strictly positive integer or None.
"""
# Make a copy of the attribute _capacity_item_list, because it may be necessary
# to modify it for resource aggregation.
capacity_item_list = node._capacity_item_list[:]
# Make a mapping between resources and its indices.
resource_map = {}
index = 0
for alist in capacity_item_list:
for pair in alist:
resource = pair[0]
# LOG('isAmountListInsideCapacity', 0,
# "resource is %s" % repr(resource))
if resource not in resource_map:
resource_map[resource] = index
index += 1
# Build a point from the amount list.
point = zeros(index, 'd') # Fill up zeros for safety.
mask_map = {} # This is used to skip items in amount_list.
for amount in amount_list:
if amount[0] in mask_map:
continue
# This will fail, if amount_list has any different resource from the capacity.
# If it has any different point, then we should ......
#
# There would be two possible different solutions:
# 1) If a missing resource is a meta-resource of resources supported by the capacity,
# it is possible to add the resource into the capacity by aggregation.
# 2) If a missing resource has a meta-resource as a parent and the capacity supports
# the meta-resource directly or indirectly (`indirectly' means `by aggregation'),
# it is possible to convert the missing resource into the meta-resource.
#
# However, another way has been implemented here. This does the following, if the resource
# is not present in the capacity:
# 1) If the value is zero, just ignore the resource, because zero is always acceptable.
# 2) Attempt to aggregate resources both of the capacity and of the amount list. This aggregation
# is performed at the depth of 'resource_aggregation_depth' under the base category
# 'resource_aggregation_base_category'.
#
resource = amount[0]
if resource in resource_map:
point[resource_map[amount[0]]] = amount[1]
else:
if amount[1] == 0:
# If the value is zero, no need to consider.
pass
elif resource_aggregation_base_category is None or resource_aggregation_depth is None:
# XXX use an appropriate error class
# XXX should guess a base category instead of emitting an exception
raise RuntimeError, "The resource '%s' is not found in the capacity, and the argument 'resource_aggregation_base_category' or the argument 'resource_aggregation_depth' is not specified" % resource
else:
# It is necessary to aggregate resources, to guess the capacity of this resource.
def getAggregationResourceUrl(url, depth):
# Return a partial url of the argument 'url'.
# If 'url' is '/foo/bar/baz' and 'depth' is 2, return '/foo/bar'.
pos = 0
for _ in range(resource_aggregation_depth):
pos = url.find('/', pos+1)
if pos < 0:
break
if pos < 0:
return None
pos = url.find('/', pos+1)
if pos < 0:
pos = len(url)
return url[:pos]
def getAggregatedResourceList(aggregation_url, category, resource_list):
# Return a list of resources which should be aggregated. 'aggregation_url' is used
# for a top url of those resources. 'category' is a base category for the aggregation.
aggregated_resource_list = []
for resource in resource_list:
for url in resource.getCategoryMembershipList(category, base=1):
if url.startswith(aggregation_url):
aggregated_resource_list.append(resource)
return aggregated_resource_list
def getAggregatedItemList(item_list, resource_list, aggregation_resource):
# Return a list of association lists, which is a result of an aggregation.
# 'resource_list' is a list of resources which should be aggregated.
# 'aggregation_resource' is a category object which is a new resource created by
# this aggregation.
# 'item_list' is a list of association lists.
new_item_list = []
for alist in item_list:
new_val = 0
new_alist = []
# If a resource is not a aggregated, then add it to the new alist as it is.
# Otherwise, aggregate it to a single value.
for pair in alist:
if pair[0] in resource_list:
new_val += pair[1]
else:
new_alist.append(pair)
# If it is zero, ignore this alist, as it is nonsense.
if new_val != 0:
new_alist.append([aggregation_resource, new_val])
new_item_list.append(new_alist)
return new_item_list
# Convert this to a string if necessary, for convenience.
if not isinstance(resource_aggregation_base_category, (tuple, list)):
resource_aggregation_base_category = (resource_aggregation_base_category,)
done = 0
# LOG('isAmountListInsideCapacity', 0,
# "resource_aggregation_base_category is %s" % repr(resource_aggregation_base_category))
for category in resource_aggregation_base_category:
for resource_url in resource.getCategoryMembershipList(category, base=1):
aggregation_url = getAggregationResourceUrl(resource_url,
resource_aggregation_depth)
if aggregation_url is None:
continue
aggregated_resource_list = getAggregatedResourceList (aggregation_url,
category,
resource_map.keys())
# If any, do the aggregation.
if len(aggregated_resource_list) > 0:
aggregation_resource = self.portal_categories.resolveCategory(aggregation_url)
# Add the resource to the mapping.
# LOG('aggregation_resource', 0, str(aggregation_resource))
resource_map[aggregation_resource] = index
index += 1
# Add the resource to the point.
point = resize(point, (index,))
val = 0
for aggregated_amount in amount_list:
for url in aggregated_amount[0].getCategoryMembershipList(category, base=1):
if url.startswith(aggregation_url):
val += aggregated_amount[1]
mask_map[aggregated_amount[0]] = None
break
point[index-1] = val
# Add capacity definitions of the resource into the capacity.
capacity_item_list += getAggregatedItemList(capacity_item_list,
aggregated_resource_list,
aggregation_resource)
done = 1
break
if done:
break
if not done:
raise RuntimeError, "Aggregation failed"
# Build a matrix from the capacity item list.
# LOG('resource_map', 0, str(resource_map))
matrix = zeros((len(capacity_item_list)+1, index), 'd')
for index in range(len(capacity_item_list)):
for pair in capacity_item_list[index]:
matrix[index,resource_map[pair[0]]] = pair[1]
# LOG('isAmountListInsideCapacity', 0,
# "matrix = %s, point = %s, capacity_item_list = %s" % (str(matrix), str(point), str(capacity_item_list)))
return solve(matrix, point)
# Asset Price Calculation
def updateAssetPrice(self, resource, variation_text, section_category, node_category,
strict_membership=0, simulation_state=None):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment