Skip to content

Commit 2956948

Browse files
authored
Merge pull request #4 from s-celles/064-symbolic-comparison-operators
feat: add symbolic comparison operators (<, >, <=, >=) and logical operators (&, |) for GiacExpr
2 parents a713ebf + ecdcc29 commit 2956948

5 files changed

Lines changed: 371 additions & 2 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "Giac"
22
uuid = "e4421f97-9838-4fd0-9fa5-94f11373bf78"
3-
version = "0.11.1"
3+
version = "0.11.2"
44
authors = ["Sébastien Celles <s.celles@gmail.com>"]
55

66
[deps]

docs/src/variables.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,62 @@ vars = @giac_several_vars c 4
5252
for v in vars
5353
println(v)
5454
end
55-
```
55+
```
56+
57+
## Symbolic Comparisons and Inequalities
58+
59+
The comparison operators `<`, `>`, `<=`, `>=` return symbolic inequality expressions
60+
(`GiacExpr`), not booleans. This enables natural syntax for building constraints:
61+
62+
```julia
63+
using Giac
64+
65+
@giac_var x y
66+
67+
# Build symbolic inequalities
68+
x > 0 # Returns a GiacExpr representing x>0
69+
x < y # Returns a GiacExpr representing x<y
70+
x >= 1//2 # Works with Rational
71+
x <= π # Works with Irrational
72+
```
73+
74+
Combine inequalities with `&` (and) and `|` (or):
75+
76+
```julia
77+
(x > 0) & (x < 10) # GIAC "and" expression
78+
(x < -1) | (x > 1) # GIAC "or" expression
79+
```
80+
81+
## Assumptions on Variables
82+
83+
Use `assume` and `additionally` to declare constraints on symbolic variables.
84+
Subsequent computations will respect these assumptions:
85+
86+
```julia
87+
using Giac
88+
using Giac.Commands: assume, about, sign, purge, additionally, sqrt
89+
90+
@giac_var x
91+
92+
# Declare x as positive
93+
assume(x > 0)
94+
sign(x) # Returns 1
95+
sqrt(x^2) # Returns x (simplified thanks to assumption)
96+
97+
# Interval constraint using &
98+
assume((x > 0) & (x < 10))
99+
about(x) # Shows interval [0, 10]
100+
101+
# Or use assume + additionally
102+
assume(x > -1)
103+
additionally(x < 1)
104+
about(x) # Shows interval [-1, 1]
105+
106+
# Clear assumptions
107+
purge(x)
108+
```
109+
110+
!!! note
111+
Julia's chained comparison syntax `0 < x < 10` does not work because Julia
112+
desugars it using `&&` (which cannot be overloaded). Use `(x > 0) & (x < 10)`
113+
or `assume` + `additionally` instead.

src/operators.jl

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,136 @@ function Base.hash(expr::GiacExpr, h::UInt)::UInt
154154
return hash(string(expr), h)
155155
end
156156

