Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/onepass.jl
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,11 @@ case of an exception, prints the originating line number and source
text before rethrowing.
"""
__wrap(e, n, line) = quote
local ex
try
$e
catch ex
catch
println("Line ", $n, ": ", $line)
throw(ex)
rethrow()
end
end

Expand All @@ -220,7 +219,7 @@ Return `x` itself if it is a range, or a one-element array `[x]`.
This is a normalisation helper used when interpreting constraint
indices.
"""
as_range(x) = is_range(x) ? x : [x]
as_range(x) = is_range(x) ? x : :(($x):($x))

# Main code

Expand Down Expand Up @@ -696,7 +695,7 @@ function p_constraint_fun!(p, p_ocp, e1, e2, e3, c_type, label)
(:variable_range, rg) => :($pref.constraint!(
$p_ocp, :variable; rg=($rg), lb=($e1), ub=($e3), label=($llabel)
))
:state_fun || control_fun || :mixed => begin # now all treated as path
:state_fun || :control_fun || :mixed => begin # now all treated as path
fun = __symgen(:fun)
xt = __symgen(:xt)
ut = __symgen(:ut)
Expand Down Expand Up @@ -825,7 +824,7 @@ function p_constraint_exa!(p, p_ocp, e1, e2, e3, c_type, label)
p.box_u = concat(p.box_u, code_box) # not __wrapped since contains definition of l_u/u_u
:()
end
:state_fun || control_fun || :mixed => begin
:state_fun || :control_fun || :mixed => begin
code = :(length($e1) == length($e3) == 1 || throw("this constraint must be scalar")) # (vs. __throw) since raised at runtime
xt = __symgen(:xt)
ut = __symgen(:ut)
Expand Down Expand Up @@ -1133,7 +1132,7 @@ PARSING_FUN[:lagrange] = p_lagrange_fun!
PARSING_FUN[:mayer] = p_mayer_fun!
PARSING_FUN[:bolza] = p_bolza_fun!

# Summary of available parsing subfunctions (:fun backend)
# Summary of available parsing subfunctions (:exa backend)

const PARSING_EXA = OrderedDict{Symbol,Function}()
PARSING_EXA[:pragma] = p_pragma_exa!
Expand Down
142 changes: 138 additions & 4 deletions test/test_onepass_exa_bis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@ function test_onepass_exa_bis()
@test CTParser.is_range(:(a:b))
@test CTParser.as_range(:(a:b:c)) == :(a:b:c)

# Fallback to single-element vector when not a range
@test CTParser.as_range(:foo) == [:foo]
# Fallback to single-element range expression when not a range
@test CTParser.as_range(:foo) == :(foo:foo)

# Edge cases
@test !CTParser.is_range(42)
@test !CTParser.is_range("string")
@test !CTParser.is_range(:(f(x)))
@test CTParser.is_range(1:10)
@test CTParser.as_range(42) == [42]
@test CTParser.as_range(:(x + y)) == [:(x + y)]
@test CTParser.as_range(42) == :((42):(42))
@test CTParser.as_range(:(x + y)) == :((x + y):(x + y))
end

@testset "p_dynamics_coord_exa! preconditions" begin
Expand Down Expand Up @@ -506,4 +506,138 @@ function test_onepass_exa_bis()
ex = CTParser.p_constraint_exa!(p, p_ocp, 0, :(x[1](0) + v), 1, :variable_fun, :c1)
@test ex isa Expr
end

# ============================================================================
# P_CONSTRAINT_EXA! - :other constraint type error (invalid constraints)
# ============================================================================

@testset "p_constraint_exa! :other constraint type error" begin
println("p_constraint_exa! :other type (bis)")

p = CTParser.ParsingInfo()
p.lnum = 1
p.line = "constraint exa :other test"
p.t = :t
p.t0 = 0
p.tf = 1
p.x = :x
p.u = :u
p.v = :v
p.dt = :dt
p_ocp = :p_ocp

# Constraint with :other type should raise an error
# This simulates what happens when constraint_type returns :other
ex = CTParser.p_constraint_exa!(p, p_ocp, 0, :(x[1](0) + u[1](t)), 1, :other, :c1)
@test ex isa Expr
@test_throws ParsingError eval(ex)

# Another :other case - the exact example from the user
ex2 = CTParser.p_constraint_exa!(
p, p_ocp, nothing, :(x[1](0) * u[1](t) + u[2](t)^2), 1, :other, :c2
)
@test ex2 isa Expr
@test_throws ParsingError eval(ex2)
end

@testset "p_constraint! detects :other constraint type (exa)" begin
println("p_constraint! detects :other for exa (bis)")

p = CTParser.ParsingInfo()
p.lnum = 1
p.line = "constraint detection test exa"
p.t = :t
p.t0 = 0
p.tf = 1
p.x = :x
p.u = :u
p.v = :v
p.dim_x = 2
p.dim_u = 2
p.dt = :dt
p_ocp = :p_ocp

# Test that p_constraint! correctly identifies invalid constraints
# Mixed initial state and control: x1(0) * u1(t) + u2(t)^2 <= 1
# This should result in constraint_type returning :other
ex = CTParser.p_constraint!(
p, p_ocp, nothing, :(x[1](0) * u[1](t) + u[2](t)^2), 1; backend=:exa
)
@test ex isa Expr
@test_throws ParsingError eval(ex)

# Mixed final state and control at initial time: x(tf) + u(t0)
# This should also result in :other
ex2 = CTParser.p_constraint!(p, p_ocp, 0, :(x[1](tf) + u[1](0)), 1; backend=:exa)
@test ex2 isa Expr
@test_throws ParsingError eval(ex2)

