--
-- ########################
-- # SMN core implementation
-- ########################
-- John Ringland 2005/08/27

include PNData.e -- includes FDData.e and SOME_Utilities.e

-- {a,b} => [a] column vector
--          [b]
global type StateVector(object sv)
    return sequence(sv) -- it may contain any objects, no need to test them
end type

global type Operator(object op)
    if integer(op) and (op >= -1) then
        return 1
    else
        return 0
    end if
end type

-- matrix element components
global constant con_    = 1 -- converging
global constant div_    = 2 -- diverging

global type SMElement(object sme)
    if sequence(sme) and length(sme)=2 
           and ((PNData(sme[con_]) and PNData(sme[div_]))
           or (Operator(sme[con_]) and Operator(sme[div_]))) then
        return 1
    elsif atom(sme) then
        return 1
    end if
    return 0
end type

-- include here so it gets SMElement definition
include SparseSM.e
include EnergyFlowUtils.e

-- {a,b} => [a b] row vector
global type SMRow(object smr)
    if SparseSMRow(smr) then
        for i = 1 to length(smr[Data_]) do
            if not SMElement(smr[Data_][i]) then
                return 0
            end if
        end for
    elsif sequence(smr) then -- should be an atom or a sequence containing Operators or PNData
        for i = 1 to length(smr) do
            if not SMElement(smr[i]) then
                return 0
            end if
        end for
    else
        return 0
    end if
    return 1
end type

-- {a,b} => [a] column vector
--          [b]
global type SMColumn(object smc)
    if SparseSMCol(smc) then
        return 1
    elsif sequence(smc) then -- should be an atom or a sequence containing Operators or PNData
        for i = 1 to length(smc) do
            if not SMElement(smc[i]) then
                return 0
            end if
        end for
    else
        return 0
    end if
    return 1
end type

-- {{a,b},{c,d}} => [a b] matrix represented as a column of rows
--                  [c d]
global type SystemMatrix(object sm)
    integer len
    if SparseSystemMatrix(sm) then -- detect sparseSM
        return 1
    end if
    
    if not sequence(sm) then 
        return 0 
    end if
    len = length(sm)
    for i = 1 to len do
        if not (SMRow(sm[i]) and length(sm[i])=len) then
            return 0
        end if
        -- the data may be any kind of object, no need to test them
    end for
    return 1
end type

-- smModel = {SM , SV}
global type smModel(object mdl)
    if sequence(mdl) and length(mdl) = 2 then
        return SystemMatrix(mdl[1]) and StateVector(mdl[2])
    end if
    return 0
end type

global function createSM(integer nRows, integer nCols, object data)
    if PNData(data) or Operator(data) then
        return repeat(repeat({data,data},nCols),nRows)
    end if
    return {} -- empty matrix
end function

global function createSV(integer nRows, object data)
    return repeat(data,nRows)
end function

global type OpSequence(object os)
    if sequence(os) then
        for i = 1 to length(os) do
            if not Operator(os[i]) then
                return 0
            end if
        end for
        return 1
    end if
    return 0
end type

function smPairwise(object out, SMElement d1, object d2)
    if atom(d1) and atom(d2) then
        return d1*d2
    elsif Operator(d1[con_]) and Operator(d1[div_]) then
        return call_func(d1[con_],{call_func(d1[div_],{d2})})
    else --if PNData(out) and PNData(d1) and PNData(d2) then
         -- type checking will ensure PNData'hood
        return smMultiply(out,d1[con_],smMultiply(out,d1[div_],d2))
    end if
end function
    
-- instead of 
-- M[i] use smGetRow(M,i)
global function smGetRow(SystemMatrix M, integer rowId)
    if SparseSystemMatrix(M) then
        return get_row_SM(M,rowId)
    else -- a full sequence row
        return M[rowId]
    end if
end function

-- equivalent to extracting the jth element out of each row
-- to form the jth column
global function smGetCol(SystemMatrix M, integer colId)
    SMColumn res res = {}
    
    if SparseSystemMatrix(M) then
        return get_col_SM(M,colId)
    else -- a full sequence row
        for i = 1 to length(M) do
            res = res & {M[i][colId]}
        end for
        return res
    end if
end function

