Skip to content

Commit 412733b

Browse files
committed
docs: add syntax rules and cross-spec substitution to @init documentation
- Add 'Syntax rules' subsection explaining LHS/RHS rules, default component names (x₁, x₂), and prohibition of indexed syntax x[i](t) - Add 'Cross-spec substitution' section with examples: temporal→temporal, transitive chain, constant→temporal, constant→constant, and mixing with aliases - Update 'Component labels and time variable' callout to mention default subscripted component names
1 parent e26f4ff commit 412733b

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

docs/src/manual-initial-guess.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ nothing # hide
5656

5757
- The `@init` macro uses the **labels** declared in the `@def` block. For `ocp1`, you can use `x`, `x₁`, `x₂`, and `u`. For `ocp2`, you can use `x`, `q`, `v`, `u`, and `tf`.
5858
- The `@init` macro uses the **time variable name** from the `@def` block. For `ocp1`, use `t` (e.g., `x(t) := ...`). For `ocp2`, use `s` (e.g., `q(s) := ...`).
59+
- When components are **not explicitly named** in `@def` (as in `ocp1` with `x ∈ R²`), they receive **default labels with subscripted indices**: `x₁`, `x₂`, etc. These default names are usable in `@init` just like custom labels.
5960
- This allows for more readable initial guess specifications that match your problem definition.
6061

6162
## Default initial guess
@@ -156,6 +157,43 @@ where `T = [0.0, 0.5, 1.0]` is the time grid.
156157
- **Variables** and **aliases**: no time argument
157158
- **Constant functions**: use either `u(t) := 2` or the simplified `u := 2`
158159

160+
### Syntax rules
161+
162+
The left-hand side of `:=` and `=` follow strict rules.
163+
164+
**Left-hand side of `:=`** : only **labels declared in the optimal control problem**:
165+
166+
- the **time variable name** declared in `@def` (`t`, `s`, ...), used as the argument of state/control functions
167+
- the **state, control or variable** label, either its global name (`x`, `u`, `tf`) or the name of one of its **components** (`q`, `v`, `x₁`, `x₂`, ...)
168+
169+
**Left-hand side of `=`** : arbitrary **alias names**, local to the `@init` block. Aliases are not labels of the problem; they are just convenient names to factor out constants or subexpressions.
170+
171+
**Right-hand side** : any Julia expression, which may reference the time variable, previously defined aliases, and other labels defined earlier in the block (see [Cross-spec substitution](@ref cross-spec-substitution)).
172+
173+
#### Default component names
174+
175+
When components are not explicitly named in `@def`, they receive default labels with subscripted indices. For example, `ocp1` declares `x ∈ R²` without naming the components, so the default labels `x₁` and `x₂` are used:
176+
177+
```@example main
178+
# use default component names x₁, x₂
179+
ig = @init ocp1 begin
180+
x₁(t) := -1.0 + t/10
181+
x₂(t) := 0.0
182+
u(t) := -0.2
183+
end
184+
185+
sol = solve(ocp1; init=ig, display=false)
186+
println("Number of iterations: ", iterations(sol))
187+
nothing # hide
188+
```
189+
190+
#### No indexed syntax
191+
192+
The indexed syntax `x[i](t)` or `x[i:j](t)` is **not supported** on the left-hand side of `:=`. `@init` works at the level of labels, not array indices.
193+
194+
-`x[1](t) := ...`, `x[1:2](t) := ...`
195+
-`x(t) := ...` (global) or `x₁(t) := ...`, `x₂(t) := ...`, `q(t) := ...`, `v(t) := ...` (per component)
196+
159197
### Constant initial guess
160198

