walopy 0.2.8__tar.gz → 0.3.0__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.2.8 → walopy-0.3.0}/PKG-INFO +637 -463
- walopy-0.2.8/src/walopy.egg-info/PKG-INFO → walopy-0.3.0/README.md +625 -495
- walopy-0.3.0/pyproject.toml +79 -0
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/__init__.py +111 -113
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/__main__.py +17 -17
- walopy-0.3.0/src/walopy/_utils.py +97 -0
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/advanced.py +387 -212
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/bottleneck.py +50 -32
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/fitting.py +54 -41
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/inventory.py +862 -560
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/kpi.py +111 -62
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/network.py +46 -43
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/operations.py +111 -68
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/plotting.py +124 -119
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/project.py +125 -80
- walopy-0.3.0/src/walopy/py.typed +0 -0
- walopy-0.3.0/src/walopy/queuing.py +628 -0
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/reliability.py +182 -91
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/scheduling.py +192 -103
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy/solver.py +193 -96
- walopy-0.2.8/README.md → walopy-0.3.0/src/walopy.egg-info/PKG-INFO +669 -458
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy.egg-info/SOURCES.txt +17 -1
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy.egg-info/requires.txt +8 -2
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_abc_xyz.py +5 -4
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_advanced.py +10 -13
- walopy-0.3.0/tests/test_cli.py +67 -0
- walopy-0.3.0/tests/test_complejidad.py +107 -0
- walopy-0.3.0/tests/test_contrato_entradas.py +222 -0
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_cpm_pert.py +22 -2
- walopy-0.3.0/tests/test_documentacion.py +31 -0
- walopy-0.3.0/tests/test_ejemplos.py +26 -0
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_exchange_curve.py +5 -4
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_fitting.py +2 -2
- walopy-0.3.0/tests/test_idioma.py +111 -0
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_inventory.py +2 -4
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_inventory_new.py +9 -12
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_kpi.py +9 -11
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_mg1.py +2 -4
- walopy-0.3.0/tests/test_mmc_estable.py +58 -0
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_mrp.py +7 -2
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_neh_flowshop.py +5 -3
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_network.py +2 -2
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_new_models.py +9 -9
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_operations.py +1 -1
- walopy-0.3.0/tests/test_plotting.py +84 -0
- walopy-0.3.0/tests/test_politicas_inventario.py +104 -0
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_queuing.py +2 -4
- walopy-0.3.0/tests/test_readme.py +42 -0
- walopy-0.3.0/tests/test_referencias_externas.py +57 -0
- walopy-0.3.0/tests/test_regresion_validacion.py +305 -0
- walopy-0.3.0/tests/test_reliability.py +91 -0
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_safety_stock_curve.py +7 -5
- walopy-0.3.0/tests/test_scheduling.py +91 -0
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_solver.py +28 -4
- walopy-0.3.0/tests/test_trazabilidad.py +72 -0
- walopy-0.3.0/tests/test_version.py +33 -0
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_weibull.py +4 -1
- walopy-0.2.8/pyproject.toml +0 -52
- walopy-0.2.8/src/walopy/_utils.py +0 -67
- walopy-0.2.8/src/walopy/queuing.py +0 -448
- walopy-0.2.8/tests/test_v026.py +0 -276
- {walopy-0.2.8 → walopy-0.3.0}/LICENSE +0 -0
- {walopy-0.2.8 → walopy-0.3.0}/setup.cfg +0 -0
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy.egg-info/dependency_links.txt +0 -0
- {walopy-0.2.8 → walopy-0.3.0}/src/walopy.egg-info/top_level.txt +0 -0
- {walopy-0.2.8 → walopy-0.3.0}/tests/test_bottleneck.py +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: walopy
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
7
7
|
Project-URL: Homepage, https://github.com/LeoSanta15/walopy
|
|
8
8
|
Project-URL: Issues, https://github.com/LeoSanta15/walopy/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/LeoSanta15/walopy/blob/main/CHANGELOG.md
|
|
10
|
-
Keywords: queuing theory,
|
|
10
|
+
Keywords: operations research,queuing theory,little's law,kingman,inventory,EOQ,MRP,ABC analysis,scheduling,flow-shop,CPM,PERT,reliability,weibull,bottleneck,OEE,KPI
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -17,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
17
18
|
Classifier: Operating System :: OS Independent
|
|
18
19
|
Classifier: Intended Audience :: Science/Research
|
|
19
20
|
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Typing :: Typed
|
|
20
22
|
Requires-Python: >=3.9
|
|
21
23
|
Description-Content-Type: text/markdown
|
|
22
24
|
License-File: LICENSE
|
|
@@ -27,8 +29,13 @@ Requires-Dist: plotly>=5.0
|
|
|
27
29
|
Provides-Extra: dev
|
|
28
30
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
29
31
|
Requires-Dist: pytest-cov>=4; extra == "dev"
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
+
Requires-Dist: pytest-timeout>=2; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff~=0.16.10; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy<3,>=1.10; extra == "dev"
|
|
35
|
+
Provides-Extra: release
|
|
36
|
+
Requires-Dist: build; extra == "release"
|
|
37
|
+
Requires-Dist: twine; extra == "release"
|
|
38
|
+
Requires-Dist: pip-audit; extra == "release"
|
|
32
39
|
Provides-Extra: docs
|
|
33
40
|
Requires-Dist: sphinx>=7; extra == "docs"
|
|
34
41
|
Requires-Dist: sphinx-rtd-theme>=2; extra == "docs"
|
|
@@ -66,15 +73,16 @@ pip install walopy
|
|
|
66
73
|
12. [Curvas de intercambio](#12-curvas-de-intercambio)
|
|
67
74
|
13. [Programación de producción (scheduling)](#13-programación-de-producción-scheduling)
|
|
68
75
|
14. [Confiabilidad](#14-confiabilidad--reliability)
|
|
69
|
-
15. [
|
|
70
|
-
16. [
|
|
71
|
-
17. [
|
|
72
|
-
18. [
|
|
73
|
-
19. [
|
|
74
|
-
20. [
|
|
75
|
-
21. [
|
|
76
|
-
22. [
|
|
77
|
-
23. [
|
|
76
|
+
15. [Proyectos: CPM y PERT](#15-proyectos-cpm-y-pert--project)
|
|
77
|
+
16. [Árboles de KPI](#16-árboles-de-kpi)
|
|
78
|
+
17. [Solvers y optimización](#17-solvers-y-optimización)
|
|
79
|
+
18. [Análisis de sensibilidad](#18-análisis-de-sensibilidad)
|
|
80
|
+
19. [Escenarios en lote (batch_model)](#19-escenarios-en-lote-batch_model)
|
|
81
|
+
20. [Comparar múltiples resultados](#20-comparar-múltiples-resultados)
|
|
82
|
+
21. [Gráficas](#21-gráficas)
|
|
83
|
+
22. [CLI](#22-cli)
|
|
84
|
+
23. [Referencia de módulos](#23-referencia-de-módulos)
|
|
85
|
+
24. [Requisitos](#24-requisitos)
|
|
78
86
|
|
|
79
87
|
---
|
|
80
88
|
|
|
@@ -119,14 +127,14 @@ import walopy as wl
|
|
|
119
127
|
|
|
120
128
|
r = wl.mm1(lam=3.0, mu=5.0)
|
|
121
129
|
print(r)
|
|
122
|
-
#
|
|
123
|
-
# λ
|
|
124
|
-
# μ
|
|
125
|
-
# ρ
|
|
126
|
-
# L
|
|
127
|
-
# Lq
|
|
128
|
-
# W
|
|
129
|
-
# Wq
|
|
130
|
+
# Modelo : M/M/1
|
|
131
|
+
# λ : 3.0 (tasa de llegada)
|
|
132
|
+
# μ : 5.0 (tasa de servicio por servidor)
|
|
133
|
+
# ρ : 0.6 (utilización por servidor)
|
|
134
|
+
# L : 1.5 (unidades promedio en el sistema)
|
|
135
|
+
# Lq : 0.9 (unidades promedio en cola)
|
|
136
|
+
# W : 0.5 (tiempo promedio en el sistema)
|
|
137
|
+
# Wq : 0.3 (tiempo promedio de espera en cola)
|
|
130
138
|
|
|
131
139
|
df = r.to_frame() # exportar a DataFrame
|
|
132
140
|
r.plot() # gráfica Wq y Lq vs ρ
|
|
@@ -193,7 +201,9 @@ r = wl.kingman(lam=3.0, mu=5.0, ca2=0.5, cs2=1.0)
|
|
|
193
201
|
r = wl.kingman(lam=3.0, mu=5.0, ca2=1.0, cs2=3.0)
|
|
194
202
|
|
|
195
203
|
# Recuperar parámetros ajustados desde datos reales y usarlos directamente
|
|
196
|
-
|
|
204
|
+
import numpy as np
|
|
205
|
+
rng = np.random.default_rng(42)
|
|
206
|
+
fit = wl.fit_from_data(inter_arrivals=rng.exponential(0.2, 500), service_times=rng.exponential(0.1, 500))
|
|
197
207
|
r = wl.kingman(**fit.to_model_kwargs())
|
|
198
208
|
```
|
|
199
209
|
|
|
@@ -266,12 +276,12 @@ Cola con **capacidad finita** K (servidor + sala de espera). Los clientes que ll
|
|
|
266
276
|
|
|
267
277
|
**Parámetros adicionales en `result.params`:**
|
|
268
278
|
- `PK (blocking prob)` — probabilidad de rechazo (sistema lleno)
|
|
269
|
-
- `
|
|
279
|
+
- `λ_eff (effective rate)` — tasa de llegada efectiva = λ · (1 − P_K)
|
|
270
280
|
|
|
271
281
|
```python
|
|
272
282
|
r = wl.mm1k(lam=5.0, mu=3.0, K=10)
|
|
273
|
-
print(r.params["PK (
|
|
274
|
-
print(r.params["
|
|
283
|
+
print(r.params["PK (prob. de bloqueo)"]) # fracción de clientes rechazados
|
|
284
|
+
print(r.params["λ_eff (tasa efectiva)"]) # tasa real de entrada al sistema
|
|
275
285
|
```
|
|
276
286
|
|
|
277
287
|
---
|
|
@@ -282,7 +292,7 @@ Multi-servidor con capacidad finita. Generaliza tanto M/M/c (K→∞) como M/M/1
|
|
|
282
292
|
|
|
283
293
|
```python
|
|
284
294
|
r = wl.mmck(lam=8.0, mu=3.0, c=2, K=20)
|
|
285
|
-
print(r.params["PK (
|
|
295
|
+
print(r.params["PK (prob. de bloqueo)"])
|
|
286
296
|
|
|
287
297
|
# Verificación: con K muy grande se acerca a M/M/c
|
|
288
298
|
r_inf = wl.mmc(lam=3.0, mu=5.0, c=2)
|
|
@@ -455,8 +465,9 @@ print(fit_ia.mu) # None
|
|
|
455
465
|
ts = np.cumsum(rng.exponential(0.2, 1000)) # timestamps acumulados
|
|
456
466
|
fit = wl.fit_from_data(arrival_timestamps=ts)
|
|
457
467
|
|
|
458
|
-
# 4. Conectar directamente con un modelo
|
|
459
|
-
|
|
468
|
+
# 4. Conectar directamente con un modelo (necesita λ, μ, ca² y cs²: ajuste con ambas muestras)
|
|
469
|
+
fit_completo = wl.fit_from_data(ia, svc)
|
|
470
|
+
r = wl.kingman(**fit_completo.to_model_kwargs()) # usa lam, mu, ca2, cs2
|
|
460
471
|
```
|
|
461
472
|
|
|
462
473
|
**`FitResult` — atributos:**
|
|
@@ -570,12 +581,12 @@ r = wl.eoq(
|
|
|
570
581
|
holding_cost=2, # h: costo por unidad por período
|
|
571
582
|
)
|
|
572
583
|
print(r)
|
|
573
|
-
# EOQ (
|
|
574
|
-
#
|
|
575
|
-
#
|
|
576
|
-
#
|
|
577
|
-
#
|
|
578
|
-
#
|
|
584
|
+
# EOQ (cantidad de pedido): 223.6 unidades
|
|
585
|
+
# Frecuencia de pedidos : 4.472 pedidos/periodo
|
|
586
|
+
# Tiempo de ciclo : 0.2236 periodos
|
|
587
|
+
# Costo total : 447.2
|
|
588
|
+
# Costo de mantener : 223.6
|
|
589
|
+
# Costo de ordenar : 223.6 ← iguales en el óptimo
|
|
579
590
|
|
|
580
591
|
r.plot() # gráfica de curvas de costo
|
|
581
592
|
df = r.to_frame()
|
|
@@ -613,11 +624,11 @@ r = wl.reorder_point(
|
|
|
613
624
|
service_level=0.95, # nivel de servicio deseado
|
|
614
625
|
)
|
|
615
626
|
print(r)
|
|
616
|
-
#
|
|
617
|
-
#
|
|
618
|
-
#
|
|
619
|
-
# z
|
|
620
|
-
#
|
|
627
|
+
# Punto de reorden : 132.4 unidades
|
|
628
|
+
# Stock de seguridad : 32.4 unidades
|
|
629
|
+
# Nivel de servicio : 95.00%
|
|
630
|
+
# z : 1.645
|
|
631
|
+
# Demanda media en LT : 100.0
|
|
621
632
|
# Std demand LT : 19.72
|
|
622
633
|
|
|
623
634
|
# Sin variabilidad → solo demanda esperada durante LT
|
|
@@ -665,14 +676,14 @@ r = wl.newsvendor(
|
|
|
665
676
|
salvage=2, # valor residual de unidades no vendidas
|
|
666
677
|
)
|
|
667
678
|
print(r)
|
|
668
|
-
#
|
|
669
|
-
#
|
|
670
|
-
#
|
|
671
|
-
#
|
|
672
|
-
#
|
|
673
|
-
#
|
|
674
|
-
#
|
|
675
|
-
#
|
|
679
|
+
# Cantidad óptima : 100.0 unidades
|
|
680
|
+
# Razón crítica : 0.5
|
|
681
|
+
# Utilidad esperada : 320.0
|
|
682
|
+
# Ventas esperadas : 100.0
|
|
683
|
+
# Sobrante esperado : 0.0
|
|
684
|
+
# Faltante esperado : 0.0
|
|
685
|
+
# Costo de subestimar Cu: 4.0
|
|
686
|
+
# Costo de sobrestimar Co: 4.0
|
|
676
687
|
|
|
677
688
|
# Producto con alto margen → pedir por encima de la media
|
|
678
689
|
r_alto = wl.newsvendor(demand_mean=100, demand_std=20,
|
|
@@ -719,15 +730,15 @@ r = wl.ebq(
|
|
|
719
730
|
production_rate=4000, # P: tasa de producción (debe ser > D)
|
|
720
731
|
)
|
|
721
732
|
print(r)
|
|
722
|
-
# EBQ (
|
|
723
|
-
#
|
|
724
|
-
#
|
|
725
|
-
#
|
|
726
|
-
#
|
|
727
|
-
#
|
|
728
|
-
#
|
|
729
|
-
#
|
|
730
|
-
#
|
|
733
|
+
# EBQ (tamaño de lote): 258.2 unidades
|
|
734
|
+
# Inventario máximo : 193.6
|
|
735
|
+
# Inventario promedio : 96.82
|
|
736
|
+
# Frecuencia de lotes : 3.873 lotes/periodo
|
|
737
|
+
# Tiempo de ciclo : 0.2582 periodos
|
|
738
|
+
# Tiempo de producción: 0.06455 periodos
|
|
739
|
+
# Costo total : 193.6
|
|
740
|
+
# Costo de mantener : 96.82
|
|
741
|
+
# Costo de preparación: 96.82
|
|
731
742
|
```
|
|
732
743
|
|
|
733
744
|
**`EBQResult` — atributos:**
|
|
@@ -833,9 +844,9 @@ r = wl.lot_for_lot(demands, setup_cost=200, holding_cost=1)
|
|
|
833
844
|
print(r.total_holding_cost) # 0.0 — sin inventario residual
|
|
834
845
|
print(r.n_orders) # 4 — un pedido por período con demanda > 0
|
|
835
846
|
print(r)
|
|
836
|
-
#
|
|
837
|
-
#
|
|
838
|
-
#
|
|
847
|
+
# Método : Lote por lote
|
|
848
|
+
# Número de pedidos : 4
|
|
849
|
+
# Costo total : 800.0
|
|
839
850
|
```
|
|
840
851
|
|
|
841
852
|
---
|
|
@@ -898,11 +909,83 @@ df = r.to_frame() # todos los candidatos evaluados
|
|
|
898
909
|
|
|
899
910
|
---
|
|
900
911
|
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
### ABC, XYZ y ABC-XYZ — clasificación de artículos
|
|
915
|
+
|
|
916
|
+
`abc_analysis` clasifica por **valor anual** (Pareto): un artículo es A mientras el acumulado *previo* a él sea
|
|
917
|
+
menor que `a_threshold` (80 %), B mientras sea menor que `b_threshold` (95 %) y C en el resto. Los artículos
|
|
918
|
+
sin demanda quedan en C. `xyz_analysis` clasifica por **variabilidad** (coeficiente de variación): X si
|
|
919
|
+
CV ≤ `x_threshold` (0.5), Y hasta `y_threshold` (1.0) y Z por encima. `abc_xyz` combina ambas y devuelve la
|
|
920
|
+
matriz 3×3.
|
|
921
|
+
|
|
922
|
+
```python
|
|
923
|
+
items = [
|
|
924
|
+
{"name": "A", "demand": 500, "unit_value": 10.0, "demand_std": 120},
|
|
925
|
+
{"name": "B", "demand": 300, "unit_value": 10.0, "demand_std": 60},
|
|
926
|
+
{"name": "C", "demand": 100, "unit_value": 10.0, "demand_std": 150},
|
|
927
|
+
{"name": "D", "demand": 700, "unit_value": 1.0, "demand_std": 300},
|
|
928
|
+
{"name": "E", "demand": 300, "unit_value": 1.0, "demand_std": 10},
|
|
929
|
+
]
|
|
930
|
+
|
|
931
|
+
abc = wl.abc_analysis(items)
|
|
932
|
+
print(abc) # resumen por clase: A = 2 artículos (80 % del valor)
|
|
933
|
+
print(abc.to_frame()[["artículo", "valor_anual", "pct_acumulado", "clase"]])
|
|
934
|
+
|
|
935
|
+
# XYZ: cada artículo aporta 'cv' directamente o 'demand_std' + 'demand_mean' (o 'demand_rate')
|
|
936
|
+
xyz = wl.xyz_analysis([
|
|
937
|
+
{"name": i["name"], "demand_std": i["demand_std"], "demand_mean": i["demand"]} for i in items
|
|
938
|
+
])
|
|
939
|
+
print(xyz.to_frame()) # columnas: artículo, cv, clase
|
|
940
|
+
|
|
941
|
+
# ABC-XYZ: matriz de conteos (filas ABC, columnas XYZ)
|
|
942
|
+
combo = wl.abc_xyz([{**i, "cv": i["demand_std"] / i["demand"]} for i in items])
|
|
943
|
+
print(combo.matrix_frame())
|
|
944
|
+
print(combo.items[0]["combined_class"]) # 'AX'
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
| Parámetro | Descripción |
|
|
948
|
+
|---|---|
|
|
949
|
+
| `items` | Lista de diccionarios. ABC: `demand` (≥ 0) y `unit_value` (> 0). XYZ: `cv` o `demand_std` + `demand_mean`/`demand_rate` |
|
|
950
|
+
| `a_threshold`, `b_threshold` | Límites acumulados de las clases A y B (0 < a < b < 1) |
|
|
951
|
+
| `x_threshold`, `y_threshold` | Límites de CV de las clases X e Y |
|
|
952
|
+
|
|
953
|
+
Errores: lista vacía, elementos que no son diccionarios, claves faltantes y valores no finitos lanzan `ValueError`/`TypeError`
|
|
954
|
+
con el elemento y la clave afectados; si todas las demandas son 0 no hay nada que clasificar y se lanza `ValueError`.
|
|
955
|
+
|
|
956
|
+
---
|
|
957
|
+
|
|
958
|
+
### MRP de un nivel — `mrp`
|
|
959
|
+
|
|
960
|
+
Plan de requerimientos de materiales para un artículo: necesidades netas, órdenes planificadas (recepción y
|
|
961
|
+
liberación) y existencias proyectadas. Admite lote por lote (`"LFL"`) o lote fijo, tiempo de entrega,
|
|
962
|
+
stock de seguridad y recepciones programadas.
|
|
963
|
+
|
|
964
|
+
```python
|
|
965
|
+
r = wl.mrp(
|
|
966
|
+
gross_requirements=[0, 50, 0, 30, 60, 20],
|
|
967
|
+
initial_on_hand=40,
|
|
968
|
+
scheduled_receipts=[0, 0, 25, 0, 0, 0],
|
|
969
|
+
lead_time=1,
|
|
970
|
+
lot_size=40, # "LFL" para lote por lote
|
|
971
|
+
safety_stock=10,
|
|
972
|
+
item_name="Eje",
|
|
973
|
+
)
|
|
974
|
+
print(r) # tabla por periodo: GR, SR, OH, NR, PR, PO
|
|
975
|
+
print(r.to_frame()) # el mismo plan como DataFrame
|
|
976
|
+
print(r.past_due_releases) # 0.0
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
Si el `lead_time` es mayor que el periodo en que se necesita la orden, la liberación cae antes del periodo 1:
|
|
980
|
+
`mrp` emite un `UserWarning` y acumula esa cantidad en `past_due_releases` (no aparece en `planned_releases`).
|
|
981
|
+
|
|
982
|
+
---
|
|
983
|
+
|
|
901
984
|
## 8. Análisis de operaciones
|
|
902
985
|
|
|
903
986
|
### `oee(availability, performance, quality)` — OEE
|
|
904
987
|
|
|
905
|
-
Calcula la **Eficiencia Global de los Equipos** (OEE =
|
|
988
|
+
Calcula la **Eficiencia Global de los Equipos** (OEE = Disponibilidad × Rendimiento × Calidad).
|
|
906
989
|
|
|
907
990
|
```python
|
|
908
991
|
r = wl.oee(
|
|
@@ -917,21 +1000,21 @@ print(r)
|
|
|
917
1000
|
r.plot() # gráfica horizontal con referencia world-class (85%)
|
|
918
1001
|
```
|
|
919
1002
|
|
|
920
|
-
### `utilization_efficiency(actual_output,
|
|
1003
|
+
### `utilization_efficiency(actual_output, capacity)` — Utilización
|
|
921
1004
|
|
|
922
1005
|
```python
|
|
923
|
-
r = wl.utilization_efficiency(actual_output=750,
|
|
1006
|
+
r = wl.utilization_efficiency(actual_output=750, capacity=1000)
|
|
924
1007
|
print(r.utilization) # 0.75 — 75% de utilización
|
|
925
|
-
print(r.
|
|
1008
|
+
print(1 - r.utilization) # 0.25 — 25% de capacidad ociosa
|
|
926
1009
|
```
|
|
927
1010
|
|
|
928
|
-
### `unit_cost(fixed_cost,
|
|
1011
|
+
### `unit_cost(fixed_cost, variable_cost_per_unit, units_produced)` — Costo unitario
|
|
929
1012
|
|
|
930
1013
|
```python
|
|
931
|
-
r = wl.unit_cost(fixed_cost=10_000,
|
|
932
|
-
print(r.unit_cost)
|
|
933
|
-
print(r.fixed_cost_per_unit)
|
|
934
|
-
print(r.
|
|
1014
|
+
r = wl.unit_cost(fixed_cost=10_000, variable_cost_per_unit=10, units_produced=500)
|
|
1015
|
+
print(r.unit_cost) # 30.0 $/unidad
|
|
1016
|
+
print(r.fixed_cost_per_unit) # 20.0
|
|
1017
|
+
print(r.variable_cost_per_unit) # 10.0
|
|
935
1018
|
```
|
|
936
1019
|
|
|
937
1020
|
---
|
|
@@ -949,7 +1032,7 @@ r = wl.bottleneck_analysis(
|
|
|
949
1032
|
demand_rate=70.0, # demanda requerida
|
|
950
1033
|
)
|
|
951
1034
|
print(r)
|
|
952
|
-
#
|
|
1035
|
+
# Cuello de botella: Soldadura (capacidad 80 < demanda 70 con menor margen)
|
|
953
1036
|
# Utilization: Corte=58.3%, Soldadura=87.5%, Pintura=70.0%
|
|
954
1037
|
|
|
955
1038
|
r.plot() # barras de utilización con cuello de botella en rojo
|
|
@@ -968,19 +1051,19 @@ takt = wl.takt_time(available_time=480, demand=60)
|
|
|
968
1051
|
print(takt) # 8.0 min/unidad
|
|
969
1052
|
```
|
|
970
1053
|
|
|
971
|
-
### `line_balance(
|
|
1054
|
+
### `line_balance(station_names, cycle_times, takt)` — Balance de línea
|
|
972
1055
|
|
|
973
1056
|
Analiza si cada estación puede cumplir con el tiempo takt y calcula la eficiencia de balance.
|
|
974
1057
|
|
|
975
1058
|
```python
|
|
976
1059
|
lb = wl.line_balance(
|
|
977
|
-
|
|
1060
|
+
station_names=["A", "B", "C"],
|
|
978
1061
|
cycle_times=[5.0, 9.0, 4.0],
|
|
979
1062
|
takt=10.0,
|
|
980
1063
|
)
|
|
981
1064
|
print(lb.bottleneck) # "B" — estación más lenta
|
|
982
|
-
print(lb.balance_efficiency) #
|
|
983
|
-
print(lb.
|
|
1065
|
+
print(lb.balance_efficiency) # eficiencia de balance de la línea
|
|
1066
|
+
print(lb.theoretical_min_stations) # número mínimo teórico de estaciones
|
|
984
1067
|
|
|
985
1068
|
lb.plot() # barras de cycle time vs takt
|
|
986
1069
|
```
|
|
@@ -1000,8 +1083,8 @@ be = wl.break_even(
|
|
|
1000
1083
|
)
|
|
1001
1084
|
print(be.bep_units) # 1000.0 — punto de equilibrio en unidades
|
|
1002
1085
|
print(be.bep_revenue) # 25000.0 — ingresos en el punto de equilibrio
|
|
1003
|
-
print(be.margin_of_safety_pct) #
|
|
1004
|
-
print(be.
|
|
1086
|
+
print(be.margin_of_safety_pct) # 0.333 — margen de seguridad (fracción) sobre ventas actuales
|
|
1087
|
+
print(be.margin_of_safety_units) # 500.0 — unidades por encima del punto de equilibrio
|
|
1005
1088
|
|
|
1006
1089
|
be.plot() # líneas de ingresos/costos con BEP y zona de beneficio
|
|
1007
1090
|
```
|
|
@@ -1024,7 +1107,7 @@ print(r.bep_units_total) # unidades totales en el punto de equilibrio
|
|
|
1024
1107
|
print(r.bep_revenue_total) # ingresos totales en el punto de equilibrio
|
|
1025
1108
|
|
|
1026
1109
|
for item in r.items:
|
|
1027
|
-
print(item["
|
|
1110
|
+
print(item["Producto"], item["PE unidades"])
|
|
1028
1111
|
|
|
1029
1112
|
r.to_frame() # DataFrame con una fila por producto
|
|
1030
1113
|
```
|
|
@@ -1058,577 +1141,668 @@ print(r.margin_of_safety_pct) # 0.375 — porcentaje del margen de seguridad
|
|
|
1058
1141
|
|
|
1059
1142
|
---
|
|
1060
1143
|
|
|
1061
|
-
##
|
|
1144
|
+
## 12. Curvas de intercambio
|
|
1062
1145
|
|
|
1063
|
-
|
|
1146
|
+
Herramientas para tomar decisiones de política agregada en familias de artículos.
|
|
1064
1147
|
|
|
1065
|
-
###
|
|
1148
|
+
### Tipo 1 — ciclo: `exchange_curve`
|
|
1149
|
+
|
|
1150
|
+
Traza la hipérbola entre número de pedidos por año (N) e inversión promedio en inventario de ciclo (I):
|
|
1066
1151
|
|
|
1067
|
-
```
|
|
1068
|
-
|
|
1069
|
-
tree.plot() # treemap (por defecto)
|
|
1070
|
-
tree.plot(kind="sunburst") # diagrama radial
|
|
1152
|
+
```
|
|
1153
|
+
N(k) = N* / k I(k) = k · I* → N · I = N* · I* (constante)
|
|
1071
1154
|
```
|
|
1072
1155
|
|
|
1073
|
-
|
|
1156
|
+
Dado un target de N o de I, resuelve el multiplicador k óptimo y recalcula Q para cada artículo.
|
|
1074
1157
|
|
|
1075
1158
|
```python
|
|
1076
|
-
|
|
1077
|
-
actual_throughput=750,
|
|
1078
|
-
capacity=1000,
|
|
1079
|
-
defect_rate=0.05,
|
|
1080
|
-
)
|
|
1081
|
-
tree.plot()
|
|
1082
|
-
```
|
|
1159
|
+
import walopy as wl
|
|
1083
1160
|
|
|
1084
|
-
|
|
1161
|
+
items = [
|
|
1162
|
+
{"name": "A", "demand": 1000, "ordering_cost": 50, "holding_cost": 2, "unit_value": 10},
|
|
1163
|
+
{"name": "B", "demand": 500, "ordering_cost": 30, "holding_cost": 1, "unit_value": 5},
|
|
1164
|
+
{"name": "C", "demand": 2000, "ordering_cost": 100, "holding_cost": 4, "unit_value": 8},
|
|
1165
|
+
]
|
|
1085
1166
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
fixed_cost=10_000,
|
|
1090
|
-
variable_cost_per_unit=8,
|
|
1091
|
-
units_sold=2_000,
|
|
1092
|
-
investment=20_000,
|
|
1093
|
-
)
|
|
1094
|
-
print(tree.value) # ROI = 0.2 (20%)
|
|
1095
|
-
tree.plot()
|
|
1167
|
+
# Sin target → punto EOQ (k = 1)
|
|
1168
|
+
r = wl.exchange_curve(items)
|
|
1169
|
+
print(r)
|
|
1096
1170
|
```
|
|
1097
1171
|
|
|
1098
|
-
|
|
1172
|
+
```
|
|
1173
|
+
Objetivo : eoq
|
|
1174
|
+
Multiplicador k : 1.0000
|
|
1175
|
+
N pedidos/año (EOQ) : 87.27
|
|
1176
|
+
N pedidos/año (óptimo) : 87.27
|
|
1177
|
+
Inversión (EOQ) : 5430
|
|
1178
|
+
Inversión (óptimo) : 5430
|
|
1179
|
+
```
|
|
1099
1180
|
|
|
1100
1181
|
```python
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
cost = root.add_child("Operating costs", value=100_000, unit="€")
|
|
1104
|
-
rev.add_child("Product A", value=120_000, unit="€")
|
|
1105
|
-
rev.add_child("Product B", value=80_000, unit="€")
|
|
1182
|
+
# Reducir a 20 pedidos/año (k > 1 → Q más grandes → más inversión)
|
|
1183
|
+
r = wl.exchange_curve(items, target_orders=20)
|
|
1106
1184
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1185
|
+
# Limitar inversión a 3 000 (k < 1 → Q más pequeños → más pedidos)
|
|
1186
|
+
r = wl.exchange_curve(items, target_investment=3000)
|
|
1187
|
+
|
|
1188
|
+
# Tabla por artículo
|
|
1189
|
+
df = r.to_frame()
|
|
1190
|
+
# columnas: artículo | Q_eoq | Q_óptima | n_pedidos | inversión
|
|
1191
|
+
|
|
1192
|
+
# Hipérbola para graficar
|
|
1193
|
+
curve = r.curve_to_frame()
|
|
1194
|
+
# columnas: N | I
|
|
1110
1195
|
```
|
|
1111
1196
|
|
|
1197
|
+
**Parámetros de cada artículo**
|
|
1198
|
+
|
|
1199
|
+
| Clave | Requerido | Descripción |
|
|
1200
|
+
|-------|-----------|-------------|
|
|
1201
|
+
| `demand` | ✓ | Demanda anual Di |
|
|
1202
|
+
| `ordering_cost` | ✓ | Costo de pedido Ki |
|
|
1203
|
+
| `holding_cost` | ✓ | Costo de mantenimiento hi |
|
|
1204
|
+
| `unit_value` | — | Valor unitario vi (default 1) |
|
|
1205
|
+
| `name` | — | Etiqueta (default I1, I2, …) |
|
|
1206
|
+
|
|
1112
1207
|
---
|
|
1113
1208
|
|
|
1114
|
-
|
|
1209
|
+
### Tipo 2 — seguridad: `safety_stock_curve`
|
|
1115
1210
|
|
|
1116
|
-
|
|
1211
|
+
Traza la curva entre inversión en stock de seguridad e nivel de servicio por ciclo usando una **política z común** para toda la familia:
|
|
1117
1212
|
|
|
1118
|
-
|
|
1213
|
+
```
|
|
1214
|
+
SS_i(z) = z · σᵢ_DLT · vᵢ σᵢ_DLT = √(Lᵢ·σ²_Di + D²ᵢ·σ²_Li)
|
|
1215
|
+
SS_total(z) = z · Σ σᵢ_DLT · vᵢ SL(z) = Φ(z)
|
|
1216
|
+
```
|
|
1119
1217
|
|
|
1120
1218
|
```python
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1219
|
+
items = [
|
|
1220
|
+
{"name": "A", "demand_rate": 100, "demand_std": 10, "lead_time": 2, "unit_value": 10},
|
|
1221
|
+
{"name": "B", "demand_rate": 50, "demand_std": 5, "lead_time": 1, "unit_value": 5},
|
|
1222
|
+
{"name": "C", "demand_rate": 200, "demand_std": 20, "lead_time": 3, "unit_value": 8},
|
|
1223
|
+
]
|
|
1224
|
+
|
|
1225
|
+
# Target: nivel de servicio 95%
|
|
1226
|
+
r = wl.safety_stock_curve(items, target_service_level=0.95)
|
|
1227
|
+
print(r)
|
|
1125
1228
|
```
|
|
1126
1229
|
|
|
1127
|
-
|
|
1230
|
+
```
|
|
1231
|
+
Objetivo : nivel_de_servicio=95.0000%
|
|
1232
|
+
z : 1.6449
|
|
1233
|
+
Nivel de servicio : 95.0000%
|
|
1234
|
+
Inversión en SS : 1 847
|
|
1235
|
+
```
|
|
1128
1236
|
|
|
1129
1237
|
```python
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
```
|
|
1238
|
+
# Target: presupuesto máximo de SS
|
|
1239
|
+
r = wl.safety_stock_curve(items, target_ss_investment=1000)
|
|
1133
1240
|
|
|
1134
|
-
|
|
1241
|
+
# Tabla por artículo (incluye punto de reorden cuando se da demand_rate)
|
|
1242
|
+
df = r.to_frame()
|
|
1243
|
+
# columnas: artículo | sigma_dlt | stock_de_seguridad | inversión | punto_de_reorden
|
|
1135
1244
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
print(r.model_result) # QueueResult para M/M/3
|
|
1245
|
+
# Curva completa (z de −2 a 4)
|
|
1246
|
+
curve = r.curve_to_frame()
|
|
1247
|
+
# columnas: z | service_level | ss_investment
|
|
1140
1248
|
```
|
|
1141
1249
|
|
|
1142
|
-
|
|
1250
|
+
**Parámetros de cada artículo**
|
|
1143
1251
|
|
|
1144
|
-
|
|
1252
|
+
| Clave | Requerido | Descripción |
|
|
1253
|
+
|-------|-----------|-------------|
|
|
1254
|
+
| `demand_std` | ✓ | Desviación estándar de la demanda |
|
|
1255
|
+
| `lead_time` | ✓ | Lead time medio |
|
|
1256
|
+
| `demand_rate` | — | Demanda media; activa columna `reorder_point` |
|
|
1257
|
+
| `lead_time_std` | — | Desviación estándar del lead time (default 0) |
|
|
1258
|
+
| `unit_value` | — | Valor unitario (default 1) |
|
|
1259
|
+
| `name` | — | Etiqueta (default I1, I2, …) |
|
|
1145
1260
|
|
|
1146
|
-
|
|
1147
|
-
r = wl.optimize_servers(
|
|
1148
|
-
lam=6.0,
|
|
1149
|
-
mu=5.0,
|
|
1150
|
-
cost_per_server=10.0, # costo fijo por servidor por unidad de tiempo
|
|
1151
|
-
cost_per_wait=5.0, # costo por unidad de tiempo de espera de cada cliente
|
|
1152
|
-
)
|
|
1153
|
-
print(r.optimal_servers) # c óptimo
|
|
1154
|
-
print(r.optimal_cost) # costo mínimo
|
|
1261
|
+
---
|
|
1155
1262
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1263
|
+
### Relación entre ambas curvas
|
|
1264
|
+
|
|
1265
|
+
| | Tipo 1 (ciclo) | Tipo 2 (seguridad) |
|
|
1266
|
+
|---|---|---|
|
|
1267
|
+
| **Función** | `exchange_curve` | `safety_stock_curve` |
|
|
1268
|
+
| **Decide** | Tamaño de lote Q | Punto de reorden r |
|
|
1269
|
+
| **Tradeoff** | N pedidos/año ↔ Inversión ciclo | Nivel de servicio ↔ SS |
|
|
1270
|
+
| **Resultado** | `ExchangeCurveResult` | `SafetyStockCurveResult` |
|
|
1271
|
+
| **Parámetro** | Multiplicador k | Factor z |
|
|
1272
|
+
|
|
1273
|
+
Usadas juntas cubren la política completa `(Q, r)` para toda la familia.
|
|
1158
1274
|
|
|
1159
1275
|
---
|
|
1160
1276
|
|
|
1161
|
-
##
|
|
1277
|
+
## 13. Programación de producción (scheduling)
|
|
1162
1278
|
|
|
1163
|
-
###
|
|
1279
|
+
### Máquina única — `schedule_single`
|
|
1164
1280
|
|
|
1165
|
-
|
|
1281
|
+
Asigna *n* trabajos a una sola máquina según una regla de prioridad.
|
|
1166
1282
|
|
|
1167
1283
|
```python
|
|
1168
|
-
import
|
|
1169
|
-
|
|
1170
|
-
df = wl.sensitivity(
|
|
1171
|
-
wl.mm1,
|
|
1172
|
-
param="lam",
|
|
1173
|
-
values=np.linspace(0.5, 4.5, 20),
|
|
1174
|
-
mu=5.0, # parámetros fijos
|
|
1175
|
-
)
|
|
1176
|
-
print(df.columns.tolist()) # ['lam', 'rho', 'L', 'Lq', 'W', 'Wq', ...]
|
|
1284
|
+
import walopy as wl
|
|
1177
1285
|
|
|
1178
|
-
#
|
|
1179
|
-
|
|
1286
|
+
p = [3, 1, 4, 1, 5] # tiempos de proceso
|
|
1287
|
+
d = [8, 3, 10, 4, 12] # fechas de entrega
|
|
1288
|
+
w = [2, 5, 1, 4, 1] # pesos de importancia
|
|
1180
1289
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1290
|
+
r = wl.schedule_single(p, rule="SPT", due_dates=d, weights=w,
|
|
1291
|
+
names=["A","B","C","D","E"])
|
|
1292
|
+
print(r)
|
|
1293
|
+
```
|
|
1183
1294
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1295
|
+
```
|
|
1296
|
+
Regla : SPT
|
|
1297
|
+
Secuencia : B → D → A → C → E
|
|
1298
|
+
Makespan (Cmax) : 14
|
|
1299
|
+
Finalización total ΣCj : 37
|
|
1300
|
+
Finalización pond. ΣwCj : 89
|
|
1301
|
+
Retraso máximo : 2
|
|
1302
|
+
Tardanza total ΣTj : 2
|
|
1303
|
+
Trabajos con retraso : 1
|
|
1188
1304
|
```
|
|
1189
1305
|
|
|
1190
|
-
|
|
1306
|
+
**Reglas disponibles**
|
|
1191
1307
|
|
|
1192
|
-
|
|
1308
|
+
| `rule` | Criterio minimizado | Datos requeridos |
|
|
1309
|
+
|--------|---------------------|-----------------|
|
|
1310
|
+
| `'SPT'` | ΣCj (tiempo total de completación) | — |
|
|
1311
|
+
| `'EDD'` | Lmax (máxima tardanza) | `due_dates` |
|
|
1312
|
+
| `'WSPT'` | ΣwjCj (completación ponderada) | `weights` |
|
|
1313
|
+
| `'CR'` | Ratio crítico dj/pj | `due_dates` |
|
|
1314
|
+
| `'FIFO'` | Orden de llegada (línea base) | — |
|
|
1193
1315
|
|
|
1194
|
-
|
|
1316
|
+
**Atributos de `ScheduleResult`**
|
|
1195
1317
|
|
|
1196
|
-
|
|
1318
|
+
| Atributo | Descripción |
|
|
1319
|
+
|----------|-------------|
|
|
1320
|
+
| `rule` | Regla utilizada |
|
|
1321
|
+
| `sequence` | Lista de nombres en orden de procesamiento |
|
|
1322
|
+
| `jobs` | Lista de `JobSchedule` (por trabajo) |
|
|
1323
|
+
| `makespan` | Cmax = suma de tiempos de proceso |
|
|
1324
|
+
| `total_completion_time` | ΣCj |
|
|
1325
|
+
| `total_weighted_completion_time` | ΣwjCj |
|
|
1326
|
+
| `max_lateness` | max(Lj) |
|
|
1327
|
+
| `total_tardiness` | ΣTj |
|
|
1328
|
+
| `n_tardy` | Número de trabajos tarde |
|
|
1197
1329
|
|
|
1198
1330
|
```python
|
|
1199
|
-
|
|
1331
|
+
df = r.to_frame() # columnas: Trabajo, p, d, w, Inicio, C, L, T, Con retraso
|
|
1332
|
+
```
|
|
1200
1333
|
|
|
1201
|
-
|
|
1202
|
-
escenarios = pd.DataFrame({
|
|
1203
|
-
"lam": [1.0, 2.0, 3.0, 4.0]
|
|
1204
|
-
})
|
|
1205
|
-
resultado = wl.batch_model(wl.mm1, escenarios, mu=5.0)
|
|
1206
|
-
print(resultado[["lam", "rho", "Wq", "L"]])
|
|
1334
|
+
---
|
|
1207
1335
|
|
|
1208
|
-
|
|
1209
|
-
escenarios2 = pd.DataFrame({
|
|
1210
|
-
"lam": [2.0, 3.0, 4.0],
|
|
1211
|
-
"mu": [5.0, 6.0, 7.0],
|
|
1212
|
-
})
|
|
1213
|
-
resultado2 = wl.batch_model(wl.mm1, escenarios2)
|
|
1336
|
+
### Flow-shop de 2 máquinas — `johnson_flowshop`
|
|
1214
1337
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1338
|
+
El algoritmo de Johnson encuentra la secuencia óptima que minimiza el makespan cuando todos los trabajos pasan primero por M1 y luego por M2.
|
|
1339
|
+
|
|
1340
|
+
```python
|
|
1341
|
+
m1 = [3, 8, 5, 7, 2] # tiempos en máquina 1
|
|
1342
|
+
m2 = [5, 2, 8, 4, 6] # tiempos en máquina 2
|
|
1343
|
+
|
|
1344
|
+
r = wl.johnson_flowshop(m1, m2)
|
|
1345
|
+
print(r)
|
|
1346
|
+
```
|
|
1347
|
+
|
|
1348
|
+
```
|
|
1349
|
+
Secuencia : J1 → J5 → J4 → J3 → J2
|
|
1350
|
+
Makespan : 34
|
|
1351
|
+
```
|
|
1220
1352
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1353
|
+
```python
|
|
1354
|
+
df = r.to_frame()
|
|
1355
|
+
# columnas: Job | M1 start | M1 end | M2 start | M2 end
|
|
1223
1356
|
```
|
|
1224
1357
|
|
|
1225
1358
|
---
|
|
1226
1359
|
|
|
1227
|
-
|
|
1360
|
+
### Flow-shop de m máquinas — `neh_flowshop`
|
|
1228
1361
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1362
|
+
Heurística de **Nawaz-Enscore-Ham** para el flow-shop de permutación con cualquier número de máquinas:
|
|
1363
|
+
ordena los trabajos por tiempo total decreciente e inserta cada uno en la mejor posición (aceleración de
|
|
1364
|
+
Taillard: O(n²·m)). Es una heurística: en casos pequeños el makespan queda típicamente a pocos puntos
|
|
1365
|
+
porcentuales del óptimo.
|
|
1232
1366
|
|
|
1233
1367
|
```python
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
)
|
|
1240
|
-
print(df[["label", "ρ (utilization)", "Wq (wait time)", "L (system)"]])
|
|
1241
|
-
# label ρ (utilization) Wq (wait time) L (system)
|
|
1242
|
-
# 0 M/M/1 0.6 0.3000 1.50
|
|
1243
|
-
# 1 M/M/2 0.3 0.0302 1.02
|
|
1244
|
-
# 2 M/D/1 0.6 0.1500 1.05
|
|
1245
|
-
|
|
1246
|
-
# Sin etiquetas → scenario_1, scenario_2, ...
|
|
1247
|
-
df2 = wl.compare(wl.mm1(1, 5), wl.mm1(2, 5), wl.mm1(3, 5))
|
|
1368
|
+
# filas = trabajos, columnas = máquinas
|
|
1369
|
+
tiempos = [[5, 9, 8], [9, 3, 10], [9, 4, 5], [4, 8, 8]]
|
|
1370
|
+
r = wl.neh_flowshop(tiempos, names=["P1", "P2", "P3", "P4"])
|
|
1371
|
+
print(r) # secuencia y makespan
|
|
1372
|
+
print(r.to_frame()) # Gantt: columnas M1_inicio, M1_fin, M2_inicio, ...
|
|
1373
|
+
print(r.sequence, r.makespan)
|
|
1248
1374
|
```
|
|
1249
1375
|
|
|
1250
1376
|
---
|
|
1251
1377
|
|
|
1252
|
-
##
|
|
1378
|
+
## 14. Confiabilidad — `reliability`
|
|
1253
1379
|
|
|
1254
|
-
|
|
1380
|
+
Modelos de confiabilidad con distribución exponencial (tasa de falla constante).
|
|
1255
1381
|
|
|
1256
|
-
|
|
1257
|
-
|---|---|---|
|
|
1258
|
-
| `QueueResult.plot()` | Matplotlib | Curvas Wq y Lq vs ρ con punto operativo |
|
|
1259
|
-
| `OEEResult.plot()` | Matplotlib | Barras horizontales OEE con referencia 85% |
|
|
1260
|
-
| `BottleneckResult.plot()` | Matplotlib | Utilización por estación, cuello de botella en rojo |
|
|
1261
|
-
| `EOQResult.plot()` | Matplotlib | Curvas de costo de mantener, ordenar y total |
|
|
1262
|
-
| `LineBalanceResult.plot()` | Plotly | Cycle time vs takt por estación |
|
|
1263
|
-
| `BreakEvenResult.plot()` | Plotly | Líneas ingreso/costo con BEP y zona de beneficio |
|
|
1264
|
-
| `SimulationResult.plot()` | Plotly | Histograma de Wq + CDF empírica con percentiles |
|
|
1265
|
-
| `OptimizeResult.plot()` | Plotly | Costo por servidor, costo de espera y total vs c |
|
|
1266
|
-
| `KPINode.plot(kind=)` | Plotly | Treemap o sunburst interactivo |
|
|
1382
|
+
### Componente individual — `mtbf_analysis`
|
|
1267
1383
|
|
|
1268
1384
|
```python
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1385
|
+
r = wl.mtbf_analysis(failure_rate=0.01, mttr=5.0, t=100)
|
|
1386
|
+
print(r)
|
|
1387
|
+
```
|
|
1272
1388
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1389
|
+
```
|
|
1390
|
+
Topología : component
|
|
1391
|
+
Componentes : 1
|
|
1392
|
+
Tasas de falla λ: ['0.01']
|
|
1393
|
+
MTBF : 100
|
|
1394
|
+
R(t=100) : 0.367879
|
|
1395
|
+
Disponibilidad : 95.2381%
|
|
1276
1396
|
```
|
|
1277
1397
|
|
|
1278
|
-
|
|
1398
|
+
### Sistema en serie — `series_system`
|
|
1399
|
+
|
|
1400
|
+
El sistema falla si **cualquier** componente falla. λ_sys = Σλi.
|
|
1401
|
+
|
|
1279
1402
|
```python
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
fig.show()
|
|
1403
|
+
r = wl.series_system([0.01, 0.02, 0.03], t=10)
|
|
1404
|
+
print(r.mtbf) # 1/0.06 ≈ 16.67
|
|
1405
|
+
print(r.R_t) # e^{-0.06·10} ≈ 0.549
|
|
1284
1406
|
```
|
|
1285
1407
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
## 21. CLI
|
|
1408
|
+
### Sistema en paralelo — `parallel_system`
|
|
1289
1409
|
|
|
1290
|
-
|
|
1410
|
+
El sistema opera si **al menos uno** de los componentes opera.
|
|
1411
|
+
R_sys(t) = 1 − ∏(1 − e^{−λi·t}). MTBF calculado numéricamente.
|
|
1291
1412
|
|
|
1292
|
-
```
|
|
1293
|
-
|
|
1294
|
-
|
|
1413
|
+
```python
|
|
1414
|
+
r = wl.parallel_system([0.01, 0.02], t=50)
|
|
1415
|
+
print(r.R_t) # mayor que el sistema en serie
|
|
1416
|
+
print(r.mtbf) # mayor que cualquier componente individual
|
|
1417
|
+
```
|
|
1295
1418
|
|
|
1296
|
-
|
|
1297
|
-
python -m walopy mmc --lam 8 --mu 5 --c 2
|
|
1419
|
+
### Sistema k-de-n — `koon_system`
|
|
1298
1420
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1421
|
+
Opera si al menos **k** de **n** componentes idénticos están en servicio.
|
|
1422
|
+
MTBF exacto = (1/λ) · Σ_{j=k}^{n} (1/j).
|
|
1301
1423
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1424
|
+
```python
|
|
1425
|
+
# 2-de-3: requiere al menos 2 de 3 componentes idénticos
|
|
1426
|
+
r = wl.koon_system(n=3, k=2, failure_rate=0.01, t=50)
|
|
1427
|
+
print(r.mtbf) # (1/0.01) * (1/2 + 1/3) ≈ 83.33
|
|
1428
|
+
print(r.R_t)
|
|
1429
|
+
```
|
|
1304
1430
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1431
|
+
**Casos especiales**
|
|
1432
|
+
- `k=1` → equivale a `parallel_system` con n componentes idénticos.
|
|
1433
|
+
- `k=n` → equivale a `series_system` con n componentes idénticos.
|
|
1307
1434
|
|
|
1308
|
-
|
|
1309
|
-
python -m walopy eoq --demand 1000 --ordering 50 --holding 2
|
|
1435
|
+
**Atributos de `ReliabilityResult`**
|
|
1310
1436
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1437
|
+
| Atributo | Descripción |
|
|
1438
|
+
|----------|-------------|
|
|
1439
|
+
| `topology` | `'component'`, `'series'`, `'parallel'`, `'k-of-n'` |
|
|
1440
|
+
| `n_components` | Número de componentes |
|
|
1441
|
+
| `failure_rates` | Lista de λi |
|
|
1442
|
+
| `mtbf` | MTBF del sistema |
|
|
1443
|
+
| `R_t` | Confiabilidad R(t) si se proporcionó `t` |
|
|
1444
|
+
| `availability` | Disponibilidad estacionaria A si se proporcionó `mttr` |
|
|
1445
|
+
| `mttr` | Tiempo medio de reparación (si se proporcionó) |
|
|
1314
1446
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
λ : 3.0 (arrival rate)
|
|
1319
|
-
μ : 5.0 (service rate per server)
|
|
1320
|
-
ρ : 0.6 (utilization)
|
|
1321
|
-
L : 1.5 (avg units in system)
|
|
1322
|
-
Lq : 0.9 (avg units in queue)
|
|
1323
|
-
W : 0.5 (avg time in system)
|
|
1324
|
-
Wq : 0.3 (avg wait time in queue)
|
|
1325
|
-
P0 : 0.4
|
|
1447
|
+
```python
|
|
1448
|
+
df = r.to_frame() # columnas: Topología, Componentes, MTBF[, R(t), Disponibilidad]
|
|
1449
|
+
r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
|
|
1326
1450
|
```
|
|
1327
1451
|
|
|
1328
1452
|
---
|
|
1329
1453
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
Herramientas para tomar decisiones de política agregada en familias de artículos.
|
|
1454
|
+
### Distribución de Weibull — `weibull_analysis`
|
|
1333
1455
|
|
|
1334
|
-
|
|
1456
|
+
Ajusta una Weibull de dos parámetros (forma β, escala η) a tiempos de falla completos por **máxima
|
|
1457
|
+
verosimilitud** (`method="MLE"`, bisección sobre la ecuación de verosimilitud) o por **regresión de rangos**
|
|
1458
|
+
(`method="RRY"`, rangos medianos de Benard). β < 1 indica mortalidad infantil, β ≈ 1 fallas aleatorias y
|
|
1459
|
+
β > 1 desgaste.
|
|
1335
1460
|
|
|
1336
|
-
|
|
1461
|
+
```python
|
|
1462
|
+
tiempos = [12.5, 18.3, 24.1, 31.7, 38.2, 45.9, 52.4, 61.0, 70.8, 85.3]
|
|
1463
|
+
w = wl.weibull_analysis(tiempos)
|
|
1464
|
+
print(w) # β ≈ 2.10, η ≈ 49.86, MTTF ≈ 44.16
|
|
1465
|
+
print(w.R(30), w.F(30)) # confiabilidad y probabilidad de falla a t = 30
|
|
1466
|
+
print(w.h(30)) # tasa de falla instantánea
|
|
1467
|
+
print(w.b10, w.b50, w.b_life(5)) # vidas B10, B50 y B5
|
|
1468
|
+
print(w.to_frame())
|
|
1337
1469
|
|
|
1338
|
-
|
|
1339
|
-
N(k) = N* / k I(k) = k · I* → N · I = N* · I* (constante)
|
|
1470
|
+
w_rry = wl.weibull_analysis(tiempos, method="RRY")
|
|
1340
1471
|
```
|
|
1341
1472
|
|
|
1342
|
-
|
|
1473
|
+
Se necesitan al menos 2 tiempos finitos y positivos; si todos son iguales la forma no es estimable
|
|
1474
|
+
(`ValueError`) y, si casi no hay dispersión, se avisa (`UserWarning`) de que β alcanzó la cota superior de 100.
|
|
1343
1475
|
|
|
1344
|
-
|
|
1345
|
-
import walopy as wl
|
|
1476
|
+
---
|
|
1346
1477
|
|
|
1347
|
-
|
|
1348
|
-
{"name": "A", "demand": 1000, "ordering_cost": 50, "holding_cost": 2, "unit_value": 10},
|
|
1349
|
-
{"name": "B", "demand": 500, "ordering_cost": 30, "holding_cost": 1, "unit_value": 5},
|
|
1350
|
-
{"name": "C", "demand": 2000, "ordering_cost": 100, "holding_cost": 4, "unit_value": 8},
|
|
1351
|
-
]
|
|
1478
|
+
## 15. Proyectos: CPM y PERT — `project`
|
|
1352
1479
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
```
|
|
1480
|
+
Programación de proyectos sobre una red de actividades. Cada actividad es un diccionario con `name`,
|
|
1481
|
+
`predecessors` (lista de nombres, puede omitirse) y su duración. Los nombres deben ser únicos; los
|
|
1482
|
+
ciclos y los predecesores desconocidos lanzan `ValueError`.
|
|
1357
1483
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
N orders/yr (EOQ) : 87.27
|
|
1362
|
-
N orders/yr (optimal) : 87.27
|
|
1363
|
-
Investment (EOQ) : 5430
|
|
1364
|
-
Investment (optimal) : 5430
|
|
1365
|
-
```
|
|
1484
|
+
### CPM — duraciones determinísticas
|
|
1485
|
+
|
|
1486
|
+
Paso hacia adelante (ES, EF) y hacia atrás (LS, LF); holgura total HT = LS − ES, holgura libre HL y ruta crítica (HT = 0).
|
|
1366
1487
|
|
|
1367
1488
|
```python
|
|
1368
|
-
|
|
1369
|
-
|
|
1489
|
+
acts = [
|
|
1490
|
+
{"name": "A", "duration": 3, "predecessors": []},
|
|
1491
|
+
{"name": "B", "duration": 4, "predecessors": ["A"]},
|
|
1492
|
+
{"name": "C", "duration": 2, "predecessors": ["A"]},
|
|
1493
|
+
{"name": "D", "duration": 1, "predecessors": ["B", "C"]},
|
|
1494
|
+
]
|
|
1495
|
+
r = wl.cpm(acts)
|
|
1496
|
+
print(r) # duración 8, ruta crítica A → B → D
|
|
1497
|
+
print(r.to_frame()) # ES, EF, LS, LF, HT, HL y si es crítica
|
|
1498
|
+
print(r.critical_path) # ['A', 'B', 'D']
|
|
1499
|
+
```
|
|
1370
1500
|
|
|
1371
|
-
|
|
1372
|
-
r = wl.exchange_curve(items, target_investment=3000)
|
|
1501
|
+
### PERT — tres estimaciones por actividad
|
|
1373
1502
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
# columnas: name | Q_eoq | Q_optimal | n_orders | investment
|
|
1503
|
+
Duración esperada tₑ = (a + 4m + b)/6 y varianza ((b − a)/6)²; la varianza del proyecto es la suma de las
|
|
1504
|
+
de la ruta crítica. `probability(T)` da P(duración ≤ T) con aproximación normal.
|
|
1377
1505
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1506
|
+
```python
|
|
1507
|
+
acts = [
|
|
1508
|
+
{"name": "A", "optimistic": 1, "most_likely": 3, "pessimistic": 5, "predecessors": []},
|
|
1509
|
+
{"name": "B", "optimistic": 2, "most_likely": 4, "pessimistic": 9, "predecessors": ["A"]},
|
|
1510
|
+
]
|
|
1511
|
+
p = wl.pert(acts)
|
|
1512
|
+
print(p) # duración esperada 7.5, σ ≈ 1.34
|
|
1513
|
+
print(p.probability(9.0)) # ≈ 0.868: probabilidad de terminar en 9 unidades o menos
|
|
1381
1514
|
```
|
|
1382
1515
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
| Clave | Requerido | Descripción |
|
|
1386
|
-
|-------|-----------|-------------|
|
|
1387
|
-
| `demand` | ✓ | Demanda anual Di |
|
|
1388
|
-
| `ordering_cost` | ✓ | Costo de pedido Ki |
|
|
1389
|
-
| `holding_cost` | ✓ | Costo de mantenimiento hi |
|
|
1390
|
-
| `unit_value` | — | Valor unitario vi (default 1) |
|
|
1391
|
-
| `name` | — | Etiqueta (default I1, I2, …) |
|
|
1516
|
+
`probability` solo está disponible en resultados PERT con varianza distinta de cero.
|
|
1392
1517
|
|
|
1393
1518
|
---
|
|
1394
1519
|
|
|
1395
|
-
|
|
1520
|
+
## 16. Árboles de KPI
|
|
1396
1521
|
|
|
1397
|
-
|
|
1522
|
+
Los árboles de KPI crean jerarquías interactivas visualizadas como **treemap** o **sunburst** con Plotly.
|
|
1398
1523
|
|
|
1399
|
-
|
|
1400
|
-
SS_i(z) = z · σᵢ_DLT · vᵢ σᵢ_DLT = √(Lᵢ·σ²_Di + D²ᵢ·σ²_Li)
|
|
1401
|
-
SS_total(z) = z · Σ σᵢ_DLT · vᵢ SL(z) = Φ(z)
|
|
1402
|
-
```
|
|
1524
|
+
### `oee_kpi_tree(availability, performance, quality)`
|
|
1403
1525
|
|
|
1404
1526
|
```python
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
{"name": "C", "demand_rate": 200, "demand_std": 20, "lead_time": 3, "unit_value": 8},
|
|
1409
|
-
]
|
|
1410
|
-
|
|
1411
|
-
# Target: nivel de servicio 95%
|
|
1412
|
-
r = wl.safety_stock_curve(items, target_service_level=0.95)
|
|
1413
|
-
print(r)
|
|
1527
|
+
tree = wl.oee_kpi_tree(0.90, 0.80, 0.95)
|
|
1528
|
+
tree.plot() # treemap (por defecto)
|
|
1529
|
+
tree.plot(kind="sunburst") # diagrama radial
|
|
1414
1530
|
```
|
|
1415
1531
|
|
|
1416
|
-
|
|
1417
|
-
Target : service_level=95.0000%
|
|
1418
|
-
z : 1.6449
|
|
1419
|
-
Service level : 95.0000%
|
|
1420
|
-
SS investment : 1 847
|
|
1421
|
-
```
|
|
1532
|
+
### `throughput_kpi_tree(actual_throughput, capacity, defect_rate)`
|
|
1422
1533
|
|
|
1423
1534
|
```python
|
|
1424
|
-
|
|
1425
|
-
|
|
1535
|
+
tree = wl.throughput_kpi_tree(
|
|
1536
|
+
actual_throughput=750,
|
|
1537
|
+
capacity=1000,
|
|
1538
|
+
defect_rate=0.05,
|
|
1539
|
+
)
|
|
1540
|
+
tree.plot()
|
|
1541
|
+
```
|
|
1426
1542
|
|
|
1427
|
-
|
|
1428
|
-
df = r.to_frame()
|
|
1429
|
-
# columnas: name | sigma_dlt | safety_stock | investment | reorder_point
|
|
1543
|
+
### `roi_kpi_tree(revenue, fixed_cost, variable_cost_per_unit, units_sold, investment)`
|
|
1430
1544
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1545
|
+
```python
|
|
1546
|
+
tree = wl.roi_kpi_tree(
|
|
1547
|
+
revenue=50_000,
|
|
1548
|
+
fixed_cost=10_000,
|
|
1549
|
+
variable_cost_per_unit=8,
|
|
1550
|
+
units_sold=2_000,
|
|
1551
|
+
investment=20_000,
|
|
1552
|
+
)
|
|
1553
|
+
print(tree.value) # ROI = 0.2 (20%)
|
|
1554
|
+
tree.plot()
|
|
1434
1555
|
```
|
|
1435
1556
|
|
|
1436
|
-
|
|
1557
|
+
### `KPINode` — árbol personalizado
|
|
1437
1558
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1559
|
+
```python
|
|
1560
|
+
rev = wl.KPINode("Revenue", value=200_000, unit="€", children=[
|
|
1561
|
+
wl.KPINode("Product A", value=120_000, unit="€"),
|
|
1562
|
+
wl.KPINode("Product B", value=80_000, unit="€"),
|
|
1563
|
+
])
|
|
1564
|
+
cost = wl.KPINode("Operating costs", value=100_000, unit="€")
|
|
1565
|
+
root = wl.KPINode("EBITDA", value=100_000, unit="€", formula="Revenue − Operating costs",
|
|
1566
|
+
children=[rev, cost])
|
|
1567
|
+
|
|
1568
|
+
from walopy.plotting import plot_kpi_tree
|
|
1569
|
+
fig = plot_kpi_tree(root, kind="treemap")
|
|
1570
|
+
fig.show()
|
|
1571
|
+
```
|
|
1446
1572
|
|
|
1447
1573
|
---
|
|
1448
1574
|
|
|
1449
|
-
|
|
1575
|
+
## 17. Solvers y optimización
|
|
1450
1576
|
|
|
1451
|
-
|
|
1452
|
-
|---|---|---|
|
|
1453
|
-
| **Función** | `exchange_curve` | `safety_stock_curve` |
|
|
1454
|
-
| **Decide** | Tamaño de lote Q | Punto de reorden r |
|
|
1455
|
-
| **Tradeoff** | N pedidos/año ↔ Inversión ciclo | Nivel de servicio ↔ SS |
|
|
1456
|
-
| **Resultado** | `ExchangeCurveResult` | `SafetyStockCurveResult` |
|
|
1457
|
-
| **Parámetro** | Multiplicador k | Factor z |
|
|
1577
|
+
### `solve_lam(target_metric, target_value, *, mu, model)` — Máxima tasa de llegada
|
|
1458
1578
|
|
|
1459
|
-
|
|
1579
|
+
Encuentra la mayor λ tal que una métrica no supere el objetivo.
|
|
1460
1580
|
|
|
1461
|
-
|
|
1581
|
+
```python
|
|
1582
|
+
r = wl.solve_lam("Wq", 0.5, mu=5.0, model="mm1")
|
|
1583
|
+
print(r.value) # λ máxima ≈ 3.33
|
|
1584
|
+
print(r.achieved_value) # Wq ≈ 0.5
|
|
1585
|
+
print(r.model_result) # QueueResult completo en la solución
|
|
1586
|
+
```
|
|
1462
1587
|
|
|
1463
|
-
|
|
1588
|
+
### `solve_mu(target_metric, target_value, *, lam, model)` — Mínima tasa de servicio
|
|
1464
1589
|
|
|
1465
|
-
|
|
1590
|
+
```python
|
|
1591
|
+
r = wl.solve_mu("Wq", 0.3, lam=3.0, model="mm1")
|
|
1592
|
+
print(r.value) # μ mínima necesaria ≈ 5.0
|
|
1593
|
+
```
|
|
1466
1594
|
|
|
1467
|
-
|
|
1595
|
+
### `solve_servers(target_metric, target_value, *, lam, mu)` — Mínimo número de servidores
|
|
1468
1596
|
|
|
1469
1597
|
```python
|
|
1470
|
-
|
|
1598
|
+
r = wl.solve_servers("Wq", 0.1, lam=8.0, mu=5.0)
|
|
1599
|
+
print(r.value) # c mínimo = 3
|
|
1600
|
+
print(r.model_result) # QueueResult para M/M/3
|
|
1601
|
+
```
|
|
1471
1602
|
|
|
1472
|
-
|
|
1473
|
-
d = [8, 3, 10, 4, 12] # fechas de entrega
|
|
1474
|
-
w = [2, 5, 1, 4, 1] # pesos de importancia
|
|
1603
|
+
### `optimize_servers(lam, mu, cost_per_server, cost_per_wait)` — Optimización de costo total
|
|
1475
1604
|
|
|
1476
|
-
|
|
1477
|
-
names=["A","B","C","D","E"])
|
|
1478
|
-
print(r)
|
|
1479
|
-
```
|
|
1605
|
+
Minimiza cost_per_server × c + cost_per_wait × λ × Wq evaluando todos los c viables.
|
|
1480
1606
|
|
|
1481
|
-
```
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1607
|
+
```python
|
|
1608
|
+
r = wl.optimize_servers(
|
|
1609
|
+
lam=6.0,
|
|
1610
|
+
mu=5.0,
|
|
1611
|
+
cost_per_server=10.0, # costo fijo por servidor por unidad de tiempo
|
|
1612
|
+
cost_per_wait=5.0, # costo por unidad de tiempo de espera de cada cliente
|
|
1613
|
+
)
|
|
1614
|
+
print(r.optimal_servers) # c óptimo
|
|
1615
|
+
print(r.min_cost) # costo mínimo
|
|
1616
|
+
|
|
1617
|
+
r.plot() # gráfica de costo vs número de servidores
|
|
1490
1618
|
```
|
|
1491
1619
|
|
|
1492
|
-
|
|
1620
|
+
---
|
|
1493
1621
|
|
|
1494
|
-
|
|
1495
|
-
|--------|---------------------|-----------------|
|
|
1496
|
-
| `'SPT'` | ΣCj (tiempo total de completación) | — |
|
|
1497
|
-
| `'EDD'` | Lmax (máxima tardanza) | `due_dates` |
|
|
1498
|
-
| `'WSPT'` | ΣwjCj (completación ponderada) | `weights` |
|
|
1499
|
-
| `'CR'` | Ratio crítico dj/pj | `due_dates` |
|
|
1500
|
-
| `'FIFO'` | Orden de llegada (línea base) | — |
|
|
1622
|
+
## 18. Análisis de sensibilidad
|
|
1501
1623
|
|
|
1502
|
-
**
|
|
1624
|
+
### `sensitivity(model_fn, param, values, **fixed_kwargs)` — Barrido paramétrico
|
|
1503
1625
|
|
|
1504
|
-
|
|
1505
|
-
|----------|-------------|
|
|
1506
|
-
| `rule` | Regla utilizada |
|
|
1507
|
-
| `sequence` | Lista de nombres en orden de procesamiento |
|
|
1508
|
-
| `jobs` | Lista de `JobSchedule` (por trabajo) |
|
|
1509
|
-
| `makespan` | Cmax = suma de tiempos de proceso |
|
|
1510
|
-
| `total_completion_time` | ΣCj |
|
|
1511
|
-
| `total_weighted_completion_time` | ΣwjCj |
|
|
1512
|
-
| `max_lateness` | max(Lj) |
|
|
1513
|
-
| `total_tardiness` | ΣTj |
|
|
1514
|
-
| `n_tardy` | Número de trabajos tarde |
|
|
1626
|
+
Evalúa un modelo sobre un rango de valores de un parámetro.
|
|
1515
1627
|
|
|
1516
1628
|
```python
|
|
1517
|
-
|
|
1629
|
+
import numpy as np
|
|
1630
|
+
|
|
1631
|
+
df = wl.sensitivity(
|
|
1632
|
+
wl.mm1,
|
|
1633
|
+
param="lam",
|
|
1634
|
+
values=np.linspace(0.5, 4.5, 20),
|
|
1635
|
+
mu=5.0, # parámetros fijos
|
|
1636
|
+
)
|
|
1637
|
+
print(df.columns.tolist()) # ['lam', 'rho', 'L', 'Lq', 'W', 'Wq', ...]
|
|
1638
|
+
|
|
1639
|
+
# Barrido sobre μ
|
|
1640
|
+
df2 = wl.sensitivity(wl.mm1, "mu", np.linspace(4.0, 10.0, 15), lam=3.0)
|
|
1641
|
+
|
|
1642
|
+
# Barrido sobre número de servidores en M/M/c
|
|
1643
|
+
df3 = wl.sensitivity(wl.mmc, "c", range(1, 6), lam=8.0, mu=5.0)
|
|
1644
|
+
|
|
1645
|
+
# Visualizar
|
|
1646
|
+
from walopy.plotting import plot_sensitivity
|
|
1647
|
+
fig = plot_sensitivity(df, "lam", metrics=["Wq", "Lq"])
|
|
1648
|
+
fig.show()
|
|
1518
1649
|
```
|
|
1519
1650
|
|
|
1520
1651
|
---
|
|
1521
1652
|
|
|
1522
|
-
|
|
1653
|
+
## 19. Escenarios en lote (batch_model)
|
|
1523
1654
|
|
|
1524
|
-
|
|
1655
|
+
### `batch_model(model_fn, df, **fixed_kwargs)` — Aplicar modelo a un DataFrame
|
|
1656
|
+
|
|
1657
|
+
Aplica cualquier función de walopy a cada fila de un DataFrame de escenarios.
|
|
1525
1658
|
|
|
1526
1659
|
```python
|
|
1527
|
-
|
|
1528
|
-
m2 = [5, 2, 8, 4, 6] # tiempos en máquina 2
|
|
1660
|
+
import pandas as pd
|
|
1529
1661
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1662
|
+
# Varios escenarios con distintas tasas de llegada
|
|
1663
|
+
escenarios = pd.DataFrame({
|
|
1664
|
+
"lam": [1.0, 2.0, 3.0, 4.0]
|
|
1665
|
+
})
|
|
1666
|
+
resultado = wl.batch_model(wl.mm1, escenarios, mu=5.0)
|
|
1667
|
+
print(resultado[["lam", "rho", "Wq", "L"]])
|
|
1533
1668
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1669
|
+
# Escenarios mixtos (lambda y mu variables)
|
|
1670
|
+
escenarios2 = pd.DataFrame({
|
|
1671
|
+
"lam": [2.0, 3.0, 4.0],
|
|
1672
|
+
"mu": [5.0, 6.0, 7.0],
|
|
1673
|
+
})
|
|
1674
|
+
resultado2 = wl.batch_model(wl.mm1, escenarios2)
|
|
1538
1675
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1676
|
+
# Con número de servidores variable (columna int)
|
|
1677
|
+
escenarios3 = pd.DataFrame({
|
|
1678
|
+
"c": [1, 2, 3, 4],
|
|
1679
|
+
})
|
|
1680
|
+
resultado3 = wl.batch_model(wl.mmc, escenarios3, lam=8.0, mu=5.0)
|
|
1681
|
+
|
|
1682
|
+
# Las filas con error quedan marcadas en "_error" (la columna solo existe si alguna fila falla;
|
|
1683
|
+
# aquí c=1 es inestable porque λ=8 > μ=5)
|
|
1684
|
+
print(resultado3[resultado3["_error"].notna()]) # filas fallidas
|
|
1685
|
+
|
|
1686
|
+
# Para que la primera excepción se propague en lugar de recogerse:
|
|
1687
|
+
# wl.batch_model(wl.mmc, escenarios3, errors="raise", lam=8.0, mu=5.0)
|
|
1542
1688
|
```
|
|
1543
1689
|
|
|
1544
1690
|
---
|
|
1545
1691
|
|
|
1546
|
-
##
|
|
1692
|
+
## 20. Comparar múltiples resultados
|
|
1547
1693
|
|
|
1548
|
-
|
|
1694
|
+
### `compare(*results, labels)` — DataFrame comparativo
|
|
1549
1695
|
|
|
1550
|
-
|
|
1696
|
+
Construye un DataFrame con una fila por resultado para facilitar la comparación.
|
|
1551
1697
|
|
|
1552
1698
|
```python
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1699
|
+
df = wl.compare(
|
|
1700
|
+
wl.mm1(3.0, 5.0),
|
|
1701
|
+
wl.mmc(3.0, 5.0, 2),
|
|
1702
|
+
wl.md1(3.0, 5.0),
|
|
1703
|
+
labels=["M/M/1", "M/M/2", "M/D/1"],
|
|
1704
|
+
)
|
|
1705
|
+
print(df[["label", "ρ (utilización)", "Wq (tiempo de espera)", "L (en el sistema)"]])
|
|
1706
|
+
# label ρ (utilización) Wq (tiempo de espera) L (en el sistema)
|
|
1707
|
+
# 0 M/M/1 0.6 0.3000 1.50
|
|
1708
|
+
# 1 M/M/2 0.3 0.0302 1.02
|
|
1709
|
+
# 2 M/D/1 0.6 0.1500 1.05
|
|
1556
1710
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
Components : 1
|
|
1560
|
-
Failure rates λ : ['0.01']
|
|
1561
|
-
MTBF : 100
|
|
1562
|
-
R(t=100) : 0.367879
|
|
1563
|
-
Availability : 95.2381%
|
|
1711
|
+
# Sin etiquetas → scenario_1, scenario_2, ...
|
|
1712
|
+
df2 = wl.compare(wl.mm1(1, 5), wl.mm1(2, 5), wl.mm1(3, 5))
|
|
1564
1713
|
```
|
|
1565
1714
|
|
|
1566
|
-
|
|
1715
|
+
---
|
|
1567
1716
|
|
|
1568
|
-
|
|
1717
|
+
## 21. Gráficas
|
|
1569
1718
|
|
|
1570
|
-
|
|
1571
|
-
r = wl.series_system([0.01, 0.02, 0.03], t=10)
|
|
1572
|
-
print(r.mtbf) # 1/0.06 ≈ 16.67
|
|
1573
|
-
print(r.R_t) # e^{-0.06·10} ≈ 0.549
|
|
1574
|
-
```
|
|
1719
|
+
Todos los resultados exponen `.plot()` que devuelve una figura de Matplotlib o Plotly. También se pueden llamar directamente desde `walopy.plotting`.
|
|
1575
1720
|
|
|
1576
|
-
|
|
1721
|
+
| Función `.plot()` | Tipo | Descripción |
|
|
1722
|
+
|---|---|---|
|
|
1723
|
+
| `QueueResult.plot()` | Matplotlib | Curvas Wq y Lq vs ρ con punto operativo |
|
|
1724
|
+
| `OEEResult.plot()` | Matplotlib | Barras horizontales OEE con referencia 85% |
|
|
1725
|
+
| `BottleneckResult.plot()` | Matplotlib | Utilización por estación, cuello de botella en rojo |
|
|
1726
|
+
| `EOQResult.plot()` | Matplotlib | Curvas de costo de mantener, ordenar y total |
|
|
1727
|
+
| `LineBalanceResult.plot()` | Plotly | Tiempo de ciclo vs takt por estación |
|
|
1728
|
+
| `BreakEvenResult.plot()` | Plotly | Líneas ingreso/costo con BEP y zona de beneficio |
|
|
1729
|
+
| `SimulationResult.plot()` | Plotly | Histograma de Wq + CDF empírica con percentiles |
|
|
1730
|
+
| `OptimizeResult.plot()` | Plotly | Costo por servidor, costo de espera y total vs c |
|
|
1731
|
+
| `KPINode.plot(kind=)` | Plotly | Treemap o sunburst interactivo |
|
|
1577
1732
|
|
|
1578
|
-
|
|
1579
|
-
|
|
1733
|
+
```python
|
|
1734
|
+
# Guardar como HTML interactivo (Plotly)
|
|
1735
|
+
fig = r.plot()
|
|
1736
|
+
fig.write_html("resultado.html")
|
|
1580
1737
|
|
|
1738
|
+
# Guardar como imagen (Matplotlib)
|
|
1739
|
+
fig = wl.oee(0.9, 0.8, 0.95).plot()
|
|
1740
|
+
fig.savefig("oee.png", dpi=150)
|
|
1741
|
+
```
|
|
1742
|
+
|
|
1743
|
+
Para la curva de sensibilidad:
|
|
1581
1744
|
```python
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1745
|
+
from walopy.plotting import plot_sensitivity
|
|
1746
|
+
df = wl.sensitivity(wl.mm1, "lam", np.linspace(0.5, 4.5, 20), mu=5.0)
|
|
1747
|
+
fig = plot_sensitivity(df, "lam", metrics=["Wq", "Lq", "L"])
|
|
1748
|
+
fig.show()
|
|
1585
1749
|
```
|
|
1586
1750
|
|
|
1587
|
-
|
|
1751
|
+
---
|
|
1588
1752
|
|
|
1589
|
-
|
|
1590
|
-
MTBF exacto = (1/λ) · Σ_{j=k}^{n} (1/j).
|
|
1753
|
+
## 22. CLI
|
|
1591
1754
|
|
|
1592
|
-
|
|
1593
|
-
# 2-de-3: requiere al menos 2 de 3 componentes idénticos
|
|
1594
|
-
r = wl.koon_system(n=3, k=2, failure_rate=0.01, t=50)
|
|
1595
|
-
print(r.mtbf) # (1/0.01) * (1/2 + 1/3) ≈ 83.33
|
|
1596
|
-
print(r.R_t)
|
|
1597
|
-
```
|
|
1755
|
+
walopy incluye una interfaz de línea de comandos para uso rápido sin escribir código.
|
|
1598
1756
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
-
|
|
1757
|
+
```bash
|
|
1758
|
+
# M/M/1
|
|
1759
|
+
python -m walopy mm1 --lam 3 --mu 5
|
|
1602
1760
|
|
|
1603
|
-
|
|
1761
|
+
# M/M/c
|
|
1762
|
+
python -m walopy mmc --lam 8 --mu 5 --c 2
|
|
1604
1763
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
| `topology` | `'component'`, `'series'`, `'parallel'`, `'k-of-n'` |
|
|
1608
|
-
| `n_components` | Número de componentes |
|
|
1609
|
-
| `failure_rates` | Lista de λi |
|
|
1610
|
-
| `mtbf` | MTBF del sistema |
|
|
1611
|
-
| `R_t` | Confiabilidad R(t) si se proporcionó `t` |
|
|
1612
|
-
| `availability` | Disponibilidad estacionaria A si se proporcionó `mttr` |
|
|
1613
|
-
| `mttr` | Tiempo medio de reparación (si se proporcionó) |
|
|
1764
|
+
# M/D/1
|
|
1765
|
+
python -m walopy md1 --lam 3 --mu 5
|
|
1614
1766
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1767
|
+
# G/G/1 (Kingman)
|
|
1768
|
+
python -m walopy gg1 --lam 3 --mu 5 --ca2 1.2 --cs2 0.8
|
|
1769
|
+
|
|
1770
|
+
# Ley de Little (resolver W)
|
|
1771
|
+
python -m walopy littles --L 2.5 --lam 5.0
|
|
1772
|
+
|
|
1773
|
+
# EOQ
|
|
1774
|
+
python -m walopy eoq --demand 1000 --ordering 50 --holding 2
|
|
1775
|
+
|
|
1776
|
+
# Versión
|
|
1777
|
+
python -m walopy --version
|
|
1778
|
+
```
|
|
1779
|
+
|
|
1780
|
+
Ejemplo de salida:
|
|
1781
|
+
```
|
|
1782
|
+
Modelo : M/M/1
|
|
1783
|
+
λ : 3.0 (tasa de llegada)
|
|
1784
|
+
μ : 5.0 (tasa de servicio por servidor)
|
|
1785
|
+
ρ : 0.6 (utilización por servidor)
|
|
1786
|
+
L : 1.5 (unidades promedio en el sistema)
|
|
1787
|
+
Lq : 0.9 (unidades promedio en cola)
|
|
1788
|
+
W : 0.5 (tiempo promedio en el sistema)
|
|
1789
|
+
Wq : 0.3 (tiempo promedio de espera en cola)
|
|
1790
|
+
P0 : 0.4
|
|
1618
1791
|
```
|
|
1619
1792
|
|
|
1620
1793
|
---
|
|
1621
1794
|
|
|
1622
|
-
##
|
|
1795
|
+
## 23. Referencia de módulos
|
|
1623
1796
|
|
|
1624
1797
|
| Módulo | Funciones y clases principales |
|
|
1625
1798
|
|---|---|
|
|
1626
1799
|
| `queuing` | `mm1`, `mmc`, `md1`, `kingman`, `mg1`, `littles_law`, `QueueResult`, `cv2_normal`, `cv2_triangular`, `cv2_uniform`, `cv2_erlang`, `cv2_gamma`, `cv2_lognormal`, `cv2_weibull` |
|
|
1627
1800
|
| `advanced` | `mm1k`, `mmck`, `erlang_b`, `mm1_priority`, `monte_carlo_gg1`, `takt_time`, `line_balance`, `break_even`, `break_even_multi`, `break_even_sales`, `queue_length_pmf`, `sojourn_cdf`, `PriorityQueueResult`, `SimulationResult`, `LineBalanceResult`, `BreakEvenResult`, `BreakEvenMultiResult` |
|
|
1628
1801
|
| `fitting` | `fit_from_data`, `FitResult` |
|
|
1629
|
-
| `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `exchange_curve`, `safety_stock_curve`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult`, `ExchangeCurveResult`, `SafetyStockCurveResult` |
|
|
1630
|
-
| `scheduling` | `schedule_single`, `johnson_flowshop`, `ScheduleResult`, `JobSchedule`, `FlowShopResult` |
|
|
1631
|
-
| `
|
|
1802
|
+
| `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `exchange_curve`, `safety_stock_curve`, `abc_analysis`, `xyz_analysis`, `abc_xyz`, `mrp`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult`, `ExchangeCurveResult`, `SafetyStockCurveResult`, `ABCResult`, `XYZResult`, `ABCXYZResult`, `MRPResult` |
|
|
1803
|
+
| `scheduling` | `schedule_single`, `johnson_flowshop`, `neh_flowshop`, `ScheduleResult`, `JobSchedule`, `FlowShopResult`, `NEHResult` |
|
|
1804
|
+
| `project` | `cpm`, `pert`, `ProjectResult`, `ActivityResult` |
|
|
1805
|
+
| `reliability` | `mtbf_analysis`, `series_system`, `parallel_system`, `koon_system`, `weibull_analysis`, `ReliabilityResult`, `WeibullResult` |
|
|
1632
1806
|
| `network` | `jackson_network`, `JacksonResult`, `StationMetrics` |
|
|
1633
1807
|
| `operations` | `oee`, `utilization_efficiency`, `unit_cost`, `OEEResult`, `UtilizationResult`, `UnitCostResult` |
|
|
1634
1808
|
| `bottleneck` | `bottleneck_analysis`, `BottleneckResult`, `StationResult` |
|
|
@@ -1638,7 +1812,7 @@ r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
|
|
|
1638
1812
|
|
|
1639
1813
|
---
|
|
1640
1814
|
|
|
1641
|
-
##
|
|
1815
|
+
## 24. Requisitos
|
|
1642
1816
|
|
|
1643
1817
|
```
|
|
1644
1818
|
Python ≥ 3.9
|