Skip to content

Commit b566680

Browse files
authored
20260207 consistent velocity prep (#5719)
1 parent d657163 commit b566680

File tree

4 files changed

+142
-15
lines changed

4 files changed

+142
-15
lines changed

motionplan/armplanning/api.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,9 @@ func PlanFrameMotion(ctx context.Context,
177177

178178
// PlanMeta is meta data about plan generation.
179179
type PlanMeta struct {
180-
Duration time.Duration
181-
Partial bool
182-
GoalsProcessed int
180+
Duration time.Duration
181+
Partial bool
182+
PartialError error
183183
}
184184

185185
// PlanMotion plans a motion from a provided plan request.
@@ -217,18 +217,17 @@ func PlanMotion(ctx context.Context, parentLogger logging.Logger, request *PlanR
217217
return nil, meta, err
218218
}
219219

220-
trajAsInps, goalsProcessed, err := sfPlanner.planMultiWaypoint(ctx)
220+
trajAsInps, err := sfPlanner.planMultiWaypoint(ctx)
221221
if err != nil {
222222
if request.PlannerOptions.ReturnPartialPlan {
223223
meta.Partial = true
224+
meta.PartialError = err
224225
logger.Infof("returning partial plan, error: %v", err)
225226
} else {
226227
return nil, meta, err
227228
}
228229
}
229230

230-
meta.GoalsProcessed = goalsProcessed
231-
232231
t, err := motionplan.NewSimplePlanFromTrajectory(trajAsInps, request.FrameSystem)
233232
if err != nil {
234233
return nil, meta, err

motionplan/armplanning/cmd-plan/cmd-plan.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func realMain() error {
146146
trace.SetProvider(ctx, sdktrace.WithResource(otelresource.Empty()))
147147
trace.AddExporters(spansExporter)
148148

149-
plan, _, err := armplanning.PlanMotion(ctx, logger, req)
149+
plan, meta, err := armplanning.PlanMotion(ctx, logger, req)
150150
if err := trace.Shutdown(ctx); err != nil {
151151
logger.Errorw("Got error while shutting down tracing", "err", err)
152152
}
@@ -229,11 +229,25 @@ func realMain() error {
229229
}
230230
}
231231

232+
if meta.PartialError != nil {
233+
mylog.Printf("partial results, error: %v", meta.PartialError)
234+
}
235+
232236
mylog.Printf("planning took %v for %d goals => trajectory length: %d",
233237
time.Since(start).Truncate(time.Millisecond), len(req.Goals), len(plan.Trajectory()))
234238
mylog.Printf("totalCartesion: %0.4f\n", totalCartesion)
235239
mylog.Printf("totalL2: %0.4f\n", totalL2)
236240

241+
// Print delta statistics if trajectory has more than 5 points
242+
if len(plan.Trajectory()) > 5 {
243+
stats := armplanning.TrajectoryDeltaStats(plan.Trajectory())
244+
mylog.Printf("\nDelta Statistics (trajectory length: %d):", len(plan.Trajectory()))
245+
for _, s := range stats {
246+
mylog.Printf(" %s:%d: avg=%0.5f stddev=%0.5f outside1=%d outside2=%d (n=%d)",
247+
s.Component, s.JointIdx, s.Mean, s.StdDev, s.Outside1, s.Outside2, s.Count)
248+
}
249+
}
250+
237251
for i := 0; i < *loop; i++ {
238252
err = visualize(req, plan, mylog)
239253
if err != nil {

motionplan/armplanning/plan_manager.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func newPlanManager(ctx context.Context, logger logging.Logger, request *PlanReq
3535
// planMultiWaypoint plans a motion through multiple waypoints, using identical constraints for each
3636
// Any constraints, etc, will be held for the entire motion.
3737
// return trajector (always, even with error), which goal we got to, error.
38-
func (pm *planManager) planMultiWaypoint(ctx context.Context) ([]*referenceframe.LinearInputs, int, error) {
38+
func (pm *planManager) planMultiWaypoint(ctx context.Context) ([]*referenceframe.LinearInputs, error) {
3939
ctx, span := trace.StartSpan(ctx, "planMultiWaypoint")
4040
defer span.End()
4141

@@ -51,17 +51,17 @@ func (pm *planManager) planMultiWaypoint(ctx context.Context) ([]*referenceframe
5151
linearTraj := []*referenceframe.LinearInputs{pm.request.StartState.LinearConfiguration()}
5252
start, err := pm.request.StartState.ComputePoses(ctx, pm.request.FrameSystem)
5353
if err != nil {
54-
return nil, 0, err
54+
return nil, err
5555
}
5656

5757
for i, g := range pm.request.Goals {
5858
if ctx.Err() != nil {
59-
return linearTraj, i, err // note: here and below, we return traj because of ReturnPartialPlan
59+
return linearTraj, err // note: here and below, we return traj because of ReturnPartialPlan
6060
}
6161

6262
to, err := g.ComputePoses(ctx, pm.request.FrameSystem)
6363
if err != nil {
64-
return linearTraj, i, err
64+
return linearTraj, err
6565
}
6666

6767
pm.logger.Info("planning step", i, "of", len(pm.request.Goals))
@@ -72,13 +72,13 @@ func (pm *planManager) planMultiWaypoint(ctx context.Context) ([]*referenceframe
7272
if len(g.Configuration()) > 0 {
7373
newTraj, err := pm.planToDirectJoints(ctx, linearTraj[len(linearTraj)-1], g)
7474
if err != nil {
75-
return linearTraj, i, err
75+
return linearTraj, err
7676
}
7777
linearTraj = append(linearTraj, newTraj...)
7878
} else {
7979
subGoals, cbirrtAllowed, err := pm.generateWaypoints(ctx, start, to)
8080
if err != nil {
81-
return linearTraj, i, err
81+
return linearTraj, err
8282
}
8383

8484
if len(subGoals) > 1 {
@@ -95,7 +95,7 @@ func (pm *planManager) planMultiWaypoint(ctx context.Context) ([]*referenceframe
9595
newTraj, err := pm.planSingleGoal(ctx, linearTraj[len(linearTraj)-1], sg, cbirrtAllowed)
9696
if err != nil {
9797
pm.logger.Infof("\t subgoal %d failed after %v with: %v", subGoalIdx, time.Since(singleGoalStart), err)
98-
return linearTraj, i, err
98+
return linearTraj, err
9999
}
100100
pm.logger.Infof("\t subgoal %d took %v", subGoalIdx, time.Since(singleGoalStart))
101101
linearTraj = append(linearTraj, newTraj...)
@@ -104,7 +104,7 @@ func (pm *planManager) planMultiWaypoint(ctx context.Context) ([]*referenceframe
104104
start = to
105105
}
106106

107-
return linearTraj, len(pm.request.Goals), nil
107+
return linearTraj, nil
108108
}
109109

110110
func (pm *planManager) planToDirectJoints(

motionplan/armplanning/smooth.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package armplanning
22

33
import (
44
"context"
5+
"fmt"
56
"math"
7+
"sort"
8+
"strconv"
9+
"strings"
610
"time"
711

812
"go.viam.com/utils/trace"
@@ -11,6 +15,116 @@ import (
1115
"go.viam.com/rdk/referenceframe"
1216
)
1317

18+
// JointDeltaStats holds statistics about delta values for a single joint.
19+
type JointDeltaStats struct {
20+
Component string
21+
JointIdx int
22+
Count int
23+
Mean float64
24+
StdDev float64
25+
Outside1 int // count of values more than 1 std dev from mean
26+
Outside2 int // count of values more than 2 std dev from mean
27+
}
28+
29+
// TrajectoryDeltaStats computes delta statistics for each joint across all steps in a trajectory.
30+
// Returns nil if trajectory has fewer than 2 steps.
31+
func TrajectoryDeltaStats(trajectory motionplan.Trajectory) []JointDeltaStats {
32+
if len(trajectory) < 2 {
33+
return nil
34+
}
35+
36+
// Collect deltas for each component:joint
37+
// key is "component:jointIdx", value is slice of delta values
38+
allDeltas := map[string][]float64{}
39+
40+
for idx := 1; idx < len(trajectory); idx++ {
41+
curr := trajectory[idx]
42+
prev := trajectory[idx-1]
43+
44+
for component, currInputs := range curr {
45+
prevInputs, ok := prev[component]
46+
if !ok || len(prevInputs) == 0 || len(currInputs) == 0 {
47+
continue
48+
}
49+
50+
for i, currVal := range currInputs {
51+
if i >= len(prevInputs) {
52+
break
53+
}
54+
key := fmt.Sprintf("%s:%d", component, i)
55+
delta := currVal - prevInputs[i]
56+
allDeltas[key] = append(allDeltas[key], delta)
57+
}
58+
}
59+
}
60+
61+
// Sort keys for consistent output
62+
keys := make([]string, 0, len(allDeltas))
63+
for k := range allDeltas {
64+
keys = append(keys, k)
65+
}
66+
sort.Strings(keys)
67+
68+
// Compute statistics for each joint
69+
results := make([]JointDeltaStats, 0, len(keys))
70+
for _, key := range keys {
71+
deltas := allDeltas[key]
72+
n := len(deltas)
73+
if n == 0 {
74+
continue
75+
}
76+
77+
// Parse component and joint index from key (format: "component:jointIdx")
78+
lastColon := strings.LastIndex(key, ":")
79+
component := key[:lastColon]
80+
jointIdx, err := strconv.Atoi(key[lastColon+1:])
81+
if err != nil {
82+
// Should not happen since we create the keys ourselves
83+
continue
84+
}
85+
86+
// Calculate mean
87+
sum := 0.0
88+
for _, d := range deltas {
89+
sum += d
90+
}
91+
mean := sum / float64(n)
92+
93+
// Calculate standard deviation
94+
sumSqDiff := 0.0
95+
for _, d := range deltas {
96+
diff := d - mean
97+
sumSqDiff += diff * diff
98+
}
99+
stdDev := math.Sqrt(sumSqDiff / float64(n))
100+
101+
// Count values outside 1 and 2 standard deviations
102+
outside1 := 0
103+
outside2 := 0
104+
for _, d := range deltas {
105+
diff := math.Abs(d - mean)
106+
if diff > stdDev {
107+
outside1++
108+
}
109+
if diff > 2*stdDev {
110+
outside2++
111+
}
112+
}
113+
114+
results = append(results, JointDeltaStats{
115+
Component: component,
116+
JointIdx: jointIdx,
117+
Count: n,
118+
Mean: mean,
119+
StdDev: stdDev,
120+
Outside1: outside1,
121+
Outside2: outside2,
122+
})
123+
}
124+
125+
return results
126+
}
127+
14128
func simpleSmoothStep(ctx context.Context, psc *planSegmentContext, steps []*referenceframe.LinearInputs, step int,
15129
) []*referenceframe.LinearInputs {
16130
ctx, span := trace.StartSpan(ctx, "simpleSmoothStep")

0 commit comments

Comments
 (0)