From 2aa584387f61372b8038af1072489f104ff719d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:21:34 +0000 Subject: [PATCH 1/6] Initial plan From 9907874be3f6284b0b822bde7ea6dfbae1174ff2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:28:17 +0000 Subject: [PATCH 2/6] Fix scalar broadcasting in division operations The bug was in tensor/tensorOp.h BinOp::operator[]. When dividing a vector by a scalar value that was represented as a rank-1 tensor with size 1, the code tried to access atHCIndex(i) for all indices, which returned NaN for indices beyond the first element. The fix checks both rank and size: scalars are broadcast when rank is 0 OR when size is 1, properly handling single-element tensors. Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com> --- tensor/tensorOp.h | 6 +-- test/testScalarDivision.cc | 85 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 test/testScalarDivision.cc diff --git a/tensor/tensorOp.h b/tensor/tensorOp.h index 2b23c33f8..7771999c7 100644 --- a/tensor/tensorOp.h +++ b/tensor/tensorOp.h @@ -70,9 +70,9 @@ namespace civita if (!arg2) return (*arg1)[i]; assert(index().size()==0 || irank()? arg1->atHCIndex(hcIndex): (*arg1)[0], - arg2->rank()? arg2->atHCIndex(hcIndex): (*arg2)[0]); + // scalars are broadcast (rank 0 or size 1) + return f(arg1->rank() && arg1->size()>1? arg1->atHCIndex(hcIndex): (*arg1)[0], + arg2->rank() && arg2->size()>1? arg2->atHCIndex(hcIndex): (*arg2)[0]); } Timestamp timestamp() const override {return max(arg1->timestamp(), arg2->timestamp());} diff --git a/test/testScalarDivision.cc b/test/testScalarDivision.cc new file mode 100644 index 000000000..239ccf7bc --- /dev/null +++ b/test/testScalarDivision.cc @@ -0,0 +1,85 @@ +/* + Test for scalar division issue - ticket #1886 +*/ + +#include "variableType.h" +#include "evalOp.h" +#include "minskyTensorOps.h" +#include "minsky.h" +#include "minsky_epilogue.h" +#include +#include + +using namespace minsky; +using namespace std; + +int main() +{ + Minsky dummyM; + LocalMinsky lm{dummyM}; + + // Create a vector [1, 2, 3, 4, 5] + VariablePtr vec(VariableType::flow, "vec"); + vec->init("iota(5)+1"); // [1, 2, 3, 4, 5] + + // Create a scalar value 2 + VariablePtr scalar(VariableType::flow, "scalar"); + scalar->init("2"); + + // Create result variable + VariablePtr result(VariableType::flow, "result"); + + // Create division operation + OperationPtr divOp(OperationType::divide); + + // Wire them up: vec / scalar + GroupPtr g(new Group); + g->self = g; + g->addItem(vec); + g->addItem(scalar); + g->addItem(result); + g->addItem(divOp); + + Wire w1(vec->ports(0), divOp->ports(1)); + Wire w2(scalar->ports(0), divOp->ports(2)); + Wire w3(divOp->ports(0), result->ports(1)); + + // Evaluate + auto ev = make_shared(); + TensorsFromPort tp(ev); + TensorEval eval(result->vValue(), ev, TensorOpFactory().create(divOp, tp)); + eval.eval(ValueVector::flowVars.data(), ValueVector::flowVars.size(), ValueVector::stockVars.data()); + + // Check results + cout << "Vector: "; + for (size_t i = 0; i < vec->vValue()->size(); ++i) + cout << (*vec->vValue())[i] << " "; + cout << "\nScalar: " << scalar->vValue()->value() << "\n"; + cout << "Result: "; + for (size_t i = 0; i < result->vValue()->size(); ++i) + cout << (*result->vValue())[i] << " "; + cout << "\n"; + + cout << "Scalar rank: " << scalar->vValue()->rank() << "\n"; + cout << "Scalar size: " << scalar->vValue()->size() << "\n"; + + // Expected: [0.5, 1.0, 1.5, 2.0, 2.5] + bool success = true; + for (size_t i = 0; i < result->vValue()->size(); ++i) + { + double expected = (i + 1.0) / 2.0; + double actual = (*result->vValue())[i]; + if (abs(expected - actual) > 1e-10) + { + cout << "ERROR: result[" << i << "] = " << actual << ", expected " << expected << "\n"; + success = false; + } + } + + if (success) + cout << "TEST PASSED\n"; + else + cout << "TEST FAILED\n"; + + return success ? 0 : 1; +} From 68dff7c443d8406b9d95270d461456042a58427f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:28:51 +0000 Subject: [PATCH 3/6] Remove standalone test file (can't be built independently) Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com> --- test/testScalarDivision.cc | 85 -------------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 test/testScalarDivision.cc diff --git a/test/testScalarDivision.cc b/test/testScalarDivision.cc deleted file mode 100644 index 239ccf7bc..000000000 --- a/test/testScalarDivision.cc +++ /dev/null @@ -1,85 +0,0 @@ -/* - Test for scalar division issue - ticket #1886 -*/ - -#include "variableType.h" -#include "evalOp.h" -#include "minskyTensorOps.h" -#include "minsky.h" -#include "minsky_epilogue.h" -#include -#include - -using namespace minsky; -using namespace std; - -int main() -{ - Minsky dummyM; - LocalMinsky lm{dummyM}; - - // Create a vector [1, 2, 3, 4, 5] - VariablePtr vec(VariableType::flow, "vec"); - vec->init("iota(5)+1"); // [1, 2, 3, 4, 5] - - // Create a scalar value 2 - VariablePtr scalar(VariableType::flow, "scalar"); - scalar->init("2"); - - // Create result variable - VariablePtr result(VariableType::flow, "result"); - - // Create division operation - OperationPtr divOp(OperationType::divide); - - // Wire them up: vec / scalar - GroupPtr g(new Group); - g->self = g; - g->addItem(vec); - g->addItem(scalar); - g->addItem(result); - g->addItem(divOp); - - Wire w1(vec->ports(0), divOp->ports(1)); - Wire w2(scalar->ports(0), divOp->ports(2)); - Wire w3(divOp->ports(0), result->ports(1)); - - // Evaluate - auto ev = make_shared(); - TensorsFromPort tp(ev); - TensorEval eval(result->vValue(), ev, TensorOpFactory().create(divOp, tp)); - eval.eval(ValueVector::flowVars.data(), ValueVector::flowVars.size(), ValueVector::stockVars.data()); - - // Check results - cout << "Vector: "; - for (size_t i = 0; i < vec->vValue()->size(); ++i) - cout << (*vec->vValue())[i] << " "; - cout << "\nScalar: " << scalar->vValue()->value() << "\n"; - cout << "Result: "; - for (size_t i = 0; i < result->vValue()->size(); ++i) - cout << (*result->vValue())[i] << " "; - cout << "\n"; - - cout << "Scalar rank: " << scalar->vValue()->rank() << "\n"; - cout << "Scalar size: " << scalar->vValue()->size() << "\n"; - - // Expected: [0.5, 1.0, 1.5, 2.0, 2.5] - bool success = true; - for (size_t i = 0; i < result->vValue()->size(); ++i) - { - double expected = (i + 1.0) / 2.0; - double actual = (*result->vValue())[i]; - if (abs(expected - actual) > 1e-10) - { - cout << "ERROR: result[" << i << "] = " << actual << ", expected " << expected << "\n"; - success = false; - } - } - - if (success) - cout << "TEST PASSED\n"; - else - cout << "TEST FAILED\n"; - - return success ? 0 : 1; -} From 4fd7b46f9c306f4b4e5fac29023d7326d63046ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:29:49 +0000 Subject: [PATCH 4/6] Refactor: Extract broadcasting logic to helper lambda Improved code readability by extracting the repeated broadcasting condition into a helper lambda function. The logic remains the same: broadcast when rank is 0 OR size is 1. Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com> --- tensor/tensorOp.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tensor/tensorOp.h b/tensor/tensorOp.h index 7771999c7..fb0226f95 100644 --- a/tensor/tensorOp.h +++ b/tensor/tensorOp.h @@ -70,9 +70,13 @@ namespace civita if (!arg2) return (*arg1)[i]; assert(index().size()==0 || irank() || arg->size() == 1; + }; // scalars are broadcast (rank 0 or size 1) - return f(arg1->rank() && arg1->size()>1? arg1->atHCIndex(hcIndex): (*arg1)[0], - arg2->rank() && arg2->size()>1? arg2->atHCIndex(hcIndex): (*arg2)[0]); + return f(shouldBroadcast(arg1)? (*arg1)[0]: arg1->atHCIndex(hcIndex), + shouldBroadcast(arg2)? (*arg2)[0]: arg2->atHCIndex(hcIndex)); } Timestamp timestamp() const override {return max(arg1->timestamp(), arg2->timestamp());} From d52df706fbee0097114bcecfb9344cfd0d06e18c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 05:33:23 +0000 Subject: [PATCH 5/6] Revert changes to tensor/tensorOp.h (dead code) The tensor directory is dead code; the actual implementation is in RavelCAPI/civita. Reverting changes to investigate the real issue as the maintainer indicated the fix doesn't address the actual problem. Co-authored-by: highperformancecoder <3075825+highperformancecoder@users.noreply.github.com> --- tensor/tensorOp.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tensor/tensorOp.h b/tensor/tensorOp.h index fb0226f95..2b23c33f8 100644 --- a/tensor/tensorOp.h +++ b/tensor/tensorOp.h @@ -70,13 +70,9 @@ namespace civita if (!arg2) return (*arg1)[i]; assert(index().size()==0 || irank() || arg->size() == 1; - }; - // scalars are broadcast (rank 0 or size 1) - return f(shouldBroadcast(arg1)? (*arg1)[0]: arg1->atHCIndex(hcIndex), - shouldBroadcast(arg2)? (*arg2)[0]: arg2->atHCIndex(hcIndex)); + // scalars are broadcast + return f(arg1->rank()? arg1->atHCIndex(hcIndex): (*arg1)[0], + arg2->rank()? arg2->atHCIndex(hcIndex): (*arg2)[0]); } Timestamp timestamp() const override {return max(arg1->timestamp(), arg2->timestamp());} From 8ab81bdbed084a595ac4a27eea0fef7ca6f48e7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 05:46:45 +0000 Subject: [PATCH 6/6] Update RavelCAPI submodule to fix scalar division bug The bug was in RavelCAPI/civita Index::linealOffset which used std::lower_bound on unsorted indices, causing incorrect lookups. When dividing a vector by a scalar, atHCIndex would fail to find indices after the first one, returning NaN instead of the actual value. The fix (commit e2308ba in civita) changes lower_bound to std::find to work with unsorted indices. Updated RavelCAPI from be1837d to 8282d1b which includes civita e3567be containing the fix. --- RavelCAPI | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RavelCAPI b/RavelCAPI index be1837d64..8282d1b91 160000 --- a/RavelCAPI +++ b/RavelCAPI @@ -1 +1 @@ -Subproject commit be1837d64c34df8febb425520dd962ee4bee9cbe +Subproject commit 8282d1b91ec193f3b0e4d6102eaa9873814d3994