resource.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2012 by Johan De Taeye, frePPLe bvba *
4  * *
5  * This library is free software; you can redistribute it and/or modify it *
6  * under the terms of the GNU Affero General Public License as published *
7  * by the Free Software Foundation; either version 3 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU Affero General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Affero General Public *
16  * License along with this program. *
17  * If not, see <http://www.gnu.org/licenses/>. *
18  * *
19  ***************************************************************************/
20 
21 #define FREPPLE_CORE
22 #include "frepple/model.h"
23 
24 namespace frepple
25 {
26 
27 template<class Resource> DECLARE_EXPORT Tree utils::HasName<Resource>::st;
31 
32 
34 {
35  // Initialize the metadata
36  metadata = new MetaCategory("resource", "resources", reader, writer);
37 
38  // Initialize the Python class
39  FreppleCategory<Resource>::getType().addMethod("plan", Resource::plan, METH_VARARGS,
40  "Return an iterator with tuples representing the resource plan in each time bucket");
42 }
43 
44 
46 {
47  // Initialize the metadata
49  "resource",
50  "resource_default",
51  Object::createString<ResourceDefault>,
52  true);
53 
54  // Initialize the Python class
56 }
57 
58 
60 {
61  // Initialize the metadata
63  "resource",
64  "resource_infinite",
65  Object::createString<ResourceInfinite>);
66 
67  // Initialize the Python class
69 }
70 
71 
73 {
74  if (m < 0)
75  throw DataException("Maximum capacity for resource '" + getName() + "' must be postive");
76 
77  // There is already a maximum calendar.
78  if (size_max_cal)
79  {
80  // We update the field, but don't use it yet.
81  size_max = m;
82  return;
83  }
84 
85  // Mark as changed
86  setChanged();
87 
88  // Set field
89  size_max = m;
90 
91  // Create or update a single timeline max event
92  for (loadplanlist::iterator oo=loadplans.begin(); oo!=loadplans.end(); oo++)
93  if (oo->getType() == 4)
94  {
95  // Update existing event
96  static_cast<loadplanlist::EventMaxQuantity *>(&*oo)->setMax(size_max);
97  return;
98  }
99  // Create new event
100  loadplanlist::EventMaxQuantity *newEvent =
101  new loadplanlist::EventMaxQuantity(Date::infinitePast, size_max);
102  loadplans.insert(newEvent);
103 }
104 
105 
107 {
108  // Resetting the same calendar
109  if (size_max_cal == c) return;
110 
111  // Mark as changed
112  setChanged();
113 
114  // Remove the current max events.
115  for (loadplanlist::iterator oo=loadplans.begin(); oo!=loadplans.end(); )
116  if (oo->getType() == 4)
117  {
118  loadplans.erase(&(*oo));
119  delete &(*(oo++));
120  }
121  else ++oo;
122 
123  // Null pointer passed. Change back to time independent maximum size.
124  if (!c)
125  {
126  setMaximum(size_max);
127  return;
128  }
129 
130  // Create timeline structures for every bucket.
131  size_max_cal = c;
132  double curMax = 0.0;
133  for (CalendarDouble::EventIterator x(size_max_cal); x.getDate()<Date::infiniteFuture; ++x)
134  if (curMax != x.getValue())
135  {
136  curMax = x.getValue();
137  loadplanlist::EventMaxQuantity *newBucket =
138  new loadplanlist::EventMaxQuantity(x.getDate(), curMax);
139  loadplans.insert(newBucket);
140  }
141 }
142 
143 
145 {
146  // Write a reference
147  if (m == REFERENCE)
148  {
149  o->writeElement(tag, Tags::tag_name, getName());
150  return;
151  }
152 
153  // Write the complete object
154  if (m != NOHEADER) o->BeginObject(tag, Tags::tag_name, XMLEscape(getName()));
155 
156  // Write my fields
159  if (getMaximum() != 1)
161  o->writeElement(Tags::tag_maximum_calendar, size_max_cal);
164  if (getCost() != 0.0) o->writeElement(Tags::tag_cost, getCost());
167  if (getSetupMatrix())
169  Plannable::writeElement(o, tag);
170 
171  // Write extra plan information
172  loadplanlist::const_iterator i = loadplans.begin();
173  if (o->getContentType() == XMLOutput::PLAN && i!=loadplans.end())
174  {
176  for (; i!=loadplans.end(); ++i)
177  if (i->getType()==1)
178  {
179  const LoadPlan *lp = dynamic_cast<const LoadPlan*>(&*i);
188  }
190  }
191 
192  // That was it
193  o->EndObject(tag);
194 }
195 
196 
198 {
199  if (pAttr.isA(Tags::tag_load)
200  && pIn.getParentElement().first.isA(Tags::tag_loads))
201  {
202  Load* l = new Load();
203  l->setResource(this);
204  pIn.readto(&*l);
205  }
206  else if (pAttr.isA(Tags::tag_resourceskill)
207  && pIn.getParentElement().first.isA(Tags::tag_resourceskills))
208  {
209  ResourceSkill *s =
210  dynamic_cast<ResourceSkill*>(MetaCategory::ControllerDefault(ResourceSkill::metadata,pIn.getAttributes()));
211  if (s) s->setResource(this);
212  pIn.readto(s);
213  }
214  else if (pAttr.isA(Tags::tag_maximum_calendar))
216  else if (pAttr.isA(Tags::tag_loadplans))
217  pIn.IgnoreElement();
218  else if (pAttr.isA(Tags::tag_location))
220  else if (pAttr.isA(Tags::tag_setupmatrix))
222  if (pAttr.isA(Tags::tag_skill)
223  && pIn.getParentElement().first.isA(Tags::tag_skills))
225  else
227 }
228 
229 
230 DECLARE_EXPORT void Resource::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
231 {
232  /* Note that while restoring the size, the parent's size is NOT
233  automatically updated. The getDescription of the 'set_size' function may
234  suggest this would be the case... */
235  if (pAttr.isA (Tags::tag_maximum))
236  setMaximum(pElement.getDouble());
237  else if (pAttr.isA (Tags::tag_maximum_calendar))
238  {
239  CalendarDouble *c = dynamic_cast<CalendarDouble*>(pIn.getPreviousObject());
240  if (c)
242  else
243  {
244  Calendar *c = dynamic_cast<Calendar*>(pIn.getPreviousObject());
245  if (!c)
246  throw LogicException("Incorrect object type during read operation");
247  throw DataException("Calendar '" + c->getName() +
248  "' has invalid type for use as resource max calendar");
249  }
250  }
251  else if (pAttr.isA (Tags::tag_maxearly))
252  setMaxEarly(pElement.getTimeperiod());
253  else if (pAttr.isA (Tags::tag_cost))
254  setCost(pElement.getDouble());
255  else if (pAttr.isA(Tags::tag_location))
256  {
257  Location * d = dynamic_cast<Location*>(pIn.getPreviousObject());
258  if (d) setLocation(d);
259  else throw LogicException("Incorrect object type during read operation");
260  }
261  else if (pAttr.isA (Tags::tag_setup))
262  setSetup(pElement.getString());
263  else if (pAttr.isA(Tags::tag_setupmatrix))
264  {
265  SetupMatrix * d = dynamic_cast<SetupMatrix*>(pIn.getPreviousObject());
266  if (d) setSetupMatrix(d);
267  else throw LogicException("Incorrect object type during read operation");
268  }
269  else
270  {
271  Plannable::endElement(pIn, pAttr, pElement);
272  HasDescription::endElement(pIn, pAttr, pElement);
273  HasHierarchy<Resource>::endElement (pIn, pAttr, pElement);
274  }
275 }
276 
277 
279 {
280  // Delete the operationplans
281  for (loadlist::iterator i=loads.begin(); i!=loads.end(); ++i)
282  OperationPlan::deleteOperationPlans(i->getOperation(),deleteLocked);
283 
284  // Mark to recompute the problems
285  setChanged();
286 }
287 
288 
290 {
291  // Delete all operationplans
292  // An alternative logic would be to delete only the loadplans for this
293  // resource and leave the rest of the plan untouched. The currently
294  // implemented method is way more drastic...
295  deleteOperationPlans(true);
296 
297  // The Load and ResourceSkill objects are automatically deleted by the
298  // destructor of the Association list class.
299 }
300 
301 
303 {
304  // No updating required this resource
305  if (!getSetupMatrix() || (ldplan && ldplan->getOperationPlan()->getOperation() != OperationSetup::setupoperation))
306  return;
307 
308  // Update later setup opplans
309  OperationPlan *opplan = ldplan ? ldplan->getOperationPlan() : NULL;
310  loadplanlist::const_iterator i = ldplan ?
311  getLoadPlans().begin(ldplan) :
312  getLoadPlans().begin();
313  string prevsetup = ldplan ? ldplan->getSetup() : getSetup();
314  Date latestCheckDate = ldplan ? ldplan->getDate() : Date::infiniteFuture;
315  for (; i != getLoadPlans().end(); ++i)
316  {
317  const LoadPlan* l = dynamic_cast<const LoadPlan*>(&*i);
318  if (l && !l->getLoad()->getSetup().empty()
320  && l->getOperationPlan() != opplan
321  && !l->isStart())
322  {
323  // Next conversion operation
325  l->getOperationPlan(),
327  Date::infinitePast,
328  l->getOperationPlan()->getDates().getEnd(),
329  true,
330  false);
331  if (x.start != l->getOperationPlan()->getDates().getStart())
332  // We need to change a setup plan
333  l->getOperationPlan()->restore(x);
334  else if (ldplan && x.start == l->getOperationPlan()->getDates().getStart())
335  // We found a setup plan that doesn't need updating. Later setup plans
336  // won't require updating either
337  return;
338  }
339  }
340 }
341 
342 
344 (XMLOutput *o, const Keyword &tag, mode m) const
345 {
346  // Writing a reference
347  if (m == REFERENCE)
348  {
349  o->writeElement
350  (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
351  return;
352  }
353 
354  // Write the complete object
355  if (m != NOHEADER) o->BeginObject
356  (tag, Tags::tag_name, XMLEscape(getName()), Tags::tag_type, getType().type);
357 
358  // Write the fields
360 }
361 
362 
364 {
365  if (attr.isA(Tags::tag_name))
366  return PythonObject(getName());
367  if (attr.isA(Tags::tag_description))
368  return PythonObject(getDescription());
369  if (attr.isA(Tags::tag_category))
370  return PythonObject(getCategory());
371  if (attr.isA(Tags::tag_subcategory))
372  return PythonObject(getSubCategory());
373  if (attr.isA(Tags::tag_owner))
374  return PythonObject(getOwner());
375  if (attr.isA(Tags::tag_location))
376  return PythonObject(getLocation());
377  if (attr.isA(Tags::tag_maximum))
378  return PythonObject(getMaximum());
381  if (attr.isA(Tags::tag_maxearly))
382  return PythonObject(getMaxEarly());
383  if (attr.isA(Tags::tag_cost))
384  return PythonObject(getCost());
385  if (attr.isA(Tags::tag_hidden))
386  return PythonObject(getHidden());
387  if (attr.isA(Tags::tag_loadplans))
388  return new LoadPlanIterator(this);
389  if (attr.isA(Tags::tag_loads))
390  return new LoadIterator(this);
391  if (attr.isA(Tags::tag_setup))
392  return PythonObject(getSetup());
393  if (attr.isA(Tags::tag_setupmatrix))
394  return PythonObject(getSetupMatrix());
395  if (attr.isA(Tags::tag_level))
396  return PythonObject(getLevel());
397  if (attr.isA(Tags::tag_cluster))
398  return PythonObject(getCluster());
399  if (attr.isA(Tags::tag_members))
400  return new ResourceIterator(this);
401  if (attr.isA(Tags::tag_resourceskills))
402  return new ResourceSkillIterator(this);
403  return NULL;
404 }
405 
406 
408 {
409  if (attr.isA(Tags::tag_name))
410  setName(field.getString());
411  else if (attr.isA(Tags::tag_description))
412  setDescription(field.getString());
413  else if (attr.isA(Tags::tag_category))
414  setCategory(field.getString());
415  else if (attr.isA(Tags::tag_subcategory))
416  setSubCategory(field.getString());
417  else if (attr.isA(Tags::tag_owner))
418  {
420  {
421  PyErr_SetString(PythonDataException, "resource owner must be of type resource");
422  return -1;
423  }
424  Resource* y = static_cast<Resource*>(static_cast<PyObject*>(field));
425  setOwner(y);
426  }
427  else if (attr.isA(Tags::tag_location))
428  {
429  if (!field.check(Location::metadata))
430  {
431  PyErr_SetString(PythonDataException, "resource location must be of type location");
432  return -1;
433  }
434  Location* y = static_cast<Location*>(static_cast<PyObject*>(field));
435  setLocation(y);
436  }
437  else if (attr.isA(Tags::tag_maximum))
438  setMaximum(field.getDouble());
439  else if (attr.isA(Tags::tag_maximum_calendar))
440  {
441  if (!field.check(CalendarDouble::metadata))
442  {
443  PyErr_SetString(PythonDataException, "resource maximum_calendar must be of type calendar_double");
444  return -1;
445  }
446  CalendarDouble* y = static_cast<CalendarDouble*>(static_cast<PyObject*>(field));
448  }
449  else if (attr.isA(Tags::tag_hidden))
450  setHidden(field.getBool());
451  else if (attr.isA(Tags::tag_cost))
452  setCost(field.getDouble());
453  else if (attr.isA(Tags::tag_maxearly))
454  setMaxEarly(field.getTimeperiod());
455  else if (attr.isA(Tags::tag_setup))
456  setSetup(field.getString());
457  else if (attr.isA(Tags::tag_setupmatrix))
458  {
459  if (!field.check(SetupMatrix::metadata))
460  {
461  PyErr_SetString(PythonDataException, "resource setup_matrix must be of type setup_matrix");
462  return -1;
463  }
464  SetupMatrix* y = static_cast<SetupMatrix*>(static_cast<PyObject*>(field));
465  setSetupMatrix(y);
466  }
467  else
468  return -1; // Error
469  return 0; // OK
470 }
471 
472 
473 extern "C" PyObject* Resource::plan(PyObject *self, PyObject *args)
474 {
475  // Get the resource model
476  Resource* resource = static_cast<Resource*>(self);
477 
478  // Parse the Python arguments
479  PyObject* buckets = NULL;
480  int ok = PyArg_ParseTuple(args, "O:plan", &buckets);
481  if (!ok) return NULL;
482 
483  // Validate that the argument supports iteration.
484  PyObject* iter = PyObject_GetIter(buckets);
485  if (!iter)
486  {
487  PyErr_Format(PyExc_AttributeError,"Argument to resource.plan() must support iteration");
488  return NULL;
489  }
490 
491  // Return the iterator
492  return new Resource::PlanIterator(resource, iter);
493 }
494 
495 
497 {
498  // Initialize the type
500  x.setName("resourceplanIterator");
501  x.setDoc("frePPLe iterator for resourceplan");
502  x.supportiter();
503  return x.typeReady();
504 }
505 
506 
508  res(r), bucketiterator(o), ldplaniter(r ? r->getLoadPlans().begin() : NULL),
509  cur_setup(0.0), cur_load(0.0), cur_size(0.0), start_date(NULL), end_date(NULL)
510 {
511  if (!r)
512  {
513  bucketiterator = NULL;
514  throw LogicException("Creating resource plan iterator for NULL resource");
515  }
516 
517  // Start date of the first bucket
518  end_date = PyIter_Next(bucketiterator);
519  if (!end_date) throw LogicException("Expecting at least two dates as argument");
520  cur_date = PythonObject(end_date).getDate();
521  prev_date = cur_date;
522 
523  // A flag to remember whether this resource has an unavailability calendar.
524  hasUnavailability = r->getLocation() && r->getLocation()->getAvailable();
525  if (hasUnavailability)
526  {
527  unavailableIterator = Calendar::EventIterator(res->getLocation()->getAvailable(), cur_date);
528  prev_value = unavailableIterator.getBucket() ?
529  unavailableIterator.getBucket()->getBool() :
530  res->getLocation()->getAvailable()->getDefault()!=0;
531  }
532 
533  // Advance loadplan iterator just beyond the starting date
534  while (ldplaniter != res->getLoadPlans().end() && ldplaniter->getDate() <= cur_date)
535  {
536  if (ldplaniter->getType() == 4)
537  // New max size
538  cur_size = ldplaniter->getMax();
539  else
540  {
541  const LoadPlan* ldplan = dynamic_cast<const LoadPlan*>(&*ldplaniter);
542  if (!ldplan) continue;
544  // Setup starting or ending
545  cur_setup = ldplan->getQuantity() < 0 ? 0.0 : cur_size;
546  else
547  // Normal load
548  cur_load = ldplan->getOnhand();
549  }
550  ++ldplaniter;
551  }
552 }
553 
554 
556 {
557  if (bucketiterator) Py_DECREF(bucketiterator);
558  if (start_date) Py_DECREF(start_date);
559  if (end_date) Py_DECREF(end_date);
560 }
561 
562 
563 void Resource::PlanIterator::update(Date till)
564 {
565  long timedelta;
566  if (hasUnavailability)
567  {
568  // Advance till the iterator exceeds the target date
569  while (unavailableIterator.getDate() <= till)
570  {
571  timedelta = unavailableIterator.getDate() - prev_date;
572  if (prev_value)
573  {
574  bucket_available += cur_size * timedelta;
575  bucket_load += cur_load * timedelta;
576  bucket_setup += cur_setup * timedelta;
577  }
578  else
579  bucket_unavailable += cur_size * timedelta;
580  prev_value = unavailableIterator.getBucket() ?
581  unavailableIterator.getBucket()->getBool() :
582  res->getLocation()->getAvailable()->getDefault()!=0;
583  prev_date = unavailableIterator.getDate();
584  ++unavailableIterator;
585  }
586  // Account for time period finishing at the "till" date
587  timedelta = till - prev_date;
588  if (prev_value)
589  {
590  bucket_available += cur_size * timedelta;
591  bucket_load += cur_load * timedelta;
592  bucket_setup += cur_setup * timedelta;
593  }
594  else
595  bucket_unavailable += cur_size * timedelta;
596  }
597  else
598  {
599  // All time is available on this resource
600  timedelta = till - prev_date;
601  bucket_available += cur_size * timedelta;
602  bucket_load += cur_load * timedelta;
603  bucket_setup += cur_setup * timedelta;
604  }
605  // Remember till which date we already have reported
606  prev_date = till;
607 }
608 
609 
611 {
612  // Reset counters
613  bucket_available = 0.0;
614  bucket_unavailable = 0.0;
615  bucket_load = 0.0;
616  bucket_setup = 0.0;
617 
618  // Get the start and end date of the current bucket
619  if (start_date) Py_DECREF(start_date);
620  start_date = end_date;
621  end_date = PyIter_Next(bucketiterator);
622  if (!end_date) return NULL;
623  cur_date = PythonObject(end_date).getDate();
624 
625  // Measure from beginning of the bucket till the first event in this bucket
626  if (ldplaniter != res->getLoadPlans().end() && ldplaniter->getDate() < cur_date)
627  update(ldplaniter->getDate());
628 
629  // Advance the loadplan iterator to the next event date
630  while (ldplaniter != res->getLoadPlans().end() && ldplaniter->getDate() <= cur_date)
631  {
632  // Measure from the previous event till the current one
633  update(ldplaniter->getDate());
634 
635  // Process the event
636  if (ldplaniter->getType() == 4)
637  // New max size
638  cur_size = ldplaniter->getMax();
639  else
640  {
641  const LoadPlan* ldplan = dynamic_cast<const LoadPlan*>(&*ldplaniter);
642  assert(ldplan);
643  if (ldplan->getOperationPlan()->getOperation() == OperationSetup::setupoperation)
644  // Setup starting or ending
645  cur_setup = ldplan->getQuantity() < 0 ? 0.0 : cur_size;
646  else
647  // Normal load
648  cur_load = ldplan->getOnhand();
649  }
650 
651  // Move to the next event
652  ++ldplaniter;
653  }
654 
655  // Measure from the previous event till the end of the bucket
656  update(cur_date);
657 
658  // Convert from seconds to hours
659  bucket_available /= 3600;
660  bucket_load /= 3600;
661  bucket_unavailable /= 3600;
662  bucket_setup /= 3600;
663 
664  // Return the result
665  return Py_BuildValue("{s:O,s:O,s:d,s:d,s:d,s:d,s:d}",
666  "start", start_date,
667  "end", end_date,
668  "available", bucket_available,
669  "load", bucket_load,
670  "unavailable", bucket_unavailable,
671  "setup", bucket_setup,
672  "free", bucket_available - bucket_load - bucket_setup);
673 }
674 
675 
676 }