1
0
mirror of https://github.com/metrostroi-repo/MetrostroiAddon.git synced 2026-05-02 00:42:29 +00:00
Files
MetrostroiAddon/lua/metrostroi/sh_failsim.lua
g_brzhezinskiy 1d05caf866 init
2021-01-02 12:51:45 +03:00

389 lines
13 KiB
Lua

local random = math.random
local sqrt = math.sqrt
local ln = math.log
local cos = math.cos
local sin = math.sin
local pi2 = 2*math.pi
--------------------------------------------------------------------------------
-- Generate random number with at least 60 bits of randomness
-- (on my machine random() was 15-bit)
--------------------------------------------------------------------------------
local function rand60()
return random() + random()/(2^15) + random()/(2^30) + random()/(2^45)
end
--------------------------------------------------------------------------------
-- Generate random gaussian-distributed value
--------------------------------------------------------------------------------
local function gauss_random(x0,sigma)
local u,v = rand60(),rand60()
if u == 0.0 then return gauss_random(x0,sigma) end -- Remove singularity
if u > 1 then u = 1 end --gauss_random returns nan, because rand60() can return 1.00001
local r = sqrt(-2 * ln(u))
local x,y = r * cos(pi2*v)
return x*(sigma or 0.5) + (x0 or 0)
end
--------------------------------------------------------------------------------
-- Generates random failure time given MTBF in units of time
-- Returns true if device has failued, given MTBF and timestep
--------------------------------------------------------------------------------
local function check_failure(mtbf,dt)
local probability = dt/mtbf
return rand60() < probability
end
--------------------------------------------------------------------------------
-- Failure simulator
--------------------------------------------------------------------------------
FailSim = {}
FailSim.Objects = {}
--------------------------------------------------------------------------------
-- Parameter description
-- nominal_value Nominal value of the parameter
-- value (same)
-- precision Precision of real value vs nominal value
-- instance_precision Precision of a single event vs real value
-- varies If true, "Value()" returns a slightly varying result each time
-- min
-- max Parameter will be clamped to these
--------------------------------------------------------------------------------
function FailSim.AddParameter(object, name, a, b)
if not FailSim.Objects[object] then
FailSim.Objects[object] = { Parameters = {}, FailurePoints = {}, Age = 0.0, Stress = 1.0 }
end
-- Create description
local d = a
if type(d) ~= "table" then
d = {
nominal_value = a,
precision = b
}
end
d.nominal_value = d.nominal_value or d.value or 0 -- Nominal value of the parameter
d.precision = d.precision or 0.05 -- Precision with which parameter is defined
d.instance_precision = d.instance_precision or d.precision -- Precision of every 'event' this parameter defines
-- Add new parameter
local parameter = {
object = object,
name = name,
failures = {}, -- List of failures for this parameter
}
for k,v in pairs(d) do parameter[k] = v end
-- Calculate current (initial) value of the parameter
parameter.value = gauss_random(d.nominal_value,d.nominal_value*d.precision)
parameter.start_value = parameter.value
-- Store it
FailSim.Objects[object].Parameters[name] = parameter
return parameter.value
end
--------------------------------------------------------------------------------
-- Failure types
--
-- age_drift (drift of parameter with time)
-- const value Average drift over mean time period
-- const mean_time Period of drift
--
--------------------------------------------------------------------------------
-- (shared parameters)
-- const mean_time Mean time between failures of this kind
--
-- on (fails to 1.0)
-- off (fails to 0.0)
-- value (parameter fails to specific value)
-- const value Value to which it fails
--
-- precision (loss of precision)
-- const value How much tolerance must be added
-- const precision How well tolerance is defined
--
-- shift (change in value)
-- const value How much must be added/subtracted
-- const precision Precision of 'value'
--
--
--------------------------------------------------------------------------------
function FailSim.AddFailurePoint(object, param_name, failure_name, d)
if not FailSim.Objects[object] then
FailSim.Objects[object] = { Parameters = {}, FailurePoints = {}, Age = 0.0 }
end
-- Create description
d.value = d.value or 0 -- Some failure parameter
d.precision = d.precision or 0.10 -- Precision of 'value'
d.mean_time = d.mean_time or d.mtbf or
(1/d.mfr) or (1/d.mean_fail_rate) or 1e9 -- Mean time to failure, seconds
d.time_precision = d.time_precision or d.dmtbf or 0.25 -- Precision of Mtime
d.duration_precision = d.duration_precision or 0.50 -- Precision of duration
-- Add new failure point
local failure = {
object = object,
parameter_name = param_name, -- Name of failing parameter
name = failure_name, -- Name of failure
}
for k,v in pairs(d) do failure[k] = v end
-- Special logic
if failure.type == "on" then
failure.type = "value"
failure.value = 1.0
elseif failure.type == "off" then
failure.type = "value"
failure.value = 0.0
end
-- Add to list of failures
table.insert(FailSim.Objects[object].FailurePoints,failure)
-- Return failure description
return failure
end
--------------------------------------------------------------------------------
-- Process failures and add object age
--------------------------------------------------------------------------------
local function doFailure(parameter,v)
if v.type == "value" then
parameter.oldvalue = parameter.value
parameter.value = v.value
parameter.failures[v] = v
elseif v.type == "precision" then
parameter.oldinstance_precision = parameter.instance_precision
parameter.instance_precision = parameter.instance_precision +
math.abs(gauss_random(v.value,v.value*v.precision))
parameter.failures[v] = v
elseif v.type == "shift" then
parameter.oldvalue = parameter.value
parameter.value = parameter.value +
math.abs(gauss_random(v.value,v.value*v.precision))
parameter.failures[v] = v
end
--[[if v.duration then
parameter.failure_end_age = object.Age +
gauss_random(v.duration,v.duration*v.duration_precision)
end]]--
end
function FailSim.Age(object, delta_time)
local object = FailSim.Objects[object]
if not object then return end
-- Age the object by given value
object.Age = object.Age + delta_time
-- Check if any of the objects failure points must be triggered
for k,v in pairs(object.FailurePoints) do
local parameter = object.Parameters[v.parameter_name]
if parameter and ((not parameter.failures[v]) or (v.recurring)) then
-- Drift of parameters over time at certain speed
if v.type == "age_drift" then
local dxdt = v.value / failure.fail_age --gauss_random(v.value / v.mean_time,v.sigma)
parameter.value = parameter.value + dxdt*dt
end
-- Single-event failures
if check_failure(v.mean_time,delta_time) then
doFailure(parameter,v)
end
end
end
end
function FailSim.RandomFailure()
-- List all failures
local failureList = {}
for _,object in pairs(FailSim.Objects) do
for k,v in pairs(object.FailurePoints) do
table.insert(failureList,v)
end
end
-- Get random failure
local count = #failureList
local i = math.floor(rand60()*count)+1
if i > count then i = count end
if i < 1 then i = 1 end
local failure = failureList[i]
local parameter = FailSim.Objects[failure.object].Parameters[failure.parameter_name]
print("Generated random failure from total of "..count.." failures:")
print("[!FAIL!] "..failure.name.." in "..(failure.object.Name or "?"))
doFailure(parameter,failure)
end
--------------------------------------------------------------------------------
-- Return value of the parameter
--------------------------------------------------------------------------------
function FailSim.Value(object,name)
local cobject = FailSim.Objects[object]
if not cobject then return end
local parameter = cobject.Parameters[name]
if not parameter then return end
-- Generate value
local value
if parameter.varies then
local instance_sigma = parameter.value*parameter.instance_precision
value = gauss_random(parameter.value,instance_sigma)
else
value = parameter.value
end
-- Clamp it
if parameter.min then value = math.max(parameter.min,value) end
if parameter.max then value = math.min(parameter.max,value) end
-- Return
if value~=value then
print("WARNING!!!",object.Name,name,value)
end
return value
end
function FailSim.ResetParameter(object,name,value)
local object = FailSim.Objects[object]
if not object then return end
local parameter = object.Parameters[name]
if not parameter then return end
parameter.failures = {}
parameter.value = value or
gauss_random(parameter.nominal_value,parameter.nominal_value*parameter.precision)
end
--------------------------------------------------------------------------------
-- Return failures report for object
--------------------------------------------------------------------------------
function FailSim.Report(obj,type)
if not obj then
print("Listing all failures:")
for k,v in pairs(FailSim.Objects) do
FailSim.Report(k,type)
end
print("...done!")
return
end
local object = FailSim.Objects[obj]
if not object then return end
-- Table of failures
if type == "parameters" then
for k,v in pairs(object.Parameters) do
print((obj.Name or "Unknown object")..": "..k)
end
end
if type == "failure_points" then
for k,v in pairs(object.FailurePoints) do
print((obj.Name or "Unknown object")..": "..v.name.." ("..v.parameter_name..")")
end
end
if type == "failures" then
local any = false
for k,v in pairs(object.Parameters) do
for k2,v2 in pairs(v.failures) do
print((obj.Name or "Unknown object")..": "..v2.name)
any = true
end
end
end
end
function FailSim.Reset()
for k,v in pairs(FailSim.Objects) do
for k2,v2 in pairs(v.Parameters) do
FailSim.ResetParameter(v,v2,0)
--if v2.oldvalue then v2.value = v2.oldvalue end
--if v2.oldinstance_precision then v2.instance_precision = v2.oldinstance_precision end
--v2.failures = {}
--[[print("!",k,v,k2,v2)]]
end
--[[for k2,v2 in pairs(v.FailurePoints) do
--FailSim.Objects.Parameters[k2] = 0
for k3,v3 in pairs(v2) do
print("#",k3,v3)
end
print("@",k,v,k2,v2)
end]]
--FailSim.ResetParameter(v,v.Name,v.Value)
end
--object.FailurePoints
end
--[[
Metrostroi = { DefineSystem = function() end }
math.randomseed(os.time())
TRAIN_SYSTEM = {}
dofile("sys_relay.lua")
RELAY = TRAIN_SYSTEM
RELAY:Initialize()
-- Start switching
local i = 0
local x = 0
for t = 0.0, 200000.0, 0.5 do
function CurTime() return t end
if (i % 8) == 0 then
if RELAY.Value ~= 0 then
print("RELAY WRONG VALUE AFTER",x,"CYCLES")
print(FailSim.Report(RELAY))
error()
end
RELAY:TriggerInput("Open",1.0)
x = x + 1
elseif (i % 8) == 2 then
if RELAY.Value ~= 1 then
print("RELAY FAILED TO OPEN AFTER",x,"CYCLES")
print(FailSim.Report(RELAY))
error()
end
elseif (i % 8) == 4 then
if RELAY.Value ~= 1 then
print("RELAY WRONG VALUE AFTER",x,"CYCLES")
print(FailSim.Report(RELAY))
error()
end
RELAY:TriggerInput("Close",1.0)
x = x + 1
elseif (i % 8) == 6 then
if RELAY.Value ~= 0 then
print("RELAY FAILED TO CLOSE AFTER",x,"CYCLES")
print(FailSim.Report(RELAY))
error()
end
end
RELAY:Think()
i = i + 1
end]]--