resource.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2013 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 head
154  if (m != NOHEAD && m != NOHEADTAIL)
156 
157  // Write my fields
158  HasDescription::writeElement(o, tag);
160  if (getMaximum() != 1)
162  o->writeElement(Tags::tag_maximum_calendar, size_max_cal);
165  if (getCost() != 0.0) o->writeElement(Tags::tag_cost, getCost());
168  if (getSetupMatrix())
170  Plannable::writeElement(o, tag);
171 
172  // Write extra plan information
173  loadplanlist::const_iterator i = loadplans.begin();
174  if (o->getContentType() == XMLOutput::PLAN && i!=loadplans.end())
175  {
177  for (; i!=loadplans.end(); ++i)
178  if (i->getType()==1)
179  {
180  const LoadPlan *lp = dynamic_cast<const LoadPlan*>(&*i);
189  }
191  bool first = true;
192  for (Problem::const_iterator j = Problem::begin(const_cast<Resource*>(this), true); j!=Problem::end(); ++j)
193  {
194  if (first)
195  {
196  first = false;
198  }
200  }
201  if (!first) o->EndObject(Tags::tag_problems);
202  }
203 
204  // Write the tail
205  if (m != NOHEADTAIL && m != NOTAIL) o->EndObject(tag);
206 }
207 
208 
210 {
211  if (pAttr.isA(Tags::tag_load)
212  && pIn.getParentElement().first.isA(Tags::tag_loads))
213  {
214  Load* l = new Load();
215  l->setResource(this);
216  pIn.readto(&*l);
217  }
218  else if (pAttr.isA(Tags::tag_resourceskill)
219  && pIn.getParentElement().first.isA(Tags::tag_resourceskills))
220  {
221  ResourceSkill *s =
222  dynamic_cast<ResourceSkill*>(MetaCategory::ControllerDefault(ResourceSkill::metadata,pIn.getAttributes()));
223  if (s) s->setResource(this);
224  pIn.readto(s);
225  }
226  else if (pAttr.isA(Tags::tag_maximum_calendar))
228  else if (pAttr.isA(Tags::tag_loadplans))
229  pIn.IgnoreElement();
230  else if (pAttr.isA(Tags::tag_location))
232  else if (pAttr.isA(Tags::tag_setupmatrix))
234  if (pAttr.isA(Tags::tag_skill)
235  && pIn.getParentElement().first.isA(Tags::tag_skills))
237  else
239 }
240 
241 
242 DECLARE_EXPORT void Resource::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
243 {
244  /* Note that while restoring the size, the parent's size is NOT
245  automatically updated. The getDescription of the 'set_size' function may
246  suggest this would be the case... */
247  if (pAttr.isA (Tags::tag_maximum))
248  setMaximum(pElement.getDouble());
249  else if (pAttr.isA (Tags::tag_maximum_calendar))
250  {
251  CalendarDouble *c = dynamic_cast<CalendarDouble*>(pIn.getPreviousObject());
252  if (c)
254  else
255  {
256  Calendar *c = dynamic_cast<Calendar*>(pIn.getPreviousObject());
257  if (!c)
258  throw LogicException("Incorrect object type during read operation");
259  throw DataException("Calendar '" + c->getName() +
260  "' has invalid type for use as resource max calendar");
261  }
262  }
263  else if (pAttr.isA (Tags::tag_maxearly))
264  setMaxEarly(pElement.getTimeperiod());
265  else if (pAttr.isA (Tags::tag_cost))
266  setCost(pElement.getDouble());
267  else if (pAttr.isA(Tags::tag_location))
268  {
269  Location * d = dynamic_cast<Location*>(pIn.getPreviousObject());
270  if (d) setLocation(d);
271  else throw LogicException("Incorrect object type during read operation");
272  }
273  else if (pAttr.isA (Tags::tag_setup))
274  setSetup(pElement.getString());
275  else if (pAttr.isA(Tags::tag_setupmatrix))
276  {
277  SetupMatrix * d = dynamic_cast<SetupMatrix*>(pIn.getPreviousObject());
278  if (d) setSetupMatrix(d);
279  else throw LogicException("Incorrect object type during read operation");
280  }
281  else
282  {
283  Plannable::endElement(pIn, pAttr, pElement);
284  HasDescription::endElement(pIn, pAttr, pElement);
285  HasHierarchy<Resource>::endElement (pIn, pAttr, pElement);
286  }
287 }
288 
289 
291 {
292  // Delete the operationplans
293  for (loadlist::iterator i=loads.begin(); i!=loads.end(); ++i)
294  OperationPlan::deleteOperationPlans(i->getOperation(),deleteLocked);
295 
296  // Mark to recompute the problems
297  setChanged();
298 }
299 
300 
302 {
303  // Delete all operationplans
304  // An alternative logic would be to delete only the loadplans for this
305  // resource and leave the rest of the plan untouched. The currently
306  // implemented method is way more drastic...
307  deleteOperationPlans(true);
308 
309  // The Load and ResourceSkill objects are automatically deleted by the
310  // destructor of the Association list class.
311 }
312 
313 
315 {
316  // No updating required this resource
317  if (!getSetupMatrix() || (ldplan && ldplan->getOperationPlan()->getOperation() != OperationSetup::setupoperation))
318  return;
319 
320  // Update later setup opplans
321  OperationPlan *opplan = ldplan ? ldplan->getOperationPlan() : NULL;
322  loadplanlist::const_iterator i = ldplan ?
323  getLoadPlans().begin(ldplan) :
324  getLoadPlans().begin();
325  string prevsetup = ldplan ? ldplan->getSetup() : getSetup();
326  Date latestCheckDate = ldplan ? ldplan->getDate() : Date::infiniteFuture;
327  for (; i != getLoadPlans().end(); ++i)
328  {
329  const LoadPlan* l = dynamic_cast<const LoadPlan*>(&*i);
330  if (l && !l->getLoad()->getSetup().empty()
332  && l->getOperationPlan() != opplan
333  && !l->isStart())
334  {
335  // Next conversion operation
337  l->getOperationPlan(),
339  Date::infinitePast,
340  l->getOperationPlan()->getDates().getEnd(),
341  true,
342  false);
343  if (x.start != l->getOperationPlan()->getDates().getStart())
344  // We need to change a setup plan
345  l->getOperationPlan()->restore(x);
346  else if (ldplan && x.start == l->getOperationPlan()->getDates().getStart())
347  // We found a setup plan that doesn't need updating. Later setup plans
348  // won't require updating either
349  return;
350  }
351  }
352 }
353 
354 
356 (XMLOutput *o, const Keyword &tag, mode m) const
357 {
358  // Writing a reference
359  if (m == REFERENCE)
360  {
361  o->writeElement
362  (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
363  return;
364  }
365 
366  // Write the complete object
367  if (m != NOHEAD && m != NOHEADTAIL) o->BeginObject
368  (tag, Tags::tag_name, XMLEscape(getName()), Tags::tag_type, getType().type);
369 
370  // Write the fields
372 }
373 
374 
376 {
377  if (attr.isA(Tags::tag_name))
378  return PythonObject(getName());
379  if (attr.isA(Tags::tag_description))
380  return PythonObject(getDescription());
381  if (attr.isA(Tags::tag_category))
382  return PythonObject(getCategory());
383  if (attr.isA(Tags::tag_subcategory))
384  return PythonObject(getSubCategory());
385  if (attr.isA(Tags::tag_owner))
386  return PythonObject(getOwner());
387  if (attr.isA(Tags::tag_location))
388  return PythonObject(getLocation());
389  if (attr.isA(Tags::tag_maximum))
390  return PythonObject(getMaximum());
393  if (attr.isA(Tags::tag_maxearly))
394  return PythonObject(getMaxEarly());
395  if (attr.isA(Tags::tag_cost))
396  return PythonObject(getCost());
397  if (attr.isA(Tags::tag_hidden))
398  return PythonObject(getHidden());
399  if (attr.isA(Tags::tag_loadplans))
400  return new LoadPlanIterator(this);
401  if (attr.isA(Tags::tag_loads))
402  return new LoadIterator(this);
403  if (attr.isA(Tags::tag_setup))
404  return PythonObject(getSetup());
405  if (attr.isA(Tags::tag_setupmatrix))
406  return PythonObject(getSetupMatrix());
407  if (attr.isA(Tags::tag_level))
408  return PythonObject(getLevel());
409  if (attr.isA(Tags::tag_cluster))
410  return PythonObject(getCluster());
411  if (attr.isA(Tags::tag_members))
412  return new ResourceIterator(this);
413  if (attr.isA(Tags::tag_resourceskills))
414  return new ResourceSkillIterator(this);
415  return NULL;
416 }
417 
418 
420 {
421  if (attr.isA(Tags::tag_name))
422  setName(field.getString());
423  else if (attr.isA(Tags::tag_description))
424  setDescription(field.getString());
425  else if (attr.isA(Tags::tag_category))
426  setCategory(field.getString());
427  else if (attr.isA(Tags::tag_subcategory))
428  setSubCategory(field.getString());
429  else if (attr.isA(Tags::tag_owner))
430  {
431  if (!field.check(Resource::metadata))
432  {
433  PyErr_SetString(PythonDataException, "resource owner must be of type resource");
434  return -1;
435  }
436  Resource* y = static_cast<Resource*>(static_cast<PyObject*>(field));
437  setOwner(y);
438  }
439  else if (attr.isA(Tags::tag_location))
440  {
441  if (!field.check(Location::metadata))
442  {
443  PyErr_SetString(PythonDataException, "resource location must be of type location");
444  return -1;
445  }
446  Location* y = static_cast<Location*>(static_cast<PyObject*>(field));
447  setLocation(y);
448  }
449  else if (attr.isA(Tags::tag_maximum))
450  setMaximum(field.getDouble());
451  else if (attr.isA(Tags::tag_maximum_calendar))
452  {
453  if (!field.check(CalendarDouble::metadata))
454  {
455  PyErr_SetString(PythonDataException, "resource maximum_calendar must be of type calendar_double");
456  return -1;
457  }
458  CalendarDouble* y = static_cast<CalendarDouble*>(static_cast<PyObject*>(field));
460  }
461  else if (attr.isA(Tags::tag_hidden))
462  setHidden(field.getBool());
463  else if (attr.isA(Tags::tag_cost))
464  setCost(field.getDouble());
465  else if (attr.isA(Tags::tag_maxearly))
466  setMaxEarly(field.getTimeperiod());
467  else if (attr.isA(Tags::tag_setup))
468  setSetup(field.getString());
469  else if (attr.isA(Tags::tag_setupmatrix))
470  {
471  if (!field.check(SetupMatrix::metadata))
472  {
473  PyErr_SetString(PythonDataException, "resource setup_matrix must be of type setup_matrix");
474  return -1;
475  }
476  SetupMatrix* y = static_cast<SetupMatrix*>(static_cast<PyObject*>(field));
477  setSetupMatrix(y);
478  }
479  else
480  return -1; // Error
481  return 0; // OK
482 }
483 
484 
485 extern "C" PyObject* Resource::plan(PyObject *self, PyObject *args)
486 {
487  // Get the resource model
488  Resource* resource = static_cast<Resource*>(self);
489 
490  // Parse the Python arguments
491  PyObject* buckets = NULL;
492  int ok = PyArg_ParseTuple(args, "O:plan", &buckets);
493  if (!ok) return NULL;
494 
495  // Validate that the argument supports iteration.
496  PyObject* iter = PyObject_GetIter(buckets);
497  if (!iter)
498  {
499  PyErr_Format(PyExc_AttributeError,"Argument to resource.plan() must support iteration");
500  return NULL;
501  }
502 
503  // Return the iterator
504  return new Resource::PlanIterator(resource, iter);
505 }
506 
507 
509 {
510  // Initialize the type
512  x.setName("resourceplanIterator");
513  x.setDoc("frePPLe iterator for resourceplan");
514  x.supportiter();
515  return x.typeReady();
516 }
517 
518 
520  res(r), bucketiterator(o), ldplaniter(r ? r->getLoadPlans().begin() : NULL),
521  cur_setup(0.0), cur_load(0.0), cur_size(0.0), start_date(NULL), end_date(NULL)
522 {
523  if (!r)
524  {
525  bucketiterator = NULL;
526  throw LogicException("Creating resource plan iterator for NULL resource");
527  }
528 
529  // Start date of the first bucket
530  end_date = PyIter_Next(bucketiterator);
531  if (!end_date) throw LogicException("Expecting at least two dates as argument");
532  cur_date = PythonObject(end_date).getDate();
533  prev_date = cur_date;
534 
535  // A flag to remember whether this resource has an unavailability calendar.
536  hasUnavailability = r->getLocation() && r->getLocation()->getAvailable();
537  if (hasUnavailability)
538  {
539  unavailableIterator = Calendar::EventIterator(res->getLocation()->getAvailable(), cur_date);
540  prev_value = unavailableIterator.getBucket() ?
541  unavailableIterator.getBucket()->getBool() :
542  res->getLocation()->getAvailable()->getDefault()!=0;
543  }
544 
545  // Advance loadplan iterator just beyond the starting date
546  while (ldplaniter != res->getLoadPlans().end() && ldplaniter->getDate() <= cur_date)
547  {
548  if (ldplaniter->getType() == 4)
549  // New max size
550  cur_size = ldplaniter->getMax();
551  else
552  {
553  const LoadPlan* ldplan = dynamic_cast<const LoadPlan*>(&*ldplaniter);
554  if (!ldplan) continue;
556  // Setup starting or ending
557  cur_setup = ldplan->getQuantity() < 0 ? 0.0 : cur_size;
558  else
559  // Normal load
560  cur_load = ldplan->getOnhand();
561  }
562  ++ldplaniter;
563  }
564 }
565 
566 
568 {
569  if (bucketiterator) Py_DECREF(bucketiterator);
570  if (start_date) Py_DECREF(start_date);
571  if (end_date) Py_DECREF(end_date);
572 }
573 
574 
575 void Resource::PlanIterator::update(Date till)
576 {
577  long timedelta;
578  if (hasUnavailability)
579  {
580  // Advance till the iterator exceeds the target date
581  while (unavailableIterator.getDate() <= till)
582  {
583  timedelta = unavailableIterator.getDate() - prev_date;
584  if (prev_value)
585  {
586  bucket_available += cur_size * timedelta;
587  bucket_load += cur_load * timedelta;
588  bucket_setup += cur_setup * timedelta;
589  }
590  else
591  bucket_unavailable += cur_size * timedelta;
592  prev_value = unavailableIterator.getBucket() ?
593  unavailableIterator.getBucket()->getBool() :
594  res->getLocation()->getAvailable()->getDefault()!=0;
595  prev_date = unavailableIterator.getDate();
596  ++unavailableIterator;
597  }
598  // Account for time period finishing at the "till" date
599  timedelta = till - prev_date;
600  if (prev_value)
601  {
602  bucket_available += cur_size * timedelta;
603  bucket_load += cur_load * timedelta;
604  bucket_setup += cur_setup * timedelta;
605  }
606  else
607  bucket_unavailable += cur_size * timedelta;
608  }
609  else
610  {
611  // All time is available on this resource
612  timedelta = till - prev_date;
613  bucket_available += cur_size * timedelta;
614  bucket_load += cur_load * timedelta;
615  bucket_setup += cur_setup * timedelta;
616  }
617  // Remember till which date we already have reported
618  prev_date = till;
619 }
620 
621 
622 PyObject* Resource::PlanIterator::iternext()
623 {
624  // Reset counters
625  bucket_available = 0.0;
626  bucket_unavailable = 0.0;
627  bucket_load = 0.0;
628  bucket_setup = 0.0;
629 
630  // Get the start and end date of the current bucket
631  if (start_date) Py_DECREF(start_date);
632  start_date = end_date;
633  end_date = PyIter_Next(bucketiterator);
634  if (!end_date) return NULL;
635  cur_date = PythonObject(end_date).getDate();
636 
637  // Measure from beginning of the bucket till the first event in this bucket
638  if (ldplaniter != res->getLoadPlans().end() && ldplaniter->getDate() < cur_date)
639  update(ldplaniter->getDate());
640 
641  // Advance the loadplan iterator to the next event date
642  while (ldplaniter != res->getLoadPlans().end() && ldplaniter->getDate() <= cur_date)
643  {
644  // Measure from the previous event till the current one
645  update(ldplaniter->getDate());
646 
647  // Process the event
648  if (ldplaniter->getType() == 4)
649  // New max size
650  cur_size = ldplaniter->getMax();
651  else
652  {
653  const LoadPlan* ldplan = dynamic_cast<const LoadPlan*>(&*ldplaniter);
654  assert(ldplan);
655  if (ldplan->getOperationPlan()->getOperation() == OperationSetup::setupoperation)
656  // Setup starting or ending
657  cur_setup = ldplan->getQuantity() < 0 ? 0.0 : cur_size;
658  else
659  // Normal load
660  cur_load = ldplan->getOnhand();
661  }
662 
663  // Move to the next event
664  ++ldplaniter;
665  }
666 
667  // Measure from the previous event till the end of the bucket
668  update(cur_date);
669 
670  // Convert from seconds to hours
671  bucket_available /= 3600;
672  bucket_load /= 3600;
673  bucket_unavailable /= 3600;
674  bucket_setup /= 3600;
675 
676  // Return the result
677  return Py_BuildValue("{s:O,s:O,s:d,s:d,s:d,s:d,s:d}",
678  "start", start_date,
679  "end", end_date,
680  "available", bucket_available,
681  "load", bucket_load,
682  "unavailable", bucket_unavailable,
683  "setup", bucket_setup,
684  "free", bucket_available - bucket_load - bucket_setup);
685 }
686 
687 
688 }