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 ...@@ -37,8 +37,6 @@ from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
from zLOG import LOG, PROBLEM, WARNING, INFO from zLOG import LOG, PROBLEM, WARNING, INFO
from Products.ERP5.Capacity.GLPK import solve
from numpy import zeros, resize
from DateTime import DateTime from DateTime import DateTime
from Products.PythonScripts.Utility import allow_class from Products.PythonScripts.Utility import allow_class
...@@ -2250,181 +2248,6 @@ class SimulationTool(BaseTool): ...@@ -2250,181 +2248,6 @@ class SimulationTool(BaseTool):
# and store the resulting new capacity in node # and store the resulting new capacity in node
node._capacity_item_list = capacity_item_list 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 # Asset Price Calculation
def updateAssetPrice(self, resource, variation_text, section_category, node_category, def updateAssetPrice(self, resource, variation_text, section_category, node_category,
strict_membership=0, simulation_state=None): 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