Factorio-Paranoidal_mod/helmod/math/SolverMatrixSimplex.lua

322 lines
9.4 KiB
Lua

-------------------------------------------------------------------------------
---Description of the module.
---@class SolverMatrixSimplex : SolverMatrix
SolverMatrixSimplex = newclass(SolverMatrix, function(base, object)
SolverMatrix.init(base, object)
end)
-------------------------------------------------------------------------------
---Calcul pivot de gauss
---@param matrix Matrix
---@param xrow integer
---@param xcol integer
---@return Matrix
function SolverMatrixSimplex:pivot(matrix, xrow, xcol)
local rows = matrix.rows
local matrix_clone = self:clone(matrix)
matrix_clone.rows = {}
local rows_clone = matrix_clone.rows
local pivot_value = rows[xrow][xcol]
for irow, row in pairs(rows) do
local parameters = matrix_clone.parameters[xrow]
parameters.coefficient = parameters.coefficient / pivot_value
rows_clone[irow] = {}
for icol, column in pairs(matrix.columns) do
local cell_value = row[icol] or 0
if irow == xrow then
--Transformation de la ligne pivot : elle est divisee par l'element pivot
rows_clone[irow][icol] = cell_value / pivot_value
elseif icol == xcol then
--Transformation de la colonne pivot : toutes les cases sauf la case pivot deviennent zero.
rows_clone[irow][icol] = 0
else
local B = rows[irow][xcol] or 0
local D = rows[xrow][icol] or 0
local value = cell_value - (B * D) / pivot_value
if math.abs(value) < 1e-8 then
rows_clone[irow][icol] = 0
else
rows_clone[irow][icol] = value
end
end
end
end
-- swap base
matrix_clone.headers[xrow] = matrix.columns[xcol]
matrix_clone.columns[xcol] = matrix.headers[xrow]
return matrix_clone
end
-------------------------------------------------------------------------------
---Retourne le pivot
---@param matrix Matrix
---@return boolean, integer, integer
function SolverMatrixSimplex:get_pivot(matrix)
local rows = matrix.rows
local zrow = matrix.rows[#matrix.rows]
local max_z_value = 0
local xcol = nil
local ratio_value = 0
local max_value = 0
local xrow = nil
-- boucle sur la derniere ligne nommee Z
for icol, column in pairs(matrix.columns) do
-- exclusion de la colonne coefficient
if icol > 1 then
local z_value = zrow[icol] or 0
if z_value > max_z_value then
-- la valeur repond au critere, la colonne est eligible
-- on recherche le ligne
ratio_value = nil
for irow, current_row in pairs(rows) do
local parameters = matrix.parameters[irow]
local x_value = rows[irow][icol]
-- on n'utilise pas la derniere ligne
-- seule les cases positives sont prises en compte
if irow < #rows and x_value > 0 then
-- calcul du ratio base / x
local c_value = parameters.coefficient
local bx_ratio = c_value / x_value
-- prend la premier valeur ou le plus grand ratio sinon la plus grande valeur
if ratio_value == nil or bx_ratio > ratio_value or c_value > max_value then
ratio_value = bx_ratio
max_value = c_value
xrow = irow
end
end
end
if ratio_value ~= nil then
-- le pivot est possible
max_z_value = z_value
xcol = icol
end
end
end
end
if max_z_value == 0 then
-- il n'y a plus d'amelioration possible fin du programmme
return false, xcol, xrow
end
return true, xcol, xrow
end
-------------------------------------------------------------------------------
---Prepare la matrice
---@param matrix Matrix
---@return Matrix
function SolverMatrixSimplex:prepare(matrix)
---ajoute la ligne Z
local irow = 1
---prepare les headers
local matrix_clone = self:clone(matrix)
local rows = matrix_clone.rows
local headers = matrix_clone.headers
local columns = matrix_clone.columns
local parameters = matrix_clone.parameters
---ajoute les recettes d'ingredient
---initialise l'analyse
local ckeck_cols = {}
for icol, column in pairs(columns) do
ckeck_cols[icol] = true
end
for irow, row in pairs(rows) do
if irow <= #rows then
for icol, column in pairs(columns) do
local cell_value = row[icol] or 0
---si une colonne est un produit au moins une fois on l'exclus
if cell_value > 0 then
ckeck_cols[icol] = false
end
end
end
end
---ajout des faux recipe
local index = 1
for xcol, check in pairs(ckeck_cols) do
if check then
-- Add new header
local new_header = table.deepcopy(columns[xcol])
table.insert(headers, new_header)
-- Add coefficient value
local rowParameters = MatrixRowParameters()
rowParameters.base = new_header
rowParameters.contraint = nil
rowParameters.factory_count = 0
rowParameters.factory_speed = 0
rowParameters.recipe_count = 0
rowParameters.recipe_production = 1
rowParameters.recipe_energy = 1
rowParameters.coefficient = 1e4 * index --important ne pas changer
--rowParameters.coefficient = math.pow(10,index)*10
table.insert(parameters, rowParameters)
local new_row = {}
for icol, column in pairs(columns) do
if icol == xcol then
table.insert(new_row, 1)
else
table.insert(new_row, 0)
end
end
table.insert(rows, new_row)
index = index + 1
end
end
self:prepare_z_and_objectives(matrix_clone, true)
-- ajout colonne coefficient
local new_column = {type="none", name="C"}
table.insert(columns, 1, new_column)
for irow, row in pairs(rows) do
local parameters = matrix_clone.parameters[irow]
local C = 0
if parameters ~= nil and parameters.coefficient ~= nil then
C = parameters.coefficient or 0
end
table.insert(row, 1, C)
end
---ajoute les row en colonne
local num_row = rawlen(matrix.rows)
local num_col = rawlen(matrix_clone.columns)
for icol = 1, num_row do
-- Add new column
local new_column = table.deepcopy(matrix_clone.headers[icol])
table.insert(columns, new_column)
for irow, row in pairs(rows) do
---ajoute les valeurs
if irow == icol then
rows[irow][num_col + icol] = 1
else
rows[irow][num_col + icol] = 0
end
end
end
return matrix_clone
end
-------------------------------------------------------------------------------
---Calcul de la ligne
---@param matrix Matrix
---@param xrow integer
---@return Matrix
function SolverMatrixSimplex:line_compute(matrix, xrow)
if matrix == nil or xrow == 0 then return matrix end
local row = matrix.rows[xrow]
local parameters = matrix.parameters[xrow]
local zrow = matrix.rows[#matrix.rows]
local R = parameters.recipe_count
local E = parameters.recipe_energy
for icol, column in pairs(matrix.columns) do
local cell_value = row[icol] or 0
if cell_value ~= 0 then
local Z = zrow[icol] ---valeur demandee Z
local X = cell_value
local C = -Z / X
if C > 0 and C > parameters.coefficient then
parameters.coefficient = C
parameters.recipe_production = R * E / C
end
end
end
local P = parameters.recipe_production
local C = parameters.coefficient
for icol, column in pairs(matrix.columns) do
local cell_value = row[icol] or 0
if cell_value ~= 0 then
local Z = zrow[icol] ---valeur demandee Z
local X = cell_value
---calcul du Z
zrow[icol] = Z + X * P * C
end
end
return matrix
end
-------------------------------------------------------------------------------
---Calcul du tableau
---@param matrix Matrix --matrix finale
---@param matrix_result Matrix --matrix intermediaire
---@return Matrix
function SolverMatrixSimplex:table_compute(matrix, matrix_result)
if matrix == nil then return matrix end
local zrow = matrix_result.rows[#matrix_result.rows]
---preparation input
self:prepare_z_and_objectives(matrix, false)
---preparation du resultat
for irow, _ in pairs(matrix.rows) do
if irow < #matrix.rows then
---colonne correspondant a la recette
local icol = #matrix_result.columns - #matrix.headers + irow
local parameters = matrix.parameters[irow]
parameters.recipe_count = -zrow[icol] ---moins la valeur affichee dans Z
parameters.recipe_production = 0
end
end
---initialise les valeurs des produits par second
for irow, row in pairs(matrix.rows) do
if irow < #matrix.rows then
local parameters = matrix.parameters[irow]
local E = parameters.recipe_energy
for icol, column in pairs(matrix.columns) do
local cell_value = row[icol] or 0
row[icol] = cell_value / E
end
end
end
---calcul du resultat
for irow, _ in pairs(matrix.rows) do
if irow < #matrix.rows then
matrix = self:line_compute(matrix, irow)
end
end
return matrix
end
-------------------------------------------------------------------------------
---Resoud la matrice
---@param Mbase table
---@param debug boolean
---@param by_factory boolean
---@param time number
---@return Matrix, {[integer] : Matrix}
function SolverMatrixSimplex:solve_matrix(Mbase, debug, by_factory, time)
if Mbase ~= nil then
local num_loop = 0
local runtime = {}
self:add_runtime(debug, runtime, "Initial", Mbase)
local Mstep = self:prepare(Mbase)
self:add_runtime(debug, runtime, "Prepare", Mstep)
local loop, xcol, xrow
loop = true
while loop do
loop, xcol, xrow = self:get_pivot(Mstep)
if loop then
self:add_runtime(debug, runtime, "Step " .. num_loop, Mstep, { x = xcol, y = xrow })
Mstep = self:pivot(Mstep, xrow, xcol)
else
self:add_runtime(debug, runtime, "Last", Mstep)
end
num_loop = num_loop + 1
end
---finalisation
local matrix_result = self:clone(Mbase)
matrix_result = self:table_compute(matrix_result, Mstep)
matrix_result = self:finalize(matrix_result)
matrix_result = self:apply_state(matrix_result)
self:add_runtime(debug, runtime, "final", matrix_result)
return matrix_result, runtime
end
end