LotSizingDataModel.Solver.Console 2.0.1
Console application for executing solvers and validating produced solutions.
Loading...
Searching...
No Matches
Program.cs
Go to the documentation of this file.
1using System.Text;
2using LotSizingDataModel.Checker.Common;
3using LotSizingDataModel.Checker.Configuration;
4using LotSizingDataModel.Checker.Facade;
5using LotSizingDataModel.Checker.Reporting;
6using LotSizingDataModel.Checker.Results;
7using LotSizingDataModel.Instance;
8using LotSizingDataModel.Instance.Common;
9using LotSizingDataModel.Instance.Creation;
10using LotSizingDataModel.Instance.Results;
11using LotSizingDataModel.Instance.Serialization;
12using LotSizingDataModel.Solution.Common;
13using LotSizingDataModel.Solver.Common;
14using LotSizingDataModel.Solver.Configuration;
16using LotSizingDataModel.Solver.Discovery;
17using LotSizingDataModel.Solver.Execution;
18using LotSizingDataModel.Solver.Formulation;
19using LotSizingDataModel.Solver.Modeling;
20
21Console.OutputEncoding =
22 Encoding.UTF8;
23
25 @"C:\Users\david\Documents\test\NouveauFormat\Petit";
26
27SolverKind preferredSolver =
29
32
34 GetOptionValue(args, "--input") ??
36
38 preferredSolver == SolverKind.Cplex
39 ? "résolu"
40 : $"résolu-{GetSolverSlug(preferredSolver)}";
41
43 Path.Combine(
46
47Console.WriteLine(
48 "LotSizingDataModel - multi-solver batch + independent checker");
49Console.WriteLine(
50 "============================================================");
51Console.WriteLine();
52Console.WriteLine(
53 $"Solveur demandé : {solverDisplayName}");
54Console.WriteLine(
55 $"Répertoire d'entrée : {inputDirectory}");
56Console.WriteLine(
57 $"Répertoire résolu : {resolvedDirectory}");
58Console.WriteLine();
59
60if (!Directory.Exists(
62{
63 Console.Error.WriteLine(
64 "ERREUR : le répertoire d'entrée n'existe pas.");
65
66 Environment.ExitCode =
67 1;
68
69 return;
70}
71
72Directory.CreateDirectory(
74
75/*
76 * Only XML files located directly in Petit are selected.
77 * Files located in Petit\\résolu are therefore never processed.
78 */
79string[] inputFiles =
80 Directory
81 .EnumerateFiles(
83 "*.xml",
84 SearchOption.TopDirectoryOnly)
85 .OrderBy(
86 path =>
87 Path.GetFileName(path),
88 StringComparer.OrdinalIgnoreCase)
89 .ToArray();
90
91if (inputFiles.Length == 0)
92{
93 Console.WriteLine(
94 "Aucune instance XML à traiter.");
95
96 return;
97}
98
99Console.WriteLine(
100 $"{inputFiles.Length} instance(s) à traiter.");
101Console.WriteLine();
102
104 new StandardLotSizingFormulationOptions();
105
107 new SolverDiscoveryOptions();
108
110 new LotSizingSolutionVerificationService();
111
113 new SolutionCheckTextReportFormatter();
114
116 new SolutionVerificationOptions
117 {
118 CheckOptions =
119 new SolutionCheckOptions
120 {
121 Level =
122 SolutionCheckLevel.Full
123 },
124 ApplyToSolutionEvaluation =
125 true,
126 UpdateKnownResultFeasibility =
127 true,
128 PromoteFullyVerifiedKnownResult =
129 true,
130 EvaluatorName =
131 "LotSizingSolutionChecker",
132 EvaluatorVersion =
133 "1.1"
134 };
135
137 new SolutionCheckReportOptions
138 {
139 IncludeStageDetails =
140 true,
141 IncludeFeasibilityMetrics =
142 true,
143 IncludeObjectiveMetrics =
144 true,
145 IncludeInformationIssues =
146 true,
147 IncludeWarningIssues =
148 true,
149 IncludeErrorIssues =
150 true,
151 MaximumDetailedIssues =
152 100,
153 SortIssuesBySeverity =
154 true
155 };
156
157Console.WriteLine(
158 $"Initialisation du solveur {solverDisplayName}...");
159
160StandardLotSizingSolverBootstrapResult bootstrap;
161
162try
163{
164 bootstrap =
165 await StandardLotSizingSolverBootstrapper.InitializeAsync(
168}
169catch (System.Exception exception)
170{
171 Console.Error.WriteLine(
172 "ERREUR : échec de l'initialisation du solveur.");
173
174 Console.Error.WriteLine(
175 exception);
176
177 Environment.ExitCode =
178 2;
179
180 return;
181}
182
183if (!bootstrap.IsSuccessful ||
184 bootstrap.SolverService is null)
185{
186 Console.Error.WriteLine(
187 "ERREUR : l'infrastructure de résolution " +
188 "n'a pas pu être initialisée.");
189
190 Environment.ExitCode =
191 3;
192
193 return;
194}
195
196if (!bootstrap.CanSolve)
197{
198 Console.Error.WriteLine(
199 "ERREUR : aucun solveur utilisable n'a été détecté.");
200
201 Environment.ExitCode =
202 4;
203
204 return;
205}
206
207Console.WriteLine(
208 $"Infrastructure prête pour {solverDisplayName}.");
209Console.WriteLine(
210 "Checker indépendant prêt.");
211Console.WriteLine();
212
214 0;
215
217 0;
218
220 0;
221
223 new List<string>();
224
226 new List<string>();
227
228for (int fileIndex = 0;
229 fileIndex < inputFiles.Length;
230 fileIndex++)
231{
232 string inputPath =
233 inputFiles[fileIndex];
234
235 string inputFileName =
236 Path.GetFileName(
237 inputPath);
238
239 string instanceStem =
240 Path.GetFileNameWithoutExtension(
241 inputFileName);
242
243 string outputPath =
244 Path.Combine(
246 inputFileName);
247
248 string mathematicalModelPath =
249 Path.Combine(
251 $"{instanceStem}.mathematical-model.txt");
252
253 string solutionCheckReportPath =
254 Path.Combine(
256 $"{instanceStem}.solution-check.txt");
257
258 Console.WriteLine(
259 new string(
260 '=',
261 78));
262
263 Console.WriteLine(
264 $"INSTANCE {fileIndex + 1}/{inputFiles.Length}");
265
266 Console.WriteLine(
267 inputFileName);
268
269 Console.WriteLine(
270 new string(
271 '-',
272 78));
273
274 try
275 {
276 DeleteIfExists(outputPath);
277 DeleteIfExists(solutionCheckReportPath);
278 DeleteIfExists(mathematicalModelPath);
279
280 Console.WriteLine(
281 "1/7 - Lecture de l'instance...");
282
283 LotSizingInstance instance =
284 LotSizingInstanceXmlSerializer.DeserializeFromFile(
285 inputPath);
286
287 Console.WriteLine(
288 $" OK - {instance.Name} " +
289 $"({instance.PlanningHorizon} périodes)");
290
291 Console.WriteLine(
292 "2/7 - Construction du modèle mathématique de diagnostic...");
293
294 MathematicalModelFormulationRegistry formulationRegistry =
295 StandardLotSizingFormulationRegistryFactory.Create(
297
298 var modelBuildOptions =
299 new MathematicalModelBuildOptions
300 {
301 RequestedFormulationId =
302 StandardLotSizingFormulation.StandardFormulationId,
303
304 AllowFallback =
305 false,
306
307 ValidateGeneratedModel =
308 true,
309
310 CloneGeneratedModel =
311 false
312 };
313
314 var modelBuildService =
315 new MathematicalModelBuildService();
316
317 MathematicalModelBuildResult modelBuildResult =
318 await modelBuildService.BuildAsync(
319 instance,
320 formulationRegistry,
321 modelBuildOptions);
322
323 if (!modelBuildResult.IsSuccessful ||
324 modelBuildResult.Model is null)
325 {
326 throw new InvalidOperationException(
327 "Impossible de construire le modèle mathématique. " +
328 string.Join(
329 " | ",
330 modelBuildResult.Diagnostics));
331 }
332
333 MathematicalModel mathematicalModel =
334 modelBuildResult.Model;
335
337 mathematicalModel,
338 mathematicalModelPath);
339
340 Console.WriteLine(
341 $" OK - {mathematicalModel.VariableCount} variables, " +
342 $"{mathematicalModel.EnabledConstraintCount} contraintes.");
343
344 Console.WriteLine(
345 $" Dump : {mathematicalModelPath}");
346
347 Console.WriteLine(
348 $"3/7 - Solveur demandé : {solverDisplayName}.");
349
350 Console.WriteLine(
351 "4/7 - Résolution...");
352
353 var request =
354 new SolverRequest(
355 instance)
356 {
357 PreferredSolver =
359
360 FormulationName =
361 StandardLotSizingFormulation.StandardFormulationId,
362
363 RunName =
364 $"{solverDisplayName} - {instanceStem}",
365
366 Parameters =
367 new SolverParameters()
368 };
369
370 SolverRunResult runResult =
371 await bootstrap.SolverService.SolveAsync(
372 request);
373
374 Console.WriteLine(
375 $" Solveur : " +
376 $"{runResult.SolverName} {runResult.SolverVersion}");
377
378 Console.WriteLine(
379 $" Statut : {runResult.TerminationReason}");
380
381 Console.WriteLine(
382 $" Durée : {runResult.ElapsedSeconds:F3} s");
383
384 if (runResult.ObjectiveValue.HasValue)
385 {
386 Console.WriteLine(
387 $" Objectif solveur : " +
388 $"{runResult.ObjectiveValue.Value:G17}");
389 }
390
391 if (runResult.RecomputedObjectiveValue.HasValue)
392 {
393 Console.WriteLine(
394 $" Objectif recalculé par Solver : " +
395 $"{runResult.RecomputedObjectiveValue.Value:G17}");
396
397 Console.WriteLine(
398 $" Vérification interne Solver : " +
399 $"{runResult.ObjectiveVerificationStatus}");
400
401 if (runResult.ObjectiveDifference.HasValue)
402 {
403 Console.WriteLine(
404 $" Écart interne Solver : " +
405 $"{runResult.ObjectiveDifference.Value:G17}");
406 }
407 }
408
409 if (!runResult.HasSolution ||
410 runResult.Solution is null)
411 {
412 string diagnostics =
413 runResult.Diagnostics.Count == 0
414 ? "Aucun diagnostic supplémentaire."
415 : string.Join(
416 " | ",
417 runResult.Diagnostics);
418
419 throw new InvalidOperationException(
420 "Aucune solution exploitable n'a été produite. " +
421 diagnostics);
422 }
423
424 Console.WriteLine(
425 "5/7 - Préparation du résultat candidat...");
426
427 string resultId =
428 $"solver-{runResult.RunId}";
429
430 string fingerprint =
431 LotSizingInstanceFactory.ComputeSupplyChainFingerprint(
432 instance.SupplyChain);
433
434 var knownResult =
435 new KnownResult(
436 resultId)
437 {
438 Name =
439 $"{runResult.SolverName} solution",
440
441 /*
442 * Keep the raw objective reported by the native solver.
443 * VerifyKnownResultAsync uses this value as the external
444 * reference and recomputes the objective independently from
445 * the mapped detailed solution.
446 */
447 ReportedObjectiveValue =
448 runResult.ObjectiveValue,
449
450 ObjectiveName =
451 "TotalCost",
452
453 OptimalityStatus =
454 runResult.TerminationReason ==
455 SolverTerminationReason.Optimal
456 ? OptimalityStatus.ProvenOptimal
457 : OptimalityStatus.NoProof,
458
459 DetailedSolution =
460 runResult.Solution,
461
462 MethodName =
463 string.IsNullOrWhiteSpace(
464 runResult.SolverVersion)
465 ? runResult.SolverName
466 : $"{runResult.SolverName} " +
467 $"{runResult.SolverVersion}",
468
469 ObtainedAtUtc =
470 runResult.CompletedAtUtc ??
471 DateTime.UtcNow,
472
473 RecordedAtUtc =
474 DateTime.UtcNow,
475
476 SupplyChainFingerprint =
477 fingerprint,
478
479 Comment =
480 $"Termination reason: " +
481 $"{runResult.TerminationReason}. " +
482 $"Elapsed time: " +
483 $"{runResult.ElapsedSeconds:F6} s."
484 };
485
486 Console.WriteLine(
487 $" OK - candidat {knownResult.ResultId} préparé.");
488
489 Console.WriteLine(
490 "6/7 - Vérification indépendante de la solution...");
491
492 LotSizingSolutionVerificationResult verification =
493 await verificationService.VerifyKnownResultAsync(
494 instance,
495 knownResult,
496 mathematicalModel,
498
499 SolutionCheckResult checkResult =
500 verification.CheckResult;
501
502 string checkReport =
503 reportFormatter.Format(
504 checkResult,
505 candidateName:
506 $"{inputFileName} / {knownResult.ResultId}",
507 options:
509
510 await File.WriteAllTextAsync(
511 solutionCheckReportPath,
512 checkReport,
513 new UTF8Encoding(
514 encoderShouldEmitUTF8Identifier: false));
515
517 checkResult,
518 solutionCheckReportPath);
519
520 if (!verification.IsValid)
521 {
523
524 rejectedFiles.Add(
525 inputFileName);
526
527 Console.Error.WriteLine(
528 " REJET - la solution ne satisfait pas le checker indépendant.");
529
530 Console.Error.WriteLine(
531 " L'instance n'est pas écrite dans le répertoire résolu " +
532 "et ce résultat n'est pas enregistré comme KnownResult.");
533
534 Console.WriteLine();
535
536 continue;
537 }
538
539 instance.AddKnownResult(
540 knownResult);
541
542 if (runResult.TerminationReason ==
543 SolverTerminationReason.Optimal ||
544 !instance.HasBestKnownResultId)
545 {
546 instance.SetBestKnownResult(
547 knownResult);
548 }
549
550 Console.WriteLine(
551 " ACCEPTÉ - résultat vérifié et ajouté à l'instance.");
552
553 Console.WriteLine(
554 "7/7 - Écriture de l'instance résolue...");
555
556 LotSizingInstanceXmlSerializer.SerializeToFile(
557 instance,
558 outputPath,
559 validateBeforeSerialization: true,
560 validateCurrentFingerprint: true,
561 indent: true);
562
563 Console.WriteLine(
564 $" OK - {outputPath}");
565
566 succeeded++;
567 }
568 catch (System.Exception exception)
569 {
570 failed++;
571
572 failedFiles.Add(
573 inputFileName);
574
575 Console.Error.WriteLine();
576 Console.Error.WriteLine(
577 $"ÉCHEC TECHNIQUE pour {inputFileName}");
578
579 Console.Error.WriteLine(
580 exception.Message);
581
582 /*
583 * The batch deliberately continues with the next instance.
584 * One invalid or unsupported instance must not stop all
585 * remaining resolutions.
586 */
587 }
588
589 Console.WriteLine();
590}
591
592Console.WriteLine(
593 new string(
594 '=',
595 78));
596
597Console.WriteLine(
598 "BILAN DU TRAITEMENT");
599
600Console.WriteLine(
601 new string(
602 '=',
603 78));
604
605Console.WriteLine(
606 $"Instances détectées : {inputFiles.Length}");
607
608Console.WriteLine(
609 $"Résolues et vérifiées : {succeeded}");
610
611Console.WriteLine(
612 $"Rejetées par le checker : {checkerRejected}");
613
614Console.WriteLine(
615 $"Échecs techniques : {failed}");
616
617Console.WriteLine(
618 $"Résultats / rapports : {resolvedDirectory}");
619
620if (rejectedFiles.Count > 0)
621{
622 Console.WriteLine();
623 Console.WriteLine(
624 "Solutions rejetées par le checker :");
625
626 foreach (
627 string rejectedFile
628 in rejectedFiles)
629 {
630 Console.WriteLine(
631 $" - {rejectedFile}");
632 }
633}
634
635if (failedFiles.Count > 0)
636{
637 Console.WriteLine();
638 Console.WriteLine(
639 "Instances en échec technique :");
640
641 foreach (
642 string failedFile
643 in failedFiles)
644 {
645 Console.WriteLine(
646 $" - {failedFile}");
647 }
648}
649
650Environment.ExitCode =
651 checkerRejected == 0 &&
652 failed == 0
653 ? 0
654 : 10;
655
656static SolverKind ParsePreferredSolver(
657 string[] arguments)
658{
659 string? value =
661 arguments,
662 "--solver");
663
664 if (string.IsNullOrWhiteSpace(value))
665 {
666 return SolverKind.Cplex;
667 }
668
669 return value.Trim().ToLowerInvariant() switch
670 {
671 "cplex" => SolverKind.Cplex,
672 "gurobi" => SolverKind.Gurobi,
673 "xpress" => SolverKind.Xpress,
674 "xpressmp" => SolverKind.Xpress,
675 "cbc" => SolverKind.CoinOrCbc,
676 "coinorcbc" => SolverKind.CoinOrCbc,
677 "coin-or-cbc" => SolverKind.CoinOrCbc,
678 _ => throw new ArgumentException(
679 $"Solveur inconnu '{value}'. Valeurs admises : " +
680 "cplex, gurobi, xpress, cbc.")
681 };
682}
683
684static string? GetOptionValue(
685 string[] arguments,
686 string optionName)
687{
688 for (int index = 0;
689 index < arguments.Length;
690 index++)
691 {
692 string argument =
693 arguments[index];
694
695 if (string.Equals(
696 argument,
697 optionName,
698 StringComparison.OrdinalIgnoreCase))
699 {
700 if (index + 1 >= arguments.Length)
701 {
702 throw new ArgumentException(
703 $"L'option '{optionName}' attend une valeur.");
704 }
705
706 return arguments[index + 1];
707 }
708
709 string prefix =
710 optionName + "=";
711
712 if (argument.StartsWith(
713 prefix,
714 StringComparison.OrdinalIgnoreCase))
715 {
716 return argument[prefix.Length..];
717 }
718 }
719
720 return null;
721}
722
724 SolverKind solverKind)
725{
726 return solverKind switch
727 {
728 SolverKind.Cplex => "IBM ILOG CPLEX",
729 SolverKind.Gurobi => "Gurobi Optimizer",
730 SolverKind.Xpress => "FICO Xpress MP",
731 SolverKind.CoinOrCbc => "COIN-OR CBC",
732 _ => solverKind.ToString()
733 };
734}
735
736static string GetSolverSlug(
737 SolverKind solverKind)
738{
739 return solverKind switch
740 {
741 SolverKind.Cplex => "cplex",
742 SolverKind.Gurobi => "gurobi",
743 SolverKind.Xpress => "xpress",
744 SolverKind.CoinOrCbc => "cbc",
745 _ => solverKind.ToString().ToLowerInvariant()
746 };
747}
748
750 SolutionCheckResult result,
751 string reportPath)
752{
753 Console.WriteLine(
754 $" Checker : {(result.IsValid ? "VALID" : "INVALID")}");
755
756 Console.WriteLine(
757 $" Structure : {FormatStage(result.StructuralCheckCompleted, result.IsStructurallyValid)}");
758
759 Console.WriteLine(
760 $" Domaines : {FormatStage(result.VariableDomainCheckCompleted, result.AreVariableDomainsValid)}");
761
762 Console.WriteLine(
763 $" Faisable : {FormatStage(result.FeasibilityCheckCompleted, result.IsFeasible)}");
764
765 Console.WriteLine(
766 $" Objectif : {FormatStage(result.ObjectiveCheckCompleted, result.IsObjectiveConsistent)}");
767
768 if (result.FeasibilityCheckCompleted)
769 {
770 Console.WriteLine(
771 $" Contraintes violées : {result.ViolatedConstraintCount}");
772
773 Console.WriteLine(
774 $" Violation maximale : {result.MaximumConstraintViolation:G17}");
775 }
776
777 if (result.ReportedObjectiveValue.HasValue)
778 {
779 Console.WriteLine(
780 $" Objectif reporté solveur : {result.ReportedObjectiveValue.Value:G17}");
781 }
782
783 if (result.RecomputedObjectiveValue.HasValue)
784 {
785 Console.WriteLine(
786 $" Objectif checker : {result.RecomputedObjectiveValue.Value:G17}");
787 }
788
789 if (result.ObjectiveDifference.HasValue)
790 {
791 Console.WriteLine(
792 $" Écart objectif : {result.ObjectiveDifference.Value:G17}");
793 }
794
795 if (result.ObjectiveComparisonTolerance.HasValue)
796 {
797 Console.WriteLine(
798 $" Tolérance comparaison : {result.ObjectiveComparisonTolerance.Value:G17}");
799 }
800
801 int errorCount =
802 result.Issues.Count(
803 issue =>
804 issue.Severity ==
805 SolutionCheckSeverity.Error);
806
807 int warningCount =
808 result.Issues.Count(
809 issue =>
810 issue.Severity ==
811 SolutionCheckSeverity.Warning);
812
813 Console.WriteLine(
814 $" Diagnostics : {errorCount} erreur(s), {warningCount} avertissement(s)");
815
816 Console.WriteLine(
817 $" Rapport : {reportPath}");
818}
819
820static string FormatStage(
821 bool completed,
822 bool passed)
823{
824 if (!completed)
825 {
826 return "NON EXÉCUTÉ";
827 }
828
829 return passed
830 ? "OK"
831 : "ÉCHEC";
832}
833
834
835static void DeleteIfExists(
836 string path)
837{
838 if (File.Exists(path))
839 {
840 File.Delete(path);
841 }
842}
int checkerRejected
Definition Program.cs:216
string resolvedDirectoryName
Definition Program.cs:37
string resolvedDirectory
Definition Program.cs:42
const string DefaultInputDirectory
Definition Program.cs:24
SolverKind preferredSolver
Definition Program.cs:27
var verificationService
Definition Program.cs:109
string[] inputFiles
Definition Program.cs:79
static string GetSolverSlug(SolverKind solverKind)
Definition Program.cs:736
static string FormatStage(bool completed, bool passed)
Definition Program.cs:820
var discoveryOptions
Definition Program.cs:106
static SolverKind ParsePreferredSolver(string[] arguments)
Definition Program.cs:656
string solverDisplayName
Definition Program.cs:30
static string GetSolverDisplayName(SolverKind solverKind)
Definition Program.cs:723
string inputDirectory
Definition Program.cs:33
var reportFormatter
Definition Program.cs:112
static void DisplayCheckerSummary(SolutionCheckResult result, string reportPath)
Definition Program.cs:749
var reportOptions
Definition Program.cs:136
int succeeded
Definition Program.cs:213
var verificationOptions
Definition Program.cs:115
int failed
Definition Program.cs:219
StandardLotSizingSolverBootstrapResult bootstrap
Definition Program.cs:160
var failedFiles
Definition Program.cs:225
static ? string GetOptionValue(string[] arguments, string optionName)
Definition Program.cs:684
var formulationOptions
Definition Program.cs:103
var rejectedFiles
Definition Program.cs:222
static void DeleteIfExists(string path)
Definition Program.cs:835
Writes a complete human-readable representation of a solver-independent mathematical model.
static void Write(MathematicalModel model, string filePath)
Writes the supplied mathematical model to a text file.