R randomforest package to Base SAS - r

Is there a way to take a randomforest built in R and convert it to SAS Code with out having to type out all the if then elses that getTree gives?
I had 30 trees that had 1900 lines in the getTree function

This is something I had lying around that should help get you started. So far only regression is supported, but classification should be do-able with a bit of extra work:
/* R code for exporting the randomForest object */
#Output dataset to csv for validation in SAS
write.csv(iris,file="C:/temp/iris.csv",row.names=FALSE)
#Train a 2-tree random forest for testing purposes
require(randomForest)
rf2 <- randomForest(iris[,-1],iris[,1],ntree=2)
# Get predictions and write to csv
write.csv(predict(rf2,iris),file="c:/temp/pred_rf2b.csv")
# Export factor levels
mydata <- iris
type <- sapply(mydata,class)
factors = type[type=="factor"]
output <- lapply(names(factors),function(x){
res <- data.frame(VarName=x,
Level=levels(mydata[,x]),
Number=1:nlevels(mydata[,x]))
return(res)
})
write.csv(do.call(rbind, output),file="c:/temp/factorlevels.csv", row.names=FALSE)
# Export all trees in one file
treeoutput <- lapply(1:rf2$ntree,function(x){
res <- getTree(rf2, x, labelVar=TRUE)
res$node <- seq.int(nrow(res))
res$treenum <- x
return(res)
})
write.csv(do.call(rbind, treeoutput),file="c:/temp/treeexport.csv", row.names=FALSE)
/*End of R code*/
/*Import into SAS, replacing . with _ so we have usable variable names*/
proc import
datafile = "c:\temp\treeexport.csv"
out = tree
dbms = csv
replace;
getnames = yes;
run;
data tree;
set tree;
SPLIT_VAR = translate(SPLIT_VAR,'_','.');
format SPLIT_POINT 8.3;
run;
proc import
datafile = "c:\temp\factorlevels.csv"
out = factorlevels
dbms = csv
replace;
getnames = yes;
run;
data _null_;
infile "c:\temp\iris.csv";
file "c:\temp\iris2.csv";
input;
if _n_ = 1 then _infile_=translate(_infile_,'_','.');
put _infile_;
run;
proc import
datafile = "c:\temp\iris2.csv"
out = iris
dbms = csv
replace;
getnames = yes;
run;
data _null_;
debug = 0;
type = "regression";
maxiterations = 10000;
file log;
if 0 then set tree factorlevels;
/*Hash to hold the whole tree*/
declare hash t(dataset:'tree');
rc = t.definekey('treenum');
rc = t.definekey('node');
rc = t.definedata(all:'yes');
rc = t.definedone();
/*Hash for looking up factor levels*/
declare hash fl(dataset:'factorlevels');
rc = fl.definekey('VARNAME','NUMBER');
rc = fl.definedata('LEVEL');
rc = fl.definedone();
do treenum = 1 by 1 while(t.find(key:treenum,key:1)=0);
/*Hash to hold the queue for current tree*/
length position qnode processed 8;
declare hash q(ordered:'a');
rc = q.definekey('position');
rc = q.definedata('qnode','position','processed');
rc = q.definedone();
declare hiter qi('q');
/*Hash for reverse queue lookup*/
declare hash q2();
rc = q2.definekey('qnode');
rc = q2.definedata('position');
rc = q2.definedone();
/*Load the starting node for the current tree*/
node = 1;
nodetype = 'L'; /*Track whether current node is a Left or Right node*/
complete = 0;
length treename $10;
treename = cats('tree',treenum);
do iteration = 1 by 1 while(complete = 0 and iteration <= maxiterations);
rc = t.find();
if debug then put "Processing node " node;
/*Logic for terminal nodes*/
if status = -1 then do;
if type ne "regression" then prediction = cats('"',prediction,'"');
put treename '=' prediction ';';
/*If current node is a right node, remove it from the queue*/
if nodetype = 'R' then do;
rc = q2.find();
if debug then put "Unqueueing node " qnode "in position " position;
processed = 1;
rc = q.replace();
end;
/*If the queue is empty, we are done*/
rc = qi.last();
do while(rc = 0 and processed = 1);
if position = 1 then complete = 1;
rc = qi.prev();
end;
/*Otherwise, process the most recently queued unprocessed node*/
if complete = 0 then do;
put "else ";
node = qnode;
nodetype = 'R';
end;
end;
/*Logic for split nodes - status ne -1*/
else do;
/*Add right_daughter to queue if present*/
position = q.num_items + 1;
qnode = right_daughter;
processed = 0;
rc = q.add();
rc = q2.add();
if debug then put "Queueing node " qnode "in position " position;
/*Check whether current split var is a (categorical) factor*/
rc = fl.find(key:split_var,key:1);
/*If yes, factor levels corresponding to 1s in the binary representation of the split point go left*/
if rc = 0 then do;
/*Get binary representation of split point (least significant bit first)*/
/*binaryw. format behaves very differently above width 58 - only 58 levels per factor supported here*/
/*This is sufficient as the R randomForest package only supports 53 levels per factor anyway*/
binarysplit = reverse(put(split_point,binary58.));
put 'if ' #;
j=0; /*Track how many levels have been encountered for this split var*/
do i = 1 to 64 while(rc = 0);
if i > 1 then rc = fl.find(key:split_var,key:i);
LEVEL = cats('"',LEVEL,'"');
if debug then put _all_;
if substr(binarysplit,i,1) = '1' then do;
if j > 0 then put ' or ' #;
put split_var ' = ' LEVEL #;
j + 1;
end;
end;
put 'then';
end;
/*If not, anything < split point goes to left child*/
else put "if " split_var "< " split_point 8.3 " then ";
if nodetype = 'R' then do;
qnode = node;
rc = q2.find();
if debug then put "Unqueueing node " qnode "in position " position;
processed = 1;
rc = q.replace();
end;
node = left_daughter;
nodetype = 'L';
end;
end;
/*End of tree function definition!*/
put ';';
/*Clear the queue between trees*/
rc = q.delete();
rc = q2.delete();
end;
/*We end up going 1 past the actual number of trees after the end of the do loop*/
treenum = treenum - 1;
if type = "regression" then do;
put 'RFprediction=(';
do i = 1 to treenum;
treename = cats('tree',i);
put treename +1 #;
if i < treenum then put '+' +1 #;
end;
put ')/' treenum ';';
end;
/*To do - write code to aggregate predictions from multiple trees for classification*/
stop;
run;
/*Sample of generated if-then-else code */
data predictions;
set iris;
if Petal_Length < 4.150 then
if Petal_Width < 1.050 then
if Petal_Width < 0.350 then
tree1 =4.91702127659574 ;
else
if Petal_Width < 0.450 then
tree1 =5.18333333333333 ;
else
if Species = "versicolor" then
tree1 =5.08888888888889 ;
else
tree1 =5.1 ;
else
if Sepal_Width < 2.550 then
tree1 =5.525 ;
else
if Petal_Length < 4.050 then
tree1 =5.8 ;
else
tree1 =5.63333333333333 ;
else
if Petal_Width < 1.950 then
if Sepal_Width < 3.050 then
if Species = "setosa" or Species = "virginica" then
if Petal_Length < 5.700 then
tree1 =6.05833333333333 ;
else
tree1 =7.2 ;
else
tree1 =6.176 ;
else
if Sepal_Width < 3.250 then
if Sepal_Width < 3.150 then
tree1 =6.62 ;
else
tree1 =6.66666666666667 ;
else
tree1 =6.3 ;
else
if Petal_Length < 6.050 then
if Petal_Width < 2.050 then
tree1 =6.275 ;
else
tree1 =6.65 ;
else
if Petal_Length < 6.550 then
tree1 =7.76666666666667 ;
else
tree1 =7.7 ;
;
if Petal_Width < 1.150 then
if Species = "setosa" then
tree2 =5.08947368421053 ;
else
tree2 =5.55714285714286 ;
else
if Species = "setosa" or Species = "versicolor" then
if Sepal_Width < 2.750 then
if Petal_Length < 4.450 then
tree2 =5.44 ;
else
tree2 =6.06666666666667 ;
else
if Petal_Width < 1.350 then
tree2 =5.85294117647059 ;
else
if Petal_Width < 1.750 then
if Petal_Width < 1.650 then
tree2 =6.3625 ;
else
tree2 =6.7 ;
else
tree2 =5.9 ;
else
if Petal_Length < 5.850 then
if Sepal_Width < 2.650 then
if Petal_Length < 4.750 then
tree2 =4.9 ;
else
if Sepal_Width < 2.350 then
tree2 =6 ;
else
if Sepal_Width < 2.550 then
tree2 =6.14 ;
else
tree2 =6.1 ;
else
tree2 =6.49166666666667 ;
else
if Petal_Length < 6.350 then
tree2 =7.125 ;
else
tree2 =7.775 ;
;
RFprediction=(
tree1 + tree2 )/2 ;
run;

