From 073bfa2cd8d5ce0df141c01e0e4f469f5fb0ba46 Mon Sep 17 00:00:00 2001 From: Lakshikka Sithamparanathan <2922185@vikes.csuohio.edu> Date: Mon, 8 Jun 2026 14:53:37 +0000 Subject: [PATCH 1/4] Add angle-producing math functions to kernels with docs and tests Adds C++ lowering for std::asin/acos/atan and Python lowering for np.tan/asin/acos/atan/log, documents the supported functions for both languages in the kernel spec, and adds per-function simulator tests plus a gate-parameter test. Addresses #2942. Signed-off-by: Lakshikka Sithamparanathan <2922185@vikes.csuohio.edu> --- docs/sphinx/specification/cudaq/kernels.rst | 6 ++ lib/Frontend/nvqpp/ConvertExpr.cpp | 15 +++++ python/cudaq/kernel/ast_bridge.py | 43 +++++++++++- python/tests/kernel/test_kernel_float.py | 72 +++++++++++++++++++++ test/AST-Quake/math_functions.cpp | 70 ++++++++++++++++++++ 5 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 test/AST-Quake/math_functions.cpp diff --git a/docs/sphinx/specification/cudaq/kernels.rst b/docs/sphinx/specification/cudaq/kernels.rst index 5d686e02d91..4b9f0095c26 100644 --- a/docs/sphinx/specification/cudaq/kernels.rst +++ b/docs/sphinx/specification/cudaq/kernels.rst @@ -59,6 +59,12 @@ Kernels can be composed of the following: * Classical control flow constructs from the classical language (:code:`if`, :code:`for`, :code:`while`, etc.) * Stack variable declarations for supported types. * Arithmetic operations on integer and floating point stack variables + * Calls to a defined set of standard mathematical functions on floating-point + operands: the real-valued, angle-producing functions :code:`sin`, + :code:`cos`, :code:`tan`, :code:`asin`, :code:`acos`, :code:`atan`, + :code:`sqrt`, :code:`exp`, and :code:`log` (in C++, the corresponding + :code:`std::` functions; in Python, the corresponding :code:`numpy` + functions) * Coherent conditional execution - :code:`if ( boolExprFromQubitMeasurement ) { x (another_qubit); }` * Syntax for common quantum programming patterns (e.g. compute-action-uncompute). diff --git a/lib/Frontend/nvqpp/ConvertExpr.cpp b/lib/Frontend/nvqpp/ConvertExpr.cpp index 4993cf3c23a..bd3db27a4f2 100644 --- a/lib/Frontend/nvqpp/ConvertExpr.cpp +++ b/lib/Frontend/nvqpp/ConvertExpr.cpp @@ -1360,6 +1360,21 @@ bool QuakeBridgeVisitor::visitMathLibFunc(clang::CallExpr *x, (funcName == "tan" || funcName == "tanf")) return floatOperator(math::TanOp{}, "tan"); + // Handle std::asin + if ((isInNamespace(func, "std") || isNotInANamespace(func)) && + (funcName == "asin" || funcName == "asinf")) + return floatOperator(math::AsinOp{}, "asin"); + + // Handle std::acos + if ((isInNamespace(func, "std") || isNotInANamespace(func)) && + (funcName == "acos" || funcName == "acosf")) + return floatOperator(math::AcosOp{}, "acos"); + + // Handle std::atan + if ((isInNamespace(func, "std") || isNotInANamespace(func)) && + (funcName == "atan" || funcName == "atanf")) + return floatOperator(math::AtanOp{}, "atan"); + // Handle std::exp if ((isInNamespace(func, "std") || isNotInANamespace(func)) && (funcName == "exp" || funcName == "expf")) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 13ec570ca8f..8dc5fab0b17 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -791,7 +791,8 @@ def isArithmeticType(self, type): type) or F32Type.isinstance(type) or ComplexType.isinstance(type) def __isSupportedNumpyFunction(self, id): - return id in ['sin', 'cos', 'sqrt', 'ceil', 'exp'] + return id in ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', + 'sqrt', 'ceil', 'exp', 'log'] def __isSupportedVectorFunction(self, id): return id in ['front', 'back', 'append'] @@ -3373,6 +3374,46 @@ def bodyBuilder(iterVar): return self.pushValue(math.CeilOp(value).result) return + if node.func.attr == 'tan': + if ComplexType.isinstance(value.type): + self.emitFatalError( + f"numpy call ({node.func.attr}) is not " + f"supported for complex numbers", node) + return + self.pushValue(math.TanOp(value).result) + return + if node.func.attr == 'asin': + if ComplexType.isinstance(value.type): + self.emitFatalError( + f"numpy call ({node.func.attr}) is not " + f"supported for complex numbers", node) + return + self.pushValue(math.AsinOp(value).result) + return + if node.func.attr == 'acos': + if ComplexType.isinstance(value.type): + self.emitFatalError( + f"numpy call ({node.func.attr}) is not " + f"supported for complex numbers", node) + return + self.pushValue(math.AcosOp(value).result) + return + if node.func.attr == 'atan': + if ComplexType.isinstance(value.type): + self.emitFatalError( + f"numpy call ({node.func.attr}) is not " + f"supported for complex numbers", node) + return + self.pushValue(math.AtanOp(value).result) + return + if node.func.attr == 'log': + if ComplexType.isinstance(value.type): + self.emitFatalError( + f"numpy call ({node.func.attr}) is not " + f"supported for complex numbers", node) + return + self.pushValue(math.LogOp(value).result) + return self.emitFatalError( f"unsupported NumPy call ({node.func.attr})", node) diff --git a/python/tests/kernel/test_kernel_float.py b/python/tests/kernel/test_kernel_float.py index db0c2a166a0..251e4d2accb 100644 --- a/python/tests/kernel/test_kernel_float.py +++ b/python/tests/kernel/test_kernel_float.py @@ -310,3 +310,75 @@ def check(c: any): check([np.float32(np.pi / 2), 0]) check([1, 0]) check([np.pi / 2, 0, True]) + + +def test_math_functions(): + """Test that math functions can be used inside kernels and match host numpy.""" + + @cudaq.kernel + def f_sin() -> np.float64: + return np.sin(np.float64(0.5)) + + assert is_close(np.sin(0.5), f_sin()) + + @cudaq.kernel + def f_cos() -> np.float64: + return np.cos(np.float64(0.5)) + + assert is_close(np.cos(0.5), f_cos()) + + @cudaq.kernel + def f_tan() -> np.float64: + return np.tan(np.float64(0.5)) + + assert is_close(np.tan(0.5), f_tan()) + + @cudaq.kernel + def f_asin() -> np.float64: + return np.asin(np.float64(0.5)) + + assert is_close(np.arcsin(0.5), f_asin()) + + @cudaq.kernel + def f_acos() -> np.float64: + return np.acos(np.float64(0.5)) + + assert is_close(np.arccos(0.5), f_acos()) + + @cudaq.kernel + def f_atan() -> np.float64: + return np.atan(np.float64(0.5)) + + assert is_close(np.arctan(0.5), f_atan()) + + @cudaq.kernel + def f_sqrt() -> np.float64: + return np.sqrt(np.float64(0.5)) + + assert is_close(np.sqrt(0.5), f_sqrt()) + + @cudaq.kernel + def f_exp() -> np.float64: + return np.exp(np.float64(0.5)) + + assert is_close(np.exp(0.5), f_exp()) + + @cudaq.kernel + def f_log() -> np.float64: + return np.log(np.float64(0.5)) + + assert is_close(np.log(0.5), f_log()) + + +def test_math_function_as_gate_parameter(): + """Test that a math function result can be used as a gate parameter.""" + + @cudaq.kernel + def prep(p: float): + q = cudaq.qubit() + ry(2.0 * np.asin(np.sqrt(p)), q) + + cudaq.set_random_seed(13) + p = 0.3 + counts = cudaq.sample(prep, p, shots_count=20000) + assert abs(counts.probability('1') - p) < 0.02 diff --git a/test/AST-Quake/math_functions.cpp b/test/AST-Quake/math_functions.cpp new file mode 100644 index 00000000000..5fd33685ef2 --- /dev/null +++ b/test/AST-Quake/math_functions.cpp @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// RUN: cudaq-quake %s | cudaq-opt | FileCheck %s + +#include +#include + +// Standard math functions are usable inside kernels and lower to the matching +// MLIR math dialect ops. The angle is a runtime parameter so the calls are not +// constant-folded away. + +struct MathFunctions { + void operator()(double x) __qpu__ { + cudaq::qubit q; + rx(std::sin(x), q); + rx(std::cos(x), q); + rx(std::tan(x), q); + rx(std::asin(x), q); + rx(std::acos(x), q); + rx(std::atan(x), q); + rx(std::sqrt(x), q); + rx(std::exp(x), q); + rx(std::log(x), q); + } +}; + +struct MathFunctionsFloat { + void operator()(float x) __qpu__ { + cudaq::qubit q; + rx(sinf(x), q); + rx(cosf(x), q); + rx(tanf(x), q); + rx(asinf(x), q); + rx(acosf(x), q); + rx(atanf(x), q); + rx(sqrtf(x), q); + rx(expf(x), q); + rx(logf(x), q); + } +}; + +// CHECK-LABEL: func.func @__nvqpp__mlirgen__MathFunctions +// CHECK-DAG: math.sin +// CHECK-DAG: math.cos +// CHECK-DAG: math.tan +// CHECK-DAG: math.asin +// CHECK-DAG: math.acos +// CHECK-DAG: math.atan +// CHECK-DAG: math.sqrt +// CHECK-DAG: math.exp +// CHECK-DAG: math.log +// CHECK: quake.rx + +// CHECK-LABEL: func.func @__nvqpp__mlirgen__MathFunctionsFloat +// CHECK-DAG: math.sin +// CHECK-DAG: math.cos +// CHECK-DAG: math.tan +// CHECK-DAG: math.asin +// CHECK-DAG: math.acos +// CHECK-DAG: math.atan +// CHECK-DAG: math.sqrt +// CHECK-DAG: math.exp +// CHECK-DAG: math.log +// CHECK: quake.rx From 3ffd148a9b9d868d153821db17462c8946fa427c Mon Sep 17 00:00:00 2001 From: Lakshikka Sithamparanathan <2922185@vikes.csuohio.edu> Date: Mon, 8 Jun 2026 17:40:57 +0000 Subject: [PATCH 2/4] Apply yapf formatting to ast_bridge.py Signed-off-by: Lakshikka Sithamparanathan <2922185@vikes.csuohio.edu> --- python/cudaq/kernel/ast_bridge.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 0baeea19195..053b3a8260c 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -815,8 +815,10 @@ def isArithmeticType(self, type): type) or F32Type.isinstance(type) or ComplexType.isinstance(type) def __isSupportedNumpyFunction(self, id): - return id in ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', - 'sqrt', 'ceil', 'exp', 'log'] + return id in [ + 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sqrt', 'ceil', 'exp', + 'log' + ] def __isSupportedVectorFunction(self, id): return id in ['front', 'back', 'append'] From 41c15fd588bf6bae79719e70b69d02fc3dd53455 Mon Sep 17 00:00:00 2001 From: Lakshikka Sithamparanathan <2922185@vikes.csuohio.edu> Date: Tue, 9 Jun 2026 13:11:09 +0000 Subject: [PATCH 3/4] Add NumPy arcsin/arccos/arctan support to Python kernel frontend Signed-off-by: Lakshikka Sithamparanathan <2922185@vikes.csuohio.edu> --- python/cudaq/kernel/ast_bridge.py | 10 +++++----- python/tests/kernel/test_kernel_float.py | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 053b3a8260c..6baf313c0f1 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -816,8 +816,8 @@ def isArithmeticType(self, type): def __isSupportedNumpyFunction(self, id): return id in [ - 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sqrt', 'ceil', 'exp', - 'log' + 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'arcsin', 'arccos', + 'arctan', 'sqrt', 'ceil', 'exp', 'log' ] def __isSupportedVectorFunction(self, id): @@ -3546,7 +3546,7 @@ def bodyBuilder(iterVar): return self.pushValue(math.TanOp(value).result) return - if node.func.attr == 'asin': + if node.func.attr in ('asin', 'arcsin'): if ComplexType.isinstance(value.type): self.emitFatalError( f"numpy call ({node.func.attr}) is not " @@ -3554,7 +3554,7 @@ def bodyBuilder(iterVar): return self.pushValue(math.AsinOp(value).result) return - if node.func.attr == 'acos': + if node.func.attr in ('acos', 'arccos'): if ComplexType.isinstance(value.type): self.emitFatalError( f"numpy call ({node.func.attr}) is not " @@ -3562,7 +3562,7 @@ def bodyBuilder(iterVar): return self.pushValue(math.AcosOp(value).result) return - if node.func.attr == 'atan': + if node.func.attr in ('atan', 'arctan'): if ComplexType.isinstance(value.type): self.emitFatalError( f"numpy call ({node.func.attr}) is not " diff --git a/python/tests/kernel/test_kernel_float.py b/python/tests/kernel/test_kernel_float.py index 251e4d2accb..366b02b7f1e 100644 --- a/python/tests/kernel/test_kernel_float.py +++ b/python/tests/kernel/test_kernel_float.py @@ -334,22 +334,22 @@ def f_tan() -> np.float64: assert is_close(np.tan(0.5), f_tan()) @cudaq.kernel - def f_asin() -> np.float64: - return np.asin(np.float64(0.5)) + def f_arcsin() -> np.float64: + return np.arcsin(np.float64(0.5)) - assert is_close(np.arcsin(0.5), f_asin()) + assert is_close(np.arcsin(0.5), f_arcsin()) @cudaq.kernel - def f_acos() -> np.float64: - return np.acos(np.float64(0.5)) + def f_arccos() -> np.float64: + return np.arccos(np.float64(0.5)) - assert is_close(np.arccos(0.5), f_acos()) + assert is_close(np.arccos(0.5), f_arccos()) @cudaq.kernel - def f_atan() -> np.float64: - return np.atan(np.float64(0.5)) + def f_arctan() -> np.float64: + return np.arctan(np.float64(0.5)) - assert is_close(np.arctan(0.5), f_atan()) + assert is_close(np.arctan(0.5), f_arctan()) @cudaq.kernel def f_sqrt() -> np.float64: From 84e441c0af4d0abf81426bfbfd82288de8f430e8 Mon Sep 17 00:00:00 2001 From: Lakshikka Sithamparanathan <2922185@vikes.csuohio.edu> Date: Wed, 10 Jun 2026 19:31:50 +0000 Subject: [PATCH 4/4] Add np.float32 math function tests and rename float64 test Signed-off-by: Lakshikka Sithamparanathan <2922185@vikes.csuohio.edu> --- python/tests/kernel/test_kernel_float.py | 62 +++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/python/tests/kernel/test_kernel_float.py b/python/tests/kernel/test_kernel_float.py index 366b02b7f1e..5142c0647ea 100644 --- a/python/tests/kernel/test_kernel_float.py +++ b/python/tests/kernel/test_kernel_float.py @@ -312,8 +312,8 @@ def check(c: any): check([np.pi / 2, 0, True]) -def test_math_functions(): - """Test that math functions can be used inside kernels and match host numpy.""" +def test_math_functions_float64(): + """Test that math functions can be used inside kernels and match host numpy (float64).""" @cudaq.kernel def f_sin() -> np.float64: @@ -370,6 +370,64 @@ def f_log() -> np.float64: assert is_close(np.log(0.5), f_log()) +def test_math_functions_float32(): + """Test that math functions can be used inside kernels and match host numpy (float32).""" + + @cudaq.kernel + def f_sin() -> np.float32: + return np.sin(np.float32(0.5)) + + assert is_close(np.sin(np.float32(0.5)), f_sin()) + + @cudaq.kernel + def f_cos() -> np.float32: + return np.cos(np.float32(0.5)) + + assert is_close(np.cos(np.float32(0.5)), f_cos()) + + @cudaq.kernel + def f_tan() -> np.float32: + return np.tan(np.float32(0.5)) + + assert is_close(np.tan(np.float32(0.5)), f_tan()) + + @cudaq.kernel + def f_arcsin() -> np.float32: + return np.arcsin(np.float32(0.5)) + + assert is_close(np.arcsin(np.float32(0.5)), f_arcsin()) + + @cudaq.kernel + def f_arccos() -> np.float32: + return np.arccos(np.float32(0.5)) + + assert is_close(np.arccos(np.float32(0.5)), f_arccos()) + + @cudaq.kernel + def f_arctan() -> np.float32: + return np.arctan(np.float32(0.5)) + + assert is_close(np.arctan(np.float32(0.5)), f_arctan()) + + @cudaq.kernel + def f_sqrt() -> np.float32: + return np.sqrt(np.float32(0.5)) + + assert is_close(np.sqrt(np.float32(0.5)), f_sqrt()) + + @cudaq.kernel + def f_exp() -> np.float32: + return np.exp(np.float32(0.5)) + + assert is_close(np.exp(np.float32(0.5)), f_exp()) + + @cudaq.kernel + def f_log() -> np.float32: + return np.log(np.float32(0.5)) + + assert is_close(np.log(np.float32(0.5)), f_log()) + + def test_math_function_as_gate_parameter(): """Test that a math function result can be used as a gate parameter."""