I am working on the following proof and the invariant result_val is proved with an induction strategy on i using begin as the base case.
The sup case is trying to prove true which holds trivially using Frama-C 24.0. But when I switch to 25.0, it tries to prove a seemingly more complicated condition, which looks closer to a correct inductive inference because it did the weakest precondition computation explicitly.
However, all SMT solvers I tried cannot prove the condition generated by Frama-C 25.0.
I am a bit worried about the correctness of version 24.0's result because using true as the inductive proof goal seems to be unlikely. Can anyone hint to me at what happened? Is that a bug in 24.0 or just some difference in the implementation?
#include <stdbool.h>
#define SIZE 1000
bool data[SIZE] ;
/*#
logic integer count(integer begin, integer end)=
begin >= end ? 0 : (data[begin]==true) ? count(begin+1, end)+1 : count(begin+1, end);
*/
/*#
requires SIZE > begin >= 0;
requires SIZE >= end >= 0;
requires begin <= end;
assigns \nothing;
ensures \result == count(begin, end);
*/
unsigned int occurrences_of(int begin, int end)
{
unsigned int result = 0;
/*#
loop invariant i_bound: begin <= i <= end;
loop invariant result_bound: 0 <= result <= i-begin;
loop invariant result_val: result == count(begin, i);
loop assigns i, result;
loop variant end-i;
*/
for (unsigned int i = begin; i < end; ++i){
result += (data[i] == true) ? 1 : 0;
}
return result;
}
Below is the result from Frama-c 24.0
Proof:
Goal Invariant 'result_val' (preserved) (Induction: proved)
+ Goal Induction (Base) (proved)
+ Goal Induction (Induction (sup)) (proved)
+ Goal Induction (Induction (inf)) (proved)
Qed.
--------------------------------------------------------------------------------
Goal Induction (Induction (sup)):
Prove: true.
Below is the result from Frama-c 25.0
--------------------------------------------------------------------------------
Proof:
Goal Invariant 'result_val' (preserved) (Induction: pending)
+ Goal Induction (Base) (proved)
+ Goal Induction (Induction (sup)) (pending)
+ Goal Induction (Induction (inf)) (proved)
End.
--------------------------------------------------------------------------------
Goal Induction (Induction (sup)):
Let x_0 = to_uint32(end#L1).
Let x_1 = to_uint32(tmp#L12).
Let x_2 = data#L1[i#L6].
Let x_3 = result#L6.
Let x_4 = result#L13.
Let x_5 = to_uint32(1 + i#L6).
Assume {
Have: begin#L1 < i#L6.
Have: i#L6 <= end#L1.
Have: i#L6 < x_0.
Have: 0 <= x_3.
Have: x_5 <= end#L1.
Have: begin#L1 <= x_5.
Have: (begin#L1 + x_3) <= i#L6.
Have: (begin#L1 + x_4) <= x_5.
Have: is_uint32(i#L6).
Have: is_bool(x_2).
Have: is_uint32(x_3).
Have: if (x_2 = 1) then (tmp#L12 = 1) else (tmp#L12 = 0).
Have: forall i_0 : Z. let x_6 = L_count(data#L1, begin#L1, i_0) in
let x_7 = to_uint32(1 + i_0) in let x_8 = to_uint32(x_1 + x_6) in
let x_9 = data#L1[i_0] in ((i_0 <= end#L1) -> ((begin#L1 <= i_0) ->
((i_0 < i#L6) -> ((i_0 < x_0) -> ((0 <= x_6) -> ((x_7 <= end#L1) ->
((begin#L1 <= x_7) -> (((begin#L1 + x_6) <= i_0) ->
(((begin#L1 + x_8) <= x_7) -> (is_uint32(i_0) -> (is_bool(x_9) ->
(is_uint32(x_6) ->
((if (x_9 = 1) then (tmp#L12 = 1) else (tmp#L12 = 0)) ->
(L_count(data#L1, begin#L1, x_7) = x_8)))))))))))))).
[...]
Stmt { L6: }
Stmt { tmp = tmp_0; }
Stmt { L12: result = x_4; }
Stmt { L13: }
}
Prove: L_count(data#L1, begin#L1, x_5) = x_4.
Goal id: typed_occurrences_of_loop_invariant_result_val_preserved
Short id: occurrences_of_loop_invariant_result_val_preserved
--------------------------------------------------------------------------------
Prover Alt-Ergo 2.4.2: Timeout (Qed:52ms) (10s).
A bug on the typing of the induction tactic was indeed fixed between Frama-C 24 and 25 (https://git.frama-c.com/pub/frama-c/-/commit/6058453cce2715f7dcf9027767559f95fb3b1679). And the symptom was indeed that the tactic could generate ill-typed formulas with true instead of a term.
Proving this example in not that easy. For two main reasons:
the function and the definition work in the opposite directions,
the definition does not have an optimal expression for reasoning.
However, one can write a lemma function to solve the problem:
#include <stdbool.h>
#define SIZE 1000
bool data[SIZE] ;
/*#
logic integer count(integer begin, integer end)=
begin >= end ? 0 : ((data[begin]==true) ? count(begin+1, end)+1 : count(begin+1, end));
*/
/*# ghost
/# requires begin < end ;
assigns \nothing ;
ensures count(begin, end) == ((data[end-1]==true) ? count(begin, end-1)+1 : count(begin, end-1));
#/
void lemma(bool* d, int begin, int end){
/# loop invariant begin <= i < end ;
loop invariant count(i, end) == ((data[end-1]==true) ? count(i, end-1)+1 : count(i, end-1));
loop assigns i ;
loop variant i - begin ;
#/
for(int i = end-1 ; i > begin ; i--);
}
*/
/*#
requires SIZE > begin >= 0;
requires SIZE >= end >= 0;
requires begin <= end;
assigns \nothing;
ensures \result == count(begin, end);
*/
unsigned int occurrences_of(int begin, int end)
{
unsigned int result = 0;
/*#
loop invariant i_bound: begin <= i <= end;
loop invariant result_bound: 0 <= result <= i-begin;
loop invariant result_val: result == count(begin, i);
loop assigns i, result;
loop variant end-i;
*/
for (unsigned int i = begin; i < end; ++i){
result += (data[i] == true) ? 1 : 0;
//# ghost lemma(data, begin, i+1);
}
return result;
}
I'd suggest to use the following definition:
/*#
logic integer count(integer begin, integer end)=
begin >= end ? 0 : ((data[end-1]==true) ? 1 : 0) + count(begin, end-1);
*/
It works in the same direction as the function and avoids the duplication of the term count(begin, end-1) which makes reasoning easier.
I've been trying to create a function in GDScript to process and calculate a string using PEMDAS rules. Below is my try on the subject. It can so far only use the MDAS rules:
Is there a better way to achieve such a function?
func _ready() -> void:
### USE CASES ###
print(Compute_String("1+2*3+3=")) # Output = 10
print(Compute_String("1+2*3*3=")) # Output = 19
print(Compute_String("1*2*3+3=")) # Output = 9
print(Compute_String("1+2+3*3=")) # Output = 12
print(Compute_String("5*2+7-3/2=")) # Output = 15.5
print(Compute_String("9+5.5*2.25=")) # Output = 21.375
print(Compute_String("5*2+7-3/2")) # Output = 1.#QNAN (Missing equals)
print(Compute_String("5*2+7-/2=")) # Output = 1.#QNAN (Adjacent operators)
print(Compute_String("*2+7-3/2=")) # Output = 1.#QNAN (Begins with operator)
print(Compute_String("")) # Output = 1.#QNAN (Empty)
print(Compute_String("=")) # Output = 1.#QNAN (Considered as empty)
print(Compute_String("1 +2=")) # Output = 1.#QNAN (Contains space)
print(Compute_String("(1+2)*3=")) # Output = 1.#QNAN (Parentheses not supported)
func Compute_String(_string: String) -> float:
var _result: float = NAN
var _elements: Array = []
if not _string.empty() and _string[_string.length() - 1] == "=":
var _current_element: String = ""
for _count in _string.length():
if _string[_count].is_valid_float() or _string[_count] == ".": _current_element += _string[_count]
else:
if _string[_count - 1].is_valid_float() and (_string[_count + 1].is_valid_float() if _string[_count] != "=" else true):
_elements.append_array([_current_element,_string[_count]]) ; _current_element = ""
else: return NAN
if not _elements.empty():
_elements.resize(_elements.size() - 1)
while _get_operators_count(_elements) != 0:
var _id: Array = [0, 0.0, 0.0]
if "*" in _elements:
_id = _add_adjacent(_elements, "*") ; _remove_adjacent(_elements, _id[0]) ; _elements.insert(_id[0] - 1, _id[1] * _id[2])
elif "/" in _elements:
_id = _add_adjacent(_elements, "/") ; _remove_adjacent(_elements, _id[0]) ; _elements.insert(_id[0] - 1, _id[1] / _id[2])
elif "+" in _elements:
_id = _add_adjacent(_elements, "+") ; _remove_adjacent(_elements, _id[0]) ; _elements.insert(_id[0] - 1, _id[1] + _id[2])
elif "-" in _elements:
_id = _add_adjacent(_elements, "-") ; _remove_adjacent(_elements, _id[0]) ; _elements.insert(_id[0] - 1, _id[1] - _id[2])
else: return NAN
if _elements.size() == 1: _result = _elements[0]
return _result
func _get_operators_count(_elements: Array) -> int:
var _result: int = 0 ; for _element in _elements: if not str(_element).is_valid_float(): _result += 1 ; return _result
func _add_adjacent(_elements: Array, _operator) -> Array:
return [_elements.find(_operator), float(_elements[_elements.find(_operator) - 1]), float(_elements[_elements.find(_operator) + 1])]
func _remove_adjacent(_elements: Array, _operator_idx: int) -> void:
_elements.remove(_operator_idx + 1) ; _elements.remove(_operator_idx) ; _elements.remove(_operator_idx - 1)
I'm trying to traverse the graph with an interval. Each edge has a property("interval") storing the intervals. I'm using withSack to propagate the intersections of the intervals to the next step. If there's no intersection the traversal should stop.
For example:
V1 e1 V2 e2 V3 e3 V4
O----------------------O----------------------O----------------------O
^
[[1,3],[5,7]] [[4,6]] [[7,9]]
e1.interval e2.interval e3.interval
If I start traversal from V1 with interval [2,8], I want it to return
V1: [[2,3],[5,7]]
V2: [[5,6]]
Notice that V3 and V4 is not included since the intersected interval on e2 stops at 6.
I'm using Tinkerpop Java API and for this purpose, I defined a method that returns the intersections of the intervals and tried to use withSack(Lambda.biFunction(...)). The function has while loop with curly braces({}) and I think it causes a problem on the gremlin server's script engine. The exception I'm getting is this:
Script28.groovy: 1: expecting '}', found 'return' # line 1, column 520.
get(j).get(1)) i++; else j++;}return int
I'm passing the function as a string to (Lambda.biFunction(...)) like this:
"x, y -> " +
"List<List<Long>> intersections = new ArrayList();" +
"if (x.isEmpty() || y.isEmpty()) return new ArrayList<>();" +
"int i = 0, j = 0;" +
"while (i < x.size() && j < y.size()) {" +
"long low = Math.max(x.get(i).get(0), y.get(j).get(0));" +
"long high = Math.min(x.get(i).get(1), y.get(j).get(1));" +
"if (low <= high) intersections.add(Arrays.asList(low, high));" +
"if (x.get(i).get(1) < y.get(j).get(1)) i++; else j++;" +
"}" +
"return intersections;";
For the readability I'm also putting the original function:
public List<List<Long>> intersections(List<List<Long>> x, List<List<Long>> y) {
List<List<Long>> intersections = new ArrayList();
if (x.isEmpty() || y.isEmpty()) {
return new ArrayList<>();
}
int i = 0, j = 0;
while (i < x.size() && j < y.size()) {
long low = Math.max(x.get(i).get(0), y.get(j).get(0));
long high = Math.min(x.get(i).get(1), y.get(j).get(1));
if (low <= high) {
intersections.add(Arrays.asList(low, high));
}
if (x.get(i).get(1) < y.get(j).get(1)) {
i++;
} else {
j++;
}
}
return intersections;
}
I have 2 questions:
How to pass a complex lambda function like this to gremlin server?
Is there a better way to accomplish this?
The string of your lambda needs to match a Groovy closure form. For your multiline and multi-argument script you need to wrap some curly braces around it:
withSack(Lambda.biFunction(
"{ x, y -> " +
" intersections = []\n" +
" if (x.isEmpty() || y.isEmpty()) return []\n" +
" i = 0\n" +
" j = 0\n" +
" while (i < x.size() && j < y.size()) {\n" +
" def low = Math.max(x[i][0], y[j][0])\n" +
" def high = Math.min(x[i][1], y[j][1])\n" +
" if (low <= high) intersections.add(Arrays.asList(low, high))\n" +
" if (x[i][1] < y[j][1]) i++; else j++\n" +
" }\n" +
" return intersections\n" +
"}"))
I also converted your Java to Groovy (hopefully correctly) which ends up being a little more succinct but that part should be unnecessary.
I have changed my post and posted the whole of my code! Could someone tell me how can I optimize it?
import Base: *, +, -, /, ^
using Images
const Π = convert(Float64, π)
#define vector
mutable struct Vec3
x::Float64
y::Float64
z::Float64
end
function +(u::Vec3, v::Vec3)
Vec3(u.x+v.x, u.y+v.y, u.z+v.z)
end
function -(u::Vec3, v::Vec3)
Vec3(u.x-v.x, u.y-v.y, u.z-v.z)
end
function /(u::Vec3, v::Float64)
Vec3(u.x/v, u.y/v, u.z/v)
end
function *(u, v::Vec3)
if typeof(u) == Float64
Vec3(u*v.x, u*v.y, u*v.z)
elseif typeof(u) == Vec3
Vec3(u.x*v.x, u.y*v.y, u.z*v.z)
end
end
function ^(u::Vec3, v::Float64)
Vec3(u.x^v, u.y^v, u.z^v)
end
function dot(u::Vec3, v::Vec3)
u.x*v.x + u.y*v.y + u.z*v.z
end
function normalize(u::Vec3)
u/sqrt(dot(u,u))
end
function cross(u::Vec3, v::Vec3)
Vec3(u.y*v.z - v.y*u.z, u.z*v.x - v.z*u.x, u.x*v.y - v.x*u.y)
end
function gamma(u::Vec3)
Vec3(u.x^(1/2.2), u.y^(1/2.2), u.z^(1/2.2))
end
function clamp(u::Vec3)
u.x = u.x <= 1 ? u.x : 1
u.y = u.y <= 1 ? u.y : 1
u.z = u.z <= 1 ? u.z : 1
u
end
#define ray
struct Ray
s::Vec3
d::Vec3
end
#define planes
struct xyRect
z; x1; x2; y1; y2::Float64
normal; emittance; reflectance::Vec3
isLight::Bool
end
struct xzRect
y; x1; x2; z1; z2::Float64
normal; emittance; reflectance::Vec3
isLight::Bool
end
struct yzRect
x; y1; y2; z1; z2::Float64
normal; emittance; reflectance::Vec3
isLight::Bool
end
#define sphere
mutable struct Sphere
radius::Float64
center; normal; emittance; reflectance::Vec3
isLight::Bool
end
#define empty object
struct Empty
normal; emittance; reflectance::Vec3
end
#define surfaces
Surfaces = Union{xyRect, xzRect, yzRect, Sphere}
#define intersection function
function intersect(surface::Surfaces, ray::Ray)
if typeof(surface) == xyRect
t = (surface.z - ray.s.z)/ray.d.z
if surface.x1 < ray.s.x + t*ray.d.x < surface.x2 && surface.y1 < ray.s.y + t*ray.d.y < surface.y2 && t > 0
t
else
Inf
end
elseif typeof(surface) == xzRect
t = (surface.y - ray.s.y)/ray.d.y
if surface.x1 < ray.s.x + t*ray.d.x < surface.x2 && surface.z1 < ray.s.z + t*ray.d.z < surface.z2 && t > 0
t
else
Inf
end
elseif typeof(surface) == yzRect
t = (surface.x - ray.s.x)/ray.d.x
if surface.y1 < ray.s.y + t*ray.d.y < surface.y2 && surface.z1 < ray.s.z + t*ray.d.z < surface.z2 && t > 0
t
else
Inf
end
elseif typeof(surface) == Sphere
a = dot(ray.d, ray.d)
b = 2dot(ray.d, ray.s - surface.center)
c = dot(ray.s - surface.center, ray.s - surface.center) - surface.radius*surface.radius
Δ = b*b - 4*a*c
if Δ > 0
Δ = sqrt(Δ)
t1 = 0.5(-b-Δ)/a
t2 = 0.5(-b+Δ)/a
if t1 > 0
surface.normal = normalize(ray.s + t1*ray.d - surface.center)
t1
elseif t2 > 0
surface.normal = normalize(ray.s + t2*ray.d - surface.center)
t2
else
Inf
end
else
Inf
end
end
end
#define nearest function
function nearest(surfaces::Array{Surfaces, 1}, ray::Ray, tMin::Float64)
hitSurface = Empty(Vec3(0,0,0), Vec3(0,0,0), Vec3(0,0,0))
for surface in surfaces
t = intersect(surface, ray)
if t < tMin
tMin = t
hitSurface = surface
end
end
tMin, hitSurface
end
#cosine weighted sampling of hemisphere
function hemiRand(n::Vec3)
ξ1 = rand()
ξ2 = rand()
x = cos(2π*ξ2)*sqrt(ξ1)
y = sin(2π*ξ2)*sqrt(ξ1)
z = sqrt(1-ξ1)
r = normalize(Vec3(2rand()-1, 2rand()-1, 2rand()-1))
b = cross(n,r)
t = cross(n,b)
Vec3(x*t.x + y*b.x + z*n.x, x*t.y + y*b.y + z*n.y, x*t.z + y*b.z + z*n.z)
end
#trace the path
function trace(surfaces::Array{Surfaces, 1}, ray::Ray, depth::Int64, maxDepth::Int64)
if depth >= maxDepth
return Vec3(0,0,0)
end
t, material = nearest(surfaces, ray, Inf)
if typeof(material) == Empty
return Vec3(0,0,0)
end
if material.isLight == true
return material.emittance
end
ρ = material.reflectance
BRDF = ρ/Π
n = material.normal
R = hemiRand(n)
In = trace(surfaces, Ray(ray.s + t*ray.d, R), depth+1, maxDepth)
return Π*BRDF*In
end
#define camera
struct Camera
eye; v_up; N::Vec3
fov; aspect; distance::Float64
end
#render function
function render(surfaces::Array{Surfaces,1},camera::Camera,xRes::Int64,yRes::Int64,numSamples::Int64,maxDepth::Int64)
n = normalize(camera.N)
e = camera.eye
c = e - camera.distance*n
θ = camera.fov*(π/180)
H = 2*camera.distance*tan(θ/2)
W = H*camera.aspect
u = normalize(cross(camera.v_up,n))
v = cross(n,u)
img = zeros(3, xRes, yRes)
pixHeight = H/yRes
pixWidth = W/xRes
L = c - 0.5*W*u - 0.5*H*v
for i=1:xRes
for j=1:yRes
cl = Vec3(0,0,0)
for s=1:numSamples
pt = L + (i-rand())*pixWidth*u + (yRes-j+rand())*pixHeight*v
cl = cl + trace(surfaces, Ray(e, pt-e), 0, maxDepth)
end
cl = gamma(clamp(cl/convert(Float64, numSamples)))
img[:,j,i] = [cl.x, cl.y, cl.z]
end
end
img
end
#the scene
p1 = xzRect(1.,0.,1.,-1.,0.,Vec3(0,-1,0),Vec3(0,0,0),Vec3(0.75,0.75,0.75),false)
p2 = xzRect(0.,0.,1.,-1.,0.,Vec3(0,1,0),Vec3(0,0,0),Vec3(0.75,0.75,0.75),false)
p3 = xyRect(-1.,0.,1.,0.,1.,Vec3(0,0,1),Vec3(0,0,0),Vec3(0.75,0.75,0.75),false)
p4 = yzRect(0.,0.,1.,-1.,0.,Vec3(1,0,0),Vec3(0,0,0),Vec3(0.75,0.25,0.25),false)
p5 = yzRect(1.,0.,1.,-1.,0.,Vec3(-1,0,0),Vec3(0,0,0),Vec3(0.25,0.25,0.75),false)
p6 = xzRect(0.999,0.35,0.65,-0.65,-0.35,Vec3(0,-1,0),Vec3(18,18,18),Vec3(0,0,0),true)
s1 = Sphere(0.15,Vec3(0.3,0.15,-0.6),Vec3(0,0,0),Vec3(0,0,0),Vec3(0.75,0.75,0.75),false)
surfs = Surfaces[p1,p2,p3,p4,p5,p6,s1]
cam = Camera(Vec3(0.5,0.5,2),Vec3(0,1,0),Vec3(0,0,1),28.07,1,2)
#time image = render(surfs, cam, 400, 400, 1, 4);
colorview(RGB, image)
I need to know why my code is bad and slow. I am a beginner programmer and I don't have enough experience. My path tracer scene contains 7 objects, its maximum depth is 4, and it takes more than 2 seconds to generate an image of size 400*400. I think It shouldn't be that slow because my cpu is core i7 4770.
Sorry for changing my post.
To start with,
struct yzRect
x; y1; y2; z1; z2::Float64
normal; emittance; reflectance::Vec3
isLight::Bool
end
ends up only applying the type to the last variable for each line:
julia> fieldtypes(yzRect)
(Any, Any, Any, Any, Float64, Any, Any, Vec3, Bool)
so Julia will basically not know about any types in your structs which slows things down.
Also, your Vec3 should really be immutable and you then just create new instances of it when you want to "modify" the Vector.
There might be many more issues but those are two standing out.
Reading through https://docs.julialang.org/en/v1/manual/performance-tips/index.html and applying the guidelines in there is strongly recommended when analyzing performance.