1+ /*
2+ * Copyright 2025 Lambda
3+ *
4+ * This program is free software: you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation, either version 3 of the License, or
7+ * (at your option) any later version.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+ package com.lambda.pathing.dstar
19+
20+ import kotlin.math.min
21+
22+ /* *
23+ * D* Lite Implementation.
24+ *
25+ * We perform a backward search from the goal to the start, so:
26+ * - rhs(goal) = 0, g(goal) = ∞
27+ * - "start" is the robot's current location from which we want a path *to* the goal
28+ * - 'km' accumulates the heuristic shift so we don't reorder the entire queue after each move
29+ *
30+ * @param graph The graph on which we plan (with forward + reverse adjacency).
31+ * @param heuristic A consistent (or at least nonnegative) heuristic function h(a,b).
32+ * @param start The robot's current position.
33+ * @param goal The fixed goal vertex.
34+ */
35+ class DStarLite (
36+ private val graph : Graph ,
37+ private val heuristic : (Int , Int ) -> Double ,
38+ var start : Int ,
39+ val goal : Int
40+ ) {
41+ private val INF = Double .POSITIVE_INFINITY
42+
43+ // gMap[u], rhsMap[u] store g(u) and rhs(u) or default to ∞ if not present
44+ private val gMap = mutableMapOf<Int , Double >()
45+ private val rhsMap = mutableMapOf<Int , Double >()
46+
47+ // Priority queue
48+ private val U = PriorityQueueDStar ()
49+
50+ // Heuristic shift
51+ private var km = 0.0
52+
53+ init {
54+ initialize()
55+ }
56+
57+ /* *
58+ * Initialize D* Lite:
59+ * - g(goal)=∞, rhs(goal)=0
60+ * - Insert goal into U with key = calculateKey(goal).
61+ */
62+ private fun initialize () {
63+ gMap.clear()
64+ rhsMap.clear()
65+ setG(goal, INF )
66+ setRHS(goal, 0.0 )
67+ U .insertOrUpdate(goal, calculateKey(goal))
68+ }
69+
70+ private fun g (u : Int ): Double = gMap[u] ? : INF
71+ private fun setG (u : Int , value : Double ) {
72+ gMap[u] = value
73+ }
74+
75+ private fun rhs (u : Int ): Double = rhsMap[u] ? : INF
76+ private fun setRHS (u : Int , value : Double ) {
77+ rhsMap[u] = value
78+ }
79+
80+ /* *
81+ * Key(u) = ( min(g(u), rhs(u)) + h(start, u) + km , min(g(u), rhs(u)) ).
82+ */
83+ private fun calculateKey (u : Int ): Key {
84+ val minGRHS = min(g(u), rhs(u))
85+ return Key (minGRHS + heuristic(start, u) + km, minGRHS)
86+ }
87+
88+ /* *
89+ * UpdateVertex(u):
90+ * 1) If u != goal, rhs(u) = min_{v in Pred(u)} [g(v) + cost(v,u)]
91+ * 2) Remove u from U
92+ * 3) If g(u) != rhs(u), insertOrUpdate(u, calculateKey(u))
93+ */
94+ fun updateVertex (u : Int ) {
95+ if (u != goal) {
96+ var tmp = INF
97+ graph.predecessors(u).forEach { (pred, c) ->
98+ val valCandidate = g(pred) + c
99+ if (valCandidate < tmp) {
100+ tmp = valCandidate
101+ }
102+ }
103+ setRHS(u, tmp)
104+ }
105+ U .remove(u)
106+ if (g(u) != rhs(u)) {
107+ U .insertOrUpdate(u, calculateKey(u))
108+ }
109+ }
110+
111+ /* *
112+ * computeShortestPath():
113+ * While the queue top is "less" than calculateKey(start)
114+ * or g(start) < rhs(start), pop and process.
115+ */
116+ fun computeShortestPath () {
117+ while (
118+ (U .topKey() < calculateKey(start)) ||
119+ (g(start) < rhs(start))
120+ ) {
121+ val u = U .top() ? : break
122+ val oldKey = U .topKey()
123+ val newKey = calculateKey(u)
124+
125+ if (oldKey < newKey) {
126+ // Priority out-of-date; update it
127+ U .insertOrUpdate(u, newKey)
128+ } else {
129+ // Remove it from queue
130+ U .pop()
131+ if (g(u) > rhs(u)) {
132+ // We found a better path for u
133+ setG(u, rhs(u))
134+ // Update successors of u
135+ graph.successors(u).forEach { (s, _) ->
136+ updateVertex(s)
137+ }
138+ } else {
139+ // g(u) <= rhs(u)
140+ val gOld = g(u)
141+ setG(u, INF )
142+ updateVertex(u)
143+ // Update successors that may have relied on old g(u)
144+ graph.successors(u).forEach { (s, _) ->
145+ if (rhs(s) == gOld + graph.cost(u, s)) {
146+ updateVertex(s)
147+ }
148+ }
149+ }
150+ }
151+ }
152+ }
153+
154+ /* *
155+ * Called when the robot moves from oldStart to newStart.
156+ * We increase km by h(oldStart, newStart), then re-key all queued vertices.
157+ */
158+ fun updateStart (newStart : Int ) {
159+ val oldStart = start
160+ start = newStart
161+ km + = heuristic(oldStart, start)
162+
163+ // Re-key everything in U
164+ val tmpList = mutableListOf<Int >()
165+ while (! U .isEmpty()) {
166+ U .top()?.let { tmpList.add(it) }
167+ U .pop()
168+ }
169+ tmpList.forEach { v ->
170+ U .insertOrUpdate(v, calculateKey(v))
171+ }
172+ }
173+
174+ /* *
175+ * Returns a path from 'start' to 'goal' by always choosing
176+ * the successor s of the current vertex that minimizes g(s)+cost(current->s).
177+ * If no path is found, the path ends prematurely.
178+ */
179+ fun getPath (): List <Int > {
180+ val path = mutableListOf<Int >()
181+ var current = start
182+ path.add(current)
183+
184+ while (current != goal) {
185+ val successors = graph.successors(current)
186+ if (successors.isEmpty()) break
187+
188+ var bestNext: Int? = null
189+ var bestVal = INF
190+ for ((s, c) in successors) {
191+ val valCandidate = g(s) + c
192+ if (valCandidate < bestVal) {
193+ bestVal = valCandidate
194+ bestNext = s
195+ }
196+ }
197+ // No path
198+ if (bestNext == null ) break
199+ current = bestNext
200+ path.add(current)
201+
202+ // Safety net to avoid infinite loops
203+ if (path.size > 100000 ) break
204+ }
205+ return path
206+ }
207+ }
0 commit comments