diff --git a/src/CTParser.jl b/src/CTParser.jl index 56fdfba..e5e066b 100644 --- a/src/CTParser.jl +++ b/src/CTParser.jl @@ -21,6 +21,7 @@ using Unicode # sources include("utils.jl") +include("reorder.jl") include("onepass.jl") -end +end \ No newline at end of file diff --git a/src/onepass.jl b/src/onepass.jl index 28e4645..35c48f9 100644 --- a/src/onepass.jl +++ b/src/onepass.jl @@ -1034,20 +1034,62 @@ end true # final boolean to show parsing log """ macro def(e) try - code = def_fun(e) + code = def_fun(e; order = false) return esc(code) catch ex - :(throw($ex)) # can be caught by user + #try # I am not sure if this try is needed + code = def_fun(e; order = true) + e_ordered = QuoteNode(reorder(e)) + code = quote + try + $code + catch ex_code_reorder + printstyled("\n▫ Parsing failed for original and reordered codes, throwing exception:\n", color = :red, bold = true) + printstyled("\n▫ Exception from original code:", color = :cyan, bold = true) + println(" " * string($ex)) + printstyled("\n▫ Exception from reordered code:", color = :cyan, bold = true) + println(" " * string(ex_code_reorder)) + printstyled("\n▫ Reordered code:", color = :cyan, bold = true) + println(" " * string($e_ordered)) + println("") + rethrow(ex_code_reorder) + end + end + return esc(code) + # catch ex_code_reorder + # println("▫ Reordering failed, throwing exception:\n") + # println("Exception from reordered code: \n", ex_code_reorder) + # println("Exception from original code: \n", ex) + # :(throw($ex)) # can be caught by user + # end end end macro def(ocp, e, log=false) # old syntax with ocp name in arguments for compatibility try - code = def_fun(e; log = log) + code = def_fun(e; log = log, order = false) code = :($ocp = $code) return esc(code) catch ex - :(throw($ex)) # can be caught by user + code = def_fun(e; log = log, order = true) + e_ordered = QuoteNode(reorder(e)) + code = quote + try + $code + catch ex_code_reorder + printstyled("\n▫ Parsing failed for original and reordered codes, throwing exception:\n", color = :red, bold = true) + printstyled("\n▫ Exception from original code:", color = :cyan, bold = true) + println(" " * string($ex)) + printstyled("\n▫ Exception from reordered code:", color = :cyan, bold = true) + println(" " * string(ex_code_reorder)) + printstyled("\n▫ Reordered code:", color = :cyan, bold = true) + println(" " * string($e_ordered)) + println("") + rethrow(ex_code_reorder) + end + end + code = :($ocp = $code) + return esc(code) end end @@ -1066,11 +1108,14 @@ $(TYPEDSIGNATURES) Core computation of `@def` macro, parsing an expression towards a CTModels.Model. """ -function def_fun(e; log = false) +function def_fun(e; log = false, order::Bool = false) pref = prefix() p_ocp = __symgen(:p_ocp) p = ParsingInfo() ee = QuoteNode(e) + if order + e = reorder(e) # to reorder the expr before parsing + end code = parse!(p, p_ocp, e; log = log, backend = :fun) code = quote $p_ocp = $pref.PreModel() diff --git a/src/reorder.jl b/src/reorder.jl new file mode 100644 index 0000000..a4ae018 --- /dev/null +++ b/src/reorder.jl @@ -0,0 +1,50 @@ +function store!(data, e) + # assume data is a dict with keys: + # variable, declaration, misc, objective + # for each key, you have a vector of Expr already initialised + @match e begin + :(PRAGMA($a)) => push!(data[:misc], e) + :($a = $b) => push!(data[:declaration], e) + :($a, variable) => push!(data[:variable], e) + :($a, time) => push!(data[:declaration], e) + :($a, state) => push!(data[:declaration], e) + :($a, control) => push!(data[:declaration], e) + :($a → max) => push!(data[:objective], e) + :($a → min) => push!(data[:objective], e) + _ => begin + if e isa LineNumberNode + nothing + elseif e isa Expr && e.head == :block + map(e -> store!(data, e), e.args) + else + push!(data[:misc], e) + end + end + end + return nothing +end + +function reorder(data::Dict) + # assume data is a dict with keys: + # variable, declaration, misc, objective + # for each key, you have a vector of Expr already initialised + code = Expr(:block) + keys = [:variable, :declaration, :misc, :objective] + for key ∈ keys + for e ∈ data[key] + code = code==Expr(:block) ? e : concat(code, e) + end + end + return code +end + +function reorder(e::Expr) + data = Dict( + :variable => Vector{Expr}(), + :declaration => Vector{Expr}(), + :misc => Vector{Expr}(), + :objective => Vector{Expr}(), + ) + store!(data, e) + return reorder(data) +end \ No newline at end of file diff --git a/test/test_onepass_fun.jl b/test/test_onepass_fun.jl index 2ad0572..8ff5b16 100644 --- a/test/test_onepass_fun.jl +++ b/test/test_onepass_fun.jl @@ -3157,4 +3157,33 @@ function test_onepass_fun() end + test_name = "wrong order" + @testset "$test_name" begin println(test_name) + + # Parameters + t0 = 0 # initial time + r0 = 1 # initial altitude + v0 = 0 # initial speed + m0 = 1 # initial mass + vmax = 0.1 # maximal authorized speed + mf = 0.6 # final mass to target + + o = @def begin + u ∈ R, control + x(t0) == [r0, v0, m0] + r(tf) → max + x = (r, v, m) ∈ R³, state + t ∈ [t0, tf], time + m(tf) == mf, (1) + 0 ≤ u(t) ≤ 1 + r(t) ≥ r0 + 0 ≤ v(t) ≤ vmax + ẋ(t) == F0(x(t)) + u(t) * F1(x(t)) + PRAGMA(1+1) + tf ∈ R, variable + end + @test o isa CTModels.Model + + end + end diff --git a/test/tmp/reorder.jl b/test/tmp/reorder.jl new file mode 100644 index 0000000..e2828d0 --- /dev/null +++ b/test/tmp/reorder.jl @@ -0,0 +1,248 @@ +using Revise +using CTParser +using CTModels +using CTBase + +CTParser.prefix!(:CTModels) # code generated by @def is prefixed by CTModels (not by OptimalControl - the default) for tests +CTParser.e_prefix!(:CTBase) # exceptions in code generated by @def are prefixed by CTBase (not by OptimalControl - the default) for tests + +# ------------------------------------------ +# Goddard problem parameters + +# Parameters +const t0 = 0 # initial time +const r0 = 1 # initial altitude +const v0 = 0 # initial speed +const m0 = 1 # initial mass +const vmax = 0.1 # maximal authorized speed +const mf = 0.6 # final mass to target + +# Dynamics +const Cd = 310 +const Tmax = 3.5 +const β = 500 +const b = 2 + +F0(x) = begin + r, v, m = x + D = Cd * v^2 * exp(-β*(r - 1)) # Drag force + return [v, -D/m - 1/r^2, 0] +end + +F1(x) = begin + r, v, m = x + return [0, Tmax/m, -b*Tmax] +end + +# ------------------------------------------ +e = quote + tf ∈ R, variable + t ∈ [t0, tf], time + x = (r, v, m) ∈ R³, state + u ∈ R, control + + x(t0) == [r0, v0, m0] + m(tf) == mf, (1) + 0 ≤ u(t) ≤ 1 + r(t) ≥ r0 + 0 ≤ v(t) ≤ vmax + PRAGMA(1+1) + + ẋ(t) == F0(x(t)) + u(t) * F1(x(t)) + + r(tf) → max +end; + +code = CTParser.reorder(e) +o = eval(CTParser.def_fun(e; order=true)) + +# Wrong order +e = quote + u ∈ R, control + x(t0) == [r0, v0, m0] + r(tf) → max + x = (r, v, m) ∈ R³, state + t ∈ [t0, tf], time + m(tf) == mf, (1) + 0 ≤ u(t) ≤ 1 + r(t) ≥ r0 + 0 ≤ v(t) ≤ vmax + ẋ(t) == F0(x(t)) + u(t) * F1(x(t)) + PRAGMA(1+1) + tf ∈ R, variable +end; + +code = CTParser.reorder(e) +o = eval(CTParser.def_fun(e; order=true)) + +e = quote + λ ∈ R^2, variable + tf = λ₂ + t ∈ [0, tf], time + x ∈ R, state + u ∈ R, control + ẋ(t) == u(t) + tf → min +end; +code = CTParser.reorder(e) +o = eval(CTParser.def_fun(e; order=true)) + +# ------------------------------------------ +# with @def +o = CTParser.@def begin + tf ∈ R, variable + t ∈ [t0, tf], time + x = (r, v, m) ∈ R³, state + u ∈ R, control + + x(t0) == [r0, v0, m0] + m(tf) == mf, (1) + 0 ≤ u(t) ≤ 1 + r(t) ≥ r0 + 0 ≤ v(t) ≤ vmax + PRAGMA(1+1) + + ẋ(t) == F0(x(t)) + u(t) * F1(x(t)) + + r(tf) → max +end + +# wrong order +o = CTParser.@def begin + u ∈ R, control + x(t0) == [r0, v0, m0] + r(tf) → max + x = (r, v, m) ∈ R³, state + t ∈ [t0, tf], time + m(tf) == mf, (1) + 0 ≤ u(t) ≤ 1 + r(t) ≥ r0 + 0 ≤ v(t) ≤ vmax + ẋ(t) == F0(x(t)) + u(t) * F1(x(t)) + PRAGMA(1+1) + tf ∈ R, variable +end + +# correct order +o = CTParser.@def begin + λ ∈ R^2, variable + tf = λ₂ + t ∈ [0, tf], time + x ∈ R, state + u ∈ R, control + ẋ(t) == u(t) + tf → min +end + +# cannot work even if reordered +o = CTParser.@def begin + ẋ(t) == u(t) + λ ∈ R^2, variable + x ∈ R, state + t ∈ [0, tf], time + tf → min + u ∈ R, control +end + +# cannot work even if reordered: error during parsing +o = CTParser.@def begin + λ ∈ R^2, variable + tf = λ₂ + t ∈ [0, tf], time + x = (r, v, m) ∈ R², state + u ∈ R, control + ẋ(t) == u(t) + tf → min +end + +# ------------------------------------------ +# +CTParser.@def o begin + λ ∈ R^2, variable + tf = λ₂ + t ∈ [0, tf], time + x ∈ R², state + u ∈ R, control + 0 ≤ r(t) + ẋ(t) == u(t) + tf → min +end + +# +CTParser.@def o begin + tf ∈ R, variable + t ∈ [t0, tf], time + x = (r, v, m) ∈ R³, state + u ∈ R, control + + x(t0) == [r0, v0, m0] + m(tf) == mf, (1) + 0 ≤ u(t) ≤ 1 + r(t) ≥ r0 + 0 ≤ v(t) ≤ vmax + PRAGMA(1+1) + + ẋ(t) == F0(x(t)) + u(t) * F1(x(t)) + + r(tf) → max +end + +# wrong order +CTParser.@def o begin + u ∈ R, control + x(t0) == [r0, v0, m0] + r(tf) → max + x = (r, v, m) ∈ R³, state + t ∈ [t0, tf], time + m(tf) == mf, (1) + 0 ≤ u(t) ≤ 1 + r(t) ≥ r0 + 0 ≤ v(t) ≤ vmax + ẋ(t) == F0(x(t)) + u(t) * F1(x(t)) + PRAGMA(1+1) + tf ∈ R, variable +end + +# correct order +CTParser.@def o begin + λ ∈ R^2, variable + tf = λ₂ + t ∈ [0, tf], time + x ∈ R, state + u ∈ R, control + ẋ(t) == u(t) + tf → min +end + +# cannot work even if reordered +CTParser.@def o begin + ẋ(t) == u(t) + λ ∈ R^2, variable + x ∈ R, state + t ∈ [0, tf], time + tf → min + u ∈ R, control +end + +# cannot work even if reordered: error during parsing +CTParser.@def o begin + λ ∈ R^2, variable + tf = λ₂ + t ∈ [0, tf], time + x = (r, v, m) ∈ R², state + u ∈ R, control + ẋ(t) == u(t) + tf → min +end + +# +CTParser.@def o begin + λ ∈ R^2, variable + tf = λ₂ + t ∈ [0, tf], time + x ∈ R², state + u ∈ R, control + 0 ≤ r(t) + ẋ(t) == u(t) + tf → min +end \ No newline at end of file