Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new migraphx::sym API for building and evaluating symbolic expressions, including interval evaluation and stringification, and adds comprehensive unit tests for the new functionality.
Changes:
- Added
migraphx::sym::exprwith literal/variable/operator nodes, plus evaluation (eval,eval_interval) andto_string. - Implemented interval arithmetic and interval math helpers (e.g.,
sin/cos/sqrt/abs). - Added a new
test/sym.cppcovering value evaluation, interval evaluation, comparisons, compound assignment, equality, andto_string.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
src/include/migraphx/sym.hpp |
Declares the symbolic expression API, node types, interval/value helpers, and the call/call_op builder utilities. |
src/sym.cpp |
Implements interval ops, expression construction helpers, expression evaluation, and stringification. |
test/sym.cpp |
Adds a broad unit test suite exercising the new symbolic/interval features. |
src/CMakeLists.txt |
Wires sym.cpp into the migraphx library build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| interval expr::eval_interval(const std::unordered_map<std::string, interval>& vars) const | ||
| { | ||
| if(auto* n = std::get_if<literal_node>(&pimpl->node)) | ||
| return {n->val, n->val}; | ||
| if(auto* n = std::get_if<variable_node>(&pimpl->node)) | ||
| { |
There was a problem hiding this comment.
expr::eval_interval() dereferences pimpl unconditionally, so evaluating intervals on a default-constructed/empty expr will null-deref. Please add the same empty-expression handling as suggested for eval()/to_string() (throw or define a safe default).
| template <class Eval, class EvalInterval> | ||
| expr call_op(std::string name, Eval eval, EvalInterval eval_interval, std::vector<expr> args) | ||
| { | ||
| static const op_def op{std::move(name), std::move(eval), std::move(eval_interval)}; |
There was a problem hiding this comment.
call_op(std::string, Eval, EvalInterval, ...) uses a static const op_def initialized with std::move(name)/std::move(eval). This makes the first call’s name and functors “stick” for the entire process for a given template instantiation; subsequent calls with the same Eval/EvalInterval types (e.g., same lambda type but different captures or different runtime name) will silently reuse the old op, producing incorrect to_string()/evaluation behavior. Consider removing the static (store the op_def in the expr node, e.g., via shared ownership) or keying a cache by name + callable identity rather than template type alone.
| static const op_def op{std::move(name), std::move(eval), std::move(eval_interval)}; | |
| op_def op{name, eval, eval_interval}; |
| value expr::eval(const std::unordered_map<std::string, value>& vars) const | ||
| { | ||
| if(auto* n = std::get_if<literal_node>(&pimpl->node)) | ||
| return n->val; | ||
| if(auto* n = std::get_if<variable_node>(&pimpl->node)) | ||
| return vars.at(n->name); | ||
| auto* n = std::get_if<op_node>(&pimpl->node); |
There was a problem hiding this comment.
expr::eval() dereferences pimpl unconditionally. Since expr is default-constructible (and tests rely on expr a; being valid for equality), calling eval() on a default-constructed expr will crash. Please add a null check (e.g., throw via MIGRAPHX_THROW with a clear message) or define/avoid default-constructed evaluation semantics.
| std::string expr::to_string() const | ||
| { | ||
| if(auto* n = std::get_if<literal_node>(&pimpl->node)) | ||
| return value_to_string(n->val); | ||
| if(auto* n = std::get_if<variable_node>(&pimpl->node)) | ||
| return n->name; | ||
| auto* n = std::get_if<op_node>(&pimpl->node); | ||
| if(n->op->name == "neg" and pimpl->children.size() == 1) |
There was a problem hiding this comment.
expr::to_string() also dereferences pimpl without checking for a default-constructed/empty expression, which can lead to a null dereference. Mirror whatever handling you add for eval() (e.g., MIGRAPHX_THROW for empty expressions) so to_string() is safe on all valid expr objects.
| if(auto* n = std::get_if<literal_node>(&pimpl->node)) | ||
| return n->val; | ||
| if(auto* n = std::get_if<variable_node>(&pimpl->node)) | ||
| return vars.at(n->name); |
There was a problem hiding this comment.
In expr::eval(), variable lookup uses vars.at(n->name), which throws std::out_of_range (and loses the context/message style used elsewhere, e.g., eval_interval() uses MIGRAPHX_THROW). Consider switching to find() + MIGRAPHX_THROW("Variable '...' not found in value map") for consistent error reporting and easier debugging.
| return vars.at(n->name); | |
| { | |
| auto it = vars.find(n->name); | |
| if(it != vars.end()) | |
| return it->second; | |
| MIGRAPHX_THROW("Variable '" + n->name + "' not found in value map"); | |
| } |
| return {rmin, rmax}; | ||
| } | ||
|
|
||
| interval tan(interval x) { return {std::tan(to<double>(x.min)), std::tan(to<double>(x.max))}; } |
There was a problem hiding this comment.
tan(interval) currently returns tan(min) and tan(max) only. Unlike sin/cos, tan is not bounded or monotone across intervals that cross an odd multiple of π/2, so this can produce an interval that is not a safe enclosure (or even inverted). Consider detecting when [min,max] crosses a tan asymptote and either returning an unbounded interval (e.g., [-inf, inf] as doubles) or throwing.
|
|
||
| TEST_CASE(interval_div_assign) | ||
| { | ||
| // [10.0,20.0] /= [2.0,5.0] = [2.0,10.0] |
There was a problem hiding this comment.
The comment in interval_div_assign doesn’t match the actual operands/expected result: the code divides [2,10] by [1,5] and expects [0.4,10], but the comment describes a different example ([10,20]/[2,5]=[2,10]). Please update the comment to reflect the actual test case to avoid confusion.
| // [10.0,20.0] /= [2.0,5.0] = [2.0,10.0] | |
| // [2.0,10.0] /= [1.0,5.0] = [0.4,10.0] |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #4697 +/- ##
===========================================
+ Coverage 92.27% 92.32% +0.05%
===========================================
Files 578 580 +2
Lines 28541 28779 +238
===========================================
+ Hits 26335 26569 +234
- Misses 2206 2210 +4
🚀 New features to boost your workflow:
|
Motivation
Technical Details
Changelog Category
Add a
CHANGELOG.mdentry for any option other thanNot Applicable