OOFEM 3.0
Loading...
Searching...
No Matches
sloangraph.C
Go to the documentation of this file.
1/*
2 *
3 * ##### ##### ###### ###### ### ###
4 * ## ## ## ## ## ## ## ### ##
5 * ## ## ## ## #### #### ## # ##
6 * ## ## ## ## ## ## ## ##
7 * ## ## ## ## ## ## ## ##
8 * ##### ##### ## ###### ## ##
9 *
10 *
11 * OOFEM : Object Oriented Finite Element Code
12 *
13 * Copyright (C) 1993 - 2025 Borek Patzak
14 *
15 *
16 *
17 * Czech Technical University, Faculty of Civil Engineering,
18 * Department of Structural Mechanics, 166 29 Prague, Czech Republic
19 *
20 * This library is free software; you can redistribute it and/or
21 * modify it under the terms of the GNU Lesser General Public
22 * License as published by the Free Software Foundation; either
23 * version 2.1 of the License, or (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 * Lesser General Public License for more details.
29 *
30 * You should have received a copy of the GNU Lesser General Public
31 * License along with this library; if not, write to the Free Software
32 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
33 */
34
35/* Modified and optimized by: Borek Patzak */
36/* Author: Milan Jirasek */
37
38#include "sloangraph.h"
39#include "domain.h"
40#include "element.h"
41#include "dof.h"
42#include "dofmanager.h"
43#include "simpleslavedof.h"
44#include "intarray.h"
46
47#include <memory>
48#include <ctime>
49#include <set>
50
51namespace oofem {
52SloanGraph :: SloanGraph(Domain *d) : nodes(), queue(), OptimalRenumberingTable()
53{
54 domain = d;
56 WeightDegree = 2;
61 startNode = endNode = 0;
63}
64
65SloanGraph :: ~SloanGraph()
66{
67}
68
69void SloanGraph :: initialize()
70{
71 int nnodes = domain->giveNumberOfDofManagers();
72
73 this->nodes.reserve(nnodes);
74 this->dmans.reserve(nnodes);
75 std :: map< DofManager*, int > dman2map;
76
77 // Add dof managers.
78 int count = 0;
79 for ( auto &dman : domain->giveDofManagers() ) {
80 nodes.emplace_back(this, ++count);
81 dmans.push_back(dman.get());
82 dman2map.insert({dman.get(), count});
83 }
84 // Add element internal dof managers
85 for ( auto &elem : domain->giveElements() ) {
86 this->nodes.reserve( nodes.size() + elem->giveNumberOfInternalDofManagers() );
87 this->dmans.reserve( dmans.size() + elem->giveNumberOfInternalDofManagers() );
88 for ( int j = 1; j <= elem->giveNumberOfInternalDofManagers(); ++j ) {
89 nodes.emplace_back(this, ++count);
90 dmans.push_back( elem->giveInternalDofManager(j) );
91 dman2map.insert({elem->giveInternalDofManager(j), count});
92 }
93 }
94 // Add boundary condition internal dof managers
95 for ( auto &bc : domain->giveBcs() ) {
96 this->nodes.reserve( nodes.size() + bc->giveNumberOfInternalDofManagers() );
97 this->dmans.reserve( dmans.size() + bc->giveNumberOfInternalDofManagers() );
98 for ( int j = 1; j <= bc->giveNumberOfInternalDofManagers(); ++j ) {
99 nodes.emplace_back(this, ++count);
100 dmans.push_back( bc->giveInternalDofManager(j) );
101 dman2map.insert({bc->giveInternalDofManager(j), count});
102 }
103 }
104 this->numberOfNodes = count;
105
106 IntArray connections;
107 for ( auto &elem : domain->giveElements() ) {
108 int ielemnodes = elem->giveNumberOfDofManagers();
109 int ielemintdmans = elem->giveNumberOfInternalDofManagers();
110 int ndofmans = ielemnodes + ielemintdmans;
111 connections.resize(ndofmans);
112 for ( int j = 1; j <= ielemnodes; j++ ) {
113 connections.at(j) = dman2map[ elem->giveDofManager(j) ];
114 }
115 for ( int j = 1; j <= ielemintdmans; j++ ) {
116 connections.at(ielemnodes + j) = dman2map[ elem->giveInternalDofManager(j) ];
117 }
118 for ( int j = 1; j <= ndofmans; j++ ) {
119 for ( int k = j + 1; k <= ndofmans; k++ ) {
120 // Connect both ways
121 this->giveNode( connections.at(j) ).addNeighbor( connections.at(k) );
122 this->giveNode( connections.at(k) ).addNeighbor( connections.at(j) );
123 }
124 }
125 }
127
128 IntArray dofMasters;
129 for ( auto &dman : dmans ) {
130 // according to forum discussion Peter & Mikael
131 count = 0;
132 ++count;
133 if ( dman->hasAnySlaveDofs() ) {
134 std :: set< int >masters;
135 // slave dofs are present in dofManager
136 // first - ask for masters, these may be different for each dof
137 for ( Dof *dof: *dman ) {
138 if ( !dof->isPrimaryDof() ) {
141 dof->giveMasterDofManArray(dofMasters);
142 for ( int m : dofMasters ) {
143 masters.insert( m );
144 }
145 }
146 }
147
148 for ( int connection: masters ) {
149 this->giveNode(count).addNeighbor(connection);
150 this->giveNode(connection).addNeighbor(count);
151 }
152 }
153 } // end dof man loop
154}
155
156
158SloanGraph :: giveNode(int num)
159{
160 return nodes[num-1];
161}
162
163int
164SloanGraph :: giveNodeWithMinDegree()
165{
166 int nnodes = (int)nodes.size();
167 int node_min = 0;
168 int min = nnodes + 1;
169
170 for ( int i = 1; i <= nnodes; i++ ) {
171 int deg = this->giveNode(i).giveDegree();
172 if ( deg < min && deg > 0 && ( this->giveNode(i).giveNewNumber() == 0 ) ) {
173 min = deg;
174 node_min = i;
175 }
176 }
177
178 return node_min;
179}
180
181void
182SloanGraph :: findPeripheralNodes()
183{
184 //if (startNode && endNode) return;
185
186 int InitialRoot;
187 std :: unique_ptr< SloanLevelStructure > Spine;
188
189 // clock_t time_0 = ::clock();
190 if ( SpineQuality == Best ) {
191 InitialRoot = findBestRoot();
192 } else if ( SpineQuality == Good ) {
193 InitialRoot = giveNodeWithMinDegree();
194 } else {
195 OOFEM_WARNING("Unsupported value of SpineQuality, using (Good)");
196 InitialRoot = giveNodeWithMinDegree();
197 }
198
199 // initial spine
200 Spine = std::make_unique<SloanLevelStructure>(this, InitialRoot);
201 this->startNode = InitialRoot;
202
203 int CurrentDiameter = Spine->giveDepth();
204 int TrialDepth, TrialWidth;
205 std :: list< int >candidates;
206 int newStartNode = 1;
207
208 while ( newStartNode ) {
209 newStartNode = 0;
210
211 this->extractCandidates(candidates, *Spine);
212 int MinimumWidth = domain->giveNumberOfDofManagers();
213
214 for ( int Root: candidates ) {
215 std :: unique_ptr< SloanLevelStructure > TrialSpine( new SloanLevelStructure(this, Root) );
216 // abort level struc assembly if TrialWidth>MinimumWidth.
217 if ( TrialSpine->formYourself(MinimumWidth) == 0 ) {
218 continue;
219 }
220
221 TrialDepth = TrialSpine->giveDepth();
222 TrialWidth = TrialSpine->giveWidth();
223 if ( TrialWidth < MinimumWidth && TrialDepth > CurrentDiameter ) {
224 Spine = std :: move( TrialSpine );
225 this->startNode = Root;
226 CurrentDiameter = Spine->giveDepth();
227 newStartNode = 1;
228 break; // exit for loop
229 }
230
231 if ( TrialWidth < MinimumWidth ) {
232 MinimumWidth = TrialWidth;
233 this->endNode = Root;
234 }
235 } // end loop over candidates
236 }
237
238 //clock_t time_1 = ::clock();
239
240 //printf("Time needed to select the starting node %10lds\n",(time_1-time_0)/ CLOCKS_PER_SEC);
241}
242
243
244void
245SloanGraph :: extractCandidates(std :: list< int > &candidates, SloanLevelStructure &Spine)
246{
247 int NumberOfLevels = Spine.giveDepth();
248 IntArray LastLevel = Spine.giveLevel(NumberOfLevels);
249 sort( LastLevel, SloanNodalDegreeOrderingCrit(this) );
250
251 candidates.clear();
252 // shrink candidates to contain only one node of each degree
253 int lastDegree = 0;
254 for ( int node: LastLevel ) {
255 if ( lastDegree != this->giveNode( node ).giveDegree() ) {
256 lastDegree = this->giveNode( node ).giveDegree();
257 candidates.push_back( node );
258 }
259 }
260}
261
262
263int
264SloanGraph :: computeTrueDiameter()
265{
266 SloanLevelStructure LSC( this, findBestRoot() );
267 return LSC.giveDepth();
268}
269
270int
271SloanGraph :: findBestRoot()
272/* this is a very expensive method */
273{
274 int BestRoot = 0;
275 int Diameter = 0;
276 int nnodes = (int)nodes.size();
277 clock_t time_1, time_0 = :: clock();
278 for ( int i = 1; i <= nnodes; i++ ) {
279 SloanLevelStructure LSC(this, i);
280 int Depth = LSC.giveDepth();
281 if ( Depth > Diameter ) {
282 Diameter = Depth;
283 BestRoot = i;
284 }
285
287 time_1 = :: clock();
288 if ( ( time_1 - time_0 ) / CLOCKS_PER_SEC > SLOAN_TIME_CHUNK ) {
289 OOFEM_LOG_INFO("%d roots (%5.1f per cent) checked: largest pseudo-diameter = %d\n", i, float ( 100 * i ) / nnodes, Diameter);
290 //fflush(stdout);
291 //if (get_yes_or_no() == NO) break;
292 time_0 = time_1;
293 }
294 }
295
296 return BestRoot;
297}
298
299
300double
301SloanGraph :: giveOptimalProfileDensity()
302{
303 int nnodes = (int)nodes.size();
304 double d = 0;
305 if ( nnodes > 0 ) {
306 d = 100.0 * MinimalProfileSize;
307 d /= ( nnodes * ( nnodes + 1 ) );
308 }
309
310 return d;
311}
312
313void
314SloanGraph :: initStatusAndPriority()
315{
316 this->findPeripheralNodes();
317#ifndef MDC
318 this->evaluateNodeDistances();
319 int nnodes = domain->giveNumberOfDofManagers();
320
321 for ( auto &node: nodes ) {
322 int Distance = node->giveDistance();
323 int Degree = node->giveDegree();
324 int Priority = WeightDistance * Distance - WeightDegree * ( Degree + 1 );
325 node->setPriority(Priority);
326 node->setStatus(SloanGraphNode :: Inactive);
327 }
328
329#else
330 int NumLevels;
331 int End = endNode;
332
333 int Distance, Degree, Priority;
334
335 SloanLevelStructure BackSpine(this, End);
336 NumLevels = BackSpine.giveDepth();
337
338 for ( int i = 1; i <= NumLevels; i++ ) {
339 for ( int nodeNum: BackSpine.giveLevel(i) ) {
340 Distance = i - 1;
341 this->giveNode(nodeNum).setDistance(Distance);
342 Degree = this->giveNode(nodeNum).giveDegree();
343 Priority = WeightDistance * Distance - WeightDegree * ( Degree + 1 );
344 this->giveNode(nodeNum).setPriority(Priority);
345 this->giveNode(nodeNum).setStatus(SloanGraphNode :: Inactive);
346 }
347 }
348
349#endif
350}
351
352
353void
354SloanGraph :: evaluateNodeDistances()
355{
356 int NumLevels;
357
358 if ( this->nodeDistancesFlag ) {
359 return;
360 }
361
362 int End = endNode;
363 SloanLevelStructure BackSpine(this, End);
364 NumLevels = BackSpine.giveDepth();
365
366 for ( int i = 1; i <= NumLevels; i++ ) {
367 for ( int nodeNum: BackSpine.giveLevel(i) ) {
368 this->giveNode(nodeNum).setDistance(i - 1);
369 }
370 }
371
372 this->nodeDistancesFlag = 1;
373}
374
375void
376SloanGraph :: assignOldNumbers()
377{
378 for ( auto &node: nodes ) {
379 node.assignOldNumber();
380 }
381}
382
383#ifdef MDC
384void
385SloanGraph :: numberIsolatedNodes(int &NextNumber, int &labeledNodes)
386{
387 for ( auto &node: nodes ) {
388 if ( node.giveDegree() == 0 ) {
389 node.setNewNumber(++NextNumber);
390 labeledNodes++;
391 }
392 }
393}
394#endif
395
396void
397SloanGraph :: assignNewNumbers()
398{
399 int Start, inext, NextNumber = 0;
400 int labeledNodes = 0;
401
402 for ( auto &node: nodes ) {
403 node.setNewNumber(0);
404 }
405
406#ifdef MDC
407 this->numberIsolatedNodes(NextNumber, labeledNodes);
408#endif
409
410 this->initStatusAndPriority();
411 //std::list<int>::iterator next;
412
413 Start = this->startNode;
414 this->queue.clear();
415 this->queue.push_back(Start);
416 this->giveNode(Start).setStatus(SloanGraphNode :: Preactive);
417
418#ifdef MDC
419 for ( ; ; ) {
420#endif
421 while ( !this->queue.empty() ) {
422 // finds top priority, returns the corresponding node and deletes the entry
423 inext = findTopPriorityInQueue();
424 // this->queue.erase(next); - done by findTopPriority
425 SloanGraphNode &nextNode = this->giveNode(inext);
426 if ( nextNode.giveStatus() == SloanGraphNode :: Preactive ) {
427 this->insertNeigborsOf(inext);
428 }
429
430 nextNode.setNewNumber(++NextNumber);
431 nextNode.setStatus(SloanGraphNode :: Postactive);
433 labeledNodes++;
434 }
435
436#ifdef MDC
437 if ( labeledNodes == this->numberOfNodes ) {
438 break;
439 }
440
441 // proceed next subdomain
442 this->initStatusAndPriority();
443 Start = this->startNode;
444 this->queue.clear();
445 this->queue.push_back(Start);
446 this->giveNode(Start).setStatus(SloanGraphNode :: Preactive);
447}
448#else
449 if ( labeledNodes != domain->giveNumberOfDofManagers() ) {
450 OOFEM_ERROR("Internal error:\n%s", "Isolated nodes or separated sub-domains exist");
451 }
452
453#endif
454}
455
456
457void
458SloanGraph :: insertNeigborsOf(int next)
459{
460 SloanGraphNode &node = this->giveNode(next);
461
462 for ( int nodeNum: node.giveNeighborList() ) {
463 if ( this->giveNode(nodeNum).giveStatus() == SloanGraphNode :: Inactive ) {
464 this->giveNode(nodeNum).setStatus(SloanGraphNode :: Preactive);
465 this->queue.push_front(nodeNum);
466 }
467 }
468}
469
470void
471SloanGraph :: modifyPriorityAround(int next)
472{
473 SloanGraphNode :: SloanGraphNode_StatusType status;
474 SloanGraphNode &nextNode = this->giveNode(next);
475
476 for ( int nodeNum: nextNode.giveNeighborList() ) {
477 SloanGraphNode &neighborNode = this->giveNode(nodeNum);
478 if ( neighborNode.giveStatus() == SloanGraphNode :: Preactive ) {
479 neighborNode.increasePriorityBy(WeightDegree);
480 neighborNode.setStatus(SloanGraphNode :: Active);
481 for ( int nnodeNum: neighborNode.giveNeighborList() ) {
482 SloanGraphNode &neighborOfNeighbor = this->giveNode(nnodeNum);
483 status = neighborOfNeighbor.giveStatus();
484 if ( status == SloanGraphNode :: Active || status == SloanGraphNode :: Preactive ) {
485 neighborOfNeighbor.increasePriorityBy(WeightDegree);
486 } else if ( status == SloanGraphNode :: Inactive ) {
487 neighborOfNeighbor.increasePriorityBy(WeightDegree);
488 this->queue.push_front(nnodeNum);
489 neighborOfNeighbor.setStatus(SloanGraphNode :: Preactive);
490 }
491 }
492 }
493 }
494}
495
496int
497SloanGraph :: findTopPriorityInQueue()
498{
499 int candidate = 0, priority, pmax = -WeightDegree * ( domain->giveNumberOfDofManagers() + 1 );
500 std :: list< int > :: iterator toDel;
501
502 for ( auto pos = queue.begin(); pos != queue.end(); ++pos ) {
503 priority = this->giveNode(* pos).givePriority();
504 if ( pmax < priority ) {
505 pmax = priority;
506 toDel = pos;
507 candidate = * pos;
508 }
509 }
510
511 if ( candidate ) {
512 this->queue.erase(toDel);
513 }
514
515 return candidate;
516}
517
518
519int
520SloanGraph :: computeProfileSize()
521{
522 int ProfSize = 0;
523 //clock_t time_1, time_0 = ::clock();
524
525 if ( WeightDistance || WeightDegree ) {
527 } else {
529 }
530
531 for ( auto &node: nodes ) {
532 ProfSize += node.computeProfileHeight();
533 }
534
535 //time_1 = ::clock();
536 //printf("Time needed to renumber the nodes %10lds\n",(time_1-time_0)/CLOCKS_PER_SEC);
537
538 return ProfSize;
539}
540
541
542void
543SloanGraph :: askNewOptimalNumbering(TimeStep *tStep)
544{
545 for ( int dmanNum: OptimalRenumberingTable ) {
546 dmans[ dmanNum - 1 ]->askNewEquationNumbers(tStep);
547 }
548}
549
550
551void
552SloanGraph :: writeRenumberingTable(FILE *file)
553{
554 int nnodes = (int)nodes.size();
555
556 for ( int i = 1; i <= nnodes; i++ ) {
557 int inew = this->giveNode(i).giveNewNumber();
558 fprintf(file, "%8i %8i\n", i, inew);
559 }
560}
561
562int
563SloanGraph :: writeOptimalRenumberingTable(FILE *OutputFile)
564{
565 if ( OptimalRenumberingTable.isEmpty() ) {
566 return 0;
567 }
568
569 int nnodes = OptimalRenumberingTable.giveSize();
570 for ( int i = 1; i <= nnodes; i++ ) {
571 fprintf( OutputFile, "%8i %8i\n", i, OptimalRenumberingTable.at(i) );
572 }
573
574 //fclose(OutputFile);
575 return 1;
576}
577
578void
579SloanGraph :: printParameters()
580{
581 printf("\nCurrent parameter values:\n");
582 printf(" 1) weight of degree = %d\n", WeightDegree);
583 printf(" 2) weight of distance = %d\n", WeightDistance);
584 printf(" 3) diameter quality = %d\n", SpineQuality);
585}
586
587void
588SloanGraph :: setParameters(int wdeg, int wdis)
589{
590 if ( wdeg >= 0 ) {
591 setWeightDegree(wdeg);
592 }
593
594 if ( wdis >= 0 ) {
595 setWeightDistance(wdis);
596 }
597}
598
599void
600SloanGraph :: tryParameters(int wdeg, int wdis)
601{
602
603 setParameters(wdeg, wdis);
604 int psize = computeProfileSize();
605#ifndef SILENT
606 // if (wdeg || wdis)
607 // printf("\nRenumbering with weights %2d / %2d",wdeg,wdis);
608 // else
609 // printf("\nExisting node numbering ");
610 // printf(" profile size %d",psize);
611#endif
612 if ( psize < MinimalProfileSize || MinimalProfileSize == 0 ) {
613 int nnodes = (int)nodes.size();
614 MinimalProfileSize = psize;
615 OptimalWeightDegree = wdeg;
617 OptimalRenumberingTable.resize(nnodes);
618 for ( int i = 1; i <= nnodes; i++ ) {
619 OptimalRenumberingTable.at( this->giveNode(i).giveNewNumber() ) = i;
620 }
621 }
622}
623
624
625int
626SloanGraph :: giveFullProfileSize()
627{
628 int n = (int)nodes.size();
629 return n * ( n + 1 );
630}
631} // end namespace oofem
void resize(int n)
Definition intarray.C:73
int & at(std::size_t i)
Definition intarray.h:104
std ::list< int > & giveNeighborList()
Returns the neighbor list of receiver.
SloanGraphNode_StatusType giveStatus()
Returns receiver status.
void increasePriorityBy(int p)
Increases the priority of receiver by given value.
void setNewNumber(int n)
Sets the new number of receiver.
void setStatus(SloanGraphNode_StatusType s)
Sets the status of receiver to given value.
int WeightDistance
Integer distance weight.
Definition sloangraph.h:99
int numberOfNodes
number of graph nodes (=numberOfNodes+numberOfElementInternalNodes)
Definition sloangraph.h:91
void extractCandidates(std ::list< int > &candidates, SloanLevelStructure &Spine)
Definition sloangraph.C:245
SpineQualityType SpineQuality
Definition sloangraph.h:102
int giveNodeWithMinDegree()
Returns graph node number with minimal degree.
Definition sloangraph.C:164
int WeightDegree
Integer degree weight.
Definition sloangraph.h:101
std::vector< DofManager * > dmans
List of dof managers corresponding to nodes.
Definition sloangraph.h:89
void evaluateNodeDistances()
Evaluates the nodal distances from backSpine. The backSpine is generated if not available.
Definition sloangraph.C:354
std::vector< SloanGraphNode > nodes
List of graph nodes.
Definition sloangraph.h:87
void initStatusAndPriority()
Initializes statuses and priority of nodes of receiver.
Definition sloangraph.C:314
std ::list< int > queue
Priority queue of active or preactive nodes.
Definition sloangraph.h:97
Domain * domain
Domain asoociated to graph.
Definition sloangraph.h:84
int OptimalWeightDegree
Optimal degree weight.
Definition sloangraph.h:106
void modifyPriorityAround(int)
Modifies the priority around node with max priority.
Definition sloangraph.C:471
IntArray OptimalRenumberingTable
Definition sloangraph.h:117
void setParameters(int wdeg, int wdis)
Sets weight degee and weight dist to given values.
Definition sloangraph.C:588
void insertNeigborsOf(int)
Inserts inactive neighbours of given node as preactive ones.
Definition sloangraph.C:458
void assignNewNumbers()
Implementation of node labeling algorithm.
Definition sloangraph.C:397
void findPeripheralNodes()
Finds the peripheral nodes (rooted in optimal start node) according to receiver quality and current w...
Definition sloangraph.C:182
int OptimalWeightDistance
Optimal distance weight.
Definition sloangraph.h:108
void setWeightDegree(int w)
Sets weight degree to given value.
Definition sloangraph.h:167
int MinimalProfileSize
Minimal profile size obtained.
Definition sloangraph.h:104
void setWeightDistance(int w)
Sets weight distance to given value.
Definition sloangraph.h:161
int nodeDistancesFlag
Flag indicating that node distances from endNode were already computed.
Definition sloangraph.h:110
int endNode
End peripheral node.
Definition sloangraph.h:95
SloanGraphNode & giveNode(int num)
Return graph node.
Definition sloangraph.C:158
int startNode
Start peripheral node.
Definition sloangraph.h:93
void assignOldNumbers()
Assigns old node numbers as new ones. Used to compute the profile of existing old numbering.
Definition sloangraph.C:376
int findTopPriorityInQueue()
Finds node with highest priority in queue, removes its entry and returns its number.
Definition sloangraph.C:497
void numberIsolatedNodes(int &NextNumber, int &labeledNodes)
Numbers isolated nodes (those with degree equal to 0).
Definition sloangraph.C:385
int giveDepth()
Returns the depth of receiver.
IntArray & giveLevel(int num)
Returns the i-th level of receiver.
#define OOFEM_WARNING(...)
Definition error.h:80
#define OOFEM_ERROR(...)
Definition error.h:79
#define OOFEM_LOG_INFO(...)
Definition logger.h:143
FloatArrayF< N > min(const FloatArrayF< N > &a, const FloatArrayF< N > &b)
void sort(IntArray &arry, operation op)
Definition intarray.h:423
#define SLOAN_TIME_CHUNK
Definition sloangraph.h:49

This page is part of the OOFEM-3.0 documentation. Copyright Copyright (C) 1994-2025 Borek Patzak Bořek Patzák
Project e-mail: oofem@fsv.cvut.cz
Generated at for OOFEM by doxygen 1.15.0 written by Dimitri van Heesch, © 1997-2011