walopy 0.2.3__tar.gz → 0.2.6__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.
Files changed (39) hide show
  1. {walopy-0.2.3/src/walopy.egg-info → walopy-0.2.6}/PKG-INFO +476 -22
  2. {walopy-0.2.3 → walopy-0.2.6}/README.md +475 -21
  3. {walopy-0.2.3 → walopy-0.2.6}/pyproject.toml +1 -1
  4. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/__init__.py +91 -0
  5. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/advanced.py +207 -0
  6. walopy-0.2.6/src/walopy/inventory.py +1620 -0
  7. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/queuing.py +146 -0
  8. walopy-0.2.6/src/walopy/reliability.py +298 -0
  9. walopy-0.2.6/src/walopy/scheduling.py +315 -0
  10. {walopy-0.2.3 → walopy-0.2.6/src/walopy.egg-info}/PKG-INFO +476 -22
  11. {walopy-0.2.3 → walopy-0.2.6}/src/walopy.egg-info/SOURCES.txt +6 -1
  12. walopy-0.2.6/tests/test_inventory_new.py +304 -0
  13. walopy-0.2.6/tests/test_mg1.py +141 -0
  14. walopy-0.2.6/tests/test_v026.py +276 -0
  15. walopy-0.2.3/src/walopy/inventory.py +0 -407
  16. {walopy-0.2.3 → walopy-0.2.6}/LICENSE +0 -0
  17. {walopy-0.2.3 → walopy-0.2.6}/setup.cfg +0 -0
  18. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/__main__.py +0 -0
  19. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/_utils.py +0 -0
  20. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/bottleneck.py +0 -0
  21. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/fitting.py +0 -0
  22. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/kpi.py +0 -0
  23. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/network.py +0 -0
  24. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/operations.py +0 -0
  25. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/plotting.py +0 -0
  26. {walopy-0.2.3 → walopy-0.2.6}/src/walopy/solver.py +0 -0
  27. {walopy-0.2.3 → walopy-0.2.6}/src/walopy.egg-info/dependency_links.txt +0 -0
  28. {walopy-0.2.3 → walopy-0.2.6}/src/walopy.egg-info/requires.txt +0 -0
  29. {walopy-0.2.3 → walopy-0.2.6}/src/walopy.egg-info/top_level.txt +0 -0
  30. {walopy-0.2.3 → walopy-0.2.6}/tests/test_advanced.py +0 -0
  31. {walopy-0.2.3 → walopy-0.2.6}/tests/test_bottleneck.py +0 -0
  32. {walopy-0.2.3 → walopy-0.2.6}/tests/test_fitting.py +0 -0
  33. {walopy-0.2.3 → walopy-0.2.6}/tests/test_inventory.py +0 -0
  34. {walopy-0.2.3 → walopy-0.2.6}/tests/test_kpi.py +0 -0
  35. {walopy-0.2.3 → walopy-0.2.6}/tests/test_network.py +0 -0
  36. {walopy-0.2.3 → walopy-0.2.6}/tests/test_new_models.py +0 -0
  37. {walopy-0.2.3 → walopy-0.2.6}/tests/test_operations.py +0 -0
  38. {walopy-0.2.3 → walopy-0.2.6}/tests/test_queuing.py +0 -0
  39. {walopy-0.2.3 → walopy-0.2.6}/tests/test_solver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: walopy
3
- Version: 0.2.3
3
+ Version: 0.2.6
4
4
  Summary: Queuing theory, operations analysis, OEE, bottleneck analysis and KPI trees for Python
5
5
  Author-email: LeoSanta15 <angelsanta1@gmail.com>
6
6
  License-Expression: MIT
