Engee documentation
Notebook

Masks and strings in the Engee Function

In Engee, you can add a simple user interface to a block. This is called block masking. Masks can be used to hide the internal structure of the unit from the user. This is a great technique, and let's add a mask to the Engee Function block from last post.

The EF_pdist2 function that we used has an optional parameter, the calculation mode (metric). During the project, we will give the user the opportunity to control it through a mask.

In [ ]:
include("PDIST2.jl");

Let's remember what the module code looks like.:

In [ ]:
;cat PDIST2.jl
module PDIST2

mode_dict_t = Dict(1=>"euclidean",2=>"squaredeuclidean",3=>"manhattan",4=>"cosine")

function EF_pdist2(X::Matrix{Float64}, Y::Matrix{Float64}; metric::String="euclidean")
    m, n = size(X)
    p, n2 = size(Y)
    n == n2 || throw(DimensionMismatch("Number of columns in X and Y must match"))

    if metric == "euclidean"
        XX = sum(X.^2, dims=2)
        YY = sum(Y.^2, dims=2)
        D = XX .+ YY' .- 2 .* (X * Y')
        return sqrt.(max.(D, 0))
    
    elseif metric == "squaredeuclidean"
        XX = sum(X.^2, dims=2)
        YY = sum(Y.^2, dims=2)
        D = XX .+ YY' .- 2 .* (X * Y')
        return max.(D, 0)
    
    elseif metric == "manhattan"
        D = zeros(m, p)
        for j in 1:p
            for i in 1:m
                D[i, j] = sum(abs.(X[i, :] .- Y[j, :]))
            end
        end
        return D
    
    elseif metric == "cosine"
        XX = sqrt.(sum(X.^2, dims=2))
        YY = sqrt.(sum(Y.^2, dims=2))
        norms = XX .* YY'
        XY = X * Y'
        
        # Handle division by zero: set invalid entries to 0, then correct cases where both vectors are zero
        sim = zeros(size(XY))
        valid = norms .> 0
        sim[valid] .= XY[valid] ./ norms[valid]
        
        # Identify pairs where both vectors are zero (cosine similarity = 1)
        both_zero = (XX .== 0) .& (YY' .== 0)
        sim[both_zero] .= 1
        
        return 1 .- sim
    
    else
        throw(ArgumentError("Unknown metric: $metric. Supported metrics are 'euclidean', 'squaredeuclidean', 'manhattan', 'cosine'"))
    end
end


end

How do I pass a string from a mask to an Engee Function?

Before masking the Engee Function, it is necessary to consider a functional limitation - the Engee Function cannot work with type parameters. String. But the calculation method is set by a string. Therefore, we need to encode the selected method with a certain number, pass this number as a parameter to the Engee Function, and then decode it. In other words, the mask will need not one parameter, but two. Then you can implement such a scheme:

image.png

We will decode the calculation mode using a dictionary, since the Dict data type implements a collection of key-value pairs. In this case, the key can be any base type. Therefore, it is possible to make even such a dictionary.:

mode_dict_t = Dict(1=>"euclidean",2=>"squaredeuclidean",3=>"manhattan",4=>"cosine")

It can be seen that the key here is an integer, and the value is a string. This allows us to do the following:

In [ ]:
EF_mode = PDIST2.mode_dict_t[1]
Out[0]:
"euclidean"

Now let's take a look at the code of the Engee Function block methods.:

include("/user/start/examples/base_simulation/advanced_block_masking/PDIST2.jl")

mutable struct Block <: AbstractCausalComponent
	cache::Matrix{Float64};
	modeC::Int64
	function Block()
    		c = zeros(Float64,INPUT_SIGNAL_ATTRIBUTES[1].dimensions);
    		info("Allocated $(Base.summarysize(c)) bytes for pdist2")
    		info("Selected $(PDIST2.mode_dict_t[mode])")
    		new(c,mode)
	end
end

function (c::Block)(t::Real, in1, in2)
    try
        c.cache = PDIST2.EF_pdist2(in1,in2;metric=PDIST2.mode_dict_t[c.modeC]);
    catch
        error("Matrix Dimensions should be equal!")
        stop_simulation()
    end

    return c.cache
end

modeC is a parameter that takes the value of a numeric variable from a mask (see ). Thus, we can pass the calculation method to our PDIST2 function as

PDIST2.mode_dict_t[c.modeC]

And we will get the number by calling back the "real" parameter from the drop-down list.:

if mask.parameters.mask_mode.value == "euclidean"
    mask.parameters.mask_mode_int.value = 1;
elseif mask.parameters.mask_mode.value == "squaredeuclidean"
    mask.parameters.mask_mode_int.value = 2;
elseif mask.parameters.mask_mode.value == "manhattan"
    mask.parameters.mask_mode_int.value = 3;
elseif mask.parameters.mask_mode.value == "cosine"
    mask.parameters.mask_mode_int.value = 4;
end

So the mask will look like this:

image.png

Note that the mask_mode_int parameter must be computable. And it's hidden because we don't want to let the user break anything.

image.png

Finally, we will open and run the final model in all modes.:

In [ ]:
demoroot = @__DIR__
mdl = engee.open(joinpath(demoroot,"EF_DF.engee"))
Out[0]:
System(
    name: root,
    id: be446814-cf00-421d-ad5b-484c5f63ca08,
    path: EF_DF
)
In [ ]:
for k in keys(PDIST2.mode_dict_t)
    println("Запускаем модель с режимом $(PDIST2.mode_dict_t[k])")
    engee.set_param!("EF_DF/PDIST2", "mask_mode"=>PDIST2.mode_dict_t[k])
    engee.run(mdl;verbose=true);
end
Запускаем модель с режимом cosine
Building...
Progress 0%
Progress 100%
Progress 100%
Запускаем модель с режимом squaredeuclidean
Building...
Progress 0%
Progress 100%
Progress 100%
Запускаем модель с режимом manhattan
Building...
Progress 0%
Progress 100%
Progress 100%
Запускаем модель с режимом euclidean
Building...
Progress 0%
Progress 100%
Progress 100%

Conclusions

During the project, we learned how to drop string parameters into the Engee Function, and also used dictionaries in a standard, albeit unobvious way.