LotSizingDataModel.Solver 2.0.1
Solver-independent modeling, execution, monitoring and adapter infrastructure.
Loading...
Searching...
No Matches
FinancialExecutionModelDecorator.cs
Go to the documentation of this file.
1using System.Reflection;
2using LotSizingDataModel.Core.DecisionModel.Finance;
3using LotSizingDataModel.Instance;
7
9
10/// <summary>
11/// Adds complete-period OPEX coverage and optional cash-flow constraints to a
12/// built economic mathematical model.
13/// </summary>
15{
17 LotSizingInstance instance,
18 MathematicalModel source)
19 {
20 ArgumentNullException.ThrowIfNull(instance);
21 ArgumentNullException.ThrowIfNull(source);
22
23 MathematicalModel model =
24 source.Clone();
25
26 LinearExpression economic =
27 NormalizeToMinimization(
28 source.Objective);
29
30 AddPeriodicOperatingExpenditureCoverage(
31 instance,
32 model,
33 economic);
34
35 CashFlowPolicy? cashPolicy =
36 instance.SupplyChain.CashFlowPolicy;
37
38 if (cashPolicy is null)
39 {
41 {
42 Model = model,
43 EconomicCriterion = economic,
44 FinancialCriterion = null,
45 FinancialHorizon = 0
46 };
47 }
48
49 if (!cashPolicy.HasConsistentPlanningHorizon ||
50 cashPolicy.PlanningHorizon != instance.PlanningHorizon)
51 {
52 throw new InvalidOperationException(
53 "CashFlowPolicy must use the instance planning horizon.");
54 }
55
56 if (Math.Abs(economic.Constant) > 1.0e-12)
57 {
58 throw new InvalidOperationException(
59 "Cash-flow execution requires a zero constant in the economic objective because an unperiodized constant has no unambiguous cash-flow timing.");
60 }
61
62 int financialHorizon =
63 instance.PlanningHorizon +
64 Math.Max(
65 cashPolicy.ReceiptDelayPeriods,
66 cashPolicy.DisbursementDelayPeriods);
67
68 var cashVariables =
69 AddCashBalanceVariables(
70 model,
71 cashPolicy,
72 financialHorizon);
73
74 AddCashBalanceConstraints(
75 model,
76 economic,
77 cashPolicy,
78 instance.PlanningHorizon,
79 financialHorizon,
80 cashVariables);
81
82 var financial =
83 new LinearExpression();
84
85 financial.AddTerm(
86 cashVariables[financialHorizon].Id,
87 -1.0);
88
89 model.EnsureValid();
90
92 {
93 Model = model,
94 EconomicCriterion = economic,
95 FinancialCriterion = financial,
96 FinancialHorizon = financialHorizon
97 };
98 }
99
100 private static LinearExpression NormalizeToMinimization(
101 MathematicalObjective objective)
102 {
103 ArgumentNullException.ThrowIfNull(objective);
104
105 LinearExpression expression =
106 objective.Expression.Clone();
107
108 if (objective.Sense == ObjectiveSense.Maximize)
109 {
110 expression.MultiplyBy(-1.0);
111 }
112 else if (objective.Sense != ObjectiveSense.Minimize)
113 {
114 throw new InvalidOperationException(
115 "Economic objective sense must be Minimize or Maximize.");
116 }
117
118 return expression;
119 }
120
121 private static Dictionary<int, MathematicalVariable>
122 AddCashBalanceVariables(
123 MathematicalModel model,
124 CashFlowPolicy policy,
125 int financialHorizon)
126 {
127 int nextId =
128 model.Variables.Count == 0
129 ? 1
130 : model.Variables.Max(variable => variable.Id) + 1;
131
132 var result =
133 new Dictionary<int, MathematicalVariable>();
134
135 for (int period = 1;
136 period <= financialHorizon;
137 period++)
138 {
139 double lowerBound =
140 policy.EnforceMinimumCashBalance &&
141 period <= policy.PlanningHorizon
142 ? policy.MinimumCashBalance[period]
143 : double.NegativeInfinity;
144
145 var variable =
147 {
148 Id = nextId++,
149 Name = $"cashBalance_t{period}",
150 DomainKey =
153 .Add(
155 period)
156 .Build(),
157 VariableType =
158 MathematicalVariableType.Continuous,
159 LowerBound = lowerBound,
160 UpperBound = double.PositiveInfinity,
161 Description =
162 "Cash balance after all receipts/disbursements due in the period."
163 };
164
165 model.AddVariable(variable);
166 result.Add(period, variable);
167 }
168
169 return result;
170 }
171
172 private static void AddCashBalanceConstraints(
173 MathematicalModel model,
174 LinearExpression economic,
175 CashFlowPolicy policy,
176 int planningHorizon,
177 int financialHorizon,
178 IReadOnlyDictionary<int, MathematicalVariable> cashVariables)
179 {
180 int nextConstraintId =
181 model.Constraints.Count == 0
182 ? 1
183 : model.Constraints.Max(constraint => constraint.Id) + 1;
184
185 var dueTerms =
186 new Dictionary<int, List<LinearTerm>>();
187
188 for (int period = 1;
189 period <= financialHorizon;
190 period++)
191 {
192 dueTerms[period] = new List<LinearTerm>();
193 }
194
195 foreach (LinearTerm term in economic.Terms)
196 {
197 MathematicalVariable variable =
198 model.FindVariableById(term.VariableId)
199 ?? throw new InvalidOperationException(
200 $"Unknown economic variable id {term.VariableId}.");
201
202 MathematicalDomainKey key =
203 MathematicalDomainKey.Parse(
204 variable.DomainKey);
205
206 if (!key.TryGetInt32(
207 MathematicalDomainKeySegment.Period,
208 out int economicPeriod))
209 {
210 if (key.Category ==
211 MathematicalDecisionCategory.InitialInventory)
212 {
213 economicPeriod = 1;
214 }
215 else
216 {
217 throw new InvalidOperationException(
218 $"Cash-flow timing is ambiguous for economic variable '{variable.Name}' because its domain key has no period.");
219 }
220 }
221
222 int delay =
223 term.Coefficient < 0.0
224 ? policy.ReceiptDelayPeriods
225 : policy.DisbursementDelayPeriods;
226
227 int duePeriod =
228 economicPeriod + delay;
229
230 if (duePeriod < 1 ||
231 duePeriod > financialHorizon)
232 {
233 throw new InvalidOperationException(
234 $"Cash-flow due period {duePeriod} is outside the financial horizon.");
235 }
236
237 dueTerms[duePeriod].Add(
238 new LinearTerm(
239 term.VariableId,
240 term.Coefficient));
241 }
242
243 for (int period = 1;
244 period <= financialHorizon;
245 period++)
246 {
247 var expression =
248 new LinearExpression();
249
250 expression.AddTerm(
251 cashVariables[period].Id,
252 1.0);
253
254 if (period > 1)
255 {
256 expression.AddTerm(
257 cashVariables[period - 1].Id,
258 -1.0);
259 }
260
261 foreach (LinearTerm term in dueTerms[period])
262 {
263 expression.AddTerm(term);
264 }
265
266 double fixedNetCashFlow =
267 period <= planningHorizon
268 ? policy.FixedNetCashFlow[period]
269 : 0.0;
270
271 double rhs =
272 fixedNetCashFlow +
273 (period == 1
274 ? policy.InitialCashBalance
275 : 0.0);
276
277 model.AddConstraint(
278 new LinearConstraint(
279 nextConstraintId++,
280 $"cashBalance_t{period}",
281 expression,
283 rhs)
284 {
285 DomainKey =
286 $"cashBalance|period={period}",
287 Description =
288 "Cash-flow balance distinct from OPEX budget."
289 });
290 }
291 }
292
293 private static void AddPeriodicOperatingExpenditureCoverage(
294 LotSizingInstance instance,
295 MathematicalModel model,
296 LinearExpression economic)
297 {
298 object? budget =
299 instance.SupplyChain
300 .PeriodicOperatingExpenditureBudget;
301
302 if (budget is null)
303 {
304 return;
305 }
306
307 int nextConstraintId =
308 model.Constraints.Count == 0
309 ? 1
310 : model.Constraints.Max(constraint => constraint.Id) + 1;
311
312 for (int period = 1;
313 period <= instance.PlanningHorizon;
314 period++)
315 {
316 var expression =
317 new LinearExpression();
318
319 foreach (LinearTerm term in economic.Terms)
320 {
321 if (term.Coefficient <= 0.0)
322 {
323 continue;
324 }
325
326 MathematicalVariable variable =
327 model.FindVariableById(term.VariableId)
328 ?? throw new InvalidOperationException(
329 $"Unknown economic variable id {term.VariableId}.");
330
331 MathematicalDomainKey key =
332 MathematicalDomainKey.Parse(
333 variable.DomainKey);
334
335 if (!key.TryGetInt32(
336 MathematicalDomainKeySegment.Period,
337 out int termPeriod))
338 {
339 if (key.Category ==
340 MathematicalDecisionCategory.InitialInventory)
341 {
342 continue;
343 }
344
345 throw new InvalidOperationException(
346 $"Periodic OPEX coverage cannot classify positive economic term '{variable.Name}' because it has no period.");
347 }
348
349 if (termPeriod == period)
350 {
351 expression.AddTerm(term);
352 }
353 }
354
355 model.AddConstraint(
356 new LinearConstraint(
357 nextConstraintId++,
358 $"alpha33PeriodicOpexBudget_t{period}",
359 expression,
360 MathematicalConstraintSense.LessThanOrEqual,
361 ReadPeriodValue(
362 budget,
363 period))
364 {
365 DomainKey =
366 $"periodicOperatingExpenditureBudget|period={period}",
367 Description =
368 "Complete positive economic-cost envelope, including scheduling setup/start-up/changeover costs."
369 });
370 }
371 }
372
373 private static double ReadPeriodValue(
374 object parameter,
375 int period)
376 {
377 Type type =
378 parameter.GetType();
379
380 MethodInfo? getter =
381 type.GetMethod(
382 "GetValue",
383 BindingFlags.Instance | BindingFlags.Public,
384 binder: null,
385 types: new[] { typeof(int) },
386 modifiers: null);
387
388 if (getter is not null)
389 {
390 object? value =
391 getter.Invoke(
392 parameter,
393 new object[] { period });
394
395 if (value is double number)
396 {
397 return number;
398 }
399 }
400
401 PropertyInfo? indexer =
402 type.GetProperties(
403 BindingFlags.Instance |
404 BindingFlags.Public)
405 .SingleOrDefault(
406 property =>
407 {
408 ParameterInfo[] indexes =
409 property.GetIndexParameters();
410
411 return indexes.Length == 1 &&
412 indexes[0].ParameterType ==
413 typeof(int) &&
414 property.PropertyType ==
415 typeof(double);
416 });
417
418 if (indexer is not null)
419 {
420 object? value =
421 indexer.GetValue(
422 parameter,
423 new object[] { period });
424
425 if (value is double number)
426 {
427 return number;
428 }
429 }
430
431 throw new InvalidOperationException(
432 $"Cannot read period {period} from {type.FullName}.");
433 }
434}
Builds stable business-domain keys for mathematical variables and constraints.
MathematicalDomainKeyBuilder Add(string name, string value)
Adds a text segment.
Adds complete-period OPEX coverage and optional cash-flow constraints to a built economic mathematica...
static FinancialExecutionModelContext Decorate(LotSizingInstance instance, MathematicalModel source)
Provides the canonical mathematical domain-key categories used to identify lot-sizing decision famili...
static MathematicalDomainKey Parse(string domainKey)
Parses a canonical mathematical domain key.
bool TryGetInt32(string name, out int value)
Attempts to get an integer segment.
Provides the canonical mathematical domain-key segment names used by lot-sizing variables and constra...
Represents a linear expression composed of variable terms and a constant value.
void MultiplyBy(double factor)
Multiplies the entire expression by a scalar.
LinearExpression Clone()
Creates an independent copy of this expression.
List< LinearTerm > Terms
Gets the linear terms of the expression.
double Constant
Gets or sets the constant value of the expression.
int VariableId
Gets or sets the identifier of the referenced mathematical variable.
Definition LinearTerm.cs:49
double Coefficient
Gets or sets the coefficient multiplying the referenced variable.
Definition LinearTerm.cs:60
Represents a solver-independent mathematical optimization model.
List< MathematicalVariable > Variables
Gets the mathematical variables.
void AddVariable(MathematicalVariable variable)
Adds a mathematical variable.
MathematicalModel Clone()
Creates an independent copy of the mathematical model.
void AddConstraint(LinearConstraint constraint)
Adds a linear constraint.
MathematicalObjective Objective
Gets or sets the mathematical objective.
MathematicalVariable? FindVariableById(int variableId)
Finds a mathematical variable by identifier.
List< LinearConstraint > Constraints
Gets the linear constraints.
void EnsureValid()
Validates the complete mathematical model.
Represents the objective function of a mathematical optimization model.
ObjectiveSense Sense
Gets or sets the optimization direction.
LinearExpression Expression
Gets or sets the linear objective expression.
Represents one decision variable in a mathematical optimization model.
string DomainKey
Gets or sets an optional domain key used to map the mathematical variable back to a lot-sizing decisi...
ObjectiveSense
Identifies the optimization direction of a mathematical objective.
MathematicalConstraintSense
Identifies the relational sense of a mathematical constraint.
MathematicalVariableType
Identifies the domain of a mathematical decision variable.