# Control at initial and final time: u(t0) + u(tf)
ex3 = CTParser.p_constraint!(p, p_ocp, 0, :(u[1](0) + u[1](tf)), 1; backend=:exa)
@test ex3 isa Expr
@test_throws ParsingError eval(ex3)
end

# ============================================================================
# @def_exa MACRO - Invalid constraints that should raise ParsingError
# ============================================================================

@testset "@def_exa macro :other constraint type error" begin
println("@def_exa macro :other constraint (bis)")

backend = nothing

# Test 1: Mixed initial state and control - x1(0) * u1(t) + u2(t)^2 <= 1
# This should trigger constraint_type to return :other and raise ParsingError
o = @def_exa begin
t ∈ [0, 1], time
x ∈ R², state
u ∈ R², control
x[1](0) * u[1](t) + u[2](t)^2 ≤ 1
ẋ₁(t) == u[1](t)
ẋ₂(t) == u[2](t)
end
@test_throws ParsingError o(; backend=backend)

# Test 2: Mixed final state and control at initial time - x(tf) + u(t0)
o = @def_exa begin
t ∈ [0, 1], time
x ∈ R, state
u ∈ R, control
x(1) + u(0) ≤ 1
ẋ(t) == u(t)
end
@test_throws ParsingError o(; backend=backend)

# Test 3: Control at both initial and final time - u(t0) + u(tf)
o = @def_exa begin
t ∈ [0, 1], time
x ∈ R, state
u ∈ R, control
u(0) + u(1) ≤ 1
ẋ(t) == u(t)
end
@test_throws ParsingError o(; backend=backend)

# Test 4: Another invalid mixing - state at t and control at t0
o = @def_exa begin
t ∈ [0, 1], time
x ∈ R, state
u ∈ R, control
x(t) + u(0) ≤ 1
ẋ(t) == u(t)
end
@test_throws ParsingError o(; backend=backend)

# Test 5: The exact user example - x1(0) * u1(t) + u2(t)^2 <= 1
o = @def_exa begin
t ∈ [0, 1], time
x ∈ R², state
u ∈ R², control
x₁(0) * u₁(t) + u₂(t)^2 ≤ 1
ẋ₁(t) == u₁(t)
ẋ₂(t) == u₂(t)
end
@test_throws ParsingError o(; backend=backend)
end
end
36 changes: 18 additions & 18 deletions test/test_onepass_fun.jl
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ function test_onepass_fun()
r = y₃
v = y₄
aa = y₁(__s)
ẏ(__s) == [aa(__s), (__s) + w(__s) + z₁, 0, 0]
ẏ(__s) == [aa(__s), (r^2)(__s) + w(__s) + z₁, 0, 0]
0 => min # generic (untested)
end
z = [5, 6]
Expand Down Expand Up @@ -851,7 +851,7 @@ function test_onepass_fun()
v = y₄
aa = y₁(__s)
∂(y[1])(__s) == aa(__s)
∂(y[2])(__s) == (__s) + w(__s) + z₁
∂(y[2])(__s) == (r^2)(__s) + w(__s) + z₁
∂(y[3])(__s) == 0
∂(y[4])(__s) == 0
0 => min # generic (untested)
Expand Down Expand Up @@ -1241,10 +1241,10 @@ function test_onepass_fun()
x(0) ≤ 0, (1)
x(1) ≤ 0
x(1) ≤ 0, (2)
(0) ≤ 0
(0) ≤ 0, (3)
(1) ≤ 0
(1) ≤ 0, (4)
(x^3)(0) ≤ 0
(x^3)(0) ≤ 0, (3)
(x^3)(1) ≤ 0
(x^3)(1) ≤ 0, (4)
x(t) ≤ 0
x(t) ≤ 0, (5)
x(t) ≤ 0
Expand All @@ -1253,10 +1253,10 @@ function test_onepass_fun()
u₁(t) ≤ 0, (7)
u₁(t) ≤ 0
u₁(t) ≤ 0, (8)
x³(t) ≤ 0
(t) ≤ 0, (9)
(t) ≤ 0
(t) ≤ 0, (10)
x(t) ≤ 0
(x^3)(t) ≤ 0, (9)
(x^3)(t) ≤ 0
(x^3)(t) ≤ 0, (10)
(u₁^3)(t) ≤ 0
(u₁^3)(t) ≤ 0, (11)
(u₁^3)(t) ≤ 0
Expand Down Expand Up @@ -1311,10 +1311,10 @@ function test_onepass_fun()
x(0) ≥ 0, (1)
x(1) ≥ 0
x(1) ≥ 0, (2)
(0) ≥ 0
(0) ≥ 0, (3)
(1) ≥ 0
(1) ≥ 0, (4)
(x^3)(0) ≥ 0
(x^3)(0) ≥ 0, (3)
(x^3)(1) ≥ 0
(x^3)(1) ≥ 0, (4)
x(t) ≥ 0
x(t) ≥ 0, (5)
x(t) ≥ 0
Expand All @@ -1323,10 +1323,10 @@ function test_onepass_fun()
u₁(t) ≥ 0, (7)
u₁(t) ≥ 0
u₁(t) ≥ 0, (8)
(t) ≥ 0
(t) ≥ 0, (9)
(t) ≥ 0
(t) ≥ 0, (10)
(x^3)(t) ≥ 0
(x^3)(t) ≥ 0, (9)
(x^3)(t) ≥ 0
(x^3)(t) ≥ 0, (10)
(u₁^3)(t) ≥ 0
(u₁^3)(t) ≥ 0, (11)
(u₁^3)(t) ≥ 0
Expand Down
Loading
Loading