161199
To initialize with constant values, use constant expressions in the function syntax:
@@ -275,6 +313,118 @@ println("Number of iterations: ", iterations(sol))
275313
nothing # hide
276314
```
277315

316+
### [Cross-spec substitution](@id cross-spec-substitution)
317+
318+
Specifications inside a single `@init` block can **reference each other**, from top to bottom. A label defined on an earlier line can be reused in the right-hand side of a later specification.
319+
320+
Rules:
321+
322+
- A reference only resolves to a label (or alias) defined **earlier** in the block.
323+
- Substitution happens by name: the referenced label is replaced by its definition when the later expression is evaluated.
324+
- References across different grid arguments are **not substituted** (see note at the end of this section).
325+
326+
#### Temporal → temporal
327+
328+
A time-dependent spec can reference another time-dependent spec:
329+
330+
```@example main
331+
# v depends on q
332+
ig = @init ocp2 begin
333+
q(s) := sin(s)
334+
v(s) := 1.0 + q(s)
335+
u(s) := 0.0
336+
tf := 2.0
337+
end
338+
339+
sol = solve(ocp2; init=ig, display=false)
340+
println("Number of iterations: ", iterations(sol))
341+
nothing # hide
342+
```
343+
344+
#### Transitive chain
345+
346+
Substitutions chain transitively: `u` below references `v`, which itself references `q`.
347+
348+
```@example main
349+
# q → v → u
350+
ig = @init ocp2 begin
351+
q(s) := sin(s)
352+
v(s) := 1.0 + q(s)
353+
u(s) := s + v(s)^2
354+
tf := 2.0
355+
end
356+
357+
sol = solve(ocp2; init=ig, display=false)
358+
println("Number of iterations: ", iterations(sol))
359+
nothing # hide
360+
```
361+
362+
#### Constant → temporal
363+
364+
A temporal spec can reference a constant-valued component defined earlier:
365+
366+
```@example main
367+
# v(s) uses the constant value of q
368+
ig = @init ocp2 begin
369+
q := -1.0
370+
v(s) := q + sin(s)
371+
u(s) := 0.0
372+
tf := 2.0
373+
end
374+
375+
sol = solve(ocp2; init=ig, display=false)
376+
println("Number of iterations: ", iterations(sol))
377+
nothing # hide
378+
```
379+
380+
#### Constant → constant
381+
382+
A constant spec can reference another constant, including for variable components. Here we define a small OCP whose variable has two components `(tf, a)`:
383+
384+
```@example main
385+
ocp_var2 = @def begin
386+
w = (tf, a) ∈ R², variable
387+
t ∈ [0, 1], time
388+
x ∈ R, state
389+
u ∈ R, control
390+
x(0) == 0
391+
x(1) == a
392+
ẋ(t) == u(t)
393+
∫(0.5u(t)^2) → min
394+
end
395+
396+
ig = @init ocp_var2 begin
397+
tf := 1.0
398+
a := tf + 0.5
399+
end
400+
401+
w = variable(ig)
402+
println("tf = ", w[1], ", a = ", w[2])
403+
nothing # hide
404+
```
405+
406+
#### Mixing aliases and cross-spec references
407+
408+
Aliases (with `=`) and cross-spec references (with `:=`) can be freely combined:
409+
410+
```@example main
411+
ig = @init ocp2 begin
412+
A = 2.0 # alias
413+
q(s) := A * sin(s) # uses alias
414+
v(s) := q(s) + 1.0 # references q
415+
u(s) := 0.0
416+
tf := 2.0
417+
end
418+
419+
sol = solve(ocp2; init=ig, display=false)
420+
println("Number of iterations: ", iterations(sol))
421+
nothing # hide
422+
```
423+
424+
!!! note "No substitution across grid specs"
425+
426+
When a spec uses a **grid argument** (e.g. `q(T) := Dq` with `T` a time vector), it is not substituted into other temporal specs written with the time variable (`v(s) := ...`). The two live in different evaluation contexts. Use either temporal functions throughout, or grids throughout, when you need to chain references.
427+
278428
## Vector initial guess (interpolated)
279429

280430
You can provide initial values on a time grid using the syntax `label(T) := data`, where `T` is a time vector and `data` contains the corresponding values.

0 commit comments

Comments
 (0)