-
Notifications
You must be signed in to change notification settings - Fork 3
/
WorldStateForPartialExpansion.cs
282 lines (254 loc) · 12.7 KB
/
WorldStateForPartialExpansion.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
namespace mapf
{
class WorldStateForPartialExpansion : WorldState
{
public bool alreadyExpanded;
/// <summary>
/// Starts at zero, incremented after a node is expanded once. Set on Expand.
/// </summary>
public ushort targetDeltaF;
/// <summary>
/// Remaining delta F towards targetDeltaF. Reset on Expand.
/// </summary>
public ushort remainingDeltaF;
/// <summary>
/// For each agent and each direction it can go, the effect of that move on F
/// byte.MaxValue means this is an illegal move. Only computed on demand.
/// </summary>
protected byte[][] singleAgentDeltaFs;
/// <summary>
/// Only computed on demand
/// </summary>
protected ushort maxDeltaF;
public enum DeltaFAchievable : sbyte
{
YES = 1,
NO = -1,
NOT_YET_COMPUTED = 0
}
/// <summary>
/// Per each agent and delta F, has 1 if that delta F is achievable by moving the agents starting from this one on,
/// -1 if it isn't, and 0 if we don't know yet.
/// Only computed on demand
/// </summary>
protected DeltaFAchievable[][] fLookup;
/// <summary>
/// The node's SIC heuristic estimate
/// </summary>
public int sic;
/// <summary>
/// Create a state with the given state for every agent.
/// </summary>
/// <param name="allAgentsState"></param>
/// <param name="minDepth"></param>
/// <param name="minCost"></param>
public WorldStateForPartialExpansion(AgentState[] allAgentState, int minDepth = -1,
int minCost = -1, MDDNode mddNode = null) :
base(allAgentState, minDepth, minCost, mddNode)
{
this.alreadyExpanded = false;
this.maxDeltaF = 0;
this.singleAgentDeltaFs = null;
this.fLookup = null;
}
/// <summary>
/// Copy constructor
/// </summary>
/// <param name="cpy"></param>
public WorldStateForPartialExpansion(WorldStateForPartialExpansion cpy)
: base(cpy)
{
alreadyExpanded = false; // Creating a new unexpanded node from cpy
// For intermediate nodes created during expansion (fully expanded nodes have these fields recalculated when they're expanded)
remainingDeltaF = cpy.remainingDeltaF;
singleAgentDeltaFs = cpy.singleAgentDeltaFs; // For the UpdateRemainingDeltaF call on temporary nodes.
// Notice that after an agent is moved its row won't be up-to-date.
fLookup = cpy.fLookup; // For the hasChildrenForCurrentDeltaF call on temporary nodes.
// Notice that after an agent is moved, all rows up to and including the one of the agent that moved
// won't be up-to-date.
maxDeltaF = cpy.maxDeltaF; // Not necessarily achievable after some of the agents moved.
// The above is OK because we won't be using data for agents that already moved.
}
/// <summary>
/// From generated nodes. Allows expansion table to be garbage collected before all generated nodes are expanded.
/// </summary>
public void ClearExpansionData()
{
this.singleAgentDeltaFs = null;
this.fLookup = null;
}
public override string ToString()
{
return $"{base.ToString()} (sic:{this.sic} target deltaF: {this.targetDeltaF})";
}
public delegate bool ValidityChecker(TimedMove move, IReadOnlyDictionary<TimedMove, int> currentMoves, int makespan, int agentIndex, WorldState node, WorldState intermediateNode);
private static readonly IReadOnlyDictionary<TimedMove, int> noMoves = new Dictionary<TimedMove, int>();
/// <summary>
/// Calculates for each agent and each direction it can go, the effect of that move on F. Illegal moves get byte.MaxValue.
/// Also calcs maxDeltaF.
/// Implicitly uses the SIC heuristic.
/// </summary>
/// <param name="problem">For GetSingleAgentOptimalCost</param>
/// <param name="isValid"></param>
/// <returns></returns>
public void calcSingleAgentDeltaFs(ProblemInstance problem, ValidityChecker isValid)
{
// Init
this.singleAgentDeltaFs = new byte[allAgentsState.Length][];
for (int i = 0; i < singleAgentDeltaFs.Length; i++)
{
this.singleAgentDeltaFs[i] = new byte[Constants.NUM_ALLOWED_DIRECTIONS];
}
int hBefore, hAfter;
this.maxDeltaF = 0;
// Set values
for (int i = 0; i < allAgentsState.Length; i++)
{
hBefore = problem.GetSingleAgentOptimalCost(allAgentsState[i]);
int singleAgentMaxLegalDeltaF = -1;
foreach (TimedMove check in allAgentsState[i].lastMove.GetNextMoves())
{
if (isValid(check, noMoves, this.makespan + 1, i, this, this) == false) // Is this move by itself invalid because of constraints or obstacles
{
singleAgentDeltaFs[i][(int)check.direction] = byte.MaxValue;
}
else
{
hAfter = problem.GetSingleAgentOptimalCost(allAgentsState[i].agent.agentNum, check);
if (Constants.sumOfCostsVariant == Constants.SumOfCostsVariant.ORIG)
{
if (hBefore != 0)
singleAgentDeltaFs[i][(int)check.direction] = (byte)(hAfter - hBefore + 1); // h difference + g difference in this specific domain
else if (hAfter != 0) // If agent moved from its goal we must count and add all the steps it was stationed at the goal, since they're now part of its g difference
singleAgentDeltaFs[i][(int)check.direction] = (byte)(hAfter - hBefore + makespan - allAgentsState[i].arrivalTime + 1);
else
singleAgentDeltaFs[i][(int)check.direction] = 0; // This is a WAIT move at the goal.
}
else if (Constants.sumOfCostsVariant == Constants.SumOfCostsVariant.WAITING_AT_GOAL_ALWAYS_FREE)
{
if (hBefore == 0 && hAfter == 0)
singleAgentDeltaFs[i][(int)check.direction] = 0; // This is a WAIT move at the goal.
else
singleAgentDeltaFs[i][(int)check.direction] = (byte)(hAfter - hBefore + 1); // h difference + g difference in this specific domain
}
singleAgentMaxLegalDeltaF = Math.Max(singleAgentMaxLegalDeltaF, singleAgentDeltaFs[i][(int)check.direction]);
}
}
if (singleAgentMaxLegalDeltaF == -1) // No legal action for this agent, so no legal children exist for this node
{
this.maxDeltaF = 0; // Can't make it negative without widening the field.
break;
}
this.maxDeltaF += (byte)singleAgentMaxLegalDeltaF;
}
fLookup = new DeltaFAchievable[allAgentsState.Length][];
for (int i = 0; i < fLookup.Length; i++)
{
fLookup[i] = new DeltaFAchievable[this.maxDeltaF + 1]; // Towards the last agents most of the row will be wasted (the last one can do delta F of 0 or 1),
// but it's easier than fiddling with array sizes
}
}
/// <summary>
/// Returns whether all possible f values were generated from this node already.
/// Assumes calcSingleAgentDeltaFs was called earlier.
/// </summary>
/// <returns></returns>
public bool hasMoreChildren()
{
return this.targetDeltaF <= this.maxDeltaF;
}
public bool IsAlreadyExpanded()
{
return alreadyExpanded;
}
/// <summary>
/// Assumes calcSingleAgentDeltaFs was called earlier.
/// </summary>
/// <param name="agentNum"></param>
/// <returns></returns>
public bool hasChildrenForCurrentDeltaF(int agentNum = 0)
{
return existsChildForF(agentNum, this.remainingDeltaF);
}
/// <summary>
/// Recursive func. Kind of dynamic programming as it updates the lookup table as it goes to refrain from computing answers twice.
/// </summary>
/// <param name="agentNum"></param>
/// <param name="remainingTargetDeltaF"></param>
/// <returns></returns>
protected bool existsChildForF(int agentNum, ushort remainingTargetDeltaF)
{
// Stopping conditions:
if (agentNum == allAgentsState.Length)
{
if (remainingTargetDeltaF == 0)
return true;
return false;
}
if (fLookup[agentNum][remainingTargetDeltaF] != DeltaFAchievable.NOT_YET_COMPUTED) // Answer known (arrays are initialized to zero).
{
return fLookup[agentNum][remainingTargetDeltaF] == DeltaFAchievable.YES; // Return known answer.
}
// Recursive actions:
for (int direction = 0; direction < Constants.NUM_ALLOWED_DIRECTIONS; direction++)
{
if (singleAgentDeltaFs[agentNum][direction] > remainingTargetDeltaF) // Small optimization - no need to make the recursive
// call just to request a negative target from it and
// get false (because we assume the heuristic function
// is consistent)
continue;
if (existsChildForF(agentNum + 1, (byte)(remainingTargetDeltaF - singleAgentDeltaFs[agentNum][direction])))
{
fLookup[agentNum][remainingTargetDeltaF] = DeltaFAchievable.YES;
return true;
}
}
fLookup[agentNum][remainingTargetDeltaF] = DeltaFAchievable.NO;
return false;
}
/// <summary>
/// An agent was moved between calculating the singleAgentDeltaFs and this call.
/// Using the data that describes its delta F potential before the move.
/// </summary>
/// <param name="agentIndex"></param>
public void UpdateRemainingDeltaF(int agentIndex)
{
if (this.remainingDeltaF == ushort.MaxValue)
Trace.Assert(false,
$"Remaining deltaF is ushort.MaxValue, a reserved value with special meaning. agentIndex={agentIndex}");
byte lastMoveDeltaF = this.singleAgentDeltaFs[agentIndex][(int)this.allAgentsState[agentIndex].lastMove.direction];
if (lastMoveDeltaF != byte.MaxValue && this.remainingDeltaF >= lastMoveDeltaF)
this.remainingDeltaF -= lastMoveDeltaF;
else
this.remainingDeltaF = ushort.MaxValue; // Either because last move was illegal or because the delta F from the last move was more than the entire remaining delta F budget
}
/// <summary>
/// For fully expanded nodes.
/// Notice ClearExpansionData does a similar thing, but for different reasons.
/// </summary>
public override void Clear()
{
this.alreadyExpanded = false; // Enables reopening
// The following info could be reused when reopening the node, saving the time it takes to generate it,
// but reopening a node is rare in our domain, and the memory saved can be significant
this.fLookup = null; // Save a lot of memory
this.singleAgentDeltaFs = null; // Save some more memory
this.targetDeltaF = 0;
this.remainingDeltaF = 0;
//this.h -= this.hBonus; // Reset the h
//this.hBonus = 0;
}
public override int f
{
get
{
return Math.Max(this.g + this.h,
this.g + this.sic + this.targetDeltaF);
}
}
}
}