157+
# =============================================================================
158+
# Symbolic Comparison Operators (064-symbolic-comparison-operators)
159+
# =============================================================================
160+
# These operators return GiacExpr (symbolic inequalities), NOT Bool.
161+
# This enables natural syntax like: assume(x > 0), additionally(x < 10)
162+
# Note: isless is intentionally NOT overridden to preserve sort/min/max semantics.
163+
164+
"""
165+
<(a::GiacExpr, b::GiacExpr) -> GiacExpr
166+
167+
Create a symbolic less-than inequality from two GIAC expressions.
168+
169+
Returns a `GiacExpr` representing the inequality (not a `Bool`).
170+
171+
# Examples
172+
```julia
173+
@giac_var x y
174+
ineq = x < y # Creates symbolic inequality x<y
175+
assume(x < giac_eval("10")) # Use with assume
176+
```
177+
"""
178+
function Base.:<(a::GiacExpr, b::GiacExpr)::GiacExpr
179+
return giac_eval("$(string(a))<$(string(b))")
180+
end
181+
182+
"""
183+
>(a::GiacExpr, b::GiacExpr) -> GiacExpr
184+
185+
Create a symbolic greater-than inequality from two GIAC expressions.
186+
187+
Returns a `GiacExpr` representing the inequality (not a `Bool`).
188+
189+
# Examples
190+
```julia
191+
@giac_var x y
192+
ineq = x > y # Creates symbolic inequality x>y
193+
assume(x > giac_eval("0")) # Assume x is positive
194+
```
195+
"""
196+
function Base.:>(a::GiacExpr, b::GiacExpr)::GiacExpr
197+
return giac_eval("$(string(a))>$(string(b))")
198+
end
199+
200+
"""
201+
<=(a::GiacExpr, b::GiacExpr) -> GiacExpr
202+
203+
Create a symbolic less-than-or-equal inequality from two GIAC expressions.
204+
205+
Returns a `GiacExpr` representing the inequality (not a `Bool`).
206+
207+
# Examples
208+
```julia
209+
@giac_var x y
210+
ineq = x <= y # Creates symbolic inequality x<=y
211+
```
212+
"""
213+
function Base.:<=(a::GiacExpr, b::GiacExpr)::GiacExpr
214+
return giac_eval("$(string(a))<=$(string(b))")
215+
end
216+
217+
"""
218+
>=(a::GiacExpr, b::GiacExpr) -> GiacExpr
219+
220+
Create a symbolic greater-than-or-equal inequality from two GIAC expressions.
221+
222+
Returns a `GiacExpr` representing the inequality (not a `Bool`).
223+
224+
# Examples
225+
```julia
226+
@giac_var x y
227+
ineq = x >= y # Creates symbolic inequality x>=y
228+
```
229+
"""
230+
function Base.:>=(a::GiacExpr, b::GiacExpr)::GiacExpr
231+
return giac_eval("$(string(a))>=$(string(b))")
232+
end
233+
234+
# Mixed-type comparisons (GiacExpr with Julia numbers)
235+
Base.:<(a::GiacExpr, b::Number) = a < convert(GiacExpr, b)
236+
Base.:<(a::Number, b::GiacExpr) = convert(GiacExpr, a) < b
237+
238+
Base.:>(a::GiacExpr, b::Number) = a > convert(GiacExpr, b)
239+
Base.:>(a::Number, b::GiacExpr) = convert(GiacExpr, a) > b
240+
241+
Base.:<=(a::GiacExpr, b::Number) = a <= convert(GiacExpr, b)
242+
Base.:<=(a::Number, b::GiacExpr) = convert(GiacExpr, a) <= b
243+
244+
Base.:>=(a::GiacExpr, b::Number) = a >= convert(GiacExpr, b)
245+
Base.:>=(a::Number, b::GiacExpr) = convert(GiacExpr, a) >= b
246+
247+
# =============================================================================
248+
# Symbolic Logical Operators (064-symbolic-comparison-operators)
249+
# =============================================================================
250+
# These operators combine symbolic inequalities using GIAC's `and`/`or`.
251+
# Primary use case: assume((x > 0) & (x < 10)) for interval constraints.
252+
# Note: Julia's && and || cannot be overloaded (short-circuit operators),
253+
# so we use & and | (bitwise operators) instead.
254+
255+
"""
256+
&(a::GiacExpr, b::GiacExpr) -> GiacExpr
257+
258+
Combine two symbolic expressions with GIAC's logical `and`.
259+
260+
This enables interval constraints with `assume`:
261+
262+
# Examples
263+
```julia
264+
@giac_var x
265+
assume((x > 0) & (x < 10)) # x in (0, 10)
266+
```
267+
"""
268+
function Base.:&(a::GiacExpr, b::GiacExpr)::GiacExpr
269+
return giac_eval("($(string(a))) and ($(string(b)))")
270+
end
271+
272+
"""
273+
|(a::GiacExpr, b::GiacExpr) -> GiacExpr
274+
275+
Combine two symbolic expressions with GIAC's logical `or`.
276+
277+
# Examples
278+
```julia
279+
@giac_var x
280+
expr = (x < -1) | (x > 1) # x outside [-1, 1]
281+
```
282+
"""
283+
function Base.:|(a::GiacExpr, b::GiacExpr)::GiacExpr
284+
return giac_eval("($(string(a))) or ($(string(b)))")
285+
end
286+
157287
# =============================================================================
158288
# Matrix Operators
159289
# =============================================================================

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ using LinearAlgebra
5050
# Equation syntax tests (024-equation-syntax)
5151
include("test_equation_syntax.jl")
5252

53+
# Comparison operators tests (064-symbolic-comparison-operators)
54+
include("test_comparison_operators.jl")
55+
5356
# Tables.jl compatibility tests (025-tables-compatibility)
5457
include("test_tables.jl")
5558

