LotSizingDataModel.Solver 2.0.1
Solver-independent modeling, execution, monitoring and adapter infrastructure.
Loading...
Searching...
No Matches
GenericProductionSetupBigMEstimator.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using LotSizingDataModel.Core.LogicalModel;
5using LotSizingDataModel.Core.Relationships;
6using LotSizingDataModel.Instance;
7
9
10/// <summary>
11/// Provides the default generic production/setup Big-M
12/// estimator.
13/// </summary>
14/// <remarks>
15/// <para>
16/// The estimator is deliberately solver-independent and does not
17/// rely on a benchmark-specific structure. It supports arbitrary
18/// acyclic bills of materials, several external demands, shared
19/// components, several production routings for the same item,
20/// minimum lot sizes, lot-size multiples, and safety-stock
21/// requirements.
22/// </para>
23/// <para>
24/// The main structural bound is the full-horizon gross requirement
25/// of the produced item. Gross requirements are obtained by
26/// recursively propagating all external demand through the BOM.
27/// Using the full horizon rather than only periods t..T keeps the
28/// bound conservative when backlog and positive lead times are
29/// present.
30/// </para>
31/// <para>
32/// Minimum lot sizes and lot-size multiples can require production
33/// above gross demand in a single setup. The estimate is therefore
34/// enlarged when those restrictions require it. Safety-stock
35/// requirements are also included because they can require
36/// production even when direct demand is zero.
37/// </para>
38/// <para>
39/// If no structural finite positive bound can be established, the
40/// configured formulation fallback is returned. The fallback is a
41/// last resort and is explicitly identified in the estimate.
42/// </para>
43/// </remarks>
46{
47 /// <summary>
48 /// Estimates a finite production upper bound for one routing
49 /// and period.
50 /// </summary>
52 LotSizingInstance instance,
53 ProductionRouting routing,
54 int period,
56 {
57 ArgumentNullException.ThrowIfNull(instance);
58 ArgumentNullException.ThrowIfNull(routing);
59 ArgumentNullException.ThrowIfNull(options);
60
61 options.EnsureValid();
62
63 if (period < 1 || period > instance.PlanningHorizon)
64 {
65 throw new ArgumentOutOfRangeException(
66 nameof(period),
67 period,
68 "The period must belong to the planning horizon.");
69 }
70
72 {
74 options.ProductionSetupBigM,
75 "Configured fixed ProductionSetupBigM.",
76 isFallback: true);
77 }
78
79 double grossRequirement;
80
81 try
82 {
83 grossRequirement =
84 CalculateFullHorizonGrossRequirement(
85 instance,
86 routing.ItemId);
87 }
88 catch (InvalidOperationException)
89 {
90 return CreateFallback(
91 options,
92 "Automatic gross-requirement propagation could " +
93 "not establish a finite bound.");
94 }
95
96 double safetyStockRequirement =
97 CalculateSafetyStockRequirement(
98 instance,
99 routing.ItemId);
100
101 double structuralBound =
102 checkedFiniteSum(
103 grossRequirement,
104 safetyStockRequirement);
105
106 double minimumLotSize =
107 routing.MinimumLotSize?[period] ?? 0.0;
108
109 if (!double.IsFinite(minimumLotSize) ||
110 minimumLotSize < 0.0)
111 {
112 return CreateFallback(
113 options,
114 "The routing minimum lot size is not a valid " +
115 "finite non-negative number.");
116 }
117
118 double bound =
119 Math.Max(
120 structuralBound,
121 minimumLotSize);
122
123 if (routing.LotSizeMultiple is not null)
124 {
125 double multiple =
126 routing.LotSizeMultiple[period];
127
128 if (!double.IsFinite(multiple) || multiple <= 0.0)
129 {
130 return CreateFallback(
131 options,
132 "The routing lot-size multiple is not a " +
133 "valid finite positive number.");
134 }
135
136 if (bound > 0.0)
137 {
138 bound =
139 Math.Ceiling(bound / multiple) * multiple;
140 }
141 }
142
143 if (!double.IsFinite(bound) || bound < 0.0)
144 {
145 return CreateFallback(
146 options,
147 "The automatically computed production bound " +
148 "is not finite.");
149 }
150
151 if (bound <= options.StructuralZeroTolerance)
152 {
154 0.0,
155 "No external, BOM-induced, safety-stock, or " +
156 "minimum-lot requirement exists for this item.",
157 isFallback: false);
158 }
159
161 bound,
162 $"Automatic structural bound: grossRequirement=" +
163 $"{grossRequirement:G17}; safetyStock=" +
164 $"{safetyStockRequirement:G17}; minimumLotSize=" +
165 $"{minimumLotSize:G17}.",
166 isFallback: false);
167 }
168
169 private static double CalculateFullHorizonGrossRequirement(
170 LotSizingInstance instance,
171 int itemId)
172 {
173 var memo =
174 new Dictionary<int, double>();
175
176 var visiting =
177 new HashSet<int>();
178
179 return CalculateGrossRequirementRecursive(
180 instance,
181 itemId,
182 memo,
183 visiting);
184 }
185
186 private static double CalculateGrossRequirementRecursive(
187 LotSizingInstance instance,
188 int itemId,
189 IDictionary<int, double> memo,
190 ISet<int> visiting)
191 {
192 if (memo.TryGetValue(itemId, out double cached))
193 {
194 return cached;
195 }
196
197 if (!visiting.Add(itemId))
198 {
199 throw new InvalidOperationException(
200 "The bill of materials contains a cycle.");
201 }
202
203 double result =
204 0.0;
205
206 foreach (
207 Demand demand
208 in instance.SupplyChain.Demands.Where(
209 demand => demand.ItemId == itemId))
210 {
211 for (int period = 1;
212 period <= instance.PlanningHorizon;
213 period++)
214 {
215 result =
216 checkedFiniteSum(
217 result,
218 demand.GetQuantity(period));
219 }
220 }
221
222 foreach (
223 ComponentRequirement requirement
224 in instance.SupplyChain.ComponentRequirements.Where(
225 requirement =>
226 requirement.ComponentItemId == itemId))
227 {
228 double parentRequirement =
229 CalculateGrossRequirementRecursive(
230 instance,
231 requirement.ParentItemId,
232 memo,
233 visiting);
234
235 double inducedRequirement =
236 parentRequirement * requirement.Quantity;
237
238 if (!double.IsFinite(inducedRequirement) ||
239 inducedRequirement < 0.0)
240 {
241 throw new InvalidOperationException(
242 "The propagated BOM requirement is not finite.");
243 }
244
245 result =
246 checkedFiniteSum(
247 result,
248 inducedRequirement);
249 }
250
251 visiting.Remove(itemId);
252 memo[itemId] = result;
253
254 return result;
255 }
256
257 private static double CalculateSafetyStockRequirement(
258 LotSizingInstance instance,
259 int itemId)
260 {
261 double total =
262 0.0;
263
264 foreach (
265 Inventory inventory
266 in instance.SupplyChain.Inventories.Where(
267 inventory => inventory.ItemId == itemId))
268 {
269 if (inventory.SafetyStock is null)
270 {
271 continue;
272 }
273
274 double maximum =
275 0.0;
276
277 for (int period = 1;
278 period <= instance.PlanningHorizon;
279 period++)
280 {
281 maximum =
282 Math.Max(
283 maximum,
284 inventory.SafetyStock[period]);
285 }
286
287 total =
288 checkedFiniteSum(
289 total,
290 maximum);
291 }
292
293 return total;
294 }
295
296 private static ProductionSetupBigMEstimate CreateFallback(
297 StandardLotSizingFormulationOptions options,
298 string reason)
299 {
300 return new ProductionSetupBigMEstimate(
301 options.ProductionSetupBigM,
302 reason + " Configured ProductionSetupBigM fallback " +
303 $"{options.ProductionSetupBigM:G17} was used.",
304 isFallback: true);
305 }
306
307 private static double checkedFiniteSum(
308 double left,
309 double right)
310 {
311 double result = left + right;
312
313 if (!double.IsFinite(result) || result < 0.0)
314 {
315 throw new InvalidOperationException(
316 "A structural Big-M calculation overflowed or " +
317 "produced a negative value.");
318 }
319
320 return result;
321 }
322}
ProductionSetupBigMEstimate Estimate(LotSizingInstance instance, ProductionRouting routing, int period, StandardLotSizingFormulationOptions options)
Estimates a finite production upper bound for one routing and period.
Configures the standard solver-independent mixed-integer lot-sizing formulation.
double ProductionSetupBigM
Gets or sets the fallback Big-M used when the automatic estimator cannot establish a finite structura...
bool UseAutomaticProductionSetupBigM
Gets or sets whether production/setup Big-M values are estimated automatically from the instance stru...
Estimates a valid finite upper bound for one production quantity used in a production/setup linking c...