-- instead of 
-- row[i] use smGetElemFromRow(row,i)
function smGetElemFromRow(SMRow row, integer colId)
    sequence rSearch
    if SparseSMRow(row) then
        -- search for element in row
        rSearch = BSearch(row[elemId_], colId)
        -- if element exists then
        if rSearch[found_] then
            return row[Data_][rSearch[index_]]
        else
            return {{fdZero},{fdZero}}
        end if
    else -- a full sequence row
        return row[colId]
    end if
end function

-- a general grouping function
function smGroup(SMRow row, StateVector sv)
    sequence out
    
    out = {}
    for i = 1 to length(sv) do
        out = out & {smPairwise(sv[i],smGetElemFromRow(row,i),sv[i])}
    end for
    return out
end function

-- a basic aggregate function
function smSummation_(sequence in) -- (PNData out, sequence of PNData (pairwise results from smGroup))
                                   -- or {atom out, sequence of atoms}
    object out
    if atom(in[1]) then
        out = 0
        for i = 1 to length(in[2]) do
            out += in[2][i]
        end for
    elsif PNData(in[1]) then
        out = eAssign(in[1],{fdZero})
        for i = 1 to length(in[2]) do
            out = smAdd(out,out, in[2][i])
        end for
    end if
    return out
end function
constant smSummation = routine_id ("smSummation_")

global type UpdateList(object cu)
    if sequence(cu) then
        for i = 1 to length(cu) do
            if not integer(cu[i]) then
                return 0
            end if
        end for
    else
        return 0
    end if
    return 1
end type


-- F for full processing
-- EF for energy flow processing
function smFGlobal_(sequence in) --(OpSequence A, SystemMatrix M, StateVector V, integer steps)
    OpSequence A
    SystemMatrix M
    integer len, steps
    StateVector new, curr

    A = in[1]
    M = in[2]
    curr = in[3]
    steps = in[4]
    
    len = length(curr)
    new = curr
    if length(A) = 1 then
        A = repeat(A[1], len)
    end if
    for t = 1 to steps do
        for i = 1 to len do
            new[i] = call_func(A[i],{{new[i],smGroup(smGetRow(M,i), curr)}})
        end for
        curr = new
    end for
    return new
end function
constant smFGlobal = routine_id ("smFGlobal_")


-- smEnergyFlowGlobal
-- implements the energy flow method on arbitrary matrices
-- smEFGlobal(OpSequence A, SystemMatrix M, StateVector V, integer steps, UpdateList cUpdate)
global function smEFGlobal_(sequence in) 
    OpSequence A
    SystemMatrix M
    UpdateList cUpdate, rUpdate
    SMColumn col
    StateVector new, curr
    integer len, rUr, steps
    
    A = in[1]
    M = in[2]
    curr = in[3]
    steps = in[4]
    cUpdate = in[5]

    len = length(curr)
    new = curr
    if length(A) = 1 then
        A = repeat(A[1], len)
    end if
    -- for each moment in time
    for n = 1 to steps do
        rUpdate = {}
        -- cUpdate is an ordered list of colId's which require updating
        -- for each element of cUpdate
        for c = 1 to length(cUpdate) do
            -- find the matrix column
            col = smGetCol(M,cUpdate[c]) --get_col_SM(M,cUpdate[c])
            -- if the column existed in the matrix; for global outputs col=0
            if SMColumn(col) then
                -- to keep only the rowIds
                -- form the union of each column with rUpdate
                if SparseSystemMatrix(M) then
                    rUpdate = union_SET(rUpdate,col[elemId_])
                else -- full SystemMatrix
                    for i = 1 to length(col) do
                        if SMElement(col[i]) and ( ( atom(col[i]) and not equal(0,col[i]) ) or (  sequence(col[i]) and not equal(fdZero,col[i][div_]) ) ) then
                            rUpdate = union_SET(rUpdate,{i})
                        end if
                    end for
                end if
            end if
        end for
        cUpdate = {} -- empty cUpdate now that it has been used
        -- rUpdate is now an ordered list of rowId's which require updating
        -- for each element of rUpdate
        for r = 1 to length(rUpdate) do
            rUr = rUpdate[r]
            new[rUr] = call_func(A[rUr],{{new[rUr],smGroup(smGetRow(M,rUr), curr)}})
            -- compare the accumulated result with the sv element
            -- if different then
            if not equal(new[rUr], curr[rUr]) then
                -- insert the rowId into cUpdate to indicate that the sv element has changed
                cUpdate = cUpdate & rUr
            end if
        end for
        curr = new
    end for
    return new
