Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@ The `-h` flag displays the help for the program execution. test
---
## Authors

* Wilhelm Carstens **@wolam**
- Wilhelm Carstens **@wolam**
- Sina Rezaei **@sina-04**
62 changes: 62 additions & 0 deletions least_cost_cell_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from abc import ABC

from approximation_method import ApproximationMethod
import numpy as np

class LeastCostCellMethod(ApproximationMethod, ABC):

def __init__(self, file):
# This will:
# - read supply/demand/costs from file
# - balance the problem (dummy rows/cols if needed)
# - create assign_table and transportation_table
super().__init__(file=file)

def solve(self) -> None:
"""
Builds an initial solution using the Least Cost method:
always pick the globally cheapest available cell.
"""
# Keep assigning while there are rows/columns left
while self.has_rows_and_columns_left():
self.choose_cost()

# Write initial solution & cost
self.writer.write_initial_solution(
self.assign_table,
demand=self.cost_table[self.demand_row],
supply=self.cost_table[:, self.supply_column],
)
self.writer.write_initial_cost(self.total_cost())

# Try to improve with the MODI/transportation algorithm
self.improve()

def choose_cost(self) -> None:
"""
Find the globally cheapest available (non-deleted) cost cell
and assign as much as possible to it.
"""
min_cost = np.inf
min_i, min_j = -1, -1

# Search over all still-available cells
for i in range(self.demand_row): # skip the demand row
if i in self.deleted_rows:
continue
for j in range(self.supply_column): # skip the supply column
if j in self.deleted_cols:
continue

c = self.cost_table[i][j]
if c < min_cost:
min_cost = c
min_i, min_j = i, j

# Safety check: should not happen if has_rows_and_columns_left() is True
if min_i == -1 or min_j == -1:
self.halt("No feasible cell found in LeastCostCellMethod.choose_cost()")

# Assign min(demand, supply) to that cell.
# best_value_at(i, j) returns (best, i, j) and also updates deleted rows/cols.
self.assign(*self.best_value_at(min_i, min_j))
91 changes: 91 additions & 0 deletions least_cost_column_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from abc import ABC

from approximation_method import ApproximationMethod
import numpy as np

class LeastCostColumnMethod(ApproximationMethod, ABC):

def __init__(self, file):
# Reads supply/demand/costs, balances, creates tables
super().__init__(file=file)
# Start from the first column (0-based, excluding the supply column)
self._current_col = 0

def solve(self) -> None:
"""
Builds an initial solution using the Least Cost Column method:
column 1, column 2, ..., last column, then back to column 1, and so on;
in each column choose the cheapest feasible cell (supply > 0, row not deleted).
"""
while self.has_rows_and_columns_left():
self.choose_cost()

# Write initial solution & cost
self.writer.write_initial_solution(
self.assign_table,
demand=self.cost_table[self.demand_row],
supply=self.cost_table[:, self.supply_column],
)
self.writer.write_initial_cost(self.total_cost())

# Try to improve with MODI / stepping-stone style improvement
self.improve()

def _next_active_column(self, start_index: int) -> int:
"""
Returns the next column index (>= 0 and < supply_column) that:
- is not deleted, and
- still has remaining demand.
Starting from start_index (inclusive) and wrapping around.
"""
n = self.supply_column # last column is supply, so usable cols are [0 .. supply_column-1]

for offset in range(n):
j = (start_index + offset) % n
if j in self.deleted_cols:
continue
# Remaining demand is in the last row of assign_table
if self.assign_table[self.demand_row][j] > 0:
return j

# No column can accept further demand
self.halt("No active columns left in LeastCostColumnMethod.")

def choose_cost(self) -> None:
"""
Pick the next column in round-robin order, then in that column choose the
cheapest feasible row (not deleted, remaining supply > 0), and assign as much as possible to that cell.
"""
# Find the column we will work on this iteration
col = self._next_active_column(self._current_col)

min_cost = np.inf
min_i = -1

# Search for the cheapest feasible row in this column
for i in range(self.demand_row): # exclude the demand row itself
if i in self.deleted_rows:
continue

# Remaining supply is stored in the last column of assign_table
if self.assign_table[i][self.supply_column] == 0:
continue

c = self.cost_table[i][col]
if c < min_cost:
min_cost = c
min_i = i

# If no row is feasible for this column, mark column as deleted and move on
if min_i == -1:
self.deleted_cols.add(col)
# Advance column pointer for next time and let the main loop call again
self._current_col = (col + 1) % self.supply_column
return

# Assign min(remaining supply, remaining demand) to that cell.
# best_value_at(min_i, col) returns (best, i, j) and updates deleted rows/cols
self.assign(*self.best_value_at(min_i, col))

# Move to the next column for the next iteration (round-robin)
self._current_col = (col + 1) % self.supply_column
94 changes: 94 additions & 0 deletions least_cost_row_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from abc import ABC

