LotSizingDataModel.Checker 2.0.1
Reusable validation engine for structural, mathematical and objective consistency checks.
Loading...
Searching...
No Matches
MathematicalSolutionValueProjector.cs
Go to the documentation of this file.
2using LotSizingDataModel.Core.PhysicalModel;
3using LotSizingDataModel.Solution;
4using LotSizingDataModel.Solution.Decisions;
5using LotSizingDataModel.Solver.Mapping;
6using LotSizingDataModel.Solver.Modeling;
7
9
10/// <summary>
11/// Projects a normalized <see cref="LotSizingSolution"/> onto the
12/// variables of a <see cref="MathematicalModel"/> by using the
13/// canonical mathematical domain-key convention.
14/// </summary>
15/// <remarks>
16/// <para>
17/// This is intentionally the inverse bridge of the Solver mapping
18/// layer. The mathematical formulation remains the single source
19/// of truth for variable meaning.
20/// </para>
21/// <para>
22/// The projector does not test feasibility. It only resolves the
23/// candidate value associated with each mathematical variable.
24/// Constraint and objective checking are implemented by later
25/// checker stages.
26/// </para>
27/// </remarks>
30{
31 /// <inheritdoc/>
33 MathematicalModel model,
34 LotSizingSolution solution)
35 {
36 ArgumentNullException.ThrowIfNull(model);
37 ArgumentNullException.ThrowIfNull(solution);
38
39 model.EnsureValid();
40
41 var result =
43
44 foreach (
45 MathematicalVariable variable
46 in model.Variables)
47 {
48 try
49 {
50 if (string.IsNullOrWhiteSpace(
51 variable.DomainKey))
52 {
53 throw new InvalidOperationException(
54 "The mathematical variable has no domain key.");
55 }
56
57 MathematicalDomainKey domainKey =
58 MathematicalDomainKey.Parse(
59 variable.DomainKey);
60
61 double value =
62 ResolveValue(
63 solution,
64 domainKey);
65
66 result.AddValue(
67 variable.Id,
68 value);
69 }
70 catch (Exception exception)
71 {
72 result.AddIssue(
74 {
75 VariableId =
76 variable.Id,
77 VariableName =
78 variable.Name,
79 DomainKey =
80 variable.DomainKey,
81 Message =
82 exception.Message
83 });
84 }
85 }
86
87 return result;
88 }
89
90 private static double ResolveValue(
91 LotSizingSolution solution,
92 MathematicalDomainKey key)
93 {
94 return key.Category switch
95 {
96 MathematicalDecisionCategory.Production =>
97 ResolveProduction(
98 solution,
99 key,
100 production =>
101 production.GetQuantity(
102 GetPeriod(key))),
103
104 MathematicalDecisionCategory.Setup =>
105 ResolveProduction(
106 solution,
107 key,
108 production =>
109 production.IsSetupActivated(
110 GetPeriod(key))
111 ? 1.0
112 : 0.0),
113
114 MathematicalDecisionCategory.AuxiliaryLotSizeMultiplier =>
115 ResolveProduction(
116 solution,
117 key,
118 production =>
119 production.GetLotMultipleCount(
120 GetPeriod(key))),
121
122 MathematicalDecisionCategory
123 .AuxiliarySmallBucketProductionActivation =>
124 ResolveProduction(
125 solution,
126 key,
127 production =>
128 Math.Abs(
129 production.GetQuantity(
130 GetPeriod(key))) > 1e-9
131 ? 1.0
132 : 0.0),
133
134 MathematicalDecisionCategory
135 .AuxiliarySchedulingSetupStart =>
136 ResolveProduction(
137 solution,
138 key,
139 production =>
140 IsSchedulingSetupStart(
141 production,
142 key)
143 ? 1.0
144 : 0.0),
145
146 MathematicalDecisionCategory.MicroPeriodProduction =>
147 ResolveMicroProduction(solution, key),
148
149 MathematicalDecisionCategory.MicroPeriodSetupState =>
150 ResolveMicroSetupState(solution, key),
151
152 MathematicalDecisionCategory.AuxiliaryMicroPeriodChangeover =>
153 ResolveMicroChangeover(solution, key),
154
155 MathematicalDecisionCategory.AuxiliaryMicroPeriodSetupStart =>
156 ResolveMicroSetupStart(solution, key),
157
158 MathematicalDecisionCategory.AuxiliaryMacroProductionActivation =>
159 ResolveProduction(solution, key, production => Math.Abs(production.GetQuantity(GetPeriod(key))) > 1e-9 ? 1.0 : 0.0),
160
161 MathematicalDecisionCategory.AuxiliaryProductionStartUp =>
162 ResolveProductionStartUp(solution, key),
163
164 MathematicalDecisionCategory.InitialInventory =>
165 ResolveInitialInventory(solution, key),
166
167 MathematicalDecisionCategory.CashBalance =>
168 ResolveCashBalance(solution, key),
169
170 MathematicalDecisionCategory.Inventory =>
171 ResolveInventory(
172 solution,
173 key,
174 inventory =>
175 inventory.GetLevel(
176 GetPeriod(key))),
177
178 MathematicalDecisionCategory.InventorySetup =>
179 ResolveInventory(
180 solution,
181 key,
182 inventory =>
183 inventory.IsSetupActivated(
184 GetPeriod(key))
185 ? 1.0
186 : 0.0),
187
188 MathematicalDecisionCategory.InventorySafetyStockViolation =>
189 ResolveInventory(
190 solution,
191 key,
192 inventory =>
193 inventory.GetSafetyStockViolation(
194 GetPeriod(key))),
195
196 MathematicalDecisionCategory.InventoryAdditionalCapacity =>
197 ResolveInventory(
198 solution,
199 key,
200 inventory =>
201 inventory.GetAdditionalCapacityUsed(
202 GetPeriod(key))),
203
204 MathematicalDecisionCategory.Delivery =>
205 ResolveDistribution(
206 solution,
207 key,
208 decision =>
209 decision.GetDeliveredQuantity(
210 GetPeriod(key))),
211
212 MathematicalDecisionCategory.Backlog =>
213 ResolveDistribution(
214 solution,
215 key,
216 decision =>
217 decision.GetBacklogLevel(
218 GetPeriod(key))),
219
220 MathematicalDecisionCategory.Shortage =>
221 ResolveDistribution(
222 solution,
223 key,
224 decision =>
225 decision.GetShortageQuantity(
226 GetPeriod(key))),
227
228 MathematicalDecisionCategory.Transport =>
229 ResolveTransport(
230 solution,
231 key,
232 decision =>
233 decision.GetTransportedQuantity(
234 GetPeriod(key))),
235
236 MathematicalDecisionCategory.TransportSetup =>
237 ResolveTransport(
238 solution,
239 key,
240 decision =>
241 decision.IsSetupActivated(
242 GetPeriod(key))
243 ? 1.0
244 : 0.0),
245
246 MathematicalDecisionCategory.TransportAdditionalCapacity =>
247 ResolveTransport(
248 solution,
249 key,
250 decision =>
251 decision.GetAdditionalCapacityUsed(
252 GetPeriod(key))),
253
254 MathematicalDecisionCategory.Procurement =>
255 ResolveProcurement(
256 solution,
257 key),
258
259 MathematicalDecisionCategory.WorkCenterActivation =>
260 ResolveWorkCenterCapacity(
261 solution,
262 key,
263 decision =>
264 decision.IsActivated(
265 GetPeriod(key))
266 ? 1.0
267 : 0.0),
268
269 MathematicalDecisionCategory.WorkCenterAdditionalCapacity =>
270 ResolveWorkCenterCapacity(
271 solution,
272 key,
273 decision =>
274 decision.GetAdditionalCapacityUsed(
275 GetPeriod(key))),
276
277 MathematicalDecisionCategory.WarehouseActivation =>
278 ResolveWarehouseCapacity(
279 solution,
280 key,
281 decision =>
282 decision.IsActivated(
283 GetPeriod(key))
284 ? 1.0
285 : 0.0),
286
287 MathematicalDecisionCategory.WarehouseAdditionalCapacity =>
288 ResolveWarehouseCapacity(
289 solution,
290 key,
291 decision =>
292 decision.GetAdditionalCapacityUsed(
293 GetPeriod(key))),
294
295 MathematicalDecisionCategory.TransportResourceActivation =>
296 ResolveTransportResourceCapacity(
297 solution,
298 key,
299 decision =>
300 decision.IsActivated(
301 GetPeriod(key))
302 ? 1.0
303 : 0.0),
304
305 MathematicalDecisionCategory.TransportResourceAdditionalCapacity =>
306 ResolveTransportResourceCapacity(
307 solution,
308 key,
309 decision =>
310 decision.GetAdditionalCapacityUsed(
311 GetPeriod(key))),
312
313 MathematicalDecisionCategory.Subcontracting =>
314 throw new NotSupportedException(
315 "The current normalized LotSizingSolution model " +
316 "does not expose a subcontracting decision family."),
317
318 _ =>
319 throw new NotSupportedException(
320 $"Mathematical decision category " +
321 $"'{key.Category}' is not supported by the " +
322 "solution value projector.")
323 };
324 }
325
326 private static double ResolveMicroProduction(
327 LotSizingSolution solution,
328 MathematicalDomainKey key)
329 {
330 int routingId = key.GetRequiredInt32(MathematicalDomainKeySegment.Routing);
331 ProductionMicroPeriodDecision decision = ResolveMicroDecision(solution, key);
332 return decision.RoutingId == routingId ? decision.Quantity : 0.0;
333 }
334
335 private static double ResolveMicroSetupState(
336 LotSizingSolution solution,
337 MathematicalDomainKey key)
338 {
339 int itemId = key.GetRequiredInt32(MathematicalDomainKeySegment.Item);
340 ProductionMicroPeriodDecision decision = ResolveMicroDecision(solution, key);
341 return decision.SetupItemId == itemId ? 1.0 : 0.0;
342 }
343
344 private static double ResolveMicroChangeover(
345 LotSizingSolution solution,
346 MathematicalDomainKey key)
347 {
348 int fromItemId = key.GetRequiredInt32(MathematicalDomainKeySegment.FromItem);
349 int toItemId = key.GetRequiredInt32(MathematicalDomainKeySegment.ToItem);
350 int plantId = key.GetRequiredInt32(MathematicalDomainKeySegment.Plant);
351 int workCenterId = key.GetRequiredInt32(MathematicalDomainKeySegment.WorkCenter);
352 int period = key.GetRequiredInt32(MathematicalDomainKeySegment.Period);
353 int micro = key.GetRequiredInt32(MathematicalDomainKeySegment.MicroPeriod);
354 WorkCenterSchedulingDecision schedule = solution.WorkCenterSchedulingDecisions.SingleOrDefault(candidate => candidate.WorkCenter.PlantId == plantId && candidate.WorkCenter.WorkCenterId == workCenterId) ?? throw new InvalidOperationException("No candidate macro/micro schedule matches the mathematical domain key.");
355 ProductionMicroPeriodDecision[] ordered = schedule.MicroPeriods.OrderBy(candidate => candidate.MicroPeriod.MacroPeriod).ThenBy(candidate => candidate.MicroPeriod.MicroPeriodIndex).ToArray();
356 int currentIndex = Array.FindIndex(ordered, candidate => candidate.MicroPeriod.MacroPeriod == period && candidate.MicroPeriod.MicroPeriodIndex == micro);
357 if (currentIndex < 0) { throw new InvalidOperationException("No candidate micro-period decision matches the changeover domain key."); }
358 if (currentIndex == 0) { return ordered[0].SetupItemId == toItemId ? 1.0 : 0.0; }
359 return ordered[currentIndex - 1].SetupItemId == fromItemId && ordered[currentIndex].SetupItemId == toItemId ? 1.0 : 0.0;
360 }
361
362 private static double ResolveCashBalance(
363 LotSizingSolution solution,
364 MathematicalDomainKey key)
365 {
366 int period =
367 key.GetRequiredInt32(
368 MathematicalDomainKeySegment.Period);
369
370 return solution.TryGetCashBalance(
371 period,
372 out double balance)
373 ? balance
374 : 0.0;
375 }
376 private static double ResolveInitialInventory(
377 LotSizingSolution solution,
378 MathematicalDomainKey key)
379 {
380 int itemId =
381 key.GetRequiredInt32(
382 MathematicalDomainKeySegment.Item);
383
384 bool hasWarehouse =
385 key.TryGetInt32(
386 MathematicalDomainKeySegment.Warehouse,
387 out int warehouseId);
388
389 bool hasPlant =
390 key.TryGetInt32(
391 MathematicalDomainKeySegment.Plant,
392 out int plantId);
393
394 if (hasWarehouse == hasPlant)
395 {
396 throw new InvalidOperationException(
397 "An initial-inventory key must identify exactly one warehouse.");
398 }
399
400 var decision =
401 solution.InventoryDecisions.SingleOrDefault(
402 candidate =>
403 candidate.ItemId == itemId &&
404 (hasWarehouse
405 ? candidate.Warehouse.Kind ==
406 WarehouseReferenceKind.StandaloneWarehouse &&
407 candidate.Warehouse.ReferenceId == warehouseId
408 : candidate.Warehouse.Kind ==
409 WarehouseReferenceKind.PlantWarehouse &&
410 candidate.Warehouse.ReferenceId == plantId));
411
412 return decision?.InitialInventoryLevel ?? 0.0;
413 }
414 private static double ResolveProductionStartUp(
415 LotSizingSolution solution,
416 MathematicalDomainKey key)
417 {
418 if (key.TryGetInt32(MathematicalDomainKeySegment.MicroPeriod, out _))
419 {
420 return ResolveMicroSetupStart(solution, key);
421 }
422
423 return ResolveProduction(
424 solution,
425 key,
426 production =>
427 IsSchedulingSetupStart(production, key)
428 ? 1.0
429 : 0.0);
430 }
431
432 private static double ResolveMicroSetupStart(
433 LotSizingSolution solution,
434 MathematicalDomainKey key)
435 {
436 int itemId = key.GetRequiredInt32(MathematicalDomainKeySegment.Item);
437 int plantId = key.GetRequiredInt32(MathematicalDomainKeySegment.Plant);
438 int workCenterId = key.GetRequiredInt32(MathematicalDomainKeySegment.WorkCenter);
439 int period = key.GetRequiredInt32(MathematicalDomainKeySegment.Period);
440 int micro = key.GetRequiredInt32(MathematicalDomainKeySegment.MicroPeriod);
441 WorkCenterSchedulingDecision schedule = solution.WorkCenterSchedulingDecisions.SingleOrDefault(candidate => candidate.WorkCenter.PlantId == plantId && candidate.WorkCenter.WorkCenterId == workCenterId) ?? throw new InvalidOperationException("No candidate macro/micro schedule matches the mathematical domain key.");
442 ProductionMicroPeriodDecision[] ordered = schedule.MicroPeriods.OrderBy(candidate => candidate.MicroPeriod.MacroPeriod).ThenBy(candidate => candidate.MicroPeriod.MicroPeriodIndex).ToArray();
443 int currentIndex = Array.FindIndex(ordered, candidate => candidate.MicroPeriod.MacroPeriod == period && candidate.MicroPeriod.MicroPeriodIndex == micro);
444 if (currentIndex < 0) { throw new InvalidOperationException("No candidate micro-period decision matches the setup-start domain key."); }
445 if (ordered[currentIndex].SetupItemId != itemId) { return 0.0; }
446 if (key.TryGetInt32(MathematicalDomainKeySegment.SetupReset, out int reset) && reset != 0) { return 1.0; }
447 if (key.TryGetInt32(MathematicalDomainKeySegment.FromItem, out int fromItem)) { return itemId == fromItem ? 0.0 : 1.0; }
448 if (currentIndex == 0) { return 1.0; }
449 return ordered[currentIndex - 1].SetupItemId == itemId ? 0.0 : 1.0;
450 }
451
452 private static ProductionMicroPeriodDecision ResolveMicroDecision(
453 LotSizingSolution solution,
454 MathematicalDomainKey key)
455 {
456 int plantId = key.GetRequiredInt32(MathematicalDomainKeySegment.Plant);
457 int workCenterId = key.GetRequiredInt32(MathematicalDomainKeySegment.WorkCenter);
458 int period = key.GetRequiredInt32(MathematicalDomainKeySegment.Period);
459 int micro = key.GetRequiredInt32(MathematicalDomainKeySegment.MicroPeriod);
460 WorkCenterSchedulingDecision schedule = solution.WorkCenterSchedulingDecisions.SingleOrDefault(candidate => candidate.WorkCenter.PlantId == plantId && candidate.WorkCenter.WorkCenterId == workCenterId) ?? throw new InvalidOperationException("No candidate macro/micro schedule matches the mathematical domain key.");
461 return schedule.MicroPeriods.SingleOrDefault(candidate => candidate.MicroPeriod.MacroPeriod == period && candidate.MicroPeriod.MicroPeriodIndex == micro) ?? throw new InvalidOperationException("No candidate micro-period decision matches the mathematical domain key.");
462 }
463
464 private static bool IsSchedulingSetupStart(
465 ProductionDecision production,
466 MathematicalDomainKey key)
467 {
468 int period = GetPeriod(key);
469 if (!production.IsSetupActivated(period)) { return false; }
470 if (key.TryGetInt32(MathematicalDomainKeySegment.SetupReset, out int reset) && reset != 0) { return true; }
471 if (period == 1 && key.TryGetInt32(MathematicalDomainKeySegment.FromItem, out int fromItem) && key.TryGetInt32(MathematicalDomainKeySegment.Item, out int itemId)) { return itemId != fromItem; }
472 return period == 1 || !production.IsSetupActivated(period - 1);
473 }
474
475 private static double ResolveProduction(
476 LotSizingSolution solution,
477 MathematicalDomainKey key,
478 Func<ProductionDecision, double> selector)
479 {
480 int routingId =
481 key.GetRequiredInt32(
482 MathematicalDomainKeySegment.Routing);
483
484 ProductionDecision decision =
485 solution.ProductionDecisions
486 .SingleOrDefault(
487 candidate =>
488 candidate.RoutingId ==
489 routingId)
490 ?? throw MissingDecision(
491 key,
492 $"production routing {routingId}");
493
494 return selector(decision);
495 }
496
497 private static double ResolveInventory(
498 LotSizingSolution solution,
499 MathematicalDomainKey key,
500 Func<InventoryDecision, double> selector)
501 {
502 int itemId =
503 key.GetRequiredInt32(
504 MathematicalDomainKeySegment.Item);
505
506 WarehouseReference warehouse =
507 ResolveWarehouse(
508 key,
509 MathematicalDomainKeySegment.Warehouse,
510 MathematicalDomainKeySegment.Plant,
511 "inventory");
512
513 InventoryDecision decision =
514 solution.InventoryDecisions
515 .SingleOrDefault(
516 candidate =>
517 candidate.ItemId ==
518 itemId &&
519 SameWarehouse(
520 candidate.Warehouse,
521 warehouse))
522 ?? throw MissingDecision(
523 key,
524 $"inventory item {itemId}");
525
526 return selector(decision);
527 }
528
529 private static double ResolveDistribution(
530 LotSizingSolution solution,
531 MathematicalDomainKey key,
532 Func<DistributionDecision, double> selector)
533 {
534 int distributionCenterId =
535 key.GetRequiredInt32(
536 MathematicalDomainKeySegment.DistributionCenter);
537
538 int itemId =
539 key.GetRequiredInt32(
540 MathematicalDomainKeySegment.Item);
541
542 WarehouseReference warehouse =
543 ResolveWarehouse(
544 key,
545 MathematicalDomainKeySegment.Warehouse,
546 MathematicalDomainKeySegment.Plant,
547 "distribution source");
548
549 DistributionDecision decision =
550 solution.DistributionDecisions
551 .SingleOrDefault(
552 candidate =>
553 candidate.Matches(
554 distributionCenterId,
555 itemId,
556 warehouse))
557 ?? throw MissingDecision(
558 key,
559 $"distribution center {distributionCenterId}, " +
560 $"item {itemId}");
561
562 return selector(decision);
563 }
564
565 private static double ResolveTransport(
566 LotSizingSolution solution,
567 MathematicalDomainKey key,
568 Func<TransportDecision, double> selector)
569 {
570 int itemId =
571 key.GetRequiredInt32(
572 MathematicalDomainKeySegment.Item);
573
574 int transportResourceId =
575 key.GetRequiredInt32(
576 MathematicalDomainKeySegment.TransportResource);
577
578 WarehouseReference origin =
579 ResolveWarehouse(
580 key,
581 MathematicalDomainKeySegment.OriginWarehouse,
582 MathematicalDomainKeySegment.OriginPlant,
583 "transport origin");
584
585 WarehouseReference destination =
586 ResolveWarehouse(
587 key,
588 MathematicalDomainKeySegment.DestinationWarehouse,
589 MathematicalDomainKeySegment.DestinationPlant,
590 "transport destination");
591
592 TransportDecision decision =
593 solution.TransportDecisions
594 .SingleOrDefault(
595 candidate =>
596 candidate.Matches(
597 itemId,
598 transportResourceId,
599 origin,
600 destination))
601 ?? throw MissingDecision(
602 key,
603 $"transport item {itemId}, resource " +
604 $"{transportResourceId}");
605
606 return selector(decision);
607 }
608
609 private static double ResolveProcurement(
610 LotSizingSolution solution,
611 MathematicalDomainKey key)
612 {
613 int supplierId =
614 key.GetRequiredInt32(
615 MathematicalDomainKeySegment.Supplier);
616
617 int itemId =
618 key.GetRequiredInt32(
619 MathematicalDomainKeySegment.Item);
620
621 WarehouseReference destination =
622 ResolveWarehouse(
623 key,
624 MathematicalDomainKeySegment.DestinationWarehouse,
625 MathematicalDomainKeySegment.DestinationPlant,
626 "procurement destination");
627
628 PurchaseDecision decision =
629 solution.PurchaseDecisions
630 .SingleOrDefault(
631 candidate =>
632 candidate.Matches(
633 supplierId,
634 itemId,
635 destination))
636 ?? throw MissingDecision(
637 key,
638 $"supplier {supplierId}, item {itemId}");
639
640 return decision.GetPurchasedQuantity(
641 GetPeriod(key));
642 }
643
644 private static double ResolveWorkCenterCapacity(
645 LotSizingSolution solution,
646 MathematicalDomainKey key,
647 Func<WorkCenterCapacityDecision, double> selector)
648 {
649 int plantId =
650 key.GetRequiredInt32(
651 MathematicalDomainKeySegment.Plant);
652
653 int workCenterId =
654 key.GetRequiredInt32(
655 MathematicalDomainKeySegment.WorkCenter);
656
657 WorkCenterCapacityDecision decision =
658 solution.WorkCenterCapacityDecisions
659 .SingleOrDefault(
660 candidate =>
661 candidate.WorkCenter.PlantId ==
662 plantId &&
663 candidate.WorkCenter.WorkCenterId ==
664 workCenterId)
665 ?? throw MissingDecision(
666 key,
667 $"work center {plantId}/{workCenterId}");
668
669 return selector(decision);
670 }
671
672 private static double ResolveWarehouseCapacity(
673 LotSizingSolution solution,
674 MathematicalDomainKey key,
675 Func<WarehouseCapacityDecision, double> selector)
676 {
677 WarehouseReference warehouse =
678 ResolveWarehouse(
679 key,
680 MathematicalDomainKeySegment.Warehouse,
681 MathematicalDomainKeySegment.Plant,
682 "warehouse capacity");
683
684 WarehouseCapacityDecision decision =
685 solution.WarehouseCapacityDecisions
686 .SingleOrDefault(
687 candidate =>
688 candidate.Matches(
689 warehouse))
690 ?? throw MissingDecision(
691 key,
692 "warehouse capacity");
693
694 return selector(decision);
695 }
696
697 private static double ResolveTransportResourceCapacity(
698 LotSizingSolution solution,
699 MathematicalDomainKey key,
700 Func<TransportResourceCapacityDecision, double> selector)
701 {
702 int transportResourceId =
703 key.GetRequiredInt32(
704 MathematicalDomainKeySegment.TransportResource);
705
706 TransportResourceCapacityDecision decision =
707 solution.TransportResourceCapacityDecisions
708 .SingleOrDefault(
709 candidate =>
710 candidate.Matches(
711 transportResourceId))
712 ?? throw MissingDecision(
713 key,
714 $"transport resource {transportResourceId}");
715
716 return selector(decision);
717 }
718
719 private static int GetPeriod(
720 MathematicalDomainKey key)
721 {
722 return key.GetRequiredInt32(
723 MathematicalDomainKeySegment.Period);
724 }
725
726 private static WarehouseReference ResolveWarehouse(
727 MathematicalDomainKey key,
728 string standaloneSegment,
729 string plantSegment,
730 string role)
731 {
732 bool hasStandalone =
733 key.TryGetInt32(
734 standaloneSegment,
735 out int warehouseId);
736
737 bool hasPlant =
738 key.TryGetInt32(
739 plantSegment,
740 out int plantId);
741
742 if (hasStandalone == hasPlant)
743 {
744 throw new InvalidOperationException(
745 $"The domain key must identify exactly one {role} " +
746 $"warehouse using either '{standaloneSegment}' or " +
747 $"'{plantSegment}'.");
748 }
749
750 return hasStandalone
751 ? WarehouseReference.ForStandaloneWarehouse(
752 warehouseId)
753 : WarehouseReference.ForPlantWarehouse(
754 plantId);
755 }
756
757 private static bool SameWarehouse(
758 WarehouseReference left,
759 WarehouseReference right)
760 {
761 return left.Kind ==
762 right.Kind &&
763 left.ReferenceId ==
764 right.ReferenceId;
765 }
766
767 private static InvalidOperationException MissingDecision(
768 MathematicalDomainKey key,
769 string decisionDescription)
770 {
771 return new InvalidOperationException(
772 $"No candidate {decisionDescription} decision matches " +
773 $"mathematical domain key '{FormatKey(key)}'.");
774 }
775
776 private static string FormatKey(
777 MathematicalDomainKey key)
778 {
779 return string.Join(
780 "|",
781 new[]
782 {
783 key.Category
784 }.Concat(
785 key.Segments.Select(
786 pair =>
787 $"{pair.Key}={pair.Value}")));
788 }
789}
Describes one issue encountered while projecting a business solution onto mathematical-model variable...
Contains mathematical-variable values projected from one normalized lot-sizing solution.
Projects a normalized LotSizingSolution onto the variables of a MathematicalModel by using the canoni...
MathematicalSolutionProjectionResult Project(MathematicalModel model, LotSizingSolution solution)
Projects candidate business-decision values onto all mathematical variables that have supported domai...
Defines projection of a normalized lot-sizing solution onto the variables of a solver-independent mat...