----------------------------------Simulated Annealing Optimiser------------------------------------- ------ Parameters to modify: initial_temperature = 100 bounds = {min = 10, max = 1200} -- only 1 dimension is actually used below dimensions = 1 -- number of iterations to do, the more the better steps = 30 ------ ------ ------ local sa = {} sa.dimensions = dimensions sa.steps = 10 sa.bounds = bounds sa.T = initial_temperature sa.candidate = {} sa.Tscale = initial_temperature sa.calctemp = function(step) sa.T = (1.0 - (step / sa.steps)) * sa.Tscale end sa.rand = function() local t = {} for i=1,sa.dimensions do t[i] = math.random() * (sa.bounds.max - sa.bounds.min) + sa.bounds.min end t.score = nil return t end sa.reset = function() math.randomseed(octane.util.getCurrentMillis()) T = initial_temperature sa.candidate = sa.rand() end sa.init = sa.reset -- calculates the probability of selecting a new candidate sa.P = function(c1, c2, temperature) -- metropolis probability local e = c1 local eprime = c2 if eprime < e then return 1.0 else return math.exp(-(eprime - e) / sa.T) end end -- calculates the energy of a particular state -- scores are saved in the same table sa.lastE = 0 sa.E = function(c) return c.score end sa.step = function() local newc = sa.rand() if sa.P(sa.E(sa.candidate), sa.E(newc), sa.T) > math.random() then sa.candidate = newc end end if not octane then -- Run outside of octane - can do some testing here. sa.reset() return end ---------------------------------------------------------------------------------------------------- local inputs, tex local MyGraphScript = {} function MyGraphScript.onInit(self, graph) local inputInfos = { {label = "input", type = octane.PT_RENDERTARGET} } local outputInfos = { { type = octane.PT_FLOAT, label = "outputvalue", fromNodeType = octane.NT_RENDERTARGET, fromPinId=octane.P_RENDERTARGET } } inputs = graph:setInputLinkers(inputInfos) local outputs = graph:setOutputLinkers(outputInfos) -- set up a node to give the graph some output value tex = octane.node.create{ type=octane.NT_FLOAT, name="float", graphOwner=graph } outputs[1]:connectTo("input", tex) end function MyGraphScript.onEvaluate(self,graph) sa.init() octane.render.reset() local rt = inputs[1]:getConnectedNode("input") local t = rt:getNodeInfo() if not rt then octane.gui.showDialog { title = "RT is nil", type = octane.gui.dialogType.BUTTON_DIALOG, text = "RT is nil" } return end for i=1,sa.steps do -- make new candidate local newc = sa.rand() -- apply newc - modify this to apply the value to something else rt:getConnectedNode("camera"):setPinValue(octane.P_FOCAL_LENGTH, newc[1], true) -- render local result = octane.render.start{ callback = nil, renderTargetNode = rt, maxRenderTime=1 } -- get and save score newc.score = 1 / (result.samplesSec / 1000000) -- see if we'll move on if sa.candidate.score == nil then sa.candidate = newc elseif sa.P(sa.E(sa.candidate), sa.E(newc), sa.T) > math.random() then sa.candidate = newc end -- update temperature sa.calctemp(i) end octane.gui.showDialog { title = "Done", type = octane.gui.dialogType.BUTTON_DIALOG, text = "Done. Best is " .. sa.candidate[1] .. " with score " .. 1 / sa.candidate.score } rt:getConnectedNode("camera"):setPinValue(octane.P_FOCAL_LENGTH, sa.candidate[1], true) return true end return MyGraphScript