You can see the entire program alttext.cpp
here or view it online in the standard distribution.
#include <ilsched/iloscheduler.h>
ILOSTLBEGIN
IloInt ResourceNumbers06 [] = {2, 0, 1, 3, 5, 4,
1, 2, 4, 5, 0, 3,
2, 3, 5, 0, 1, 4,
1, 0, 2, 3, 4, 5,
2, 1, 4, 5, 0, 3,
1, 3, 5, 0, 4, 2};
IloNum Durations06 [] = { 1, 3, 6, 7, 3, 6,
8, 5, 10, 10, 10, 4,
5, 4, 8, 9, 1, 7,
5, 5, 5, 3, 8, 9,
9, 3, 5, 4, 3, 1,
3, 3, 9, 10, 4, 1};
IloInt ResourceNumbers10 [] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 2, 4, 9, 3, 1, 6, 5, 7, 8,
1, 0, 3, 2, 8, 5, 7, 6, 9, 4,
1, 2, 0, 4, 6, 8, 7, 3, 9, 5,
2, 0, 1, 5, 3, 4, 8, 7, 9, 6,
2, 1, 5, 3, 8, 9, 0, 6, 4, 7,
1, 0, 3, 2, 6, 5, 9, 8, 7, 4,
2, 0, 1, 5, 4, 6, 8, 9, 7, 3,
0, 1, 3, 5, 2, 9, 6, 7, 4, 8,
1, 0, 2, 6, 8, 9, 5, 3, 4, 7};
IloNum Durations10 [] = {29, 78, 9, 36, 49, 11, 62, 56, 44, 21,
43, 90, 75, 11, 69, 28, 46, 46, 72, 30,
91, 85, 39, 74, 90, 10, 12, 89, 45, 33,
81, 95, 71, 99, 9, 52, 85, 98, 22, 43,
14, 6, 22, 61, 26, 69, 21, 49, 72, 53,
84, 2, 52, 95, 48, 72, 47, 65, 6, 25,
46, 37, 61, 13, 32, 21, 32, 89, 30, 55,
31, 86, 46, 74, 32, 88, 19, 48, 36, 79,
76, 69, 76, 51, 85, 11, 40, 89, 26, 74,
85, 13, 61, 7, 64, 76, 47, 52, 90, 45};
IloInt ResourceNumbers20 [] = {0, 1, 2, 3, 4,
0, 1, 3, 2, 4,
1, 0, 2, 4, 3,
1, 0, 4, 2, 3,
2, 1, 0, 3, 4,
2, 1, 4, 0, 3,
1, 0, 2, 3, 4,
2, 1, 0, 3, 4,
0, 3, 2, 1, 4,
1, 2, 0, 3, 4,
1, 3, 0, 4, 2,
2, 0, 1, 3, 4,
0, 2, 1, 3, 4,
2, 0, 1, 3, 4,
0, 1, 4, 2, 3,
1, 0, 3, 4, 2,
0, 2, 1, 3, 4,
0, 1, 4, 2, 3,
1, 2, 0, 3, 4,
0, 1, 2, 3, 4};
IloNum Durations20 [] = {29, 9, 49, 62, 44,
43, 75, 69, 46, 72,
91, 39, 90, 12, 45,
81, 71, 9, 85, 22,
14, 22, 26, 21, 72,
84, 52, 48, 47, 6,
46, 61, 32, 32, 30,
31, 46, 32, 19, 36,
76, 76, 85, 40, 26,
85, 61, 64, 47, 90,
78, 36, 11, 56, 21,
90, 11, 28, 46, 30,
85, 74, 10, 89, 33,
95, 99, 52, 98, 43,
6, 61, 69, 49, 53,
2, 95, 72, 65, 25,
37, 13, 21, 89, 55,
86, 74, 88, 48, 79,
69, 51, 11, 89, 74,
13, 7, 76, 52, 45};
///////////////////////////////////////////////////////////////////
//
// TEXTURE CHOICE POINT FOR SCHEDULING WITH ALTERNATIVES
//
///////////////////////////////////////////////////////////////////
class AltTextureGoalI : public IlcGoalI {
private:
IlcSchedule _schedule;
protected:
IlcResourceTexture chooseResource();
IlcBool choosePair(IlcResourceTexture,
IlcResourceConstraint&,
IlcResourceConstraint&, IlcFloat&);
IlcBool chooseSequence(IlcResourceConstraint&,
IlcResourceConstraint&,
IlcResourceConstraint,
IlcResourceConstraint);
IlcBool calcBiasedSlack(IlcResourceConstraint,
IlcResourceConstraint,
IlcFloat&, IlcFloat&);
IlcBool chooseAlternative(IlcResourceTexture,
IlcResourceConstraint&, IlcFloat&);
public:
AltTextureGoalI(IlcSchedule s)
: IlcGoalI(s.getSolver()), _schedule(s) {}
~AltTextureGoalI() {}
virtual IlcGoal execute();
};
IlcGoal AltTextureGoalI::execute() {
IlcResourceTexture selectedTexture = chooseResource();
while(0 != selectedTexture.getImpl()) {
IlcResourceConstraint rctA, rctB, rctC;
IlcFloat maxPairCriticality = 0;
IlcFloat altCriticality = 0;
IlcBool pairFound = choosePair(selectedTexture,
rctA, rctB, maxPairCriticality);
IlcBool altFound = chooseAlternative(selectedTexture,
rctC, altCriticality);
if (!pairFound && !altFound) {
// If neither a pair nor an alternative is found, then
// there are no commitments that this goal can make at
// the critical point.
selectedTexture.setNoCommitmentsAtCriticalPoint();
selectedTexture = chooseResource();
}
else {
if (!pairFound ||
(altFound && (maxPairCriticality <= altCriticality)))
return IlcAnd(IlcTryNotPossible(rctC.getResource(),
rctC.getAlternative()),
this);
else {
IlcResourceConstraint before, after;
IlcBool sequenceOK = chooseSequence(before, after,
rctA, rctB);
assert(sequenceOK);
return IlcAnd(IlcTrySetSuccessor(before, after), this);
}
}
}
return 0;
}
IlcResourceTexture AltTextureGoalI::chooseResource() {
IlcResourceTexture selectedTexture = 0;
IlcFloat criticality = 0;
for(IlcResourceIterator iter(_schedule); iter.ok(); ++iter) {
if ((*iter).isDiscreteResource()) {
IlcDiscreteResource res = (IlcDiscreteResource) (*iter);
IlcResourceTexture texture = res.getTextureMeasurement();
if (texture.getMaxCriticality() > criticality) {
selectedTexture = texture;
criticality = texture.getMaxCriticality();
}
}
}
return selectedTexture;
}
IlcBool AltTextureGoalI::choosePair(IlcResourceTexture texture,
IlcResourceConstraint& rctA,
IlcResourceConstraint& rctB,
IlcFloat& maxCriticality) {
IlcRCTextureArray rcTextures =
texture.getCriticalityOrderedRCTextures();
IlcInt nbRCTextures = rcTextures.getSize();
IlcInt i;
for(i = 0; i < nbRCTextures - 1; ++i) {
IlcFloat c = rcTextures[i].getCriticalContribution();
if (c == 0)
return IlcFalse;
if (!rcTextures[i].hasAlternatives()) {
rctA = rcTextures[i].getResourceConstraint();
maxCriticality = c;
IlcInt j;
for(j = i+1;
(j < nbRCTextures) &&
(rcTextures[j].getCriticalContribution() > 0); ++j) {
if (!rcTextures[j].hasAlternatives()) {
rctB = rcTextures[j].getResourceConstraint();
if (!rctA.isSucceededBy(rctB) &&
!rctB.isSucceededBy(rctA)) {
// found the pair to sequence
return IlcTrue;
}
}
}
}
}
return IlcFalse;
}
IlcBool AltTextureGoalI::chooseSequence(
IlcResourceConstraint& selectedRct1,
IlcResourceConstraint& selectedRct2,
IlcResourceConstraint rctA,
IlcResourceConstraint rctB) {
IlcFloat biasedSlackBA, biasedSlackAB;
if (!calcBiasedSlack(rctA, rctB,
biasedSlackAB, biasedSlackBA))
return IlcFalse;
selectedRct1 = rctA;
selectedRct2 = rctB;
// pick sequence that preserves the most slack
if (biasedSlackAB < biasedSlackBA) {
selectedRct1 = rctB;
selectedRct2 = rctA;
}
return IlcTrue;
}
IlcBool AltTextureGoalI::calcBiasedSlack(
IlcResourceConstraint rctA,
IlcResourceConstraint rctB,
IlcFloat& biasedSlackAB,
IlcFloat& biasedSlackBA)
{
// The PCPSlack sequencing calculation from:
// @InProceedings{Cheng93,
// author = "Smith, S. F. and Cheng, C. C.",
// title = "Slack-based heuristics for constraint satisfaction
// scheduling",
// key = "scheduling",
// year = "1993",
// pages = "139--144",
// booktitle = "Proceedings of the Eleventh National
// Conference on Artificial Intelligence
// (AAAI-93)",
// }
// Returns IlcTrue if the pair has a valid biasedSlack value.
// IlcFalse means there is no valid value for this pair (for
// example, because they do not overlap).
IlcActivity actA = rctA.getActivity();
IlcActivity actB = rctB.getActivity();
IlcInt estA = actA.getStartMin();
IlcInt lftA = actA.getEndMax();
IlcInt estB = actB.getStartMin();
IlcInt lftB = actB.getEndMax();
if ((lftA <= estB) || (lftB <= estA))
// Activities already sequenced
return IlcFalse;
IlcInt lambda = lftA - estB;
IlcInt mu = lftB - estA;
IlcInt delta = actA.getProcessingTimeMin() +
actB.getProcessingTimeMin();
IlcFloat slackAB = mu - delta;
IlcFloat slackBA = lambda - delta;
IlcFloat rtS;
// Either (or both) of slackAB and slackBA might be 0!
// Assign to 0.1 (note that lambda, delta, and mu are
// all integral it is not possible for slackAB or
// slackBA to actually be 0.1 by themselves)
if (mu == delta)
slackAB = 0.1;
if (lambda == delta)
slackBA = 0.1;
if (slackAB < slackBA)
rtS = sqrt(slackAB / slackBA);
else
rtS = sqrt(slackBA / slackAB);
biasedSlackAB = slackAB / rtS;
biasedSlackBA = slackBA / rtS;
return IlcTrue;
}
IlcBool AltTextureGoalI::chooseAlternative(
IlcResourceTexture texture,
IlcResourceConstraint& altRC,
IlcFloat& criticality) {
IlcRCTextureArray rcTextures =
texture.getCriticalityOrderedRCTextures();
IlcInt nbRCTextures = rcTextures.getSize();
for(IlcInt i = 0; i < nbRCTextures; ++i) {
IlcFloat c = rcTextures[i].getCriticalContribution();
if (c == 0)
return IlcFalse;
if (rcTextures[i].hasAlternatives()) {
// rcTextures is sorted in descending order of
// criticality so the first rct we find with
// alternatives is the one with the highest crit.
altRC = rcTextures[i].getResourceConstraint();
criticality = c;
return IlcTrue;
}
}
return IlcFalse;
}
ILOCPGOALWRAPPER1(AltTextureIloGoal, solver, IloNumVar, cost) {
IlcScheduler scheduler(solver);
return IlcAnd(IlcGoal(new (solver.getHeap())
AltTextureGoalI(scheduler)),
IlcInstantiate(solver.getIntVar(cost)));
}
///////////////////////////////////////////////////////////////////
//
// PRINTING OF SOLUTIONS
//
///////////////////////////////////////////////////////////////////
void PrintRange(IloEnv& env, IloNum min, IloNum max) {
if (min == max)
env.out() << (IlcInt)min;
else
env.out() << (IlcInt)min << ".." << (IlcInt)max;
}
void PrintSolution(IloEnv& env,
const IloSchedulerSolution solution,
const IloNumVar makespan)
{
env.out() << "Solution with makespan ["
<< solution.getMin(makespan) << ".."
<< solution.getMax(makespan) << "]" << endl;
for (IloSchedulerSolution::ResourceConstraintIterator
iter(solution);
iter.ok();
++iter)
{
IloResourceConstraint rc = *iter;
if (!solution.isResourceSelected(rc))
IloSchedulerException("No resource assigned!");
IloActivity activity = rc.getActivity();
env.out() << activity.getName() << "[";
PrintRange(env,
solution.getStartMin(activity),
solution.getStartMax(activity));
env.out() << " -- ";
PrintRange(env,
solution.getDurationMin(activity),
solution.getDurationMax(activity));
env.out() << " --> ";
PrintRange(env,
solution.getEndMin(activity),
solution.getEndMax(activity));
env.out() << "]: " << solution.getSelected(rc).getName()
<< endl;
}
}
////////////////////////////////////////////////////////////////////
//
// SOLVE THE MODEL USING TEXTURE-BASED HEURISTIC
//
////////////////////////////////////////////////////////////////////
IloBool SolveModel(IloModel model, IloNumVar makespan,
IloSchedulerSolution solution) {
IloEnv env = model.getEnv();
IloSolver solver(model);
IlcScheduler scheduler(solver);
IloBool solved = IloFalse;
IloNum timeLimit = 200;
IloGoal goal = AltTextureIloGoal(env, makespan);
IloGoal limitedGoal = IloLimitSearch(env, goal,
IloTimeLimit(env,
timeLimit));
solver.startNewSearch(limitedGoal);
while(solver.next()) {
solver.out() << "Solution with makespan: "
<< solver.getIntVar(makespan).getValue() << endl;
solution.store(scheduler);
solved = IloTrue;
}
solver.endSearch();
solver.printInformation();
solver.end();
return solved;
}
////////////////////////////////////////////////////////////////////
//
// DEFINE THE MODEL WITH ALTERNATIVE RESOURCES
//
////////////////////////////////////////////////////////////////////
IloModel
DefineModel(IloEnv& env,
IloInt numberOfJobs,
IloInt numberOfResources,
IloInt* resourceNumbers,
IloNum* durations,
IloRandom randomGenerator,
IloSchedulerSolution solution,
IloNumVar& makespan)
{
IloModel model(env);
/* CREATE THE MAKESPAN VARIABLE. */
IloInt numberOfActivities = numberOfJobs * numberOfResources;
IloNum horizon = 0;
IloInt k;
for (k = 0; k < numberOfActivities; k++)
horizon += durations[k];
makespan = IloNumVar(env, 0, horizon, ILOINT);
/* CREATE THE RESOURCES. */
IloSchedulerEnv schedEnv(env);
schedEnv.getResourceParam().
setCapacityEnforcement(IloMediumHigh);
schedEnv.getResourceParam().
setPrecedenceEnforcement(IloMediumHigh);
IloRCTextureFactory rcFactory = IloRCTextureProbabilisticFactory(env);
schedEnv.getTextureParam().setRCTextureFactory(rcFactory);
IloTextureCriticalityCalculator critCalc =
IloProbabilisticCriticalityCalculator(env);
schedEnv.getTextureParam().setCriticalityCalculator(critCalc);
char buffer[128];
IloInt j;
IloUnaryResource *resources =
new (env) IloUnaryResource[numberOfResources];
for (j = 0; j < numberOfResources; j++) {
sprintf(buffer, "R%ld", j);
resources[j] = IloUnaryResource(env, buffer);
}
/* CREATE THE ALTERNATIVE RESOURCE SETS */
env.out() << "Creating resource sets" << endl;
IloInt *altResourceNumbers = new (env) IloInt[numberOfResources];
IloAltResSet *altResSets =
new (env) IloAltResSet[numberOfResources];
for (j = 0; j < numberOfResources; j++) {
altResSets[j] = IloAltResSet(env);
altResSets[j].add(resources[j]);
// RANDOMLY PICK ANOTHER RESOURCE TO BE IN THE SET
assert(numberOfResources > 1);
IloInt index = randomGenerator.getInt(numberOfResources);
while(index == j)
index = randomGenerator.getInt(numberOfResources);
altResSets[j].add(resources[index]);
altResourceNumbers[j] = index;
env.out() << "Set #" << j << ":\t" << resources[j].getName()
<< " " << resources[index].getName() << endl;
}
/* CREATE THE ALTERNATIVE DURATIONS */
IloNum *altDurations = new (env) IloNum[numberOfActivities];
for(k = 0; k < numberOfActivities; k++) {
IloNum multiplier = 1.0 + (randomGenerator.getFloat() / 2.0);
altDurations[k] = IloCeil(multiplier * durations[k]);
}
/* CREATE THE ACTIVITIES. */
env.out() << "Setting alternative processing times" << endl;
k = 0;
IloInt i;
for (i = 0; i < numberOfJobs; i++) {
IloActivity previousActivity;
for (j = 0; j < numberOfResources; j++) {
IloNum ptMin = IloMin(durations[k], altDurations[k]);
IloNum ptMax = IloMax(durations[k], altDurations[k]);
IloNumVar ptVar(env, ptMin, ptMax, ILOINT);
IloActivity activity(env, ptVar);
sprintf(buffer, "J%ldS%ldR%ld", i, j, resourceNumbers[k]);
activity.setName(buffer);
solution.add(activity);
IloResourceConstraint rc =
activity.requires(altResSets[resourceNumbers[k]]);
// SET THE DIFFERENT DURATIONS DEPENDING ON
// RESOURCE SELECTION
rc.setProcessingTimeMax(resources[resourceNumbers[k]],
durations[k]);
rc.setProcessingTimeMin(resources[resourceNumbers[k]],
durations[k]);
rc.setProcessingTimeMax(
resources[altResourceNumbers[resourceNumbers[k]]],
altDurations[k]);
rc.setProcessingTimeMin(
resources[altResourceNumbers[resourceNumbers[k]]],
altDurations[k]);
model.add(rc);
solution.add(rc);
env.out() << activity.getName()
<< ":\tProcessing Time("
<< resources[resourceNumbers[k]].getName()
<< "): " << durations[k]
<< "\n\tProcessing Time("
<< resources[altResourceNumbers[
resourceNumbers[k]]].getName()
<< "): " << altDurations[k]
<< endl;
if (j != 0)
model.add(activity.startsAfterEnd(previousActivity));
previousActivity = activity;
k++;
}
model.add(previousActivity.endsBefore(makespan));
}
model.add(IloMinimize(env, makespan));
solution.getSolution().add(makespan);
/* RETURN THE MODEL. */
return model;
}
/////////////////////////////////////////////////////////////////
//
// INITIALIZE THE PROGRAM ARGUMENTS
//
/////////////////////////////////////////////////////////////////
void
InitParameters(int argc,
char** argv,
IloInt& numberOfJobs,
IloInt& numberOfResources,
IloInt*& resourceNumbers,
IloNum*& durations)
{
numberOfJobs = 6;
numberOfResources = 6;
resourceNumbers = ResourceNumbers06;
durations = Durations06;
if (argc > 1) {
IloInt number = atol(argv[1]);
if (number == 10) {
numberOfJobs = 10;
numberOfResources = 10;
resourceNumbers = ResourceNumbers10;
durations = Durations10;
}
else if (number == 20) {
numberOfJobs = 20;
numberOfResources = 5;
resourceNumbers = ResourceNumbers20;
durations = Durations20;
}
}
}
/////////////////////////////////////////////////////////////////
//
// MAIN FUNCTION
//
/////////////////////////////////////////////////////////////////
int main(int argc, char** argv)
{
IloInt numberOfJobs;
IloInt numberOfResources;
IloInt* resourceNumbers;
IloNum* durations;
InitParameters(argc,
argv,
numberOfJobs,
numberOfResources,
resourceNumbers,
durations);
try {
IloEnv env;
IloNumVar makespan;
IloRandom randGen(env, 8975324);
IloSchedulerSolution solution(env);
IloModel model = DefineModel(env,
numberOfJobs,
numberOfResources,
resourceNumbers,
durations,
randGen,
solution,
makespan);
IloBool solved = SolveModel(model, makespan, solution);
if (!solved)
throw IloSchedulerException( "No solution found" );
PrintSolution(env, solution, makespan);
env.end();
}
catch (IloSchedulerException& exc) {
cout << exc << endl;
}
catch (IloException& exc) {
cout << exc << endl;
}
return 0;
}
/*
% alttext
Creating resource sets
Set #0: R0 R5
Set #1: R1 R2
Set #2: R2 R3
Set #3: R3 R0
Set #4: R4 R1
Set #5: R5 R2
Setting alternative processing times
J0S0R2: Processing Time(R2): 1
Processing Time(R3): 2
J0S1R0: Processing Time(R0): 3
Processing Time(R5): 4
J0S2R1: Processing Time(R1): 6
Processing Time(R2): 9
J0S3R3: Processing Time(R3): 7
Processing Time(R0): 9
J0S4R5: Processing Time(R5): 3
Processing Time(R2): 5
J0S5R4: Processing Time(R4): 6
Processing Time(R1): 7
J1S0R1: Processing Time(R1): 8
Processing Time(R2): 9
J1S1R2: Processing Time(R2): 5
Processing Time(R3): 7
J1S2R4: Processing Time(R4): 10
Processing Time(R1): 14
J1S3R5: Processing Time(R5): 10
Processing Time(R2): 11
J1S4R0: Processing Time(R0): 10
Processing Time(R5): 13
J1S5R3: Processing Time(R3): 4
Processing Time(R0): 6
J2S0R2: Processing Time(R2): 5
Processing Time(R3): 6
J2S1R3: Processing Time(R3): 4
Processing Time(R0): 6
J2S2R5: Processing Time(R5): 8
Processing Time(R2): 11
J2S3R0: Processing Time(R0): 9
Processing Time(R5): 12
J2S4R1: Processing Time(R1): 1
Processing Time(R2): 2
J2S5R4: Processing Time(R4): 7
Processing Time(R1): 8
J3S0R1: Processing Time(R1): 5
Processing Time(R2): 7
J3S1R0: Processing Time(R0): 5
Processing Time(R5): 6
J3S2R2: Processing Time(R2): 5
Processing Time(R3): 6
J3S3R3: Processing Time(R3): 3
Processing Time(R0): 4
J3S4R4: Processing Time(R4): 8
Processing Time(R1): 9
J3S5R5: Processing Time(R5): 9
Processing Time(R2): 11
J4S0R2: Processing Time(R2): 9
Processing Time(R3): 14
J4S1R1: Processing Time(R1): 3
Processing Time(R2): 4
J4S2R4: Processing Time(R4): 5
Processing Time(R1): 8
J4S3R5: Processing Time(R5): 4
Processing Time(R2): 6
J4S4R0: Processing Time(R0): 3
Processing Time(R5): 4
J4S5R3: Processing Time(R3): 1
Processing Time(R0): 2
J5S0R1: Processing Time(R1): 3
Processing Time(R2): 4
J5S1R3: Processing Time(R3): 3
Processing Time(R0): 5
J5S2R5: Processing Time(R5): 9
Processing Time(R2): 11
J5S3R0: Processing Time(R0): 10
Processing Time(R5): 11
J5S4R4: Processing Time(R4): 4
Processing Time(R1): 6
J5S5R2: Processing Time(R2): 1
Processing Time(R3): 2
ILOG Scheduler 5.000, licensed to "ILOG Gentilly".
ILOG Solver 5.000, licensed to "ILOG Gentilly", options: LS
Solution with makespan: 61
Solution with makespan: 59
Solution with makespan: 58
Solution with makespan: 57
Solution with makespan: 56
Solution with makespan: 54
Solution with makespan: 53
Solution with makespan: 51
Solution with makespan: 50
Solution with makespan: 49
Solution with makespan: 48
Number of fails : 260
Number of choice points : 270
Number of variables : 146
Number of constraints : 0
Reversible stack (bytes) : 88464
Solver heap (bytes) : 172884
Solver global heap (bytes) : 237204
And stack (bytes) : 4044
Or stack (bytes) : 8064
Search Stack (bytes) : 4044
Constraint queue (bytes) : 11144
Total memory used (bytes) : 525848
Running time since creation : 0.73
Solution with makespan [48..48]
J0S0R2[0 -- 2 --> 2]: R3
J0S1R0[2 -- 4 --> 6]: R5
J0S2R1[8..19 -- 6 --> 14..25]: R1
J0S3R3[25 -- 7 --> 32]: R3
J0S4R5[32 -- 5 --> 37]: R2
J0S5R4[37..41 -- 7 --> 44..48]: R1
J1S0R1[0 -- 9 --> 9]: R2
J1S1R2[9 -- 5 --> 14]: R2
J1S2R4[14 -- 10 --> 24]: R4
J1S3R5[24 -- 10 --> 34]: R5
J1S4R0[34 -- 10 --> 44]: R0
J1S5R3[44 -- 4 --> 48]: R3
J2S0R2[6 -- 6 --> 12]: R3
J2S1R3[12 -- 4 --> 16]: R3
J2S2R5[16 -- 8 --> 24]: R5
J2S3R0[25 -- 9 --> 34]: R0
J2S4R1[34..40 -- 1 --> 35..41]: R1
J2S5R4[37..41 -- 7 --> 44..48]: R4
J3S0R1[3..5 -- 5 --> 8..10]: R1
J3S1R0[8..10 -- 5 --> 13..15]: R0
J3S2R2[16 -- 6 --> 22]: R3
J3S3R3[22 -- 3 --> 25]: R3
J3S4R4[25..29 -- 8 --> 33..37]: R4
J3S5R5[37 -- 11 --> 48]: R2
J4S0R2[14..18 -- 9 --> 23..27]: R2
J4S1R1[23..27 -- 3 --> 26..30]: R1
J4S2R4[26..30 -- 8 --> 34..38]: R1
J4S3R5[34..38 -- 4 --> 38..42]: R5
J4S4R0[38..42 -- 4 --> 42..46]: R5
J4S5R3[44..46 -- 2 --> 46..48]: R0
J5S0R1[0 -- 3 --> 3]: R1
J5S1R3[3 -- 3 --> 6]: R3
J5S2R5[6 -- 9 --> 15]: R5
J5S3R0[15 -- 10 --> 25]: R0
J5S4R4[33..37 -- 4 --> 37..41]: R4
J5S5R2[37..42 -- 2 --> 39..44]: R3
*/