Skip to content

Commit

Permalink
fix bruteforce not checking every stock sorting, use sorted set for b…
Browse files Browse the repository at this point in the history
…est results to skip duplicates, sort results by biggest trimmings; update and fix tests, add test for #68; update v1.1.1
  • Loading branch information
ModischFabrications committed May 6, 2024
1 parent 6ecbe7e commit cf3b94d
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 20 deletions.
2 changes: 1 addition & 1 deletion app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from pydantic_settings import BaseSettings

# constant; used for git tags
version = "v1.1.0"
version = "v1.1.1"


class SolverSettings(BaseSettings):
Expand Down
11 changes: 6 additions & 5 deletions app/solver/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ def solve(job: Job) -> Result:
# slowest, but perfect solver; originally O(n!), now much faster (see Job.n_combinations())
def _solve_bruteforce(job: Job) -> tuple[ResultEntry, ...]:
minimal_trimmings = float('inf')
best_results = []
best_results: set[tuple[ResultEntry, ...]] = set()

required_orderings = distinct_permutations(job.iterate_required())
required_orderings = list(distinct_permutations(job.iterate_required()))
for stock_ordering in distinct_permutations(job.iterate_stocks()):
for required_ordering in required_orderings:
result = _group_into_lengths(stock_ordering, required_ordering, job.cut_width)
Expand All @@ -45,12 +45,13 @@ def _solve_bruteforce(job: Job) -> tuple[ResultEntry, ...]:
trimmings = sum(lt.trimming for lt in result)
if trimmings < minimal_trimmings:
minimal_trimmings = trimmings
best_results = [result]
best_results = set()
best_results.add(sort_entries(result))
elif trimmings == minimal_trimmings:
best_results.append(result)
best_results.add(sort_entries(result))

assert best_results, "No valid solution found"
return sort_entries(find_best_solution(best_results))
return find_best_solution(best_results)


def _group_into_lengths(stocks: tuple[NS, ...], sizes: tuple[NS, ...], cut_width: int) \
Expand Down
7 changes: 3 additions & 4 deletions app/solver/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@ def calc_trimming(stock_length: int, lengths: Collection[NS], cut_width: int) ->
return trimmings


def find_best_solution(solutions: Sequence):
def find_best_solution(solutions: set[tuple[ResultEntry, ...]]):
if len(solutions) <= 0:
raise ValueError("no solution to search")

# TODO evaluate which one aligns with user expectations best (see #68)
# always sort for determinism!
return sorted(solutions, reverse=True)[0]
return sorted(solutions, key=lambda x: max(x), reverse=True)[0]


def create_result_entry(stock: NS, cuts: list[NS], cut_width: int) -> ResultEntry:
Expand All @@ -37,7 +36,7 @@ def create_result_entry(stock: NS, cuts: list[NS], cut_width: int) -> ResultEntr
)


def sort_entries(result_entries: list[ResultEntry]) -> tuple[ResultEntry, ...]:
def sort_entries(result_entries: Sequence[ResultEntry]) -> tuple[ResultEntry, ...]:
if len(result_entries) <= 0:
raise ValueError("no entries to sort")
return tuple(sorted(result_entries))
14 changes: 7 additions & 7 deletions tests/res/out/testresult_s.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
]
},
"solver_type": "bruteforce",
"time_us": 1868,
"time_us": 1683,
"layout": [
{
"stock": {
Expand All @@ -34,8 +34,8 @@
"name": "Part1"
},
{
"length": 200,
"name": "Part2"
"length": 500,
"name": "Part1"
},
{
"length": 200,
Expand All @@ -46,7 +46,7 @@
"name": "Part2"
}
],
"trimming": 392
"trimming": 92
},
{
"stock": {
Expand All @@ -55,15 +55,15 @@
},
"cuts": [
{
"length": 500,
"name": "Part1"
"length": 200,
"name": "Part2"
},
{
"length": 200,
"name": "Part2"
}
],
"trimming": 796
"trimming": 1096
}
]
}
9 changes: 6 additions & 3 deletions tests/solver/test_large.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def test_m(solver):

solved = solver(testjob_m)

assert sum(lt.trimming for lt in solved) == 855

# I don't care about ordering here
assert sorted([r.cuts for r in solved]) == sorted([
(NS(length=500), NS(length=300), NS(length=100)),
Expand All @@ -27,7 +29,7 @@ def test_m(solver):
# close to the max for bruteforce!
@pytest.mark.parametrize("solver", [_solve_bruteforce, _solve_FFD, _solve_gapfill])
def test_m_multi(solver):
testjob_m = Job(stocks=(INS(length=900), INS(length=500, quantity=2), INS(length=100, quantity=1)),
testjob_m = Job(stocks=(INS(length=900, quantity=3), INS(length=500, quantity=2), INS(length=100, quantity=1)),
cut_width=10,
required=(
QNS(length=500, quantity=4), QNS(length=300, quantity=3),
Expand All @@ -41,10 +43,11 @@ def test_m_multi(solver):
perfect_trimmings = 520
perfect_result = (
ResultEntry(stock=NS(length=500), cuts=(NS(length=500),), trimming=0),
ResultEntry(stock=NS(length=500), cuts=(NS(length=300),), trimming=190),
ResultEntry(stock=NS(length=500), cuts=(NS(length=300), NS(length=100)), trimming=80),
ResultEntry(stock=NS(length=900), cuts=(NS(length=500), NS(length=300)), trimming=80),
ResultEntry(stock=NS(length=900), cuts=(NS(length=500), NS(length=300)), trimming=80),
ResultEntry(stock=NS(length=900), cuts=(NS(length=500), NS(length=100), NS(length=100)), trimming=170))
ResultEntry(stock=NS(length=900), cuts=(NS(length=500), NS(length=100)), trimming=280)
)

if solver == _solve_bruteforce:
assert trimmings == perfect_trimmings
Expand Down
16 changes: 16 additions & 0 deletions tests/solver/test_special.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,19 @@ def test_close_stocks(solver):
ResultEntry(stock=NS(length=100), cuts=(NS(length=100),), trimming=0),
ResultEntry(stock=NS(length=100), cuts=(NS(length=100),), trimming=0),
ResultEntry(stock=NS(length=100), cuts=(NS(length=100),), trimming=0))


# @pytest.mark.xfail(reason="bug #68")
def test_solution_priorities():
testjob_equal = Job(stocks=(INS(length=2400, quantity=1), (INS(length=6000, quantity=1))), cut_width=20,
required=(QNS(length=1820, quantity=1), QNS(length=666, quantity=3)))
# other solvers have singular result, so no priorities
solved = _solve_bruteforce(testjob_equal)

# assert sum(lt.trimming for lt in solved) == 2102

assert solved == (
ResultEntry(stock=NS(length=6000),
cuts=(NS(length=1820), NS(length=666), NS(length=666), NS(length=666)),
trimming=2102),
)

0 comments on commit cf3b94d

Please sign in to comment.