[Pntool-developers] SF.net SVN: pntool:[74] spnbox
Brought to you by:
compaqdrew,
miordache
From: <Ste...@us...> - 2009-06-18 20:18:19
|
Revision: 74 http://pntool.svn.sourceforge.net/pntool/?rev=74&view=rev Author: StephenCamp Date: 2009-06-18 20:18:15 +0000 (Thu, 18 Jun 2009) Log Message: ----------- StructuredIO.c: Added some necessary comments, changed DisplayStructure's handling of empty matrices. pn2acpn: Testing still in progress. DO NOT USE yet. test-pn2acpn.c/mak/txt: The test files for pn2acpn. spnbox: Added the definitions for pn2acpn. test-nltrans.txt/mak/c: The test files for nltrans. clean.bsh: Deleted. This was a shell script for me to use during development. Modified Paths: -------------- spnbox/spnbox.h spnbox/tests/StructuredIO.c Added Paths: ----------- spnbox/pn2acpn.c spnbox/tests/test-nltrans.mak spnbox/tests/test-nltrans.txt spnbox/tests/test-pn2acpn.c spnbox/tests/test-pn2acpn.mak spnbox/tests/test-pn2acpn.txt Removed Paths: ------------- spnbox/tests/clean.bsh Property Changed: ---------------- spnbox/tests/ Added: spnbox/pn2acpn.c =================================================================== --- spnbox/pn2acpn.c (rev 0) +++ spnbox/pn2acpn.c 2009-06-18 20:18:15 UTC (rev 74) @@ -0,0 +1,614 @@ +#include "MemoryManager.h" +#include "spnbox.h" +#include "matrixmath.h" + +/*Subroutine declarations. See subroutine implementation for descriptions.*/ +void AddPlaces(pn2acpn_r* result, matrix* Dm, int Transition, int* Qflag, int QLength, int* ipl, int ipCount, int* dpl, int dpCount); + +int GetQPlace(matrix* U, int Pair, int* Qflag, int* mask, pn2acpn_r* result); + +matrix GetU(int* cardbt, pn2acpn_r* result, int Transition, int OriginalPlaces); + +int InitParts(MemoryManager* mem, pn2acpn_r* result, int** cardbt, int** cardbtx, int** Qflag, matrix* Dm, matrix* Dp, matrix* MX, matrix* L0); + +int CheckParams(MemoryManager* mem, matrix* Dm, matrix* Dp, int** Mask, matrix* MX, matrix* L0, matrix* L, int **ipl, int *ipCount, int **dpl, int *dpCount); + +int CheckParams(MemoryManager* mem, matrix* Dm, matrix* Dp, int** Mask, matrix* MX, matrix* L0, matrix* L, int **ipl, int *ipCount, int **dpl, int *dpCount); + +void FinalizeResult(pn2acpn_r* result, matrix* L, matrix* Dm, int* ipl, int ipCount); + +/*The function itself. pn2acpn transforms a Petri net to an asymmetric choice +Petri net*/ +pn2acpn_r pn2acpn(matrix* Dm, matrix* Dp, int* Mask, matrix* MX, matrix* L0, matrix* L, int *ipl, int ipCount, int* dpl, int dpCount) +{ + int Transition, Transitions, Places, i, j, x; + int *cardbt, *cardbtx, *Qflag; + MemoryManager mem; + matrix U; + pn2acpn_r result; + + /*Initialize the value to be returned to an error value.*/ + memset(&result, 0, sizeof(pn2acpn_r)); + + /*Initialize the memory manager.*/ + mem = CreateMemoryManager(10, 5, 0, 0); + + if (! CheckParams(&mem, Dm, Dp, &Mask, MX, L0, L, &ipl, &ipCount, &dpl, &dpCount)) + { + return result; + } + Places = NumberOfRows(*Dm); + Transitions = NumberOfColumns(*Dm); + + /*Initialize parts of the return value and the flag matrices cardbt and + cardbtx. This function returns the index of the first transition which should + be processed.*/ + Transition = InitParts(&mem, &result, &cardbt, &cardbtx, &Qflag, Dm, Dp, MX, L0); + + /*Loop until there are no more transitions to be processed. This will be when + Transition has been set to -1 by either of the functions responsible for + finding the next transition to be processed.*/ + while (Transition >= 0) + { + + cardbtx[Transition] = 0; + /*Get U, a 2xn matrix each column of which contains a pair of place + indices for which a particular relationship holds.*/ + U = GetU(cardbt, &result, Transition, NumberOfRows(*Dm)); + + /*Iterate through each of the place pairs. From each pair we will develop + the index of a single place, for which we will set the Q-flag and with + which we will make an alteration to the cardbtx flag array.*/ + /*The Q-flag array defaults to all cleared.*/ + memset(Qflag, 0, sizeof(int) * Places); + for (i = 0; i < NumberOfColumns(U); i++) + { + /*Get the index of some particular place algorithmically.*/ + if ((x = GetQPlace(&U, i, Qflag, Mask, &result)) >= 0) + { + /*Set the corresponding q flag.*/ + Qflag[x] = 1; + /*We OR cardbtx with cardbt AND the place from Dm*/ + for (j = 0; j < NumberOfColumns(*Dm); j++) + { + cardbtx[j] = cardbtx[j] || (cardbt[j] && GetMatrixEl(Dm, x, j)); + } + } + } + /*Free the memory allocated for the U vector now that we don't need it + anymore.*/ + DeallocateMatrix(&U); + /*Fill in the new places and transitions we reserved memory for above.*/ + AddPlaces(&result, Dm, Transition, Qflag, Places, ipl, ipCount, dpl, dpCount); + /*Clear the current element of cardbtx in case it was reset some how.*/ + cardbtx[Transition] = 0; + /*Find the next transition to be processed. This will be the first one with + a set cardbtx flag. The next transition number defaults to -1 to indicate + that there are no more transitions to be processed.*/ + Transition = -1; + for (i = 0; i < Transitions; i++) + { + if (cardbtx[i]) + { + Transition = i; + break; + } + } + } + /*Finalize the result structure; fill in the last few values.*/ + FinalizeResult(&result, L, Dm, ipl, ipCount); + FreeMemory(&mem); + return result; +} + + +/************************************************** + Subroutines +**************************************************/ +/* +FinalizeResult takes the result structure and fills in values that are not +computed during the main processing loop: the incidence matrix Df, the new +marking constraint matrix Lf, and the new independent place list iplf. +*/ +void FinalizeResult(pn2acpn_r* result, matrix* L, matrix* Dm, int* ipl, int ipCount) +{ + int places, lrows, oldplaces, i, place; + /*Fill the incidence matrix.*/ + result->Df = SubtractMatrix(&result->Dpf, &result->Dmf, (matrix*) 2); + + if (L) + { + lrows = NumberOfRows(*L); + oldplaces = NumberOfColumns(*L); + } + else + { + lrows = 0; + oldplaces = NumberOfRows(*Dm); + } + places = NumberOfRows(result->Df); + + /*The new L matrix should be the same as the old with columns of zeros added + for all the new places.*/ + AllocateMatrixType(2, &result->LF, lrows, places); + CopyBlock(lrows, oldplaces, L, 0, 0, &result->LF, 0, 0); + + /*The new independent place list should contain all the places that were + added and each of the old places.*/ + result->iplfCount = ipCount + (places - oldplaces); + result->iplf = tcalloc(result->iplfCount, sizeof(int)); + for (i = 0; i < ipCount; i++) + { + result->iplf[i] = ipl[i]; + } + place = oldplaces; + for (; i < result->iplfCount; i++) + { + result->iplf[i] = place++; + } +} + +/* +AddPlaces is responsible for filling in the values in the transitions and +places that are added during each iteration of the main processing loop. It +fills in values in the Petri net matrices, in the initial marking constraint +matrix L0f, and in the independent-to-dependent marking transformation matrix +MXf. It requires the result vector, the original output matrix, the transition +currently being processed by the main processing loop, the vector of Q flags, +the length of the vector of Q flags, and the lists of dependent and independent +places. +*/ +void AddPlaces(pn2acpn_r* result, matrix* Dm, int Transition, int* Qflag, int Qlength, int* ipl, int ipCount, int* dpl, int dpCount) +{ + int Rows, Cols, i, j, x, ToAdd = 0; + matrix Dmf, Dpf, MX, L0; + /*We will be adding a place and transition for each set Qflag. Count them.*/ + for (i = 0; i < Qlength; i++) + { + if (Qflag[i]) ToAdd++; + } + + /*We will be adding new columns to MX and L0. Reseve space and copy + existing columns.*/ + if (NumberOfRows(result->MXF)) + { + AllocateMatrixType(2, &MX, NumberOfRows(result->MXF), NumberOfColumns(result->MXF) + ToAdd); + CopyBlock(NumberOfRows(result->MXF), NumberOfColumns(result->MXF), &result->MXF, 0, 0, &MX, 0, 0); + } + else + { + /*Zero MX.*/ + AllocateMatrix(&MX, 0, 0); + } + if (NumberOfRows(result->L0F)) + { + AllocateMatrixType(2, &L0, NumberOfRows(result->L0F), NumberOfColumns(result->L0F) + ToAdd); + CopyBlock(NumberOfRows(result->L0F), NumberOfColumns(result->L0F), &result->L0F, 0, 0, &L0, 0, 0); + } + else + { + AllocateMatrix(&L0, 0, 0); + } + + /*We add rows and columns to the Petri net matrices. Store them temporarily + in the local Dmf and Dpf matrices. Allocate space and copy the existing + matrices into the new space.*/ + Rows = NumberOfRows(result->Dmf); + Cols = NumberOfColumns(result->Dmf); + AllocateMatrixType(2, &Dmf, Rows + ToAdd, Cols + ToAdd); + AllocateMatrixType(2, &Dpf, Rows + ToAdd, Cols + ToAdd); + CopyBlock(Rows, Cols, &result->Dmf, 0, 0, &Dmf, 0, 0); + CopyBlock(Rows, Cols, &result->Dpf, 0, 0, &Dpf, 0, 0); + /*Iterate through the places which have been Q-flagged. At each one add a new + place and transition. Use j to keep track of which place or transition is + being added.*/ + j = 0; + for (i = 0; i < Qlength; i++) + { + if (! Qflag[i]) continue; + /*Zero the output arc at the place and transition of interest*/ + SetMatrixEl(&Dmf, i, Transition, 0); + /*The place gets an output arc added at the new transition*/ + SetMatrixEl(&Dmf, i, Cols + j, 1); + /*The new place gets an input arc added at the new transition*/ + SetMatrixEl(&Dpf, Rows + j, Cols + j, 1); + /*The new place gets an output arc added to the current transition, with + the same weight as the original output arc at the place and transition of + interest.*/ + SetMatrixEl(&Dmf, Rows + j, Transition, GetMatrixEl(Dm, i, Transition)); + /*Modify MX and L0 if they exist.*/ + if (NumberOfRows(MX) || NumberOfRows(L0)) + { + /*Find out if the current place is independent..*/ + for (x = 0; x < ipCount; x++) + { + if (ipl[x] == i) + { + break; + } + } + /*If it is, we want to copy to the new columns of MX and L0 the values + in their respective existing columns corresponding to the independent + place just found. (if they exist)*/ + if (x < ipCount) + { + if (NumberOfRows(MX)) + { + CopyBlock(NumberOfRows(MX), 1, &MX, 0, x, &MX, 0, NumberOfColumns(MX) - ToAdd + j); + } + if (NumberOfRows(L0)) + { + CopyBlock(NumberOfRows(L0), 1, &L0, 0, x, &L0, 0, NumberOfColumns(L0) - ToAdd + j); + } + } + /*If not, we fill the new column of MX with a -1 in the row corresponding + to the dependent place indexed to by i. The new column of L0 remains zeroed.*/ + else + { + /*Get the index of the place.*/ + for (x = 0; x < dpCount; x++) + { + if (dpl[x] == i) break; + } + if (x < dpCount) + { + SetMatrixEl(&MX, x, NumberOfColumns(MX) - ToAdd + j, -1); + } + } + } + /*Increment the added-place counter.*/ + j++; + } + /*Now deallocate the old matrices and replace them with the new.*/ + DeallocateMatrix(&result->Dmf); + result->Dmf = Dmf; + DeallocateMatrix(&result->Dpf); + result->Dpf = Dpf; + if (NumberOfRows(MX)) + { + DeallocateMatrix(&result->MXF); + result->MXF = MX; + } + if (NumberOfRows(L0)) + { + DeallocateMatrix(&result->L0F); + result->L0F = L0; + } +} + +/*GetQPlace examines the pair of place indices in the U matrix that is +currently being processed and selects and returns on of the indicies, to be used +to set a Q-flag and perform operations on the cardbtx flag vector. +It requires the U matrix, the pair number ot process, the vector of Q-flags, +the mask provided to pn2acpn, and the result structure for read access to Dmf +and Dpf.*/ +int GetQPlace(matrix* U, int Pair, int* Qflag, int* mask, pn2acpn_r* result) +{ + int U1, U2, i, arcCount1, arcCount2; + U1 = GetMatrixEl(U, 0, Pair); + U2 = GetMatrixEl(U, 1, Pair); + + /*If neither U1 nor U2 indexes a place that has a high mask bit, there is no + valid Q index. Return -1.*/ + if (! (mask[U1] || mask[U2])) + { + return -1; + } + /*If only one of U1 or U2 index a place with a high mask bit, that place is + the Q index.*/ + if (mask[U1] && (! mask[U2])) + { + return U1; + } + if ((! mask[U1]) && mask[U2]) + { + return U2; + } + /*Otherwise, we do some other tests to eliminate one of the numbers.*/ + /*If the qflags for both of the U indices are still 0...*/ + if (! (Qflag[U1] && Qflag[U2])) + { + //If we are examining any but the last pair of place indexes: + if (Pair < NumberOfColumns(*U)) + { + /*If the first index in the next pair is the same as the first index in this pair, return that index.*/ + if (U1 == GetMatrixEl(U, 0, Pair + 1)) + { + return U1; + } + else + { + /*Otherwise, if there exist any pairs after the current one such that one of their indicies is the same as the second index of the current pair, return the second index of the current pair.*/ + for (i = Pair; i < NumberOfColumns(*U); i++) + { + if (GetMatrixEl(U, 0, i) == U2 || GetMatrixEl(U, 1, i) == U2) + { + return U2; + } + } + } + } + /*If we get here, then we count the number of nonzero output arcs in the places specified by U1 and U2. We return the index of the place with the most nonzero output arcs.*/ + arcCount1 = 0; + arcCount2 = 0; + for (i = 0; i < NumberOfColumns(result->Dmf); i++) + { + if (GetMatrixEl(&result->Dmf, U1, i)) arcCount1++; + if (GetMatrixEl(&result->Dpf, U2, i)) arcCount1++; + } + if (arcCount1 >= arcCount2) + { + return U1; + } + else + { + return U2; + } + } + //If we get here, no valid place number was found. Return -1. + return -1; +} + +/*GetU creates and returns a 2 x n matrix, each column of which is a pair of +place indices such that the places exhibit some special relationship. The U +matrix is specific to the transition currently being investigated. Its +calculation requires the cardbt flag vector, the result structure for access +to the Petri net matrices, the index of the transition currently being processed, +and a count of the number of places in the original Petri net.*/ +matrix GetU(int* cardbt, pn2acpn_r* result, int Transition, int OriginalPlaces) +{ + int *Row0, *Row1; + int NextCol, ColCapacity, i, j, k, flag0, flag1, Transitions, dmi, dmj; + Transitions = NumberOfColumns(result->Dmf); + matrix U; + + /*U will be a 2xn matrix, where each column contains a pair of place + indices. The pairs are determined algorithmically by examining all places in + the current transition that are nonzero.*/ + + /*It is hard to predict the number of pairs that will be found. Store pairs + in an array that will be reallocated periodically if necessary. Allocated + in blocks the size of the number of places.*/ + Row0 = tcalloc(OriginalPlaces, sizeof(int)); + Row1 = tcalloc(OriginalPlaces, sizeof(int)); + NextCol = 0; + ColCapacity = OriginalPlaces; + + /*Iterate through all the places in the transition of interest.*/ + for (i = 0; i < OriginalPlaces - 1; i++) + { + //If the output arc here is nonzero... + if (GetMatrixEl(&result->Dmf, i, Transition)) + { + /*Iterate through all the remaining places*/ + for (j = i; j < OriginalPlaces; j++) + { + //If the output arc is nonzero... + if (GetMatrixEl(&result->Dmf, j, Transition)) + { + /*The original Matlab algorithm set a flag according to this + algorithm: (some variable names changed to make it apparent in terms + of variables already used): + prj = Dmf(i,:) ~= 0; % p_j\bullet + prk = Dmf(j,:) ~= 0; % p_k\bullet + flag = sum((~prj) & (prj | prk)) & sum((~prk) & (prj | prk)); + The same operation can be reduced to a pair of examinations, one + to get the results of the operation logical of + sum((~prj) & (prj | prk)) and the next to get the results of the + operation logical of sum((~prk) & (prj | prk))*/ + flag0 = 0; + flag1 = 0; + for (k = 0; k < Transitions; k++) + { + dmi = GetMatrixEl(&result->Dmf, i, k); + dmj = GetMatrixEl(&result->Dmf, j, k); + if ((! dmi) && dmj) + { + flag0 = 1; + } + if ((! dmj) && dmi) + { + flag1 = 1; + } + } + /*If both flags are set, we add the current pair of places to the + U-matrix.*/ + if (flag0 && flag1) + { + /*If there is space left in the temporary holding places, use it. If + not, reallocate with more space.*/ + if (NextCol == ColCapacity) + { + Row0 = realloc(Row0, (ColCapacity += OriginalPlaces) * sizeof(int)); + Row1 = realloc(Row1, (ColCapacity += OriginalPlaces) * sizeof(int)); + } + Row0[NextCol] = i; + Row1[NextCol++] = j; + } + } + } + } + } + /*Copy the contents of Row0 and Row1 into the U-matrix.*/ + AllocateMatrixType(2, &U, 2, NextCol); + for (i = 0; i < NextCol; i++) + { + SetMatrixEl(&U, 0, i, Row0[i]); + SetMatrixEl(&U, 1, i, Row1[i]); + } + free(Row0); + free(Row1); + return U; +} + +/* +InitParts initializes intermediate variables cardbt and cardbtx, flag arrays, +Qflag, a flag array, and the Petri net matrices in the result structure. +*/ +int InitParts(MemoryManager* mem, pn2acpn_r* result, int** cardbt, int** cardbtx, int** Qflag, matrix* Dm, matrix* Dp, matrix* MX, matrix* L0) +{ + int Places, Transitions, i, j, res = -1; + Places = NumberOfRows(*Dm); + Transitions = NumberOfColumns(*Dp); + + /*The result Petri net matrices should default to the same as the passed + matrices. The result MX and L0 should also default to the same as the passed + MX and L0, if they were given.*/ + AllocateMatrixType(2, &result->Dmf, Places, Transitions); + CopyMatrix(Dm, &result->Dmf); + AllocateMatrixType(2, &result->Dpf, Places, Transitions); + CopyMatrix(Dp, &result->Dpf); + if (MX) + { + AllocateMatrixType(2, &result->MXF, NumberOfRows(*MX), NumberOfColumns(*MX)); + CopyMatrix(MX, &result->MXF); + } + if (L0) + { + AllocateMatrixType(2, &result->L0F, NumberOfRows(*L0), NumberOfColumns(*L0)); + CopyMatrix(L0, &result->L0F); + } + + /*The Q-flag array has an element for each place.*/ + *Qflag = mcalloc(mem, Places, sizeof(int)); + + /*cardbt and cardbts are flag arrays with elements for each transition. They + default to containing a count of the number of nonzero output arcs associated + with the appropriate transition.*/ + *cardbt = mcalloc(mem, Transitions, sizeof(int)); + *cardbtx = mcalloc(mem, Transitions, sizeof(int)); + /*The return value should be the index of the first transition which will be + processed. This is the index of the first transition such that the associated + value of cardbt is greater than one.*/ + for (i = 0; i < Transitions; i++) + { + for (j = 0; j < Places; j++) + { + if (GetMatrixEl(Dm, j, i)) + { + (*cardbt)[i]++; + (*cardbtx)[i]++; + } + if (res < 0 && (*cardbt)[i] > 1) + { + res = i; + } + } + } + + return res; +} + +/* +CheckParams examines the parameters to make sure that required parameters are +present and dimensions and array counts are consistent. It also fills in default +values where appropriate. +*/ +int CheckParams(MemoryManager* mem, matrix* Dm, matrix* Dp, int** Mask, matrix* MX, matrix* L0, matrix* L, int **ipl, int *ipCount, int **dpl, int *dpCount) +{ + int Places, Transitions, i, j; + + /*Dm - output matrix. Required. Same size as Dp. + Dp - input matrix. Required. Same size as Dm. */ + if (! (Dm && Dp)) + { + merror(0, "PN2ACPN: One of the required parameters Dm or Dp was null"); + return 0; + } + Places = NumberOfRows(*Dm); + Transitions = NumberOfColumns(*Dm); + if (NumberOfRows(*Dp) != Places || NumberOfColumns(*Dm) != Transitions) + { + merror(0, "PN2ACPN: The sizes of the input and output matrices Dp and Dm do not agree"); + return 0; + } + + /*Mask = array of int flags. Not required. Defaults to array of 1s, one for + each place (row of D).*/ + if (! *Mask) + { + (*Mask) = mcalloc(mem, Places, sizeof(int)); + for (i = 0; i < Places; i++) + { + (*Mask)[i] = 1; + } + } + + /*L0 - matrix, initial marking constraint. Not required. If present must have + the same number of columns as there are places.*/ + if (L0) + { + if (NumberOfColumns(*L0) != Places) + { + FreeMemory(mem); + merror(0, "PN2ACPN: The size of L0 and the Petri net matrices do not agree"); + return 0; + } + } + + /*L - matrix, marking constraint. Not required. If present must have the same + number of columns as there are places.*/ + if (L) + { + if (NumberOfColumns(*L) != Places) + { + FreeMemory(mem); + merror(0, "PN2ACPN: The size of L and the Petri net matrices do not agree"); + return 0; + } + } + + /*ipl, dpl: unpredictable-length int arrays, holding indices of independent + and dependent places. Not required. If either array pointer is null the count + should be set to zero and vice-versa. + Total number of ipl and dpl must be same as number of places if given. ipl + defaults to indexes of all places, dpl defaults to empty set.*/ + if (! *ipl) + { + *ipCount = 0; + } + else if (! ipCount) + { + *ipl = 0; + } + if (! *dpl) + { + *dpCount = 0; + } + else if (! (*dpCount)) + { + *dpl = 0; + } + + if (! (*ipCount)) + { + *ipl = mcalloc(mem, Places, sizeof(int)); + for (i = 0; i < Places; i++) + { + (*ipl)[i] = i; + } + *ipCount = Places; + } + + if (*ipCount + *dpCount != Places) + { + FreeMemory(mem); + merror(0, "PN2ACPN: The independent and dependent place list lengths do not agree with the number of places"); + return 0; + } + + /*MX - matrix, same meaning as in msplit: is the transformation matrix from + the independent marking to the dependent marking. Not required. If present, + must have the same number of rows as there are dependent places and the same + number of columns as there are independent places (if those counts are given)*/ + if (MX && ipCount && dpCount) + { + if (NumberOfRows(*MX) != *dpCount || NumberOfColumns(*MX) != *ipCount) + { + FreeMemory(mem); + merror(0, "PN2ACPN: The size of the transformation matrix MX and the dependent and independent place counts do not agree"); + return 0; + } + } + return 1; +} Modified: spnbox/spnbox.h =================================================================== --- spnbox/spnbox.h 2009-06-18 19:27:22 UTC (rev 73) +++ spnbox/spnbox.h 2009-06-18 20:18:15 UTC (rev 74) @@ -71,6 +71,18 @@ int dtrCount; } nltrans_r; +typedef struct pn2acpn_r +{ + matrix Df; + matrix Dmf; + matrix Dpf; + matrix MXF; + matrix L0F; + matrix LF; + int *iplf; + int iplfCount; +} pn2acpn_r; + /*Constants returned in by functions to indicate the nature of the result.*/ #define HOW_ERROR "error" #define HOW_OK "OK" @@ -319,4 +331,37 @@ Written by Marian V. Iordache, mar...@le.... Converted to C by Marian V. Iordache and Stephen Camp, ste...@le... */ + +pn2acpn_r pn2acpn(matrix* Dm, matrix* Dp, int* Mask, matrix* MX, matrix* L0, matrix* L, int *ipl, int ipCount, int* dpl, int dpCount); +/* +PN2ACPN - Transforms a Petri net to an asymmetric choice Petri net + +[Df, Dmf, Dpf] = pn2acpn(D) + +[Df, Dmf, Dpf] = pn2acpn(Dm, Dp) + +[Df, Dmf, Dpf] = pn2acpn(Dm, Dp, mask) + +[Df, Dmf, Dpf, MF, L0F, LF, iplf] = pn2acpn(Dm, Dp, mask, M, L0, L, ipl, dpl) + +mask is an optional argument: mask(i) = 1 if the place i is +in the set M of the technical report. (M in the tech. report has +a different meaning than M in the TLE program.) + +The last four parameters are optional. See TLE code for meaning. +Their updated values are given as output. + +If called before the first iteration, take LF = MF, as the computation +of LF does not consider this case! + +See MSPLIT. + + +Written by Marian V. Iordache, mar...@le... +M. Iordache -- Sep. 4, 2000 +revised on Oct. 24, 2001 + +Converted to C by Marian V. Iordache and Stephen Camp, ste...@le... +Stephen Camp -- Jun. 18, 2009 +*/ #endif Property changes on: spnbox/tests ___________________________________________________________________ Added: svn:ignore + *.bsh *.exe *.stackdump Modified: spnbox/tests/StructuredIO.c =================================================================== --- spnbox/tests/StructuredIO.c 2009-06-18 19:27:22 UTC (rev 73) +++ spnbox/tests/StructuredIO.c 2009-06-18 20:18:15 UTC (rev 74) @@ -75,24 +75,29 @@ the end of the parse.*/ ParamsAssigned = mcalloc(&memory, ParamCount, sizeof(int)); } - + while (1) { + /*Read a token. If we have reached end of file return 0.*/ if (fscanf(file, "%s", Token) == EOF) { FreeMemory(&memory); return 0; } + /*If it is the problem-end keyword return 1.*/ else if (! strcmp(Token, "done")) { FreeMemory(&memory); return 1; } + /*If it is the file-end keyword return 0.*/ else if (! strcmp(Token, "quit")) { FreeMemory(&memory); return 0; } + /*If it is a request to echo a line to the screen, read the line, move past + any leading whitespace, and print to the screen.*/ else if (! strcmp(Token, "echo")) { fgets(Line, 128, file); @@ -100,20 +105,29 @@ while (isspace(*LineStart) && *LineStart != '\0') LineStart++; printf("%s", LineStart); } + /*If it is a line comment read the remainder of the line and do nothing.*/ else if (! strcmp(Token, "rem")) { fgets(Line, 128, file); } + /*Otherwise try to parse it as a user-defined token.*/ else { + /*Get the ID (index in the parameter array) of the parameter to which + the token corresponds. FindParameter will return -1 if it cannot find + a parameter with the given name.*/ if ((PID = FindParameter(Token, Param, ParamCount)) >= 0) { + /*If the parameter has already been assigned it is a fatal error. Fail.*/ if (ParamsAssigned[PID]) { FreeMemory(&memory); merror(0, "PARSE: Parameter defined twice. Failing."); return 0; } + /*If the parameter is a matrix, read using the readmatrix function. + ReadMatrix will return an empty matrix in case of failure. Such an error + is fatal.*/ if (! strcmp(Param[PID].Type, "matrix")) { *((matrix*)Data[Param[PID].Start]) = ReadMatrix(file); @@ -125,32 +139,44 @@ } ManageMatrix(Memory, (matrix*)Data[Param[PID].Start]); } + /*If it is an int array read that. There are two members of the + variable-length parameter list for each array; the first is the pointer + to the array pointer and the second is a pointer to an int to hold the + array length.*/ else if (! (strcmp(Param[PID].Type, "arrayi") && strcmp(Param[PID].Type, "indexarray"))) { *((int**) Data[Param[PID].Start]) = ReadIntArray(file, &Length); *((int*) Data[Param[PID].Start + 1]) = Length; ManageMemory(Memory, *((void**) Data[Param[PID].Start])); } + /*Array of doubles..*/ else if (! strcmp(Param[PID].Type, "arrayd")) { *((double**) Data[Param[PID].Start]) = ReadDblArray(file, &Length); *((int*) Data[Param[PID].Start + 1]) = Length; ManageMemory(Memory, *((void**) Data[Param[PID].Start])); } + /*A single integer.*/ else if (! strcmp(Param[PID].Type, "int")) { fscanf(file, "%d", ((int*) Data[Param[PID].Start])); } + /*A single double.*/ else if (! strcmp(Param[PID].Type, "double")) { fscanf(file, "%lf", ((double*) Data[Param[PID].Start])); } + /*A string. Read the remainder of the line. Note that this assumes that + the corresponding pointer in the variable-length parameter list is + a pre-allocated pointer to a char array of at least length 128.*/ else if (! strcmp(Param[PID].Type, "string")) { fgets(((char*) Data[Param[PID].Start]), 128, file); } else { + /*If we get here it means that somehow the Type string of the + parameter array was mis-set. Fail due to internal error.*/ FreeMemory(&memory); merror(0, "PARSE: Internal Error"); return 0; @@ -159,6 +185,8 @@ } else { + /*If we get here, it means that FindParam could not find a parameter + with the name given. Warn about the unrecognized token and continue.*/ printf("Warning: Unrecognized Token '%s'\n", Token); } } @@ -182,18 +210,22 @@ memory = CreateMemoryManager(10, 10, 0, 0); + /*Parse the data description and get a parameter type array.*/ if (! (ParamCount = ParseDescription(&memory, DataDescription, &Params, &DataLength))) { merror(0, "DISPLAYDATA: Bad Data Description"); return; } - + /*Iterate through the parameters. Since the variable-length parameter list + is given in the same order as the parameters, we can just read it one + parameter at a time.*/ for (i = 0; i < ParamCount; i++) { if (! strcmp(Params[i].Type, "matrix")) - { + { Matrix = va_arg(args, matrix*); - if (Mask ? Mask[i] : 1) + /*If the matrix is zero-sized don't show it.*/ + if (Mask ? Mask[i] : 1 && NumberOfRows(*Matrix) && NumberOfColumns(*Matrix)) { ShowMatrix(Matrix, Params[i].Name); } @@ -202,6 +234,8 @@ { IntP = va_arg(args, int*); Length = va_arg(args, int); + /*Print the name of the array and then all its elements after it on one + line.*/ if (Mask ? Mask[i] : 1) { printf("%s:\t", Params[i].Name); @@ -214,8 +248,20 @@ } else if (! strcmp(Params[i].Type, "indexarray")) { + /*An indexarray is stored like a normal integer array except for a third + element in the variable parameter list. It is displayed differently as + well. The indexarray represents a variable-length array of column + indices. When it is displayed, rather than showing the indices, the + columns corresponding the indices are marked in stdout with an X. Columns + without a corresponding index entry are left blank. Columns are separated + by tabs; the display is intended to be compatible with matrix display, + which also separates columns with tabs*/ IntP = va_arg(args, int*); Length = va_arg(args, int); + /*The third parameter is another integer representing the total number + of columns in the matrix. This is used so that the list name can be + printed not immediately after the last index mark but instead to the right + of the last column of the matrix.*/ MarkWidth = va_arg(args, int); if (Mask ? Mask[i] : 1) { @@ -236,7 +282,9 @@ } else if (! strcmp(Params[i].Type, "arrayd")) { - DblP = va_arg(args, double*); + /*The first parameter is the array pointer and the second is the array + length.*/ + DblP = va_arg(args, double*); Length = va_arg(args, int); if (Mask ? Mask[i] : 1) { @@ -274,6 +322,7 @@ } else { + /*If we get here it means that the parameter type name was invalid.*/ merror(0, "DISPLAYDATA: Data Description Error"); va_end(args); return; @@ -286,10 +335,13 @@ { int* ret; int i; + /*Return a null pointer in case of failure to read the array length.*/ if (! fscanf(file, "%d", length)) { return 0; } + /*Allocate array space, read the array elements, and return. If an error + occurs free the allocated space and return a null pointer.*/ ret = tcalloc(*length, sizeof(int)); for (i = 0; i < *length; i++) { @@ -306,10 +358,13 @@ { double* ret; int i; + /*Return a null pointer in case of failure to read the array length.*/ if (! fscanf(file, "%d", length)) { return 0; } + /*Allocate array space, read the array elements, and return. If an error + occurs free the allocated space and return a null pointer.*/ ret = tcalloc(*length, sizeof(double)); for (i = 0; i < *length; i++) { @@ -327,13 +382,15 @@ int* data; int rows, cols, i; matrix Ret; - + /*Zero the matrix initially. An empty matrix will be the rror return value.*/ AllocateMatrix(&Ret, 0, 0); + /*Read matrix size.*/ if (fscanf(file, "%d %d", &rows, &cols) != 2) { return Ret; } + /*Allocate space for and read a 1D array containing all the elements by row.*/ data = tcalloc(rows * cols, sizeof(int)); for (i = 0; i < rows * cols; i++) { @@ -343,6 +400,7 @@ return Ret; } } + /*Use vector2matrix to convert the 1D array into a matrix.*/ Ret = vector2matrix(data, rows, cols); free(data); return Ret; @@ -352,6 +410,7 @@ { int i; char* start; + /*Search the parameter list for a parameter with the given name.*/ for (i = 0; i < ParamCount; i++) { if (! strcmp(Name, Param[i].Name)) @@ -359,9 +418,15 @@ return i; } } + /*If no such parameter is found, return a -1.*/ return -1; } +/*ParseDescription parses the given data description string and fills the +Param array of parameters with corresponding parameter structures. It returns +the number of parameters found, and fills the integer pointed to by Items with +the number of parameters to expect in the variable-length parameter list in +ParseStructure.*/ int ParseDescription(MemoryManager* memory, char* DataDescription, Parameter** Param, int* Items) { char Description[256]; @@ -375,44 +440,62 @@ (*Items) = 0; strcpy(Description, DataDescription); - + /*Tokenize the string around commas, the delimiters.*/ Token = strtok(Description, ","); if (Token) { do { + /*Reac the parameter type and name strings.*/ if (sscanf(Token, "%s %s", Type, Name) == 2) { + /*For search purposes, get a copy of the type string and add a space to + the end.*/ strcpy(Type2, Type); strcat(Type2, " "); ToAdd = 0; + /*If the type is a matrix, int, double, or string, then only one item + should be expected in the parameter list corresponding to this parameter.*/ if (strstr("matrix int double string ", Type2)) { ToAdd = 1; } + /*If the type is an array type, there will be two items in + ParseStructure's parameter list: the array pointer and the pointer to + the integer destined to hold the array length.*/ else if (strstr("arrayi arrayd indexarray ", Type2)) { ToAdd = 2; } + /*If the parameter was recognized...*/ if (ToAdd) { + /*If the parameter has not already been defined...*/ if (FindParameter(Name, (*Param), TokenCount) >= 0) { + /*If it has, empty the list and fail.*/ free(*Param); merror(0, "READER: Data description containts duplicate item names"); return 0; } + /*Add a new element to the array of parameter description structures + and fill in its members.*/ (*Param) = realloc(*Param, (TokenCount + 1) * sizeof(Parameter)); (*Param + TokenCount)->Name = mcalloc(memory, strlen(Name) + 1, sizeof(char)); strcpy((*Param + TokenCount)->Name, Name); (*Param + TokenCount)->Type = mcalloc(memory, strlen(Type) + 1, sizeof(char)); strcpy((*Param + TokenCount)->Type, Type); + /*Start indicates which element in a variable-length parameter list + will be the first element corresponding to this parameter. It has + to be adjusted separately from TokenCount because some parameters + need multiple elements in the parameter list.*/ (*Param + TokenCount)->Start = *Items; TokenCount++; (*Items) += ToAdd; } else { + /*If we get here the parameter type was not recognized. Fail.*/ free(*Param); merror(0, "READER: Bad data description type"); return 0; @@ -420,17 +503,22 @@ } else { + /*If we get here there were problems reading the description group (a + single comma-surrounded token from the original data description). + Fail.*/ free(*Param); printf("Warning: Bad Data Description Group '%s %s'.\n", Type, Name); merror(0, "READER: Bad data description group"); return 0; } } while (Token = strtok(0, ",")); + /*Add the parameter structure array to the list of memory to be freed later.*/ ManageMemory(memory, *Param); return TokenCount; } else { + /*If there was an error tokenizing the string, return 0.*/ return 0; } } Deleted: spnbox/tests/clean.bsh =================================================================== --- spnbox/tests/clean.bsh 2009-06-18 19:27:22 UTC (rev 73) +++ spnbox/tests/clean.bsh 2009-06-18 20:18:15 UTC (rev 74) @@ -1,3 +0,0 @@ -rm *.o -rm *.exe -rm *.stackdump Added: spnbox/tests/test-nltrans.mak =================================================================== --- spnbox/tests/test-nltrans.mak (rev 0) +++ spnbox/tests/test-nltrans.mak 2009-06-18 20:18:15 UTC (rev 74) @@ -0,0 +1,34 @@ +COMPILER=gcc -g -ggdb + +test-nltrans: test-nltrans.o pns.o matrix.o general.o matrixmath.o ipsolve.o MemoryManager.o ipslv.o StructuredIO.o nltrans.o + $(COMPILER) -o test-nltrans.exe pns.o matrix.o general.o matrixmath.o test-nltrans.o ipsolve.o ipslv.o MemoryManager.o StructuredIO.o nltrans.o ../lp_solve_5.5/lpsolve55/liblpsolve55.a + +matrix.o: ../../pnheaders/matrix.c ../../pnheaders/matrix.h ../../pnheaders/pns.h + $(COMPILER) -c ../../pnheaders/matrix.c + +pns.o: ../../pnheaders/pns.h ../../pnheaders/pns.c ../../pnheaders/general.h + $(COMPILER) -c ../../pnheaders/pns.c + +general.o: ../../pnheaders/general.h ../../pnheaders/general.c + $(COMPILER) -c ../../pnheaders/general.c + +matrixmath.o: ../matrixmath.c ../matrixmath.h ../../pnheaders/general.h ../../pnheaders/matrix.h + $(COMPILER) -c ../matrixmath.c + +ipsolve.o: ../spnbox.h ../matrixmath.h ../../pnheaders/general.h ../../pnheaders/matrix.h ../MemoryManager.h ../ipsolve.c + $(COMPILER) -c ../ipsolve.c + +ipslv.o: ../spnbox.h ../ipslv.c + $(COMPILER) -c ../ipslv.c + +MemoryManager.o: ../MemoryManager.h ../MemoryManager.c ../../pnheaders/general.h ../../pnheaders/matrix.h + $(COMPILER) -c ../MemoryManager.c + +test-nltrans.o: test-nltrans.c ../spnbox.h ../MemoryManager.h + $(COMPILER) -c test-nltrans.c + +StructuredIO.o: StructuredIO.c StructuredIO.h ../../pnheaders/matrix.h + $(COMPILER) -c StructuredIO.c + +nltrans.o: ../nltrans.c ../spnbox.h ../../pnheaders/matrix.h ../matrixmath.h ../MemoryManager.h + $(COMPILER) -c ../nltrans.c Added: spnbox/tests/test-nltrans.txt =================================================================== --- spnbox/tests/test-nltrans.txt (rev 0) +++ spnbox/tests/test-nltrans.txt 2009-06-18 20:18:15 UTC (rev 74) @@ -0,0 +1,26 @@ +rem test-nltrans type/keyword pairs: matrix D, arrayi URT +echo Problem 1 +echo No predefined unraisable transitions. + +D 3 3 +-1 0 0 +-1 2 -1 +-3 1 0 + + +done + +echo Problem 2 +D 4 4 +1 1 2 2 +1 2 1 3 +3 1 0 1 +1 1 1 1 + +URT 3 +0 2 3 + +done + +quit + \ No newline at end of file Added: spnbox/tests/test-pn2acpn.c =================================================================== --- spnbox/tests/test-pn2acpn.c (rev 0) +++ spnbox/tests/test-pn2acpn.c 2009-06-18 20:18:15 UTC (rev 74) @@ -0,0 +1,154 @@ +#include <stdio.h> +#include "../../pnheaders/pns.h" +#include "../../pnheaders/matrix.h" +#include "../matrixmath.h" +#include "../spnbox.h" +#include "../MemoryManager.h" +#include "StructuredIO.h" + +/*Reference materil: + +pn2acpn function definition: +pn2acpn_r pn2acpn(matrix* Dm, matrix* Dp, int* Mask, matrix* MX, matrix* L0, matrix* L, int *ipl, int ipCount, int* dpl, int dpCount) + +pn2acpn_r structure definition: +typedef struct pn2acpn_r +{ + matrix Df; + matrix Dmf; + matrix Dpf; + matrix MXF; + matrix L0F; + matrix LF; + int *iplf; + int iplfCount; +} pn2acpn_r; + +ParseStructure test function definition: +int ParseStructure(FILE *file, char* DataDescription, int** FilledMask, MemoryManager* Memory, ...); + +DisplayStructure test function definition: +void DisplayStructure(char* DataDescription, int* Mask, ...); +*/ + +void FillDmDp(int* Filled, matrix* D, matrix* Dm, matrix* Dp); +int HasSelfLoops(matrix* Dm, matrix* Dp); + +int main(int argc, char* argv[]) +{ + char InputDesc[] = "matrix D, matrix Dm, matrix Dp, matrix MX, matrix L0, matrix L, arrayi Mask, arrayi ipl, arrayi dpl"; + char OutputDesc[] = "matrix Df, matrix Dmf, matrix Dpf, matrix MXF, matrix L0F, matrix LF, arrayi ipl"; + FILE* input; + MemoryManager mem; + matrix Dm, Dp, MX, L0, L, D; + int *Mask, *ipl, *dpl, *Filled; + int MaskCount, ipCount, dpCount, ResFilled[8], i; + pn2acpn_r result; + + /*Initialize the filled mask that will be used with the result display*/ + for (i = 0; i < 8; i++) + { + ResFilled[i] = 1; + } + + /*Get the input stream. If there was a command line argument, treat it as a + filename and attempt to open it for read access, terminating on failure. + Otherwise set stdin as the input stream.*/ + if (argc > 1) + { + if (input = fopen(argv[1], "r")) + { + printf("Taking input from file '%s'...\n", argv[1]); + } + else + { + printf("Failed to open file '%s' for read access! Terminating.\n", argv[1]); + return 1; + } + } + else + { + input = stdin; + printf("Taking input from console...\n"); + } + + /*Initialize the memory manager. We don't know how many matrices and arrays + we'll need so just pick a nice round number as the initial address block size + and use the default additional block size.*/ + mem = CreateMemoryManager(5, 5, 0, 0); + + /*Attempt to parse a problem from the file and fill all the problem parts. + Continue doing so until a fatal input error or a quit command is encountered.*/ + while(ParseStructure(input, InputDesc, &Filled, &mem, &D, &Dm, &Dp, &MX, &L0, &L, &Mask, &MaskCount, &ipl, &ipCount, &dpl, &dpCount)) + { + /*If D was given, fill Dm and Dp appropriately.*/ + FillDmDp(Filled, &D, &Dm, &Dp); + /*Display the problem.*/ + DisplayStructure(InputDesc, Filled, &D, &Dm, &Dp, &MX, &L0, &L, Mask, MaskCount, ipl, ipCount, dpl, dpCount); + + /*Get the solution.*/ + result = pn2acpn(Filled[1] ? &Dm : 0, Filled[2] ? &Dp : 0, Filled[6] ? Mask : 0, Filled[3] ? &MX : 0, Filled[4] ? &L0 : 0, Filled[5] ? &L : 0, Filled[7] ? ipl : 0, ipCount, Filled[8] ? dpl : 0, dpCount); + + /*Display the solution. Show Dm and Dp only if there are self-loops and + D only if there are not self-loops.*/ + if (HasSelfLoops(&Dm, &Dp)) + { + ResFilled[0] = 0; + ResFilled[1] = 1; + ResFilled[2] = 1; + } + else + { + ResFilled[0] = 1; + ResFilled[1] = 0; + ResFilled[2] = 0; + } + printf("Solution:\n"); + DisplayStructure(OutputDesc, ResFilled, &result.Df, &result.Dmf, &result.Dpf, &result.MXF, &result.L0F, &result.LF, result.iplf, result.iplfCount); + printf("---------------------------------------------------------------------------\n"); + FreeMemory(&mem); + mem = CreateMemoryManager(5, 5, 0, 0); + } + FreeMemory(&mem); + return 0; +} + +int HasSelfLoops(matrix* Dm, matrix* Dp) +{ + int i, j; + for (i = 0; i < NumberOfRows(*Dm); i++) + { + for (j = 0; j < NumberOfColumns(*Dm); j++) + { + if (GetMatrixEl(Dm, i, j) && GetMatrixEl(Dp, i, j)) + { + return 1; + } + } + } + return 0; +} + +void FillDmDp(int* Filled, matrix* D, matrix* Dm, matrix* Dp) +{ + /*If D has been filled...*/ + if (Filled[0]) + { + /*We want to ignore any given Dm and Dp and instead fill Dm and Dp with + matrices developed from D.*/ + if (Filled[1]) + { + DeallocateMatrix(Dm); + } + if (Filled[2]) + { + DeallocateMatrix(Dp); + } + AllocateMatrixType(2, Dm, NumberOfRows(*D), NumberOfColumns(*D)); + AllocateMatrixType(2, Dp, NumberOfRows(*D), NumberOfColumns(*D)); + d2dd(D, Dm, Dp); + Filled[0] = 0; + Filled[1] = 1; + Filled[2] = 1; + } +} Added: spnbox/tests/test-pn2acpn.mak =================================================================== --- spnbox/tests/test-pn2acpn.mak (rev 0) +++ spnbox/tests/test-pn2acpn.mak 2009-06-18 20:18:15 UTC (rev 74) @@ -0,0 +1,32 @@ +COMPILER=gcc -g -ggdb + +test-mpsplit: test-pn2acpn.o matrix.o StructuredIO.o MemoryManager.o general.o pns.o matrixmath.o pn2acpn.o + $(COMPILER) -o test-pn2acpn.exe test-pn2acpn.o matrix.o StructuredIO.o MemoryManager.o general.o pns.o pn2acpn.o matrixmath.o ../lp_solve_5.5/lpsolve55/liblpsolve55.a + +general.o: ../../pnheaders/general.c ../../pnheaders/general.h + $(COMPILER) -c ../../pnheaders/general.c + +pns.o: ../../pnheaders/pns.c ../../pnheaders/pns.h + $(COMPILER) -c ../../pnheaders/pns.c + +matrix.o: ../../pnheaders/matrix.c ../../pnheaders/matrix.h ../../pnheaders/pns.h + $(COMPILER) -c ../../pnheaders/matrix.c + +reader.o: ../spnbox.h ../MemoryManager.h reader.c + $(COMPILER) -c reader.c + +MemoryManager.o: ../MemoryManager.h ../MemoryManager.c ../../pnheaders/general.h ../../pnheaders/matrix.h + $(COMPILER) -c ../MemoryManager.c + +StructuredIO.o: ../MemoryManager.h ../../pnheaders/matrix.h StructuredIO.h StructuredIO.c + $(COMPILER) -c StructuredIO.c + +test-pn2acpn.o: test-pn2acpn.c ../spnbox.h ../MemoryManager.h StructuredIO.h ../matrixmath.h + $(COMPILER) -c test-pn2acpn.c + +matrixmath.o: ../matrixmath.c ../matrixmath.h ../../pnheaders/matrix.h + $(COMPILER) -c ../matrixmath.c + +pn2acpn.o: ../spnbox.h ../../pnheaders/matrix.h ../MemoryManager.h ../pn2acpn.c + $(COMPILER) -c ../pn2acpn.c + \ No newline at end of file Added: spnbox/tests/test-pn2acpn.txt =================================================================== --- spnbox/tests/test-pn2acpn.txt (rev 0) +++ spnbox/tests/test-pn2acpn.txt 2009-06-18 20:18:15 UTC (rev 74) @@ -0,0 +1,52 @@ +rem Acceptable problem parts: +rem D incidence matrix +rem Dm output matrix +rem Dp input matrix +rem MX Transformation matrix from independent to dependent marking +rem L0 Initial marking constraint matrix +rem L Marking constraint matrix. +rem Mask An integer array, one element for each place of the Petri net. Serves +rem as a mask on which places will be processed. +rem ipl An array of the indices of independent places. +rem dpl An array of the indices of dependent places. +rem As always, matrices are defined by the keyword followed by the matrix +rem dimensions followed by the elements of the matrix. Arrays are defined by the +rem keyword followed by the length of the array followed by the array elements. +rem If D is given Dm and Dp are ignored. + +rem ---------------------------------------------------------------------------- +echo Problem 1 +echo Example 5.2 from the thesis. + +Dp 3 5 +0 1 0 1 0 +0 0 1 0 1 +1 0 0 0 0 + +Dm 3 5 +1 0 0 0 1 +1 0 0 1 0 +0 2 2 0 0 + +done + +rem ---------------------------------------------------------------------------- +echo Problem 2 +echo Example 5.1 from the thesis. +Dm 4 5 +1 0 1 0 1 +0 0 0 1 0 +0 1 0 0 1 +1 0 1 0 0 + +Dp 4 5 +0 0 0 1 0 +0 0 2 0 1 +3 0 0 0 0 +0 1 0 0 0 + +done + +quit + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |