Block type: directLookup
| Parameter | Description |
|---|---|
| input ports | 2 input port(s) declared. |
| Parameter | Description |
|---|---|
| output ports | 1 output port(s) declared. |
Direct (n-D) lookup table without interpolation.
| Module |
nflow_blocks
|
| Library | Lookup Tables |
| Type |
directLookup
|
| Label | Direct Lookup Table (n-D) |
Description
N integer index inputs select one element of a static column-major Table (Element mode). Per-dimension sizes come from TableDimensions; each index is rounded and clamped (zero-based).
This page describes the native runtime behavior observed in the module C++ sources. Declared phases indicate when the simulation engine calls the block.
Code generation: supported for C and Rust.
Implementation Sources
modules/nflow_blocks/libraries/lookup/library.json{
"id": "builtin.lookup",
"title": "Lookup Tables",
"version": "0.1.0",
"format": "nflow-2",
"builtin": true,
"blocks": [
{
"type": "lookup1D",
"label": "1-D Lookup Table",
"icon": "lookup1D.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"BreakpointsForDimension1": [
0,
1,
2,
3,
4
],
"Table": [
0,
1,
4,
9,
16
],
"InterpMethod": "Linear point-slope",
"ExtrapMethod": "Linear"
},
"render": {
"type": "image",
"src": "exports/lookup1D.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 90,
"height": 80
}
},
{
"type": "lookup2D",
"label": "2-D Lookup Table",
"icon": "lookup2D.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 80,
"inputs": [
{
"x": 0,
"y": 30,
"side": "left"
},
{
"x": 0,
"y": 50,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"BreakpointsForDimension1": [
1,
2,
3
],
"BreakpointsForDimension2": [
1,
2,
3
],
"Table": [
4,
5,
6,
5,
7,
8,
6,
8,
10
],
"InterpMethod": "Linear point-slope",
"ExtrapMethod": "Linear"
},
"render": {
"type": "image",
"src": "exports/lookup2D.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 90,
"height": 80
}
},
{
"type": "lookupND",
"label": "n-D Lookup Table",
"icon": "lookupND.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 80,
"inputs": [
{
"x": 0,
"y": 30,
"side": "left"
},
{
"x": 0,
"y": 50,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"NumberOfTableDimensions": 2,
"BreakpointsForDimension1": [
1,
2,
3
],
"BreakpointsForDimension2": [
1,
2,
3
],
"Table": [
4,
5,
6,
5,
7,
8,
6,
8,
10
],
"InterpMethod": "Linear point-slope",
"ExtrapMethod": "Linear"
},
"render": {
"type": "image",
"src": "exports/lookupND.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 90,
"height": 80
}
},
{
"type": "directLookup",
"label": "Direct Lookup Table (n-D)",
"icon": "directLookup.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 80,
"inputs": [
{
"x": 0,
"y": 30,
"side": "left"
},
{
"x": 0,
"y": 50,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"NumberOfTableDimensions": 2,
"TableDimensions": [
2,
3
],
"Table": [
0,
1,
10,
11,
20,
21
]
},
"render": {
"type": "image",
"src": "exports/directLookup.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 90,
"height": 80
}
},
{
"type": "prelookup",
"label": "Prelookup",
"icon": "prelookup.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 30,
"side": "right"
},
{
"x": 90,
"y": 50,
"side": "right"
}
],
"defaultParams": {
"BreakpointsForDimension1": [
0,
1,
2,
3,
4
]
},
"render": {
"type": "image",
"src": "exports/prelookup.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 90,
"height": 80
}
},
{
"type": "interpolationPrelookup",
"label": "Interpolation Using Prelookup",
"icon": "interpolationPrelookup.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 80,
"inputs": [
{
"x": 0,
"y": 30,
"side": "left"
},
{
"x": 0,
"y": 50,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"Table": [
0,
1,
4,
9,
16
]
},
"render": {
"type": "image",
"src": "exports/interpolationPrelookup.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 90,
"height": 80
}
},
{
"type": "lookupDynamic",
"label": "Lookup Table Dynamic",
"icon": "lookupDynamic.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 80,
"inputs": [
{
"x": 0,
"y": 20,
"side": "left"
},
{
"x": 0,
"y": 40,
"side": "left"
},
{
"x": 0,
"y": 60,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 40,
"side": "right"
}
],
"defaultParams": {},
"render": {
"type": "image",
"src": "exports/lookupDynamic.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 90,
"height": 80
}
}
]
}
modules/nflow_blocks/src/cpp/lookup/directLookup.cpp//=============================================================================
// Copyright (c) 2016-present Allan CORNET (Nelson)
//=============================================================================
// This file is part of Nelson.
//=============================================================================
// LICENCE_BLOCK_BEGIN
// SPDX-License-Identifier: LGPL-3.0-or-later
// LICENCE_BLOCK_END
//=============================================================================
// directLookup: Direct Lookup Table (n-D), Element mode -- no interpolation.
// N integer index inputs select one element of a static column-major Table.
// The per-dimension sizes are given by TableDimensions = [n0, n1, ...]; the
// flat offset is sum_k idx_k * stride_k (stride_0 = 1, stride_k = prod_{m<k}
// n_m), with each index rounded to the nearest integer and clamped to
// [0, n_k-1] (zero-based). ALGEBRAIC feedthrough, real double, no state.
// The Column / 2-D Matrix output modes and a table-driven-by-input port are a
// follow-up.
//=============================================================================
#include "SimEngineTypes.hpp"
#include "BlockRegistry.hpp"
#include "FieldNames.hpp"
#include "NFlowBlockDescriptor.hpp"
#include "lookup_blocks.hpp"
#include <cmath>
#include <string>
#include <vector>
//=============================================================================
namespace Nelson {
namespace NFlow {
//=============================================================================
namespace {
int
numDims(const nflow::BlockDescriptor& bd)
{
const int n = (int)bd.paramDouble("NumberOfTableDimensions", 0.0);
return (n >= 1) ? n : 0;
}
int
clampIndex(double u, int n)
{
int idx = (int)std::lround(u);
if (idx < 0) {
idx = 0;
}
if (idx > n - 1) {
idx = n - 1;
}
return idx;
}
// Parse via BlockDescriptor so JSON arrays and string expressions both
// resolve (matching the simulator / inspector-edited params).
std::vector<double>
paramVec(const BlockCodegenArgs& a, const char* key)
{
if (!a.block || !a.variables) {
return {};
}
nflow::BlockDescriptor bd(*a.block, *a.variables);
return bd.paramList(key);
}
void
emitDirectLookup(const BlockCodegenArgs& a, bool rust)
{
nflow::BlockDescriptor bdp(*a.block, *a.variables);
int N = (int)bdp.paramDouble("NumberOfTableDimensions", 0.0);
const std::vector<double> dimsD = paramVec(a, "TableDimensions");
const std::vector<double> tbl = paramVec(a, "Table");
size_t expected = 1;
for (int k = 0; k < (int)dimsD.size(); ++k) {
expected *= std::max<size_t>(1, (size_t)dimsD[k]);
}
if (N < 1 || (int)dimsD.size() != N || tbl.size() != expected) {
a.line("out_" + a.id + (rust ? " = 0.0_f64;" : " = 0.0;"));
return;
}
std::vector<int> dims(N, 1), stride(N, 1);
for (int k = 0; k < N; ++k) {
dims[k] = std::max(1, (int)dimsD[k]);
}
for (int k = 1; k < N; ++k) {
stride[k] = stride[k - 1] * dims[k - 1];
}
const std::string tv = "__dl_tbl_" + a.id;
std::string td = rust ? "let " + tv + " = [" : "static const double " + tv + "[] = {";
for (size_t i = 0; i < tbl.size(); ++i) {
td += (i ? ", " : "") + a.fmt(tbl[i]);
}
td += rust ? "];" : "};";
a.line(td);
std::string offExpr;
for (int k = 0; k < N; ++k) {
const std::string iv = "__di_" + a.id + "_" + std::to_string(k);
const std::string hi = std::to_string(dims[k] - 1);
if (rust) {
a.line("let mut " + iv + ": i32 = (" + a.in[k] + ").round() as i32;");
a.line("if " + iv + " < 0 { " + iv + " = 0; } if " + iv + " > " + hi + " { "
+ iv + " = " + hi + "; }");
} else {
a.line("int " + iv + " = (int)lround(" + a.in[k] + ");");
a.line("if (" + iv + " < 0) " + iv + " = 0; if (" + iv + " > " + hi + ") " + iv
+ " = " + hi + ";");
}
offExpr += (k ? " + " : "") + iv + " * " + std::to_string(stride[k]);
}
const std::string off = "__doff_" + a.id;
a.line((rust ? "let " : "int ") + off + " = " + offExpr + ";");
a.line("out_" + a.id + " = " + tv + "[" + off + (rust ? " as usize" : "") + "];");
}
} // namespace
//=============================================================================
bool
handleDirectLookup(SimCtx& ctx, const Block& b, Phase phase)
{
if (phase != Phase::ALGEBRAIC) {
return false;
}
nflow::BlockDescriptor bd(b, ctx.variables);
const int N = numDims(bd);
if (N < 1) {
return false;
}
const std::vector<double> dimsD = bd.paramList("TableDimensions");
if ((int)dimsD.size() != N) {
return false;
}
std::vector<int> dims(N, 1), stride(N, 1);
for (int k = 0; k < N; ++k) {
dims[k] = std::max(1, (int)dimsD[k]);
if (!hasInput(ctx, b.nid, k)) {
return false;
}
}
for (int k = 1; k < N; ++k) {
stride[k] = stride[k - 1] * dims[k - 1];
}
const std::vector<double> tbl = bd.paramList("Table");
std::vector<SigView> in(N);
for (int k = 0; k < N; ++k) {
in[k] = getInputSig(ctx, b.nid, k);
}
return emitElementwise(ctx, b.nid, [&](int e) {
int off = 0;
for (int k = 0; k < N; ++k) {
off += clampIndex(sigAt(in[k], e), dims[k]) * stride[k];
}
return (off >= 0 && off < (int)tbl.size()) ? tbl[off] : 0.0;
});
}
//=============================================================================
bool
resolveDirectLookupDims(const Block& b, const ValMap& vars, const std::vector<PortSig>& inSigs,
std::vector<PortSig>& outSigs, std::string& err)
{
nflow::BlockDescriptor bd(b, vars);
const int N = numDims(bd);
if (N < 1) {
err = "directLookup requires NumberOfTableDimensions >= 1";
return false;
}
const std::vector<double> dimsD = bd.paramList("TableDimensions");
if ((int)dimsD.size() != N) {
err = "directLookup requires TableDimensions with one size per dimension";
return false;
}
size_t expected = 1;
for (int k = 0; k < N; ++k) {
expected *= std::max<size_t>(1, (size_t)dimsD[k]);
}
const std::vector<double> tbl = bd.paramList("Table");
if (tbl.size() != expected) {
err = "directLookup table size must equal the product of TableDimensions";
return false;
}
int w = 1;
for (const auto& s : inSigs) {
w = std::max(w, s.width());
}
outSigs[0].setVector(w);
outSigs[0].type = SigType::Double;
return true;
}
//=============================================================================
BlockCodegenTemplate
getCodeGenCDirectLookup()
{
BlockCodegenTemplate t;
t.emitStep = [](const BlockCodegenArgs& a) { emitDirectLookup(a, false); };
return t;
}
//=============================================================================
BlockCodegenTemplate
getCodeGenRustDirectLookup()
{
BlockCodegenTemplate t;
t.emitStep = [](const BlockCodegenArgs& a) { emitDirectLookup(a, true); };
return t;
}
//=============================================================================
} // namespace NFlow
} // namespace Nelson
//=============================================================================
| Version | Description |
|---|---|
| 1.0.0 | initial version |