Related

How to compute the histogram of a 2d list in BSV?

How would I create a module that will accept a 2d array of integers and return only the items above a certain count?
interface TestIfc;
method Action putInput(??? _input);
method ActionValue#(???) getOutput;
endinterface
module mkTest (TestIfc);
Vector#(PE_N, FIFO#(Vector#(Max_itemN, Item))) inputQ <- replicateM(mkFIFO);
Vector#(PE_N, FIFO#(Vector#(Max_itemN, Item))) accQ <- replicateM(mkFIFO);
FIFO#(Vector#(Max_itemN, Item)) mergeQ <- mkFIFO;
FIFO#(Tuple2#(Vector#(Max_itemN, Item), Bit#(32))) outputQ <- mkFIFO;
rule count;
let row1 <- input???.first; input???.deq;
Vector#(???, item) sum = replicate(0);
for(Bit#(8) j = 0; j < ???; j = j + 1)
begin
let n <- row1[j];
sum[n] <= sum[n] + 1;
end
accumulator.enq(sum);
endrule
rule filter;
accumulator.deq;
let acc_t = accumulator.first;
Int cnt = 0;
Vector#(???, Item) result = replicate(0);
for(Bit#(8) i = 0; i < ???; i = i + 1)
begin
if (acc_t[i] > SOMEBUMBER)
begin
result[cnt] = i;
cnt = cnt + 1;
end
end
endrule
...
I'm thinking of maybe splitting my input data vertically or horizontally to do more work efficiently, but right now I'm not confident in creating even a simple version and I'd like some help.

A potential bug in the induction strategy of Frama-C 24.0

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.

(Godot Engine) Compute string as PEMDAS

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)

How to pass a complex lambda function to gremlin server by using Java API?

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 written a path tracer using julia programming language but i think it is slow

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.

Resources