test/test_comparison_operators.jl

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Tests for symbolic comparison operators (064-symbolic-comparison-operators)
2+
# This file tests <, >, <=, >= operators returning GiacExpr for symbolic inequalities.
3+
4+
using Test
5+
using Giac
6+
using Giac.Commands: assume, about, sign, purge, additionally
7+
8+
@testset "Comparison Operators (064-symbolic-comparison-operators)" begin
9+
10+
# ========================================================================
11+
# User Story 1: Build symbolic inequalities (Priority: P1) - MVP
12+
# ========================================================================
13+
14+
@testset "US1: Less-than operator (<) between GiacExpr" begin
15+
x = giac_eval("x")
16+
y = giac_eval("y")
17+
result = x < y
18+
@test result isa GiacExpr
19+
# GIAC may normalize x<y to y>x — check both representations
20+
s = string(result)
21+
@test occursin("<", s) || occursin(">", s)
22+
end
23+
24+
@testset "US1: Greater-than operator (>) between GiacExpr" begin
25+
x = giac_eval("x")
26+
y = giac_eval("y")
27+
result = x > y
28+
@test result isa GiacExpr
29+
@test occursin(">", string(result))
30+
end
31+
32+
@testset "US1: Less-than-or-equal operator (<=) between GiacExpr" begin
33+
x = giac_eval("x")
34+
y = giac_eval("y")
35+
result = x <= y
36+
@test result isa GiacExpr
37+
# GIAC may normalize x<=y to y>=x — check both representations
38+
s = string(result)
39+
@test occursin("<=", s) || occursin(">=", s)
40+
end
41+
42+
@testset "US1: Greater-than-or-equal operator (>=) between GiacExpr" begin
43+
x = giac_eval("x")
44+
y = giac_eval("y")
45+
result = x >= y
46+
@test result isa GiacExpr
47+
@test occursin(">=", string(result))
48+
end
49+
50+
@testset "US1: Regression — existing operators unaffected" begin
51+
x = giac_eval("x")
52+
y = giac_eval("y")
53+
54+
# == still returns Bool
55+
@test (x == x) isa Bool
56+
@test x == x
57+
@test !(x == y)
58+
59+
# ~ still returns GiacExpr
60+
@test (x ~ y) isa GiacExpr
61+
62+
# hash still works for Dict/Set
63+
d = Dict(x => 1, y => 2)
64+
@test d[x] == 1
65+
@test d[y] == 2
66+
s = Set([x, y])
67+
@test length(s) == 2
68+
end
69+
70+
# ========================================================================
71+
# User Story 2: Use with assume and additionally (Priority: P1)
72+
# ========================================================================
73+
74+
@testset "US2: assume(x > 0) sets assumption" begin
75+
x = giac_eval("x")
76+
assume(x > 0)
77+
@test sign(x) == giac_eval("1")
78+
about_str = string(about(x))
79+
@test occursin("assume", about_str)
80+
purge(x)
81+
end
82+
83+
@testset "US2: purge clears assumption" begin
84+
x = giac_eval("x")
85+
assume(x > 0)
86+
purge(x)
87+
@test string(about(x)) == "x"
88+
end
89+
90+
@testset "US2: assume + additionally for interval constraints" begin
91+
x = giac_eval("x")
92+
assume(x > giac_eval("-1"))
93+
additionally(x < giac_eval("1"))
94+
about_str = string(about(x))
95+
@test occursin("assume", about_str)
96+
purge(x)
97+
end
98+
99+
# ========================================================================
100+
# User Story 3: Mixed-type comparisons (Priority: P2)
101+
# ========================================================================
102+
103+
@testset "US3: GiacExpr vs Int" begin
104+
x = giac_eval("x")
105+
@test (x > 0) isa GiacExpr
106+
@test (x < 0) isa GiacExpr
107+
@test (x >= 1) isa GiacExpr
108+
@test (x <= -1) isa GiacExpr
109+
end
110+
111+
@testset "US3: Int vs GiacExpr" begin
112+
x = giac_eval("x")
113+
@test (0 < x) isa GiacExpr
114+
@test (0 > x) isa GiacExpr
115+
@test (1 <= x) isa GiacExpr
116+
@test (-1 >= x) isa GiacExpr
117+
end
118+
119+
@testset "US3: GiacExpr vs Float64" begin
120+
x = giac_eval("x")
121+
@test (x > 1.5) isa GiacExpr
122+
@test (x < 1.5) isa GiacExpr
123+
end
124+
125+
@testset "US3: GiacExpr vs Rational" begin
126+
x = giac_eval("x")
127+
@test (x > 1//2) isa GiacExpr
128+
@test (x >= 1//3) isa GiacExpr
129+
end
130+
131+
@testset "US3: GiacExpr vs Irrational" begin
132+
x = giac_eval("x")
133+
@test (x > π) isa GiacExpr
134+
@test (x <= ℯ) isa GiacExpr
135+
end
136+
137+
@testset "US3: Edge cases" begin
138+
x = giac_eval("x")
139+
140+
# Self-comparison returns GiacExpr (not Bool)
141+
result = x > x
142+
@test result isa GiacExpr
143+
144+
# Infinity comparisons
145+
@test (x > Inf) isa GiacExpr
146+
@test (x < -Inf) isa GiacExpr
147+
end
148+
149+
# ========================================================================
150+
# Logical operators & and | for combining inequalities
151+
# ========================================================================
152+
153+
@testset "Logical AND (&) between GiacExpr" begin
154+
x = giac_eval("x")
155+
result = (x > 0) & (x < 10)
156+
@test result isa GiacExpr
157+
s = string(result)
158+
@test occursin("and", s)
159+
end
160+
161+
@testset "Logical OR (|) between GiacExpr" begin
162+
x = giac_eval("x")
163+
result = (x < -1) | (x > 1)
164+
@test result isa GiacExpr
165+
s = string(result)
166+
@test occursin("or", s)
167+
end
168+
169+
@testset "assume with interval via & operator" begin
170+
x = giac_eval("x")
171+
assume((x > 0) & (x < 10))
172+
@test sign(x) == giac_eval("1")
173+
about_str = string(about(x))
174+
@test occursin("assume", about_str)
175+
purge(x)
176+
end
177+
178+
end

0 commit comments

Comments
 (0)