end function
constant smEFGlobal = routine_id ("smEFGlobal_")


--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=-
-- in each of these two general functions one may use:

-- with this function one may implement any type of SMN
-- full (F) or energy flow (EF) processing depending on G
-- linear or nonlinear depending on A
-- full or sparse matrices depending on M
global function smGeneral(Operator G, OpSequence A, SystemMatrix M, StateVector V, integer steps, UpdateList cU)
    return call_func(G, {{A,M,V,steps,cU}})
end function

-- linear SMN
global function smLinear(Operator G, SystemMatrix M, StateVector V, integer steps, UpdateList cU)
    return smGeneral(G,{smSummation},M,V,steps,cU)
end function

-- full processing linear SMN
global function smFLinear(SystemMatrix M, StateVector V, integer steps)
    return smLinear(smFGlobal,M,V,steps,{})
end function

-- full processing general SMN
global function smFGeneral(OpSequence A, SystemMatrix M, StateVector V, integer steps)
    return smGeneral(smFGlobal,A,M,V,steps,{})
end function

-- energy flow processing linear SMN
global function smEFLinear(SystemMatrix M, StateVector V, integer steps, UpdateList cU)
    return smLinear(smEFGlobal,M,V,steps,cU)
end function

-- energy flow processing general SMN
global function smEFGeneral(OpSequence A, SystemMatrix M, StateVector V, integer steps, UpdateList cU)
    return smGeneral(smEFGlobal,A,M,V,steps,cU)
end function


--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=-

-- function used later in the test code
-- in[1] is output type
-- in[2] is the data, containing all three pair results
-- in[2][2] and in[2][3] are the desired datums
function NAND(sequence in)
    -- return a*b = 0
    if equal(smMultiply(in[1],in[2][2],in[2][3]), repeat(fdZero,length(in[1]))) then
        return {fdOne}
    else
        return {fdZero}
    end if
end function
constant NAND_id = routine_id("NAND")

procedure testSMN()
---------------------------------------------
-- A summary of the test procedure:
-- test linear SMN processing
    -- test non-sparse matrices
        -- test full processing with non-sparse linear Matrices
        -- test energy flow processing with non-sparse linear Matrices
    -- test sparse matrices
        -- test full processing with sparse linear Matrices
        -- test energy flow processing with sparse linear Matrices
-- test non-linear SMN processing
    -- test non-sparse matrices
        -- test full processing with non-sparse non-linear Matrices
        -- test energy flow processing with non-sparse non-linear Matrices
    -- test sparse matrices
        -- test full processing with sparse non-linear Matrices
        -- test energy flow processing with sparse non-linear Matrices
---------------------------------------------
-- let a = isLinear = 0 or 1   l means linear
--     b = isSparse = 0 or 1   1 means sparse
--     c = isEFlow  = 0 or 1   1 means uses energy flow processing
--
-- the permutation table is:
--------------------------------------------
--  a | b | c |    type     |   test order
--------------------------------------------
--  0 | 0 | 0 | nl, ns, nef |   5
--  0 | 0 | 1 | nl, ns,  ef |   6
--  0 | 1 | 0 | nl,  s, nef |   7
--  0 | 1 | 1 | nl,  s,  ef |   8   working
--  1 | 0 | 0 |  l, ns, nef |   1   working
--  1 | 0 | 1 |  l, ns,  ef |   2   working
--  1 | 1 | 0 |  l,  s, nef |   3
--  1 | 1 | 1 |  l,  s,  ef |   4
------------------------------------------
-- There are 2^3 = 8 basic types of SMN
-- So in this process there are 8 different ways in which
-- the kings of Edom can unite into a single king of Israel
------------------------------------
-- Test Code: ----------------------
------------------------------------
    SMElement r0, r1
    SystemMatrix M1, M2
    StateVector Vi, Vnew

    OpSequence st
    SystemMatrix sm
    StateVector sv
    UpdateList cUpdate
    object data
