Created simple linear-equation creation mechanism

This commit is contained in:
Evgeniy L 2015-12-22 18:55:06 +03:00
parent b475a0f730
commit 8864affd92
3 changed files with 294 additions and 0 deletions

View File

@ -0,0 +1,170 @@
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See then
# License for the specific language governing permissions and limitations
# under the License.
import six
from scipy.optimize import linprog
from scipy.ndimage.interpolation import shift
import numpy as np
from numpy import array
from numpy import zeros
from numpy import append
from numpy import vstack
from numpy import concatenate
from numpy import put
from numpy import roll
def shift(arr, steps, val=0):
res_arr = roll(arr, steps)
put(res_arr, range(steps), val)
return res_arr
class Disk(object):
def __init__(self, **kwargs):
for k, v in six.iteritems(kwargs):
setattr(self, k, v)
class Space(object):
def __init__(self, **kwargs):
for k, v in six.iteritems(kwargs):
setattr(self, k, v)
class DynamicAllocator(object):
def __init__(self, hw_info, schema):
self.disks = [Disk(**disk) for disk in hw_info['disks']]
self.spaces = [Space(**space) for space in schema]
# Add fake volume Unallocated, in order to be able
# to have only volumes with minimal size, without
# additional space allocation
self.lp = DynamicAllocationLinearProgram(self.disks, self.spaces)
def generate_static(self):
sizes = self.lp.solve()
return sizes
class DynamicAllocationLinearProgram(object):
"""Use Linear Programming method [0] (the method itself has nothing to do
with computer-programming) in order to formulate and solve the problem
of spaces allocation on disks, with the best outcome.
In this implementation scipy is being used since it already implements
simplex algorithm to find the best feasible solution.
[0] https://en.wikipedia.org/wiki/Linear_programming
[1] http://docs.scipy.org/doc/scipy-0.16.0/reference/generated
/scipy.optimize.linprog.html
[2] https://en.wikipedia.org/wiki/Simplex_algorithm
"""
def __init__(self, disks, spaces):
# Coefficients of the linear objective minimization function.
# During iteration over vertexes the function is used to identify
# if current solution (vertex) satisfies the equation more, than
# previous one.
# Example of equation: c[0]*x1 + c[1]*x2
self.objective_function_coefficients = []
# A matrix which, gives the values of the equality constraints at x,
# when multipled by x.
self.equality_constraint_matrix = []
# An array of values representing right side of equation,
# left side is represented by row of `equality_constraint_matrix`
# matrix
self.equality_constraint_vector = array([])
# Specify boundaries of each x in the next format (min, max). Use
# None for one of min or max when there is no bound.
self.bounds = array([])
self._initialize_equation(disks, spaces)
print '*' * 30
print self.equality_constraint_matrix
print self.objective_function_coefficients
def solve(self):
solution = linprog(
self.objective_function_coefficients,
A_eq=self.equality_constraint_matrix,
b_eq=self.equality_constraint_vector,
bounds=self.bounds,
options={"disp": True})
return solution.x
def _initialize_equation(self, disks, spaces):
for d in disks:
# Initialize constraints, each row in the matrix should
# be equal to size of the disk
self.equality_constraint_vector = append(self.equality_constraint_vector, d.size)
# Initialize the matrix
# In case of 2 spaces and 3 disks the result should be:
# [[1, 1, 0, 0, 0, 0],
# [0, 0, 1, 1, 0, 0],
# [0, 0, 0, 0, 1, 1]]
#
# Explanation of the first row
# [1, - x1 multiplier, size of space 1 on the first disk
# 1, - x2 multiplier, size of space 2 on the first disk
# 0, - x3 multiplier, size of space 1 on 2nd disk, 0 for the first
# 0, - x4 multiplier, size of space 2 on 2nd disk, 0 for the first
# 0, - x5 multiplier, size of space 1 on 3rd disk, 0 for the first
# 0] - x6 multiplier, size of space 2 on 3rd disk, 0 for the first
# For each space x (size of the space) is represented
# for each disk as separate variable, so for each
# disk we have len(spaces) * len(disks) sizes
equality_matrix_row = zeros(len(spaces) * len(disks))
self._init_objective_function_coefficient(len(spaces) * len(disks))
# Set first len(spaces) elements to 1
equality_matrix_row = shift(equality_matrix_row, len(spaces), val=1)
for _ in range(len(disks)):
self.equality_constraint_matrix.append(equality_matrix_row)
equality_matrix_row = shift(equality_matrix_row, len(spaces), val=0)
def _add_disk(self):
pass
def _add_space(self):
pass
def _add_objective_function_coefficient(self):
# By default the algorithm tries to minimize the solution
# we should invert sign, in order to make it as a maximization
# function, we want disks to be maximally allocated.
# Coefficient for space per disk is 1, because all spaces
# are equal and should not be adjusted.
self.objective_function_coefficients.append(-1)
def _init_objective_function_coefficient(self, size):
self.objective_function_coefficients = [-1] * size
def _add_bound(self, min_, max_):
append(self.bounds.append, (min_, max_))

View File

@ -0,0 +1,101 @@
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See then
# License for the specific language governing permissions and limitations
# under the License.
import sys
from oslo_config import cfg
from oslo_log import log
from bareon_dynamic_allocator import utils
from bareon_dynamic_allocator.allocators import DynamicAllocator
cli_opts = [
cfg.StrOpt(
'schema',
required=True,
help='Input file, path to a file with dynamic partitioning schema'
),
cfg.StrOpt(
'file',
required=True,
help='Output path to a file with static partitioning schema'
),
cfg.StrOpt(
'hw-info',
required=True,
help='Hardware information'
)
]
def make_config():
conf = cfg.ConfigOpts()
conf.register_cli_opts(cli_opts)
log.register_options(conf)
return conf
def parse_args(conf, args=None):
project = 'bareon_dynamic_allocator'
version = '1.0.0'
conf(args=args if args else sys.argv[1:],
project=project,
version=version)
log.setup(conf,
project,
version=version)
CONF = make_config()
parse_args(CONF)
LOG = log.getLogger()
def parse_configs(conf):
hw_info = utils.parse_yaml(conf.hw_info)
schema = utils.parse_yaml(conf.schema)
return (hw_info, schema)
def save_result(data, output_file):
print data
def validate_schema(schema):
# TODO should be implemented
return schema
def validate_hw_info(hw_info):
# TODO should be implemented
return hw_info
def allocator():
LOG.debug('hi')
conf = parse_configs(CONF)
validate_schema(conf[0])
validate_hw_info(conf[1])
schema = DynamicAllocator(*conf).generate_static()
save_result(schema, CONF.file)
if __name__ == '__main__':
allocator()

View File

@ -0,0 +1,23 @@
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See then
# License for the specific language governing permissions and limitations
# under the License.
import yaml
def parse_yaml(path):
"""Parses yaml file
:param str path: path to the file
:returns: dict or list
"""
return yaml.load(open(path))