walopy 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {walopy-0.4.1/src/walopy.egg-info → walopy-0.4.2}/PKG-INFO +1 -1
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/__init__.py +1 -1
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/inventory.py +13 -2
- {walopy-0.4.1 → walopy-0.4.2/src/walopy.egg-info}/PKG-INFO +1 -1
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy.egg-info/SOURCES.txt +2 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_complejidad.py +11 -4
- walopy-0.4.2/tests/test_propiedades_diferenciales.py +118 -0
- walopy-0.4.2/tests/test_wagner_whitin_ceros.py +61 -0
- {walopy-0.4.1 → walopy-0.4.2}/LICENSE +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/README.md +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/pyproject.toml +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/setup.cfg +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/__main__.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/_catalogo_en.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/_catalogo_es.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/_i18n.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/_utils.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/advanced.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/bottleneck.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/fitting.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/kpi.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/network.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/operations.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/plotting.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/project.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/py.typed +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/queuing.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/reliability.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/scheduling.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy/solver.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy.egg-info/dependency_links.txt +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy.egg-info/requires.txt +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/src/walopy.egg-info/top_level.txt +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_abc_xyz.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_advanced.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_bottleneck.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_cli.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_contrato_entradas.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_cpm_pert.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_documentacion.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_documentacion_ingles.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_ejemplos.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_exchange_curve.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_fitting.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_i18n.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_i18n_paridad.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_idioma.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_inventario_i18n.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_inventory.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_inventory_new.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_kpi.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_kpi_grafica.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_mg1.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_mmc_estable.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_mrp.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_neh_flowshop.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_network.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_new_models.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_notas_release.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_operations.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_plotting.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_politicas_inventario.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_queuing.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_readme.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_referencias_externas.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_regresion_validacion.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_reliability.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_safety_stock_curve.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_salida_identica.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_salida_por_idioma.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_scheduling.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_scorecard.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_solver.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_trazabilidad.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_version.py +0 -0
- {walopy-0.4.1 → walopy-0.4.2}/tests/test_weibull.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: walopy
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Herramientas de investigación de operaciones: colas, inventarios, scheduling, CPM/PERT, confiabilidad, OEE, cuellos de botella y árboles de KPI
|
|
5
5
|
Author-email: LeoSanta15 <angelsanta1@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -164,7 +164,7 @@ from .solver import (
|
|
|
164
164
|
solve_servers,
|
|
165
165
|
)
|
|
166
166
|
|
|
167
|
-
__version__ = "0.4.
|
|
167
|
+
__version__ = "0.4.2" # fuente única de la versión; pyproject.toml la lee con [tool.setuptools.dynamic]
|
|
168
168
|
|
|
169
169
|
__all__ = [
|
|
170
170
|
# idioma de los textos
|
|
@@ -1455,13 +1455,21 @@ def wagner_whitin(
|
|
|
1455
1455
|
n = len(demands_v)
|
|
1456
1456
|
|
|
1457
1457
|
INF = float("inf")
|
|
1458
|
-
dp = [INF] * (n + 1) # dp[j] =
|
|
1458
|
+
dp = [INF] * (n + 1) # dp[j] = costo mínimo de satisfacer las demandas 1..j
|
|
1459
1459
|
dp[0] = 0.0
|
|
1460
|
-
last = [-1] * (n + 1) # last[j] =
|
|
1460
|
+
last = [-1] * (n + 1) # last[j] = periodo del último pedido (0 = ninguno: demanda nula)
|
|
1461
1461
|
|
|
1462
1462
|
# Recursión hacia adelante: el costo de mantener se acumula de forma incremental
|
|
1463
1463
|
# (O(n²) en total; antes se recalculaba la suma completa para cada par (i, j): O(n³)).
|
|
1464
|
+
# Solo se pide en periodos con demanda positiva: pedir antes de que haga falta nunca
|
|
1465
|
+
# abarata el plan, y un periodo de demanda nula no justifica pagar la preparación.
|
|
1464
1466
|
for i in range(1, n + 1):
|
|
1467
|
+
if demands_v[i - 1] == 0:
|
|
1468
|
+
# Sin demanda no hay pedido: se arrastra el costo del periodo anterior.
|
|
1469
|
+
if dp[i - 1] < dp[i]:
|
|
1470
|
+
dp[i] = dp[i - 1]
|
|
1471
|
+
last[i] = 0
|
|
1472
|
+
continue
|
|
1465
1473
|
acum = 0.0 # Σ_{k=i..j} (k - i) · d_k
|
|
1466
1474
|
for j in range(i, n + 1):
|
|
1467
1475
|
acum += (j - i) * demands_v[j - 1]
|
|
@@ -1476,6 +1484,9 @@ def wagner_whitin(
|
|
|
1476
1484
|
j = n
|
|
1477
1485
|
while j > 0:
|
|
1478
1486
|
i = last[j]
|
|
1487
|
+
if i == 0: # periodo sin demanda ni pedido
|
|
1488
|
+
j -= 1
|
|
1489
|
+
continue
|
|
1479
1490
|
qty = sum(demands_v[k - 1] for k in range(i, j + 1))
|
|
1480
1491
|
orders.append({
|
|
1481
1492
|
"period": i,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: walopy
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Herramientas de investigación de operaciones: colas, inventarios, scheduling, CPM/PERT, confiabilidad, OEE, cuellos de botella y árboles de KPI
|
|
5
5
|
Author-email: LeoSanta15 <angelsanta1@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -56,6 +56,7 @@ tests/test_notas_release.py
|
|
|
56
56
|
tests/test_operations.py
|
|
57
57
|
tests/test_plotting.py
|
|
58
58
|
tests/test_politicas_inventario.py
|
|
59
|
+
tests/test_propiedades_diferenciales.py
|
|
59
60
|
tests/test_queuing.py
|
|
60
61
|
tests/test_readme.py
|
|
61
62
|
tests/test_referencias_externas.py
|
|
@@ -69,4 +70,5 @@ tests/test_scorecard.py
|
|
|
69
70
|
tests/test_solver.py
|
|
70
71
|
tests/test_trazabilidad.py
|
|
71
72
|
tests/test_version.py
|
|
73
|
+
tests/test_wagner_whitin_ceros.py
|
|
72
74
|
tests/test_weibull.py
|
|
@@ -77,10 +77,18 @@ def test_neh_n200_m10_es_rapido():
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
def _ww_fuerza_bruta(d, S, h):
|
|
80
|
+
"""Óptimo por enumeración, independiente de la programación dinámica.
|
|
81
|
+
|
|
82
|
+
Un pedido solo se coloca en un periodo con demanda positiva (no hay nada que cubrir en uno nulo) y el primero
|
|
83
|
+
de ellos debe tener pedido. Un horizonte sin demanda cuesta 0.
|
|
84
|
+
"""
|
|
85
|
+
positivos = [t for t, x in enumerate(d) if x > 0]
|
|
86
|
+
if not positivos:
|
|
87
|
+
return 0.0
|
|
80
88
|
n = len(d)
|
|
81
89
|
mejor = math.inf
|
|
82
|
-
for mascara in range(1 << (
|
|
83
|
-
inicios = [0] + [i + 1 for i in range(
|
|
90
|
+
for mascara in range(1 << (len(positivos) - 1)):
|
|
91
|
+
inicios = [positivos[0]] + [positivos[i + 1] for i in range(len(positivos) - 1) if mascara >> i & 1]
|
|
84
92
|
costo = 0.0
|
|
85
93
|
for si, a in enumerate(inicios):
|
|
86
94
|
fin = inicios[si + 1] if si + 1 < len(inicios) else n
|
|
@@ -93,8 +101,7 @@ def _ww_fuerza_bruta(d, S, h):
|
|
|
93
101
|
def test_wagner_whitin_coincide_con_fuerza_bruta(semilla):
|
|
94
102
|
r = random.Random(semilla)
|
|
95
103
|
n = r.randint(2, 9)
|
|
96
|
-
d = [r.
|
|
97
|
-
d[0] = d[0] or 1
|
|
104
|
+
d = [r.choice([0, 0, r.randint(1, 40)]) for _ in range(n)] # ceros en cualquier posición (W-27)
|
|
98
105
|
S, h = r.randint(10, 100), r.choice([0.5, 1, 2])
|
|
99
106
|
assert wl.wagner_whitin(d, S, h).total_cost == pytest.approx(_ww_fuerza_bruta(d, S, h), abs=1e-9)
|
|
100
107
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Relaciones que deben cumplir funciones distintas del mismo modelo, sobre escenarios aleatorios con semilla fija.
|
|
2
|
+
|
|
3
|
+
Complementan a los tests de valores concretos: detectan errores que un caso a mano no cubre (como W-27). Carga moderada
|
|
4
|
+
(``N`` escenarios por familia); la semilla fija hace que un fallo sea reproducible.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
import random
|
|
10
|
+
|
|
11
|
+
import walopy as wl
|
|
12
|
+
|
|
13
|
+
N = 300
|
|
14
|
+
SEMILLA = 20261007
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _cerca(a: float, b: float, rel: float = 1e-7) -> bool:
|
|
18
|
+
return math.isclose(a, b, rel_tol=rel, abs_tol=1e-12)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _costo_eoq(D: float, K: float, h: float, q: float) -> float:
|
|
22
|
+
return D / q * K + h * q / 2
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _escenarios(familia: str):
|
|
26
|
+
r = random.Random(f"{SEMILLA}-{familia}")
|
|
27
|
+
return [random.Random(r.random()) for _ in range(N)]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _cola(r):
|
|
31
|
+
mu = r.uniform(0.2, 50)
|
|
32
|
+
lam = r.uniform(0.01, 0.97) * mu
|
|
33
|
+
return lam, mu, wl.mm1(lam, mu)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_mg1_con_cs2_igual_a_1_es_mm1():
|
|
37
|
+
for r in _escenarios("mg1"):
|
|
38
|
+
lam, mu, m = _cola(r)
|
|
39
|
+
g = wl.mg1(lam, mu, 1.0)
|
|
40
|
+
assert _cerca(g.L, m.L) and _cerca(g.Wq, m.Wq), (lam, mu)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_kingman_con_cv_1_coincide_con_mm1():
|
|
44
|
+
for r in _escenarios("kingman"):
|
|
45
|
+
lam, mu, m = _cola(r)
|
|
46
|
+
assert _cerca(wl.kingman(lam, mu, 1.0, 1.0).Wq, m.Wq), (lam, mu)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_md1_tiene_la_mitad_de_cola_que_mm1():
|
|
50
|
+
for r in _escenarios("md1"):
|
|
51
|
+
lam, mu, m = _cola(r)
|
|
52
|
+
assert _cerca(wl.md1(lam, mu).Lq, m.Lq / 2), (lam, mu)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_mmc_con_un_servidor_es_mm1():
|
|
56
|
+
for r in _escenarios("mmc1"):
|
|
57
|
+
lam, mu, m = _cola(r)
|
|
58
|
+
c1 = wl.mmc(lam, mu, 1)
|
|
59
|
+
assert _cerca(c1.Wq, m.Wq) and _cerca(c1.L, m.L), (lam, mu)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_erlang_b_decrece_al_añadir_servidores():
|
|
63
|
+
for r in _escenarios("erlang"):
|
|
64
|
+
c, a = r.randint(1, 40), r.uniform(0.1, 60)
|
|
65
|
+
b0, b1 = wl.erlang_b(a, 1.0, c), wl.erlang_b(a, 1.0, c + 1)
|
|
66
|
+
assert 0 <= b1 <= b0 + 1e-15 and b0 <= 1, (a, c, b0, b1)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_mmc_la_espera_decrece_al_añadir_un_servidor():
|
|
70
|
+
for r in _escenarios("mmc_c"):
|
|
71
|
+
c, mu = r.randint(1, 15), r.uniform(1, 20)
|
|
72
|
+
lam = r.uniform(0.05, 0.95) * c * mu
|
|
73
|
+
w1, w2 = wl.mmc(lam, mu, c).Wq, wl.mmc(lam, mu, c + 1).Wq
|
|
74
|
+
assert w2 <= w1 + 1e-12, (lam, mu, c, w1, w2)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_lotes_wagner_whitin_no_supera_a_las_heuristicas():
|
|
78
|
+
for r in _escenarios("lotes"):
|
|
79
|
+
d = [r.choice([0, 0, r.uniform(1, 200)]) for _ in range(r.randint(2, 12))]
|
|
80
|
+
if sum(d) == 0:
|
|
81
|
+
continue
|
|
82
|
+
S, h = r.uniform(1, 500), r.uniform(0.05, 5)
|
|
83
|
+
ww = wl.wagner_whitin(d, S, h).total_cost
|
|
84
|
+
assert ww <= wl.silver_meal(d, S, h).total_cost + 1e-6, (d, S, h)
|
|
85
|
+
assert ww <= wl.lot_for_lot(d, S, h).total_cost + 1e-6, (d, S, h)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_fiabilidad_serie_y_paralelo_acotan_a_los_componentes():
|
|
89
|
+
for r in _escenarios("fiabilidad"):
|
|
90
|
+
n = r.randint(2, 6)
|
|
91
|
+
lams = [r.uniform(1e-3, 0.2) for _ in range(n)]
|
|
92
|
+
t = r.uniform(0.5, 100)
|
|
93
|
+
Ri = [math.exp(-x * t) for x in lams]
|
|
94
|
+
assert wl.series_system(lams, t=t).R_t <= min(Ri) + 1e-12, (lams, t)
|
|
95
|
+
assert wl.parallel_system(lams, t=t).R_t >= max(Ri) - 1e-12, (lams, t)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_k_de_n_esta_entre_serie_y_paralelo():
|
|
99
|
+
for r in _escenarios("koon"):
|
|
100
|
+
n = r.randint(2, 6)
|
|
101
|
+
k, lam, t = r.randint(1, n), r.uniform(1e-3, 0.2), r.uniform(0.5, 100)
|
|
102
|
+
rk = wl.koon_system(n=n, k=k, failure_rate=lam, t=t).R_t
|
|
103
|
+
assert math.exp(-lam * t) ** n - 1e-12 <= rk <= 1 - (1 - math.exp(-lam * t)) ** n + 1e-12, (n, k, lam, t)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_eoq_es_el_minimo_del_costo_y_coincide_con_la_formula():
|
|
107
|
+
for r in _escenarios("eoq"):
|
|
108
|
+
D, K, h = r.uniform(1, 5000), r.uniform(1, 500), r.uniform(0.01, 10)
|
|
109
|
+
e = wl.eoq(D, K, h)
|
|
110
|
+
|
|
111
|
+
assert _cerca(e.total_cost, _costo_eoq(D, K, h, e.eoq), 1e-9), (D, K, h)
|
|
112
|
+
assert all(e.total_cost <= _costo_eoq(D, K, h, e.eoq * f) + 1e-9 for f in (0.5, 0.9, 1.1, 2)), (D, K, h)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_eoq_decrece_con_el_costo_de_mantener():
|
|
116
|
+
for r in _escenarios("eoq_h"):
|
|
117
|
+
D, K, h = r.uniform(1, 5000), r.uniform(1, 500), r.uniform(0.01, 10)
|
|
118
|
+
assert wl.eoq(D, K, h * 1.5).eoq < wl.eoq(D, K, h).eoq, (D, K, h)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""W-27: ``wagner_whitin`` cobraba preparación en periodos sin demanda y obligaba a pedir en el periodo 1."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
import random
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
import walopy as wl
|
|
10
|
+
from tests.test_complejidad import _ww_fuerza_bruta
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _pedidos(r):
|
|
14
|
+
return [(o["period"], o["order_qty"]) for o in r.orders]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_ceros_iniciales_no_obligan_a_pedir_en_el_periodo_1():
|
|
18
|
+
r = wl.wagner_whitin([0, 0, 100, 0, 0], setup_cost=100, holding_cost=1)
|
|
19
|
+
assert r.total_cost == pytest.approx(100.0) # antes 200: pedido vacío en el periodo 1 + el real
|
|
20
|
+
assert _pedidos(r) == [(3, 100.0)]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_ceros_intermedios_no_pagan_preparacion():
|
|
24
|
+
r = wl.wagner_whitin([0, 50, 0, 100], setup_cost=100, holding_cost=1)
|
|
25
|
+
assert r.total_cost == pytest.approx(200.0) # antes 250
|
|
26
|
+
assert _pedidos(r) == [(2, 50.0), (4, 100.0)]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_horizonte_sin_demanda_cuesta_cero_y_no_pide():
|
|
30
|
+
r = wl.wagner_whitin([0, 0, 0], setup_cost=100, holding_cost=1)
|
|
31
|
+
assert r.total_cost == 0.0
|
|
32
|
+
assert r.orders == [] and r.n_orders == 0
|
|
33
|
+
assert r.total_setup_cost == 0.0 and r.total_holding_cost == 0.0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_ningun_pedido_es_de_cantidad_cero():
|
|
37
|
+
r = random.Random(27)
|
|
38
|
+
for _ in range(200):
|
|
39
|
+
d = [r.choice([0, 0, r.randint(1, 40)]) for _ in range(r.randint(1, 12))]
|
|
40
|
+
res = wl.wagner_whitin(d, r.randint(5, 80), r.choice([0.5, 1, 2]))
|
|
41
|
+
assert all(o["order_qty"] > 0 for o in res.orders)
|
|
42
|
+
assert sum(o["order_qty"] for o in res.orders) == pytest.approx(sum(d))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.parametrize("semilla", range(150))
|
|
46
|
+
def test_costo_optimo_exacto_con_ceros_en_cualquier_posicion(semilla):
|
|
47
|
+
r = random.Random(7000 + semilla)
|
|
48
|
+
d = [r.choice([0, 0, r.randint(1, 60)]) for _ in range(r.randint(1, 10))]
|
|
49
|
+
S, h = r.uniform(1, 200), r.uniform(0.05, 3)
|
|
50
|
+
res = wl.wagner_whitin(d, S, h)
|
|
51
|
+
assert res.total_cost == pytest.approx(_ww_fuerza_bruta(d, S, h), rel=1e-9, abs=1e-9)
|
|
52
|
+
assert res.total_cost == pytest.approx(res.total_setup_cost + res.total_holding_cost)
|
|
53
|
+
assert res.total_setup_cost == pytest.approx(res.n_orders * S)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_con_demanda_positiva_el_resultado_no_cambia():
|
|
57
|
+
# Caso de referencia del docstring: sin ceros el plan es el de siempre.
|
|
58
|
+
r = wl.wagner_whitin([100, 80, 120, 60], setup_cost=200, holding_cost=1)
|
|
59
|
+
assert r.total_cost == pytest.approx(_ww_fuerza_bruta([100, 80, 120, 60], 200, 1))
|
|
60
|
+
assert r.orders[0]["period"] == 1
|
|
61
|
+
assert math.isfinite(r.total_cost)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|