@@ -63,15 +63,17 @@ pip install walopy
63
63
  9. [Análisis de cuellos de botella](#9-análisis-de-cuellos-de-botella)
64
64
  10. [Balance de línea y tiempo takt](#10-balance-de-línea-y-tiempo-takt)
65
65
  11. [Análisis de punto de equilibrio](#11-análisis-de-punto-de-equilibrio)
66
- 12. [Árboles de KPI](#12-árboles-de-kpi)
67
- 13. [Solvers y optimización](#13-solvers-y-optimización)
68
- 14. [Análisis de sensibilidad](#14-análisis-de-sensibilidad)
69
- 15. [Escenarios en lote (batch_model)](#15-escenarios-en-lote-batch_model)
70
- 16. [Comparar múltiples resultados](#16-comparar-múltiples-resultados)
71
- 17. [Gráficas](#17-gráficas)
72
- 18. [CLI](#18-cli)
73
- 19. [Referencia de módulos](#19-referencia-de-módulos)
74
- 20. [Requisitos](#20-requisitos)
66
+ 12. [Programación de producción (scheduling)](#12-programación-de-producción-scheduling)
67
+ 13. [Confiabilidad](#13-confiabilidad--reliability)
68
+ 14. [Árboles de KPI](#14-árboles-de-kpi)
69
+ 15. [Solvers y optimización](#15-solvers-y-optimización)
70
+ 16. [Análisis de sensibilidad](#16-análisis-de-sensibilidad)
71
+ 17. [Escenarios en lote (batch_model)](#17-escenarios-en-lote-batch_model)
72
+ 18. [Comparar múltiples resultados](#18-comparar-múltiples-resultados)
73
+ 19. [Gráficas](#19-gráficas)
74
+ 20. [CLI](#20-cli)
75
+ 21. [Referencia de módulos](#21-referencia-de-módulos)
76
+ 22. [Requisitos](#22-requisitos)
75
77
 
76
78
  ---
77
79
 
@@ -200,6 +202,49 @@ r = wl.kingman(**fit.to_model_kwargs())
200
202
 
201
203
  ---
202
204
 
205
+ ### `mg1(lam, mu, cs2)` — M/G/1 (Pollaczek-Khinchine exacto)
206
+
207
+ Llegadas Poisson, servicio de **distribución arbitraria** parametrizado por su media (1/μ) y su variabilidad (cs²). La fórmula P-K es **exacta**, no una aproximación.
208
+
209
+ **Fórmula P-K:**
210
+ Wq = λ · E[S²] / (2 · (1 − ρ)), donde E[S²] = (1 + cs²) / μ²
211
+
212
+ ```python
213
+ # Servicio con distribución triangular: calcular cs² primero
214
+ cs2 = wl.cv2_triangular(a=2, m=5, b=10) # a=mín, m=moda, b=máx
215
+ r = wl.mg1(lam=0.15, mu=1/5, cs2=cs2)
216
+
217
+ # Equivalencia con M/M/1 (cs²=1) y M/D/1 (cs²=0)
218
+ r_mm1 = wl.mg1(lam=3.0, mu=5.0, cs2=1.0) # idéntico a mm1(3, 5)
219
+ r_md1 = wl.mg1(lam=3.0, mu=5.0, cs2=0.0) # idéntico a md1(3, 5)
220
+ ```
221
+
222
+ #### Helpers `cv2_*` — CV² por distribución
223
+
224
+ El CV² (varianza / media²) es el único parámetro de distribución que M/G/1 necesita:
225
+
226
+ | Función | Distribución | CV² |
227
+ |---|---|---|
228
+ | `cv2_normal(mean, std)` | Normal | (σ/μ)² |
229
+ | `cv2_triangular(a, m, b)` | Triangular | fórmula exacta |
230
+ | `cv2_uniform(a, b)` | Uniforme | (b−a)² / (12·μ²) |
231
+ | `cv2_erlang(k)` | Erlang-k | 1/k |
232
+ | `cv2_gamma(shape)` | Gamma | 1/shape |
233
+ | `cv2_lognormal(mean, std)` | Lognormal | (σ/μ)² |
234
+ | `cv2_weibull(shape)` | Weibull | Γ(1+2/k)/Γ(1+1/k)²−1 |
235
+
236
+ ```python
237
+ # Servicio con distribución Weibull de forma 2 (rayleigh)
238
+ cs2 = wl.cv2_weibull(shape=2.0) # ≈ 0.273
239
+ r = wl.mg1(lam=1.0, mu=2.0, cs2=cs2)
240
+ print(r.Wq) # tiempo medio de espera en cola
241
+
242
+ # Servicio Erlang-3 (menos variable que exponencial)
243
+ r = wl.mg1(lam=3.0, mu=5.0, cs2=wl.cv2_erlang(3))
244
+ ```
245
+
246
+ ---
247
+
203
248
  ### `littles_law(*, L, lam, W)` — Ley de Little
204
249
 
205
250
  Resuelve L = λ · W para la variable que falte. Exactamente uno de los tres parámetros debe ser `None`.
@@ -654,6 +699,204 @@ print(r_bajo.optimal_qty < 100) # True
654
699
 
655
700
  ---
656
701
 
702
+ ### `ebq(demand_rate, setup_cost, holding_cost, production_rate)` — Lote Económico de Producción (EBQ/EPQ)
703
+
704
+ Extiende el EOQ al caso en que la producción y el consumo ocurren **simultáneamente** (tasa de producción finita P > D).
705
+
706
+ **Fórmula:**
707
+ ```
708
+ Q* = √(2 · D · S / (h · (1 − D/P)))
709
+ Inventario máximo = Q* · (1 − D/P)
710
+ Inventario medio = Inventario máximo / 2
711
+ ```
712
+
713
+ ```python
714
+ r = wl.ebq(
715
+ demand_rate=1000, # D: unidades/período
716
+ setup_cost=50, # S: costo fijo de setup por lote
717
+ holding_cost=2, # h: costo/unidad/período
718
+ production_rate=4000, # P: tasa de producción (debe ser > D)
719
+ )
720
+ print(r)
721
+ # EBQ (batch size) : 258.2 units
722
+ # Max inventory : 193.6
723
+ # Avg inventory : 96.82
724
+ # Order frequency : 3.873 runs/period
725
+ # Cycle time : 0.2582 periods
726
+ # Production time/cyc : 0.06455 periods
727
+ # Total cost : 193.6
728
+ # Holding cost : 96.82
729
+ # Setup cost : 96.82
730
+ ```
731
+
732
+ **`EBQResult` — atributos:**
733
+
734
+ | Atributo | Descripción |
735
+ |---|---|
736
+ | `ebq` | Lote óptimo Q* |
737
+ | `max_inventory` | Nivel máximo de inventario Q*(1−D/P) |
738
+ | `avg_inventory` | Inventario promedio = max/2 |
739
+ | `production_time` | Tiempo de producción por ciclo = Q*/P |
740
+ | `cycle_time` | Duración del ciclo = Q*/D |
741
+ | `total_cost` | Costo total mínimo |
742
+
743
+ ---
744
+
745
+ ### `eoq_multi(demand_rates, ordering_costs, holding_costs, *, names)` — EOQ multi-ítem independiente
746
+
747
+ Resuelve el EOQ para cada ítem por separado sin restricciones compartidas.
748
+
749
+ ```python
750
+ r = wl.eoq_multi(
751
+ demand_rates = [1000, 500, 800],
752
+ ordering_costs = [50, 30, 40 ],
753
+ holding_costs = [2, 1, 1.5],
754
+ names = ["A", "B", "C"],
755
+ )
756
+ print(r.total_cost) # suma de costos óptimos individuales
757
+ df = r.to_frame() # DataFrame con EOQ, costos y frecuencias por ítem
758
+ ```
759
+
760
+ ---
761
+
762
+ ### `ebq_multi(demand_rates, setup_costs, holding_costs, production_rates, *, names)` — EBQ multi-ítem independiente
763
+
764
+ ```python
765
+ r = wl.ebq_multi(
766
+ demand_rates = [1000, 500],
767
+ setup_costs = [50, 30 ],
768
+ holding_costs = [2, 1 ],
769
+ production_rates= [4000, 2000],
770
+ )
771
+ df = r.to_frame() # EBQ, inventario máximo/medio, costo por ítem
772
+ ```
773
+
774
+ ---
775
+
776
+ ### `eoq_multi_constrained(demand_rates, ordering_costs, holding_costs, ...)` — EOQ multi-ítem con restricciones
777
+
778
+ Minimiza el costo total de inventario sujeto a **restricciones lineales** sobre las cantidades de pedido mediante **relajación Lagrangiana** (búsqueda binaria sobre el multiplicador).
779
+
780
+ **Restricciones soportadas:**
781
+
782
+ | Parámetro | Restricción |
783
+ |---|---|
784
+ | `budget`, `budget_unit_costs` | Σ(c_i · Q_i / 2) ≤ budget — valor promedio en inventario |
785
+ | `space`, `space_per_unit` | Σ(s_i · Q_i / 2) ≤ space — espacio promedio ocupado |
786
+ | `constraints` | Lista de `{"name", "weights", "bound"}` — Σ(a_i · Q_i) ≤ B |
787
+
788
+ ```python
789
+ # Restricción de presupuesto
790
+ r = wl.eoq_multi_constrained(
791
+ [1000, 500, 800], [50, 30, 40], [2, 1, 1.5],
792
+ budget=5000,
793
+ budget_unit_costs=[10, 8, 12], # costo unitario de compra
794
+ )
795
+ print(r.binding_constraints) # ["budget"] si la restricción está activa
796
+ print(r.lagrange_multipliers) # {"budget": λ}
797
+ df = r.to_frame() # Q*, holding cost, ordering cost por ítem
798
+
799
+ # Restricción de espacio
800
+ r2 = wl.eoq_multi_constrained(
801
+ [1000, 500, 800], [50, 30, 40], [2, 1, 1.5],
802
+ space=200,
803
+ space_per_unit=[0.5, 0.3, 0.4],
804
+ )
805
+
806
+ # Restricción genérica: suma total de unidades pedidas ≤ 400
807
+ r3 = wl.eoq_multi_constrained(
808
+ [1000, 500, 800], [50, 30, 40], [2, 1, 1.5],
809
+ constraints=[{"name": "total_qty", "weights": [1, 1, 1], "bound": 400}],
810
+ )
811
+ ```
812
+
813
+ **`ConstrainedMultiEOQResult` — atributos:**
814
+
815
+ | Atributo | Descripción |
816
+ |---|---|
817
+ | `items` | Lista de dicts por ítem: nombre, Q*, costos |
818
+ | `total_cost` | Costo total bajo las restricciones |
819
+ | `unconstrained_total_cost` | Costo sin restricciones (cota inferior) |
820
+ | `lagrange_multipliers` | Diccionario {nombre_restricción: λ} |
821
+ | `binding_constraints` | Nombres de restricciones activas (λ > 0) |
822
+
823
+ ---
824
+
825
+ ### `lot_for_lot(demands, setup_cost, holding_cost)` — Lote por Lote
826
+
827
+ Heurística de dimensionado dinámico: ordena **exactamente la demanda de cada período**. Minimiza el costo de almacenamiento (cero inventario en tránsito) a cambio de un setup por período.
828
+
829
+ ```python
830
+ demands = [100, 80, 0, 120, 60] # demandas por período
831
+ r = wl.lot_for_lot(demands, setup_cost=200, holding_cost=1)
832
+ print(r.total_holding_cost) # 0.0 — sin inventario residual
833
+ print(r.n_orders) # 4 — un pedido por período con demanda > 0
834
+ print(r)
835
+ # Method : Lot-for-Lot
836
+ # Number of orders : 4
837
+ # Total cost : 800.0
838
+ ```
839
+
840
+ ---
841
+
842
+ ### `silver_meal(demands, setup_cost, holding_cost)` — Silver-Meal
843
+
844
+ Heurística de dimensionado dinámico que **agrupa períodos** mientras el costo promedio por período decrece, reduciendo el número de setups.
845
+
846
+ ```python
847
+ r = wl.silver_meal(demands, setup_cost=200, holding_cost=1)
848
+ print(r.total_cost <= wl.lot_for_lot(demands, 200, 1).total_cost) # True
849
+ df = r.to_frame() # pedidos: período, cantidad, períodos cubiertos
850
+ ```
851
+
852
+ **`LotSizingResult` — atributos (Lot-for-Lot y Silver-Meal):**
853
+
854
+ | Atributo | Descripción |
855
+ |---|---|
856
+ | `orders` | Lista de pedidos: período, cantidad, períodos cubiertos |
857
+ | `total_cost` | Costo total (setup + almacenamiento) |
858
+ | `total_setup_cost` | Costo total de setups |
859
+ | `total_holding_cost` | Costo total de almacenamiento |
860
+ | `n_orders` | Número de pedidos emitidos |
861
+ | `method` | Nombre de la heurística usada |
862
+
863
+ ---
864
+
865
+ ### `eoq_quantity_discount(demand_rate, ordering_cost, holding_cost_rate, price_breaks)` — EOQ con descuentos por cantidad
866
+
867
+ Evalúa todos los tramos de precio (all-units) y selecciona la cantidad que **minimiza el costo total anual** (compra + pedido + almacenamiento), ajustando el EOQ al tramo válido cuando cae fuera de rango.
868
+
869
+ ```python
870
+ r = wl.eoq_quantity_discount(
871
+ demand_rate=1000,
872
+ ordering_cost=50,
873
+ holding_cost_rate=0.20, # 20% del precio unitario por año
874
+ price_breaks=[
875
+ (0, 10.00), # precio base
876
+ (500, 9.50), # ≥ 500 unidades → $9.50
877
+ (1000, 9.00), # ≥ 1000 unidades → $9.00
878
+ ],
879
+ )
880
+ print(r.optimal_qty) # cantidad óptima seleccionada
881
+ print(r.unit_price) # precio en ese tramo
882
+ print(r.total_cost) # costo anual mínimo
883
+ df = r.to_frame() # todos los candidatos evaluados
884
+ ```
885
+
886
+ **`QuantityDiscountResult` — atributos:**
887
+
888
+ | Atributo | Descripción |
889
+ |---|---|
890
+ | `optimal_qty` | Cantidad óptima ajustada al tramo |
891
+ | `unit_price` | Precio unitario en el tramo seleccionado |
892
+ | `total_cost` | Costo total anual (compra + pedido + almacenamiento) |
893
+ | `purchase_cost` | Costo de compra anual D · precio |
894
+ | `ordering_cost_total` | Costo de pedido anual (D/Q) · K |
895
+ | `holding_cost_total` | Costo de almacenamiento anual (Q/2) · h · precio |
896
+ | `candidates` | Lista de todos los tramos evaluados |
897
+
898
+ ---
899
+
657
900
  ## 8. Análisis de operaciones
658
901
 
659
902
  ### `oee(availability, performance, quality)` — OEE
@@ -762,9 +1005,59 @@ print(be.profit) # 5000.0 — beneficio con actual_units
762
1005
  be.plot() # líneas de ingresos/costos con BEP y zona de beneficio
763
1006
  ```
764
1007
 
1008
+ ### `break_even_multi(fixed_cost, prices, variable_costs, sales_mix)` — Multi-producto
1009
+
1010
+ Calcula el punto de equilibrio para una mezcla de varios productos usando la **contribución marginal ponderada (WACM)**.
1011
+
1012
+ **Fórmula:** WACM = Σ(CMᵢ × mixᵢ) / Σmixᵢ, luego BEP_total = FC / WACM
1013
+
1014
+ ```python
1015
+ r = wl.break_even_multi(
1016
+ fixed_cost=120_000,
1017
+ prices=[50, 80, 120],
1018
+ variable_costs=[30, 50, 70],
1019
+ sales_mix=[3, 2, 1], # proporción de ventas
1020
+ )
1021
+ print(r.weighted_avg_cm) # WACM ≈ 36.67
1022
+ print(r.bep_units_total) # unidades totales en el punto de equilibrio
1023
+ print(r.bep_revenue_total) # ingresos totales en el punto de equilibrio
1024
+
1025
+ for item in r.items:
1026
+ print(item["Name"], item["BEP units"])
1027
+
1028
+ r.to_frame() # DataFrame con una fila por producto
1029
+ ```
1030
+
1031
+ | Atributo | Descripción |
1032
+ |---|---|
1033
+ | `weighted_avg_cm` | Contribución marginal ponderada |
1034
+ | `bep_units_total` | Unidades totales en el BEP |
1035
+ | `bep_revenue_total` | Ingresos totales en el BEP |
1036
+ | `items` | Lista de dicts por producto con `BEP units`, `BEP revenue` |
1037
+
1038
+ ---
1039
+
1040
+ ### `break_even_sales(fixed_cost, variable_cost_ratio, *, actual_revenue)` — Punto de equilibrio en ingresos
1041
+
1042
+ Calcula el BEP en términos de ingresos cuando los costos variables se expresan como porcentaje de las ventas.
1043
+
1044
+ **Fórmula:** BEP = FC / (1 − RCV)
1045
+
1046
+ ```python
1047
+ r = wl.break_even_sales(
1048
+ fixed_cost=50_000,
1049
+ variable_cost_ratio=0.60, # 60% de los ingresos son costos variables
1050
+ actual_revenue=200_000,
1051
+ )
1052
+ print(r.bep_revenue) # 125 000 — ingresos en el BEP
1053
+ print(r.contribution_margin_ratio) # 0.40 — margen de contribución
1054
+ print(r.margin_of_safety_units) # 75 000 — margen de seguridad en ingresos
1055
+ print(r.margin_of_safety_pct) # 0.375 — porcentaje del margen de seguridad
1056
+ ```
1057
+
765
1058
  ---
766
1059
 
767
- ## 12. Árboles de KPI
1060
+ ## 14. Árboles de KPI
768
1061
 
769
1062
  Los árboles de KPI crean jerarquías interactivas visualizadas como **treemap** o **sunburst** con Plotly.
770
1063
 
@@ -817,7 +1110,7 @@ fig.show()
817
1110
 
818
1111
  ---
819
1112
 
820
- ## 13. Solvers y optimización
1113
+ ## 15. Solvers y optimización
821
1114
 
822
1115
  ### `solve_lam(metric, target, *, mu, model)` — Máxima tasa de llegada
823
1116
 
@@ -864,7 +1157,7 @@ r.plot() # gráfica de costo vs número de servidores
864
1157
 
865
1158
  ---
866
1159
 
867
- ## 14. Análisis de sensibilidad
1160
+ ## 16. Análisis de sensibilidad
868
1161
 
869
1162
  ### `sensitivity(model_fn, param, values, **fixed_kwargs)` — Barrido paramétrico
870
1163
 
@@ -895,7 +1188,7 @@ fig.show()
895
1188
 
896
1189
  ---
897
1190
 
898
- ## 15. Escenarios en lote (batch_model)
1191
+ ## 17. Escenarios en lote (batch_model)
899
1192
 
900
1193
  ### `batch_model(model_fn, df, **fixed_kwargs)` — Aplicar modelo a un DataFrame
901
1194
 
@@ -930,7 +1223,7 @@ print(resultado[resultado["_error"].notna()]) # filas fallidas
930
1223
 
931
1224
  ---
932
1225
 
933
- ## 16. Comparar múltiples resultados
1226
+ ## 18. Comparar múltiples resultados
934
1227
 
935
1228
  ### `compare(*results, labels)` — DataFrame comparativo
936
1229
 
@@ -955,7 +1248,7 @@ df2 = wl.compare(wl.mm1(1, 5), wl.mm1(2, 5), wl.mm1(3, 5))
955
1248
 
956
1249
  ---
957
1250
 
958
- ## 17. Gráficas
1251
+ ## 19. Gráficas
959
1252
 
960
1253
  Todos los resultados exponen `.plot()` que devuelve una figura de Matplotlib o Plotly. También se pueden llamar directamente desde `walopy.plotting`.
961
1254
 
@@ -991,7 +1284,7 @@ fig.show()
991
1284
 
992
1285
  ---
993
1286
 
994
- ## 18. CLI
1287
+ ## 20. CLI
995
1288
 
996
1289
  walopy incluye una interfaz de línea de comandos para uso rápido sin escribir código.
997
1290
 
@@ -1033,14 +1326,175 @@ Wq : 0.3 (avg wait time in queue)
1033
1326
 
1034
1327
  ---
1035
1328
 
1036
- ## 19. Referencia de módulos
1329
+ ## 12. Programación de producción (scheduling)
1330
+
1331
+ ### Máquina única — `schedule_single`
1332
+
1333
+ Asigna *n* trabajos a una sola máquina según una regla de prioridad.
1334
+
1335
+ ```python
1336
+ import walopy as wl
1337
+
1338
+ p = [3, 1, 4, 1, 5] # tiempos de proceso
1339
+ d = [8, 3, 10, 4, 12] # fechas de entrega
1340
+ w = [2, 5, 1, 4, 1] # pesos de importancia
1341
+
1342
+ r = wl.schedule_single(p, rule="SPT", due_dates=d, weights=w,
1343
+ names=["A","B","C","D","E"])
1344
+ print(r)
1345
+ ```
1346
+
1347
+ ```
1348
+ Rule : SPT
1349
+ Sequence : B → D → A → C → E
1350
+ Makespan (Cmax) : 14
1351
+ Total completion ΣCj : 37
1352
+ Weighted completion ΣwCj: 89
1353
+ Max lateness : 2
1354
+ Total tardiness ΣTj : 2
1355
+ Tardy jobs : 1
1356
+ ```
1357
+
1358
+ **Reglas disponibles**
1359
+
1360
+ | `rule` | Criterio minimizado | Datos requeridos |
1361
+ |--------|---------------------|-----------------|
1362
+ | `'SPT'` | ΣCj (tiempo total de completación) | — |
1363
+ | `'EDD'` | Lmax (máxima tardanza) | `due_dates` |
1364
+ | `'WSPT'` | ΣwjCj (completación ponderada) | `weights` |
1365
+ | `'CR'` | Ratio crítico dj/pj | `due_dates` |
1366
+ | `'FIFO'` | Orden de llegada (línea base) | — |
1367
+
1368
+ **Atributos de `ScheduleResult`**
1369
+
1370
+ | Atributo | Descripción |
1371
+ |----------|-------------|
1372
+ | `rule` | Regla utilizada |
1373
+ | `sequence` | Lista de nombres en orden de procesamiento |
1374
+ | `jobs` | Lista de `JobSchedule` (por trabajo) |
1375
+ | `makespan` | Cmax = suma de tiempos de proceso |
1376
+ | `total_completion_time` | ΣCj |
1377
+ | `total_weighted_completion_time` | ΣwjCj |
1378
+ | `max_lateness` | max(Lj) |
1379
+ | `total_tardiness` | ΣTj |
1380
+ | `n_tardy` | Número de trabajos tarde |
1381
+
1382
+ ```python
1383
+ df = r.to_frame() # columnas: Name, p, d, w, Start, C, L, T, Tardy
1384
+ ```
1385
+
1386
+ ---
1387
+
1388
+ ### Flow-shop de 2 máquinas — `johnson_flowshop`
1389
+
1390
+ El algoritmo de Johnson encuentra la secuencia óptima que minimiza el makespan cuando todos los trabajos pasan primero por M1 y luego por M2.
1391
+
1392
+ ```python
1393
+ m1 = [3, 8, 5, 7, 2] # tiempos en máquina 1
1394
+ m2 = [5, 2, 8, 4, 6] # tiempos en máquina 2
1395
+
1396
+ r = wl.johnson_flowshop(m1, m2)
1397
+ print(r)
1398
+ ```
1399
+
1400
+ ```
1401
+ Sequence : J1 → J5 → J4 → J3 → J2
1402
+ Makespan : 34
1403
+ ```
1404
+
1405
+ ```python
1406
+ df = r.to_frame()
1407
+ # columnas: Job | M1 start | M1 end | M2 start | M2 end
1408
+ ```
1409
+
1410
+ ---
1411
+
1412
+ ## 13. Confiabilidad — `reliability`
1413
+
1414
+ Modelos de confiabilidad con distribución exponencial (tasa de falla constante).
1415
+
1416
+ ### Componente individual — `mtbf_analysis`
1417
+
1418
+ ```python
1419
+ r = wl.mtbf_analysis(failure_rate=0.01, mttr=5.0, t=100)
1420
+ print(r)
1421
+ ```
1422
+
1423
+ ```
1424
+ Topology : component
1425
+ Components : 1
1426
+ Failure rates λ : ['0.01']
1427
+ MTBF : 100
1428
+ R(t=100) : 0.367879
1429
+ Availability : 95.2381%
1430
+ ```
1431
+
1432
+ ### Sistema en serie — `series_system`
1433
+
1434
+ El sistema falla si **cualquier** componente falla. λ_sys = Σλi.
1435
+
1436
+ ```python
1437
+ r = wl.series_system([0.01, 0.02, 0.03], t=10)
1438
+ print(r.mtbf) # 1/0.06 ≈ 16.67
1439
+ print(r.R_t) # e^{-0.06·10} ≈ 0.549
1440
+ ```
1441
+
1442
+ ### Sistema en paralelo — `parallel_system`
1443
+
1444
+ El sistema opera si **al menos uno** de los componentes opera.
1445
+ R_sys(t) = 1 − ∏(1 − e^{−λi·t}). MTBF calculado numéricamente.
1446
+
1447
+ ```python
1448
+ r = wl.parallel_system([0.01, 0.02], t=50)
1449
+ print(r.R_t) # mayor que el sistema en serie
1450
+ print(r.mtbf) # mayor que cualquier componente individual
1451
+ ```
1452
+
1453
+ ### Sistema k-de-n — `koon_system`
1454
+
1455
+ Opera si al menos **k** de **n** componentes idénticos están en servicio.
1456
+ MTBF exacto = (1/λ) · Σ_{j=k}^{n} (1/j).
1457
+
1458
+ ```python
1459
+ # 2-de-3: requiere al menos 2 de 3 componentes idénticos
1460
+ r = wl.koon_system(n=3, k=2, failure_rate=0.01, t=50)
1461
+ print(r.mtbf) # (1/0.01) * (1/2 + 1/3) ≈ 83.33
1462
+ print(r.R_t)
1463
+ ```
1464
+
1465
+ **Casos especiales**
1466
+ - `k=1` → equivale a `parallel_system` con n componentes idénticos.
1467
+ - `k=n` → equivale a `series_system` con n componentes idénticos.
1468
+
1469
+ **Atributos de `ReliabilityResult`**
1470
+
1471
+ | Atributo | Descripción |
1472
+ |----------|-------------|
1473
+ | `topology` | `'component'`, `'series'`, `'parallel'`, `'k-of-n'` |
1474
+ | `n_components` | Número de componentes |
1475
+ | `failure_rates` | Lista de λi |
1476
+ | `mtbf` | MTBF del sistema |
1477
+ | `R_t` | Confiabilidad R(t) si se proporcionó `t` |
1478
+ | `availability` | Disponibilidad estacionaria A si se proporcionó `mttr` |
1479
+ | `mttr` | Tiempo medio de reparación (si se proporcionó) |
1480
+
1481
+ ```python
1482
+ df = r.to_frame() # columnas: Topology, Components, MTBF[, R(t), Availability]
1483
+ r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
1484
+ ```
1485
+
1486
+ ---
1487
+
1488
+ ## 21. Referencia de módulos
1037
1489
 
1038
1490
  | Módulo | Funciones y clases principales |
1039
1491
  |---|---|
1040
- | `queuing` | `mm1`, `mmc`, `md1`, `kingman`, `littles_law`, `QueueResult` |
1041
- | `advanced` | `mm1k`, `mmck`, `erlang_b`, `mm1_priority`, `monte_carlo_gg1`, `takt_time`, `line_balance`, `break_even`, `queue_length_pmf`, `sojourn_cdf`, `PriorityQueueResult`, `SimulationResult`, `LineBalanceResult`, `BreakEvenResult` |
1492
+ | `queuing` | `mm1`, `mmc`, `md1`, `kingman`, `mg1`, `littles_law`, `QueueResult`, `cv2_normal`, `cv2_triangular`, `cv2_uniform`, `cv2_erlang`, `cv2_gamma`, `cv2_lognormal`, `cv2_weibull` |
1493
+ | `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` |
1042
1494
  | `fitting` | `fit_from_data`, `FitResult` |
1043
- | `inventory` | `eoq`, `reorder_point`, `newsvendor`, `EOQResult`, `ReorderResult`, `NewsvendorResult` |
1495
+ | `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult` |
1496
+ | `scheduling` | `schedule_single`, `johnson_flowshop`, `ScheduleResult`, `JobSchedule`, `FlowShopResult` |
1497
+ | `reliability` | `mtbf_analysis`, `series_system`, `parallel_system`, `koon_system`, `ReliabilityResult` |
1044
1498
  | `network` | `jackson_network`, `JacksonResult`, `StationMetrics` |
1045
1499
  | `operations` | `oee`, `utilization_efficiency`, `unit_cost`, `OEEResult`, `UtilizationResult`, `UnitCostResult` |
1046
1500
  | `bottleneck` | `bottleneck_analysis`, `BottleneckResult`, `StationResult` |
@@ -1050,7 +1504,7 @@ Wq : 0.3 (avg wait time in queue)
1050
1504
 
1051
1505
  ---
1052
1506
 
1053
- ## 20. Requisitos
1507
+ ## 22. Requisitos
1054
1508
 
1055
1509
  ```
1056
1510
  Python ≥ 3.9