Models backlash with a dead band around the previous output.
Models mechanical backlash (deadband / play) behavior. The output sticks until input moves past a width.
These serialized keys are exposed by the block inspector.
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.
Manifestmodules/nflow_blocks/libraries/nonlinear/library.json
{
"id": "builtin.nonlinear",
"title": "Non-Linear",
"version": "1.0.0",
"format": "nflow-2",
"metadata": {
"author": "Allan CORNET",
"created": "2026-03-21",
"tool": "Nelson nflow"
},
"comment": "Blocks for non-linearities",
"license": "LGPL-3.0",
"builtin": true,
"blocks": [
{
"type": "saturation",
"icon": "saturation.svg",
"label": "Saturation",
"phases": [
"ALGEBRAIC"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"LowerLimit": -1,
"UpperLimit": 1
},
"render": {
"type": "image",
"src": "saturation.svg"
}
},
{
"type": "hysteresis",
"label": "Relay",
"icon": "hysteresis.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"uHigh": 1,
"uLow": -1,
"yHigh": 1,
"yLow": 0
},
"render": {
"type": "image",
"src": "hysteresis.svg"
}
},
{
"type": "rate",
"label": "Rate Lim.",
"icon": "rate.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"RisingSlewLimit": 1,
"FallingSlewLimit": 1
},
"render": {
"type": "image",
"src": "rate.svg"
}
},
{
"type": "backlash",
"label": "Backlash",
"icon": "backlash.svg",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"BacklashWidth": 1
},
"render": {
"type": "image",
"src": "backlash.svg"
}
},
{
"type": "deadZone",
"label": "Dead Zone",
"icon": "deadZone.svg",
"phases": [
"ALGEBRAIC"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"LowerValue": -1,
"UpperValue": 1
},
"render": {
"type": "image",
"src": "deadZone.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 80,
"height": 80
}
},
{
"type": "quantizer",
"label": "Quantizer",
"icon": "quantizer.svg",
"phases": [
"ALGEBRAIC"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"QuantizationInterval": 1
},
"render": {
"type": "image",
"src": "quantizer.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 80,
"height": 80
}
},
{
"type": "hitCrossing",
"icon": "hitCrossing.svg",
"label": "Hit Crossing",
"phases": [
"INIT",
"OUTPUT",
"UPDATE"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"HitCrossingOffset": 0,
"HitCrossingDirection": "either"
},
"render": {
"type": "image",
"src": "hitCrossing.svg"
}
},
{
"type": "coulombViscousFriction",
"label": "Coulomb & Viscous Friction",
"icon": "coulombViscousFriction.svg",
"phases": [
"ALGEBRAIC"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"Gain": 1,
"Offset": 1
},
"render": {
"type": "image",
"src": "coulombViscousFriction.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 80,
"height": 80
}
}
]
}
Runtimemodules/nflow_blocks/src/cpp/nonlinear/backlash.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
//=============================================================================
#include "SimEngineTypes.hpp"
#include "BlockRegistry.hpp"
#include "FieldNames.hpp"
#include "NFlowBlockDescriptor.hpp"
#include <cmath>
#include <algorithm>
#include "nonlinear_blocks.hpp"
//=============================================================================
bool
Nelson::NFlow::handleBacklash(SimCtx& ctx, const Block& b, Phase phase)
{
auto& st = getState(ctx, b.nid);
const int w = outputWidth(ctx, b.nid, 0);
if (phase == Phase::INIT) {
st.scalar = 0.0;
st.output = 0.0;
if (w > 1) {
st.vec.assign(w, 0.0);
}
return false;
}
if (phase == Phase::ALGEBRAIC) {
// Direct feedthrough, as the emitted C/Rust step is: the play is
// resolved against the current input, not against the value latched at
// the end of the previous step (which delayed the block by one step).
// Pure: only UPDATE commits, so RK stages and algebraic sweeps may
// re-enter this freely.
nflow::BlockDescriptor bd(b, ctx.variables);
const double width = std::max(0.0, bd.paramDouble(nflow::kBacklashWidth, 0.0));
SigView u = getInputSig(ctx, b.nid, 0);
return emitElementwise(ctx, b.nid, [&](int i) {
const double prev = (w <= 1) ? st.scalar : ((i < (int)st.vec.size()) ? st.vec[i] : 0.0);
const double inp = sigAt(u, i);
if (inp > prev + width / 2.0) {
return inp - width / 2.0;
}
if (inp < prev - width / 2.0) {
return inp + width / 2.0;
}
return prev;
});
}
if (phase == Phase::UPDATE) {
nflow::BlockDescriptor bd(b, ctx.variables);
double width = std::max(0.0, bd.paramDouble(nflow::kBacklashWidth, 0.0));
auto step = [&](double inp, double prev) {
double out = prev;
if (inp > prev + width / 2.0) {
out = inp - width / 2.0;
}
if (inp < prev - width / 2.0) {
out = inp + width / 2.0;
}
return out;
};
if (w <= 1) {
double out = step(getInput(ctx, b.nid, 0, 0.0), st.scalar);
st.scalar = out;
st.output = out;
} else {
SigView u = getInputSig(ctx, b.nid, 0);
if ((int)st.vec.size() != w) {
st.vec.assign(w, 0.0);
}
for (int i = 0; i < w; ++i) {
st.vec[i] = step(sigAt(u, i), st.vec[i]);
}
}
return false;
}
return false;
}
//=============================================================================
Nelson::NFlow::BlockCodegenTemplate
Nelson::NFlow::getCodeGenCBacklash()
{
BlockCodegenTemplate t;
t.emitState = [](const BlockCodegenStateArgs& a) { a.addState("backlash_" + a.id, "", ""); };
t.emitStep = [](const BlockCodegenArgs& a) {
nflow::BlockDescriptor bd(*a.block, *a.variables);
double width = std::fmax(0.0, bd.paramDouble(nflow::kBacklashWidth, 0.0));
a.line("{ double v = " + a.in[0] + ";");
a.line(" if (v > s->backlash_" + a.id + " + " + a.fmt(width) + " / 2.0) s->backlash_"
+ a.id + " = v - " + a.fmt(width) + " / 2.0;");
a.line(" if (v < s->backlash_" + a.id + " - " + a.fmt(width) + " / 2.0) s->backlash_"
+ a.id + " = v + " + a.fmt(width) + " / 2.0;");
a.line(" out_" + a.id + " = s->backlash_" + a.id + "; }");
};
return t;
}
//=============================================================================
Nelson::NFlow::BlockCodegenTemplate
Nelson::NFlow::getCodeGenRustBacklash()
{
BlockCodegenTemplate t;
t.emitState = [](const BlockCodegenStateArgs& a) { a.addState("backlash_" + a.id, "", ""); };
t.emitStep = [](const BlockCodegenArgs& a) {
nflow::BlockDescriptor bd(*a.block, *a.variables);
double width = std::fmax(0.0, bd.paramDouble(nflow::kBacklashWidth, 0.0));
a.line("{");
a.line(" let v = " + a.in[0] + ";");
a.line(" if v > s.backlash_" + a.id + " + " + a.fmt(width) + " / 2.0_f64 { s.backlash_"
+ a.id + " = v - " + a.fmt(width) + " / 2.0_f64; }");
a.line(" if v < s.backlash_" + a.id + " - " + a.fmt(width) + " / 2.0_f64 { s.backlash_"
+ a.id + " = v + " + a.fmt(width) + " / 2.0_f64; }");
a.line(" out_" + a.id + " = s.backlash_" + a.id + ";");
a.line("}");
};
return t;
}
//=============================================================================