LotSizingDataModel.Solution 2.0.1
Solution objects for production, setup, inventory and related decisions.
Loading...
Searching...
No Matches
PurchaseDecision.cs
Go to the documentation of this file.
1using System;
2using System.ComponentModel;
3using System.Linq;
4using System.Xml.Serialization;
5using LotSizingDataModel.Core.Common;
6using LotSizingDataModel.Core.PhysicalModel;
7
9
10/// <summary>
11/// Stores the purchase decisions associated with one supplier,
12/// one item and one destination warehouse over the complete
13/// planning horizon.
14/// </summary>
15/// <remarks>
16/// Period numbers are one-based.
17///
18/// Supplier capacities, purchase costs and delivery lead times
19/// belong to the supply-chain instance and are not duplicated
20/// in this solution object.
21/// </remarks>
22[Serializable]
23[XmlType(TypeName = "purchaseDecision")]
24public sealed class PurchaseDecision :
25 ModelObject,
26 IPlanningHorizonAware
27{
28 private int _supplierId;
29 private int _itemId;
30
31 private WarehouseReference _destinationWarehouse =
32 new();
33
34 private DoubleTimeSeries _purchasedQuantities =
35 new();
36
37 /// <summary>
38 /// Initializes an empty purchase decision.
39 /// </summary>
40 /// <remarks>
41 /// This constructor is required by
42 /// <see cref="XmlSerializer"/>.
43 /// </remarks>
45 {
46 SubscribeToObject(_destinationWarehouse);
47 SubscribeToObject(_purchasedQuantities);
48 }
49
50 /// <summary>
51 /// Initializes a purchase decision for a supplier,
52 /// an item, a destination warehouse and a planning horizon.
53 /// </summary>
54 /// <param name="supplierId">
55 /// Identifier of the supplier.
56 /// </param>
57 /// <param name="itemId">
58 /// Identifier of the purchased item.
59 /// </param>
60 /// <param name="destinationWarehouse">
61 /// Warehouse to which the purchased item is delivered.
62 /// </param>
63 /// <param name="planningHorizon">
64 /// Strictly positive number of planning periods.
65 /// </param>
67 int supplierId,
68 int itemId,
69 WarehouseReference destinationWarehouse,
70 int planningHorizon)
71 : this()
72 {
73 if (supplierId <= 0)
74 {
75 throw new ArgumentOutOfRangeException(
76 nameof(supplierId),
77 supplierId,
78 "The supplier identifier must be strictly positive.");
79 }
80
81 if (itemId <= 0)
82 {
83 throw new ArgumentOutOfRangeException(
84 nameof(itemId),
85 itemId,
86 "The item identifier must be strictly positive.");
87 }
88
89 ArgumentNullException.ThrowIfNull(
90 destinationWarehouse);
91
92 if (planningHorizon <= 0)
93 {
94 throw new ArgumentOutOfRangeException(
95 nameof(planningHorizon),
96 planningHorizon,
97 "The planning horizon must be strictly positive.");
98 }
99
100 SupplierId = supplierId;
101 ItemId = itemId;
102
104 destinationWarehouse;
105
106 ResizeTimeSeries(planningHorizon);
107 }
108
109 /// <summary>
110 /// Gets or sets the identifier of the supplier
111 /// associated with the purchase decision.
112 /// </summary>
113 [XmlAttribute("supplierId")]
114 public int SupplierId
115 {
116 get => _supplierId;
117 set
118 {
119 /*
120 * Zero is tolerated for an empty object created
121 * by XmlSerializer. The solution validator will
122 * require a strictly positive identifier.
123 */
124 if (value < 0)
125 {
126 throw new ArgumentOutOfRangeException(
127 nameof(value),
128 value,
129 "The supplier identifier cannot be negative.");
130 }
131
132 SetProperty(
133 ref _supplierId,
134 value);
135 }
136 }
137
138 /// <summary>
139 /// Gets or sets the identifier of the purchased item.
140 /// </summary>
141 [XmlAttribute("itemId")]
142 public int ItemId
143 {
144 get => _itemId;
145 set
146 {
147 /*
148 * Zero is tolerated during XML deserialization.
149 */
150 if (value < 0)
151 {
152 throw new ArgumentOutOfRangeException(
153 nameof(value),
154 value,
155 "The item identifier cannot be negative.");
156 }
157
158 SetProperty(
159 ref _itemId,
160 value);
161 }
162 }
163
164 /// <summary>
165 /// Gets or sets the warehouse to which the purchased
166 /// item is delivered.
167 /// </summary>
168 [XmlElement("destinationWarehouse")]
169 public WarehouseReference DestinationWarehouse
170 {
171 get => _destinationWarehouse;
172 set
173 {
174 ArgumentNullException.ThrowIfNull(value);
175
176 if (ReferenceEquals(
177 _destinationWarehouse,
178 value))
179 {
180 return;
181 }
182
183 UnsubscribeFromObject(
184 _destinationWarehouse);
185
186 SetProperty(
187 ref _destinationWarehouse,
188 value);
189
190 SubscribeToObject(
191 _destinationWarehouse);
192
193 NotifyDerivedProperties();
194 }
195 }
196
197 /// <summary>
198 /// Gets or sets the quantities purchased during
199 /// each planning period.
200 /// </summary>
201 /// <remarks>
202 /// Values must be finite and non-negative.
203 ///
204 /// The period represents the purchase-decision period.
205 /// The corresponding receipt period may differ when
206 /// a positive supplier lead time is defined in the instance.
207 /// </remarks>
208 [XmlElement("purchasedQuantities")]
209 public DoubleTimeSeries PurchasedQuantities
210 {
211 get => _purchasedQuantities;
212 set
213 {
214 ArgumentNullException.ThrowIfNull(value);
215
216 if (ReferenceEquals(
217 _purchasedQuantities,
218 value))
219 {
220 return;
221 }
222
223 UnsubscribeFromObject(
224 _purchasedQuantities);
225
226 SetProperty(
227 ref _purchasedQuantities,
228 value);
229
230 SubscribeToObject(
231 _purchasedQuantities);
232
233 NotifyDerivedProperties();
234 }
235 }
236
237 /// <summary>
238 /// Gets the number of planning periods represented
239 /// by the purchased-quantity series.
240 /// </summary>
241 [XmlIgnore]
242 public int PlanningHorizon =>
243 PurchasedQuantities.PeriodCount;
244
245 /// <summary>
246 /// Gets a value indicating whether every purchased
247 /// quantity is finite and non-negative.
248 /// </summary>
249 [XmlIgnore]
252 quantity =>
253 double.IsFinite(quantity) &&
254 quantity >= 0.0);
255
256 /// <summary>
257 /// Gets a value indicating whether the destination
258 /// warehouse reference is initialized.
259 /// </summary>
260 [XmlIgnore]
262 DestinationWarehouse.ReferenceId > 0;
263
264 /// <summary>
265 /// Gets a value indicating whether the purchase decision
266 /// is internally consistent.
267 /// </summary>
268 /// <remarks>
269 /// This property does not verify that the supplier,
270 /// item or warehouse exists in a particular
271 /// supply-chain instance.
272 /// </remarks>
273 [XmlIgnore]
274 public bool IsInternallyValid =>
275 SupplierId > 0 &&
276 ItemId > 0 &&
277 PlanningHorizon > 0 &&
280
281 /// <summary>
282 /// Gets the purchased quantity for a planning period.
283 /// </summary>
284 /// <param name="period">
285 /// One-based planning period.
286 /// </param>
287 /// <returns>
288 /// Purchased quantity recorded for the period.
289 /// </returns>
290 public double GetPurchasedQuantity(int period)
291 {
292 return PurchasedQuantities[period];
293 }
294
295 /// <summary>
296 /// Sets the purchased quantity for a planning period.
297 /// </summary>
298 /// <param name="period">
299 /// One-based planning period.
300 /// </param>
301 /// <param name="quantity">
302 /// Finite and non-negative purchased quantity.
303 /// </param>
305 int period,
306 double quantity)
307 {
308 ValidateNonNegativeFiniteValue(
309 quantity,
310 nameof(quantity));
311
312 PurchasedQuantities[period] =
313 quantity;
314 }
315
316 /// <summary>
317 /// Determines whether this decision identifies the
318 /// specified supplier, item and destination warehouse.
319 /// </summary>
320 /// <param name="supplierId">
321 /// Supplier identifier to compare.
322 /// </param>
323 /// <param name="itemId">
324 /// Item identifier to compare.
325 /// </param>
326 /// <param name="destinationWarehouse">
327 /// Destination warehouse to compare.
328 /// </param>
329 /// <returns>
330 /// True when all key elements match; otherwise, false.
331 /// </returns>
332 public bool Matches(
333 int supplierId,
334 int itemId,
335 WarehouseReference destinationWarehouse)
336 {
337 ArgumentNullException.ThrowIfNull(
338 destinationWarehouse);
339
340 return SupplierId == supplierId &&
341 ItemId == itemId &&
342 SameWarehouse(
344 destinationWarehouse);
345 }
346
347 /// <summary>
348 /// Resizes the purchased-quantity series to the specified
349 /// planning horizon.
350 /// </summary>
351 /// <param name="periodCount">
352 /// Non-negative number of planning periods.
353 /// </param>
354 /// <remarks>
355 /// Existing values are preserved whenever possible.
356 /// New periods are initialized with zero.
357 /// </remarks>
358 public void ResizeTimeSeries(int periodCount)
359 {
360 if (periodCount < 0)
361 {
362 throw new ArgumentOutOfRangeException(
363 nameof(periodCount),
364 periodCount,
365 "The period count cannot be negative.");
366 }
367
368 PurchasedQuantities.Resize(
369 periodCount,
370 defaultValue: 0.0);
371
372 NotifyDerivedProperties();
373 }
374
375 /// <summary>
376 /// Resets every purchased quantity to zero.
377 /// </summary>
378 public void Clear()
379 {
380 PurchasedQuantities.Fill(0.0);
381
382 NotifyDerivedProperties();
383 }
384
385 /// <inheritdoc/>
386 public override string ToString()
387 {
388 double totalPurchasedQuantity =
390
391 return
392 $"Supplier {SupplierId}, item {ItemId}, " +
393 $"destination " +
394 $"{FormatWarehouse(DestinationWarehouse)}: " +
395 $"total purchased quantity " +
396 $"{totalPurchasedQuantity}";
397 }
398
399 private void SubscribeToObject(
400 ModelObject modelObject)
401 {
402 modelObject.PropertyChanged +=
403 OnNestedPropertyChanged;
404 }
405
406 private void UnsubscribeFromObject(
407 ModelObject modelObject)
408 {
409 modelObject.PropertyChanged -=
410 OnNestedPropertyChanged;
411 }
412
413 private void OnNestedPropertyChanged(
414 object? sender,
415 PropertyChangedEventArgs eventArgs)
416 {
417 NotifyDerivedProperties();
418 }
419
420 private void NotifyDerivedProperties()
421 {
422 OnPropertyChanged(
423 nameof(PlanningHorizon));
424
425 OnPropertyChanged(
427
428 OnPropertyChanged(
430
431 OnPropertyChanged(
432 nameof(IsInternallyValid));
433 }
434
435 private static bool SameWarehouse(
436 WarehouseReference first,
437 WarehouseReference second)
438 {
439 return first.Kind ==
440 second.Kind &&
441 first.ReferenceId ==
442 second.ReferenceId;
443 }
444
445 private static string FormatWarehouse(
446 WarehouseReference warehouse)
447 {
448 return
449 $"{warehouse.Kind}:{warehouse.ReferenceId}";
450 }
451
452 private static void ValidateNonNegativeFiniteValue(
453 double value,
454 string parameterName)
455 {
456 if (!double.IsFinite(value) ||
457 value < 0.0)
458 {
459 throw new ArgumentOutOfRangeException(
460 parameterName,
461 value,
462 "The value must be finite and non-negative.");
463 }
464 }
465}
int PlanningHorizon
Gets the number of planning periods represented by the purchased-quantity series.
WarehouseReference DestinationWarehouse
Gets or sets the warehouse to which the purchased item is delivered.
PurchaseDecision()
Initializes an empty purchase decision.
int ItemId
Gets or sets the identifier of the purchased item.
DoubleTimeSeries PurchasedQuantities
Gets or sets the quantities purchased during each planning period.
int SupplierId
Gets or sets the identifier of the supplier associated with the purchase decision.
bool HasValidDestinationWarehouse
Gets a value indicating whether the destination warehouse reference is initialized.
void ResizeTimeSeries(int periodCount)
Resizes the purchased-quantity series to the specified planning horizon.
double GetPurchasedQuantity(int period)
Gets the purchased quantity for a planning period.
void SetPurchasedQuantity(int period, double quantity)
Sets the purchased quantity for a planning period.
bool IsInternallyValid
Gets a value indicating whether the purchase decision is internally consistent.
void Clear()
Resets every purchased quantity to zero.
PurchaseDecision(int supplierId, int itemId, WarehouseReference destinationWarehouse, int planningHorizon)
Initializes a purchase decision for a supplier, an item, a destination warehouse and a planning horiz...
bool HasValidPurchasedQuantities
Gets a value indicating whether every purchased quantity is finite and non-negative.
bool Matches(int supplierId, int itemId, WarehouseReference destinationWarehouse)
Determines whether this decision identifies the specified supplier, item and destination warehouse.