-- test linear SMN processing

    -- test non-sparse matrices
        
        r0 = {{fdZero},{fdZero}}
        r1 = {{fdOne},{fdOne}}
        M1 = {{r1,r0},{r0,r1}} -- identity
        M2 = {{r0,r1},{r1,r0}} -- flip
        Vi = {{defaultFDV(9)},{defaultFDV(15)}}
        -- test full processing with non-sparse linear Matrices
            puts(1,"\nTest:    Linear,    nonSparse,      nonEF   SMN\n")
            trace (1)
            ?{fdGet(first(Vi[1])),fdGet(first(Vi[2]))}
            Vnew = smFLinear(M1,Vi,1) -- identity
            puts(1,"After one iteration:\n")
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2]))}
            Vnew = smFLinear(M1,Vi,1000) -- identity
            puts(1,"After 1000 iterations, notice the drift due to FDData:\n")
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2]))}
            puts(1,"There's a steady drift upwards of the decimal digits.\n")
            Vnew = smFLinear(M2,Vi,1) -- flip
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2]))}
        -- test energy flow processing with non-sparse linear Matrices
            puts(1,"\nTest:    Linear,    nonSparse,         EF   SMN\n")
            trace(1)
            Vnew = smEFLinear(M1,Vi,1,{1,2}) -- identity
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2]))}
            Vnew = smEFLinear(M2,Vi,1,{1,2}) -- flip
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2]))}
    -- test sparse matrices
            M1 = createSparseSM(2,2)
            M1 = set_SM(M1, {1,1}, r1)
            M1 = set_SM(M1, {2,2}, r1)
            M2 = createSparseSM(2,2)
            M2 = set_SM(M2, {1,2}, r1)
            M2 = set_SM(M2, {2,1}, r1)
            Vi = {{defaultFDV(9)},{defaultFDV(15)}}
        
        -- test full processing with sparse linear Matrices
            puts(1,"\nTest:    Linear,       Sparse,      nonEF   SMN\n")
            trace(1)
            ?{fdGet(first(Vi[1])),fdGet(first(Vi[2]))}
            Vnew = smFLinear(M1,Vi,1)
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2]))}
            Vnew = smFLinear(M2,Vi,1)
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2]))}

        -- test energy flow processing with sparse linear Matrices
            puts(1,"\nTest:    Linear,       Sparse,         EF   SMN\n")
            trace(1)
            cUpdate = {1,2} -- flag initial inputs for processing
            Vnew = smEFLinear(M1,Vi,1,cUpdate)
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2]))}
            Vnew = smEFLinear(M2,Vi,1,cUpdate)
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2]))}
             
-- test non-linear SMN processing

    -- test non-sparse matrices
        -- test full processing with non-sparse non-linear Matrices
            puts(1,"\nTest: nonLinear,    nonSparse,      nonEF   SMN\n")
        -- test energy flow processing with non-sparse non-linear Matrices
            puts(1,"\nTest: nonLinear,    nonSparse,         EF   SMN\n")
    -- test sparse matrices
        -- test full processing with sparse non-linear Matrices
            puts(1,"\nTest: nonLinear,       Sparse,      nonEF   SMN\n")
        -- test energy flow processing with sparse non-linear Matrices
            data = r1 -- input selector
            st = {NAND_id,-1,-1} -- OpSequence undefined for rows 2 and 3
            -- construct the Metric System Matrix element by element
            sm = createSparseSM(3,3)
            sm = set_SM(sm, {1,2}, data)
            sm = set_SM(sm, {1,3}, data)

            -- construct state vector, sv = {0,1,1}
            -- 1 NAND 1 = 0
            sv = {{fdZero},{fdOne},{fdOne}}
            puts(1,"\nTest: nonLinear,       Sparse,         EF   SMN\n")
            trace(1)
            ?{fdGet(first(sv[1])),fdGet(first(sv[2])),fdGet(first(sv[3]))}
            cUpdate = {2,3} -- flag initial inputs for processing
            Vnew = smEFGeneral(st,sm,sv,1,cUpdate)
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2])),fdGet(first(Vnew[3]))}

            -- construct state vector, sv = {0,0,1}
            -- 0 NAND 1 = 1
            sv = {{fdZero},{fdZero},{fdOne}}
            ?{fdGet(first(sv[1])),fdGet(first(sv[2])),fdGet(first(sv[3]))}
            Vnew = smEFGeneral(st,sm,sv,1,cUpdate)
            ?{fdGet(first(Vnew[1])),fdGet(first(Vnew[2])),fdGet(first(Vnew[3]))}

end procedure

--testSMN()
-- It works!

--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=--=#=-

-- General SMN tools for construction, manipulation and visualistion of SM's and SV's

-- smLinear tools:
-- ###############
--
-- The SM and SV contains PNData

www.Anandavala.info