from approximation_method import ApproximationMethod
import numpy as np


class LeastCostRowMethod(ApproximationMethod, ABC):

def __init__(self, file):
# Reads supply/demand/costs, balances, creates tables
super().__init__(file=file)
# Start from the first row (0-based)
self._current_row = 0

def solve(self) -> None:
"""
Builds an initial solution using the Least Cost Row method:
row 1, row 2, ..., last row, then back to row 1, and so on;
in each row choose the cheapest feasible cell (demand > 0,
column not deleted).
"""
while self.has_rows_and_columns_left():
self.choose_cost()

# Write initial solution & cost
self.writer.write_initial_solution(
self.assign_table,
demand=self.cost_table[self.demand_row],
supply=self.cost_table[:, self.supply_column],
)
self.writer.write_initial_cost(self.total_cost())

# Try to improve with MODI / stepping-stone style improvement
self.improve()

def _next_active_row(self, start_index: int) -> int:
"""
Returns the next row index (>= 0 and < demand_row) that:
- is not deleted, and
- still has remaining supply.
Starting from start_index (inclusive) and wrapping around.
"""
n = self.demand_row # last row is demand row, so rows are [0 .. demand_row-1]

for offset in range(n):
i = (start_index + offset) % n
if i in self.deleted_rows:
continue
# Remaining supply is in the last column of assign_table
if self.assign_table[i][self.supply_column] > 0:
return i

# No row can provide further supply
self.halt("No active rows left in LeastCostRowMethod.")

def choose_cost(self) -> None:
"""
Pick the next row in round-robin order, then in that row choose the
cheapest feasible column (not deleted, remaining demand > 0), and
assign as much as possible to that cell.
"""
# Find the row we will work on this iteration
row = self._next_active_row(self._current_row)

min_cost = np.inf
min_j = -1

# Search for the cheapest feasible column in this row
for j in range(self.supply_column): # exclude the supply column itself
if j in self.deleted_cols:
continue

# Remaining demand is stored in the last row of assign_table
if self.assign_table[self.demand_row][j] == 0:
continue

c = self.cost_table[row][j]
if c < min_cost:
min_cost = c
min_j = j

# If no column is feasible for this row, mark row as deleted and move on
if min_j == -1:
self.deleted_rows.add(row)
# Advance row pointer for next time and let the main loop call again
self._current_row = (row + 1) % self.demand_row
return

# Assign min(remaining supply, remaining demand) to that cell.
# best_value_at(row, min_j) returns (best, i, j) and updates deleted rows/cols
self.assign(*self.best_value_at(row, min_j))

# Move to the next row for the next iteration (round-robin)
self._current_row = (row + 1) % self.demand_row
4 changes: 3 additions & 1 deletion method_type.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from enum import IntEnum


# values of the possible methods to get an initial solution
class MethodType(IntEnum):
NORTH_WEST_METHOD = 1
VOGEL_METHOD = 2
RUSSELL_METHOD = 3
LEAST_COST_CELL_METHOD = 4
LEAST_COST_ROW_METHOD = 5
LEAST_COST_COLUMN_METHOD = 6
23 changes: 12 additions & 11 deletions transporte.py → transport.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#!/usr/bin/python3

import argparse
import textwrap

from method_type import MethodType
from north_west import NorthWestMethod
from russell import RussellMethod
from vogel import VogelMethod
from least_cost_cell_method import LeastCostCellMethod
from least_cost_row_method import LeastCostRowMethod
from least_cost_column_method import LeastCostColumnMethod

# Argument handler
parser = argparse.ArgumentParser(
Expand All @@ -30,11 +31,11 @@
The file has the following structure, separated by commas.
Supply column, demand row, transportation costs.
For example if the problem comes with the following form:
D1 D2 D3 Supply
S1 8 6 10 2000
S2 10 4 9 2500
Demand 1500 2000 1000

D1 D2 D3 Supply
S1 8 6 10 2000
S2 10 4 9 2500
Demand 1500 2000 1000

The file must come as the next example:
2000,2500
Expand All @@ -48,20 +49,20 @@

parser.add_argument('file', metavar='file.txt', type=argparse.FileType("r"),
help='Text file with the transportation problem in the correct format')

args = parser.parse_args()


def main():
solving_methods = {
MethodType.NORTH_WEST_METHOD: NorthWestMethod,
MethodType.VOGEL_METHOD: VogelMethod,
MethodType.RUSSELL_METHOD: RussellMethod
MethodType.RUSSELL_METHOD: RussellMethod,
MethodType.LEAST_COST_CELL_METHOD: LeastCostCellMethod,
MethodType.LEAST_COST_ROW_METHOD: LeastCostRowMethod,
MethodType.LEAST_COST_COLUMN_METHOD: LeastCostColumnMethod
}
desired_method = MethodType(args.method)
solver = solving_methods.get(desired_method)(file=args.file)
solver.solve()


if __name__ == "__main__":
main()