[kplato] Some code for KPTProject::getDuration()
John D Lamb
kplato@kde.org
Sat, 11 Aug 2001 20:36:04 +0100
This is a multi-part message in MIME format.
--------------D5025E5856975D2BFEDF476C
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
I've had a look at Thomas Zander's code. I don't actually follow all the
concepts; so I've had a first stab at writing the code to handle
PERT/CPM. I've used a substantially simplified and slightly modified
version of kptnode because it made it easier to test the code robustly.
At the moment, it will compile with
g++ kptnode.cc
I've used STL a lot because it is very efficient and gives reasonably
compact source and object code. I don't know how easy it is to
substitute the STL list with a QList. In any case I think there's a
couple of good arguments for writing a core library that doesn't use Qt
(even though I suggested durations as the Qt class). First, we'll need a
lot of algorithms that are already available efficiently in STL. Second,
the library could be used outside this project. Third, it may be easier
to test (debug) the code as a library seperate from the GUI. The obvious
argument against is that we'd have to write conversion functions for an
interface to the Qt part - I can't think of a good way to counter that.
:-)
The code itself raised some issues for me. First, a lot of the core
functions like getDuration need PERT/CPM to have been done before they
can return a sensible value. Second, I haven't yet understood how kplato
will deal with activities that have already finished in a project.
Third, functions like getDuration will have to call the basic PERT/CPM
routines many times if we want a Monte Carlo simulation for the likely
outcome of a project.
I think subprojects are going to be very easy to handle.
Please let me know if you can do anything with this or see improvements.
Clearly, we need more data-hiding.
The two files attached are kptnode.h and kptnode.cc. Don't overwrite the
versions in the kplato archive. If this code can be used, it will need
careful merging.
JDL
--------------D5025E5856975D2BFEDF476C
Content-Type: text/plain; charset=us-ascii;
name="kptnode.h"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="kptnode.h"
/*
* $id$
* Copyright (C) 2001 John D Lamb
*/
#include"kptnode.h"
/*
* Simplified example of kptnode
*/
#include<list>
#include<algorithm>
#include<string>
#include<iostream>
using namespace std;
#ifndef kptnode_h
#define kptnode_h
class KPTNode
{
public:
KPTNode( const string &description );
virtual ~KPTNode();
virtual double getDuration();
virtual void dump() const;
public: //Really should be private
double duration; //Really KPTDuration
list<KPTNode*> uncompletedPredecessors;
list<KPTNode*> uncompletedSuccessors;
double earliestStart;
double latestFinish;
list<KPTNode*> predecessors;
list<KPTNode*> successors;
int nodeType; //Really should be an enumerated type
string getDescription() const;
private:
string description;
};
class KPTProject : public KPTNode
{
public:
KPTProject( const string &description );
virtual ~KPTProject();
void addNode( KPTNode &node );
void createDependency( KPTNode &start, KPTNode &finish );
virtual void dump() const;
virtual double getDuration();
public: //Really should be private
KPTNode startNode;
KPTNode endNode;
private: //maybe could be protected
static bool emptyUncompletedSuccessor( KPTNode* );
static bool emptyUncompletedPredecessor( KPTNode* );
};
#endif
--------------D5025E5856975D2BFEDF476C
Content-Type: text/plain; charset=us-ascii;
name="kptnode.cc"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="kptnode.cc"
/*
* $id$
* Copyright (C) 2001 John D Lamb
*/
#include"kptnode.h"
KPTNode::KPTNode( const string &description )
{
this->description.assign( description );
duration = 0;
nodeType = 0; // A task if not specified otherwise
}
KPTNode::~KPTNode()
{
}
double KPTNode::getDuration()
{
return duration;
}
string KPTNode::getDescription() const
{
return description;
}
void KPTNode::dump() const
{
cout << "Node: " << description << " - type " << nodeType << endl;
cout << "Duration: " << duration << endl;
cout << "Earliest start: " << earliestStart << endl;
cout << "Latest finish: " << latestFinish << endl;
if( !uncompletedPredecessors.empty() )
cout << uncompletedPredecessors.size() << " uncompleted predecesors.\n";
else if( !uncompletedSuccessors.empty() )
cout << uncompletedSuccessors.size() << " uncompleted successors.\n";
else
cout << "Most recent PERT/CPM iteration OK on this node.\n";
cout << endl;
}
KPTProject::KPTProject( const string &description )
: KPTNode( description ),
startNode( "start" ),
endNode( "finish" )
{
nodeType = 1; // A project
/* startNode and endNode should have duration zero because they are only
* there to aid the PERT/CPM calculation.
*/
startNode.duration = 0;
endNode.duration = 0;
createDependency( startNode, endNode );
}
KPTProject::~KPTProject()
{
}
void KPTProject::addNode( KPTNode &node )
{
/* indicate that a node has been added
*/
createDependency( startNode, node );
createDependency( node, endNode );
}
void KPTProject::createDependency( KPTNode &start, KPTNode &finish )
{
/* Tell project that start has finish as a successor (in time) and
* finish has start as a predecessor.
*/
start.successors.push_back( &finish );
finish.predecessors.push_back( &start );
}
double KPTProject::getDuration()
{
/* This is where PERT/CPM gets used in earnest.
* First run through all the nodes of the project
* and make sure the (should be) private variable
* duration has a sensible value. This should be possible by running
* getDuration on each node.
* Note that it is important to set the value of duration rather than
* use a function call if (say) getDuration returns a random value.
* In general the following is not nonsensical - even if it is in this
* example.
*/
list<KPTNode*>::iterator p, q;
list<KPTNode*> unfinishedNodes;
for( p = startNode.successors.begin(); p != startNode.successors.end(); ++p )
(*p)->duration = (*p)->getDuration(); /* in general getDuration() may
* return soomething other than
* duration. */
/* Now that duration is fixed for all subnodes of the project,
* we can get a value for the project itself using PERT/CPM.
* Also copy successors and predecessors to uncompleted versions.
*/
startNode.earliestStart = 0;
for( p = startNode.successors.begin(); p != startNode.successors.end(); ++p )
{
(*p)->earliestStart = 0; // Maybe this should be current time?
(*p)->uncompletedSuccessors.assign( (*p)->successors.begin(),
(*p)->successors.end() );
(*p)->uncompletedPredecessors.assign( (*p)->predecessors.begin(),
(*p)->predecessors.end() );
}
startNode.uncompletedSuccessors.assign( startNode.successors.begin(),
startNode.successors.end() );
/* Now do a forward pass to get earliest starts.
* Mark all nodes initially as unfinished.
*/
unfinishedNodes.assign( startNode.successors.begin(),
startNode.successors.end() );
unfinishedNodes.push_back( &startNode );
while( !unfinishedNodes.empty() )
{
/* Find the first node that has been completed. Initially it must be the
* start node. ***Note that this could fail if the Project is allowed
* to contain any directed cycle*** ...but a directed cycle would
* violate time-dependency assumptions.
*/
p = find_if( unfinishedNodes.begin(), unfinishedNodes.end(),
emptyUncompletedPredecessor );
/* Propagate earliest start time + duration to each succesor of (*p)
* and remove (*p) from uncompletedPredecessors of each succesor.
*/
double nextStart = (*p)->earliestStart + (*p)->duration;
for( q = (*p)->successors.begin(); q != (*p)->successors.end(); ++q )
{
if( nextStart > (*q)->earliestStart )
(*q)->earliestStart = nextStart;
(*q)->uncompletedPredecessors
.erase( find( (*q)->uncompletedPredecessors.begin(),
(*q)->uncompletedPredecessors.end(), (*p) ) );
}
unfinishedNodes.erase( p );
}
/* copy earliest start at end node to latest finish
* and note duration.
*/
endNode.latestFinish = endNode.earliestStart;
duration = endNode.latestFinish - startNode.earliestStart;
for( p = endNode.predecessors.begin(); p != endNode.predecessors.end(); ++p )
{
(*p)->latestFinish = endNode.latestFinish;
}
/* Now do a backward pass to get earliest starts.
* Mark all nodes initially as unfinished.
*/
unfinishedNodes.assign( endNode.predecessors.begin(),
endNode.predecessors.end() );
unfinishedNodes.push_back( &endNode );
while( !unfinishedNodes.empty() )
{
/* Find the first node that has been completed. Initially it must be the
* end node. ***Note that this could fail if the Project is allowed
* to contain any directed cycle*** ...but a directed cycle would
* violate time-dependency assumptions.
*/
p = find_if( unfinishedNodes.begin(), unfinishedNodes.end(),
emptyUncompletedSuccessor );
/* Propagate latest finish time - duration to each predecessor of (*p)
* and remove (*p) from uncompletedSuccessors of each predecessor.
*/
double nextFinish = (*p)->latestFinish - (*p)->duration;
for( q = (*p)->predecessors.begin(); q != (*p)->predecessors.end(); ++q )
{
if( nextFinish < (*q)->latestFinish )
(*q)->latestFinish = nextFinish;
(*q)->uncompletedSuccessors
.erase( find( (*q)->uncompletedSuccessors.begin(),
(*q)->uncompletedSuccessors.end(), (*p) ) );
}
unfinishedNodes.erase( p );
}
/* Save earliest start/latest finish to project. These will probably
* immediately change if the project is a subproject. Is this right?
*/
earliestStart = startNode.earliestStart;
latestFinish = endNode.latestFinish;
return duration;
}
/* private functions needed by STL algorithm. */
bool KPTProject::emptyUncompletedSuccessor( KPTNode *node )
{
return node->uncompletedSuccessors.empty();
}
bool KPTProject::emptyUncompletedPredecessor( KPTNode *node )
{
return node->uncompletedPredecessors.empty();
}
void KPTProject::dump() const
{
cout << "===============================" << endl;
KPTNode::dump();
cout << "===============================" << endl;
list<KPTNode*>::const_iterator p;
for( p = startNode.successors.begin(); p != startNode.successors.end(); ++p )
(*p)->dump();
cout << "===============================" << endl;
}
int main()
{
KPTNode A( "A" );
KPTNode B( "B" );
KPTNode C( "C" );
KPTNode D( "D" );
KPTProject project( "Project" );
project.addNode( A );
project.addNode( B );
project.addNode( C );
project.addNode( D );
A.duration = 7.3;
B.duration = 1.4;
C.duration = 5.2;
D.duration = 6.0;
project.createDependency( A, B );
project.createDependency( C, B );
project.createDependency( C, D );
project.getDuration();
project.dump();
return 0;
}
--------------D5025E5856975D2BFEDF476C--