Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Select specialization at runtime & support broadcasting

  • Loading branch information...
commit b8ab0f3a2f17db4aab5850d8f03ad2e4c374e834 1 parent f4496bc
@markflorisson authored
View
4 Cython/Compiler/CythonScope.py
@@ -127,6 +127,10 @@ def create_cython_scope(context):
# it across different contexts)
return CythonScope(context)
+def get_cython_scope(env):
+ "Return the CythonScope given an env in some context"
+ return env.global_scope().context.cython_scope
+
# Load test utilities for the cython scope
def load_testscope_utility(cy_util_name, **kwargs):
View
4 Cython/Compiler/ExprNodes.py
@@ -7713,7 +7713,9 @@ def infer_type(self, env):
return self.get_cython_array_type(env)
def get_cython_array_type(self, env):
- return env.global_scope().context.cython_scope.viewscope.lookup("array").type
+ from Cython.Compiler import CythonScope
+ cython_scope = CythonScope.get_cython_scope(env)
+ return cython_scope.viewscope.lookup("array").type
def generate_result_code(self, code):
import Buffer
View
249 Cython/Compiler/Vector.py
@@ -1,6 +1,8 @@
+import copy
+
from Cython.Compiler import (ExprNodes, Nodes, PyrexTypes, Visitor,
- Code, Naming)
-from Cython.Compiler.Errors import error
+ Code, Naming, MemoryView, Errors)
+from Cython.Compiler.Errors import error, CompileError
from Cython.minivect import miniast
from Cython.minivect import minitypes
@@ -38,7 +40,7 @@ def map_type(self, type, wrap=False):
class CythonSpecializerMixin(object):
def visit_NodeWrapper(self, node):
- for op in node.cython_ops:
+ for op in node.operands:
op.variable = self.visit(op.variable)
return node
@@ -70,6 +72,9 @@ def visit_FunctionNode(self, node):
'__Pyx_RefNannySetupContext("%s", 1);' % node.mangled_name)
def visit_NodeWrapper(self, node):
+ for operand in node.operands:
+ operand.codegen = self
+
node = node.opaque_node
code = create_hybrid_code(self, self.code)
@@ -105,25 +110,10 @@ def visit_NodeWrapper(self, node):
class Context(miniast.CContext):
- #codegen_cls = CCodeGen
+ codegen_cls = CCodeGen
cleanup_codegen_cls = CCodeGenCleanup
specializer_mixin_cls = CythonSpecializerMixin
- def __init__(self, astbuilder=None, typemapper=None):
- super(Context, self).__init__(astbuilder, typemapper)
- # [OperandNode]
- self.cython_operand_nodes = []
-
- def codegen_cls(self, _, codewriter):
- """
- Monkeypatch all OperandNodes to have a codegen attribute, so they
- can generate code for the miniast they wrap.
- """
- codegen = CCodeGen(self, codewriter)
- for node in self.cython_operand_nodes:
- node.codegen = codegen
- return codegen
-
def getchildren(self, node):
return node.child_attrs
@@ -215,16 +205,85 @@ def result(self):
return ""
def generate_result_code(self, code):
- function = self.function
-
- if self.all_contig:
- specializer = specializers.ContigSpecializer
- else:
- specializer = specializers.StridedSpecializer
+ specializer_transforms = [
+ specializers.ContigSpecializer,
+ specializers.StridedSpecializer,
+ ]
self.context.original_cython_code = code
- codes = self.context.run(function, [specializer])
- (specialized_function, codewriter, (proto, impl)), = codes
+ codes = self.context.run(self.function, specializer_transforms)
+
+ self.temps = []
+
+ code.begin_block()
+ # Initialize a dest_shape and broadcasting variable
+ ndim = self.function.ndim
+ ones = ",".join("1" for i in range(ndim))
+ shape_temp = "__pyx_shape_temp"
+ broadcast_temp = "__pyx_broadcasting"
+ code.putln("Py_ssize_t %s[%d] = { %s };" % (shape_temp, ndim, ones))
+ code.putln("int %s = 0;" % broadcast_temp)
+
+ # broadcast all array operands
+ for idx, operand in enumerate(self.operands):
+ if operand.type.is_memoryviewslice:
+ self.broadcast(code, shape_temp, broadcast_temp, operand, ndim)
+
+ if_guard = "if"
+ for result in codes:
+ specializer = iter(result).next()
+ condition = self.condition(specializer, broadcast_temp)
+ if condition:
+ code.putln("%s (%s) {" % (if_guard, condition))
+ if_guard = " elif"
+ else:
+ code.putln(" else {")
+
+ self.put_specialized_call(code, shape_temp, broadcast_temp, *result)
+ code.put("}")
+
+ code.putln("")
+ code.end_block()
+
+ for temp in self.temps:
+ code.funcstate.release_temp(temp)
+
+ def broadcast(self, code, shape_temp, broadcast_temp, operand, ndim):
+ strides_temp = code.funcstate.allocate_temp(PyrexTypes.CArrayType(
+ PyrexTypes.c_py_ssize_t_type, operand.type.ndim), False)
+ for i in range(operand.type.ndim):
+ code.putln("%s[%d] = 0;" % (strides_temp, i))
+
+ self.temps.append(strides_temp)
+ # cdef void broadcast(Py_ssize_t *dst_shape, Py_ssize_t *dst_strides,
+ # int max_ndim, int ndim,
+ # Py_ssize_t *input_shape, Py_ssize_t *input_strides,
+ # bint *p_broadcast) nogil:
+ code.putln("__pyx_memoryview_broadcast(&%s[0], &%s[0], "
+ "%d, %d, "
+ "&%s.shape[0], &%s.strides[0],"
+ "&%s);" %
+ (shape_temp, strides_temp,
+ ndim, operand.type.ndim,
+ operand.result(), operand.result(),
+ broadcast_temp))
+
+ def condition(self, specializer, broadcast_temp):
+ if specializer.is_contig_specializer:
+ if not self.all_contig:
+ # todo: implement a memoryview flag to quickly check whether
+ # it is contig for each operand
+ return "0"
+ return "!%s" % broadcast_temp
+
+ def put_specialized_call(self, code, shape_temp, broadcast_temp,
+ specializer, specialized_function,
+ codewriter, result_code):
+ proto, impl = result_code
+
+ function = self.function
+ ndim = function.ndim
+
utility = Code.UtilityCode(proto=proto, impl=impl)
code.globalstate.use_utility_code(utility)
@@ -235,26 +294,17 @@ def generate_result_code(self, code):
print marker, 'impl', marker
print impl
- array_type = PyrexTypes.c_array_type(PyrexTypes.c_py_ssize_t_type,
- function.ndim)
- shape = code.funcstate.allocate_temp(array_type, manage_ref=False)
- for i in range(function.ndim):
- code.putln("%s[%d] = 0;" % (shape, i))
-
- args = ["&%s[0]" % shape, "&%s" % Naming.filename_cname,
+ # all function call arguments
+ args = ["&%s[0]" % shape_temp, "&%s" % Naming.filename_cname,
"&%s" % Naming.lineno_cname, "NULL"]
- for operand in self.operands:
+
+ # broadcast all array operands
+ for idx, operand in enumerate(self.operands):
result = operand.result()
if operand.type.is_memoryviewslice:
- for i in range(function.ndim):
- code.putln("if (%s.shape[%d] > %s[%d]) {" % (result, i, shape, i))
- code.putln( "%s[%d] = %s.shape[%d];" % (shape, i, result, i))
- code.putln("}")
-
- tp = operand.type.dtype.declaration_code("")
- args.append('(%s *) %s.data' % (tp, result))
- #args.append('&%s.shape[0]' % result)
- if not self.all_contig:
+ dtype_pointer_decl = operand.type.dtype.declaration_code("")
+ args.append('(%s *) %s.data' % (dtype_pointer_decl, result))
+ if not specializer.is_contig_specializer:
args.append("&%s.strides[0]" % result)
else:
args.append(result)
@@ -264,9 +314,13 @@ def generate_result_code(self, code):
code.funcstate.use_label(lbl)
code.putln("if (unlikely(%s < 0)) { goto %s; }" % (call, lbl))
- code.funcstate.release_temp(shape)
def need_wrapper_node(type):
+ """
+ Return whether a Cython node that needs to be mapped to a miniast Node,
+ should be mapped or wrapped (i.e., should minivect or Cython generate
+ the code to evaluate the expression?).
+ """
while True:
if type.is_ptr:
type = type.base_type
@@ -278,7 +332,36 @@ def need_wrapper_node(type):
type = type.resolve()
return type.is_pyobject or type.is_complex
+def get_dtype(type):
+ if type.is_memoryviewslice:
+ return type.dtype
+ return type
+
+class CythonASTInMiniastTransform(Visitor.VisitorTransform):
+
+ def __init__(self, env):
+ super(CythonASTInMiniastTransform, self).__init__()
+ self.env = env
+ self.operands = []
+
+ def visit_BinopNode(self, node):
+ dtype = get_dtype(node.type)
+ node = type(node)(node.pos, type=dtype, operator=node.operator,
+ operand1=self.visit(node.operand1),
+ operand2=self.visit(node.operand2))
+ node.analyse_types(self.env)
+ return node
+
+ def visit_ExprNode(self, node):
+ node = OperandNode(node.pos, type=get_dtype(node.type), node=node)
+ self.operands.append(node)
+ return node
+
class ElementalMapper(specializers.ASTMapper):
+ """
+ When some elementwise expression is found in the Cython AST, convert that
+ tree to a minivect AST.
+ """
wrapping = 0
@@ -289,9 +372,6 @@ def __init__(self, context, env):
self.operands = []
# miniast function arguments to the function
self.funcargs = []
- # All OperandNodes founds in the Cython AST held by a
- # miniast.NodeWrapper
- self.cython_ops = []
self.error = False
def map_type(self, node, **kwds):
@@ -302,17 +382,13 @@ def map_type(self, node, **kwds):
"operation: %s" % (node.type,))
raise
- def get_dtype(self, type):
- if type.is_memoryviewslice:
- return type.dtype
- return type
-
def register_operand(self, node):
"""
Register a non-elemental subexpression, and pass it in to the function
we are generating as an argument.
"""
assert not node.is_elemental
+
b = self.astbuilder
node = node.coerce_to_simple(self.env)
@@ -326,42 +402,37 @@ def register_operand(self, node):
funcarg = b.funcarg(b.variable(minitype, varname))
self.funcargs.append(funcarg)
-
- if self.wrapping:
- # we are inside a Cython AST, return something compatible
- result = OperandNode(node.pos, type=self.get_dtype(node.type),
- variable=funcarg.variable)
- self.context.cython_operand_nodes.append(result)
- self.cython_ops.append(result)
- return result
- else:
- # we are in a miniast
- return funcarg.variable
+ return funcarg.variable
def register_wrapper_node(self, node):
- if not node.is_elemental:
- return self.register_operand(node)
+ """
+ Create a miniast.NodeWrapper for functionality that Cython provides,
+ but that we want to use inside miniast expressions.
+ """
+ assert node.is_elemental
- self.wrapping += 1
- self.visitchildren(node)
- self.wrapping -= 1
+ transform = CythonASTInMiniastTransform(self.env)
+ try:
- if self.wrapping == 0:
- dtype = node.type
- if dtype.is_memoryviewslice:
- dtype = dtype.dtype
+ node = transform.visit(node)
+ except CompileError, e:
+ error(e.position, e.message_only)
+ return None
- node = type(node)(node.pos, type=dtype, operator=node.operator,
- operand1=node.operand1, operand2=node.operand2)
- node.analyse_types(self.env)
+ for operand in transform.operands:
+ operand.variable = self.register_operand(operand.node)
- result = self.astbuilder.wrap(node, cython_ops=self.cython_ops)
- self.cython_ops = []
- return result
- else:
- return node
+ def specialize_node(nodewrapper, memo):
+ return copy.deepcopy(node, memo)
+
+ return self.astbuilder.wrap(node, specialize_node,
+ operands=transform.operands)
def visit_ExprNode(self, node):
+ """
+ Some expression which cannot be converted to a miniast, but is passed
+ in as an argument to the function generated from the miniast.
+ """
return self.register_operand(node)
def visit_SingleAssignmentNode(self, node):
@@ -378,6 +449,12 @@ def visit_BinopNode(self, node):
return self.astbuilder.binop(minitype, node.operator, op1, op2)
class ElementWiseOperationsTransform(Visitor.EnvTransform):
+ """
+ Find elementwise expressions and run ElementalMapper to turn it into
+ a minivect AST. Our Cython tree ends here in an ElementalNode, which
+ responsibility is to call the function generated by minivect (as well
+ as to perform broadcasting and selection of the right specialization).
+ """
in_elemental = 0
@@ -393,6 +470,8 @@ def visit_elemental(self, node):
self.in_elemental -= 1
if not self.in_elemental:
+ load_utilities(self.current_env())
+
b = self.minicontext.astbuilder
b.pos = node.pos
astmapper = ElementalMapper(self.minicontext, self.current_env())
@@ -441,3 +520,11 @@ def visit_SingleAssignmentNode(self, node):
self.visitchildren(node)
return node
+
+def load_utilities(env):
+ from Cython.Compiler import CythonScope
+ cython_scope = CythonScope.get_cython_scope(env)
+ broadcast_utility.declare_in_scope(cython_scope.viewscope,
+ cython_scope=cython_scope, used=True)
+
+broadcast_utility = MemoryView.load_memview_cy_utility("Broadcasting")
View
50 Cython/Utility/MemoryView.pyx
@@ -79,6 +79,13 @@ cdef extern from *:
{{memviewslice_name}} *slice2,
int ndim, size_t itemsize) nogil
+ bint broadcast "__pyx_memoryview_broadcast" (
+ Py_ssize_t *dst_shape, Py_ssize_t *dst_strides,
+ int max_ndim, int ndim,
+ Py_ssize_t *input_shape, Py_ssize_t *input_strides) nogil
+ cdef int _err_extents "__pyx_memoryview_err_extents" (
+ int i, Py_ssize_t extent1,
+ Py_ssize_t extent2) except -1 with gil
cdef extern from "stdlib.h":
void *malloc(size_t) nogil
@@ -86,8 +93,6 @@ cdef extern from "stdlib.h":
void *memcpy(void *dest, void *src, size_t n) nogil
-
-
#
### cython.array class
#
@@ -1206,12 +1211,6 @@ cdef void *copy_data_to_temp({{memviewslice_name}} *src,
# Use 'with gil' functions and avoid 'with gil' blocks, as the code within the blocks
# has temporaries that need the GIL to clean up
-@cname('__pyx_memoryview_err_extents')
-cdef int _err_extents(int i, Py_ssize_t extent1,
- Py_ssize_t extent2) except -1 with gil:
- raise ValueError("got differing extents in dimension %d (got %d and %d)" %
- (i, extent1, extent2))
-
@cname('__pyx_memoryview_err_dim')
cdef int _err_dim(object error, char *msg, int dim) except -1 with gil:
raise error(msg.decode('ascii') % dim)
@@ -1379,6 +1378,41 @@ cdef void _slice_assign_scalar(char *data, Py_ssize_t *shape,
ndim - 1, itemsize, item)
data += stride
+############## Broadcasting ###############
+# This utility is used to broadcast multiple operands in a vector expression.
+# The caller allocates a destination shape that all operands share, and
+# passes in the input shape and strides for each operand. The destination
+# strides are set accordingly for each operand as well as the output shape,
+# or an exception is raised in case of a mismatch.
+
+@cname('__pyx_memoryview_err_extents')
+cdef int __pyx_err_extents(int i, Py_ssize_t extent1,
+ Py_ssize_t extent2) except -1 with gil:
+ raise ValueError("got differing extents in dimension %d (got %d and %d)" %
+ (i, extent1, extent2))
+
+@cname('__pyx_memoryview_broadcast')
+cdef void __pyx_broadcast(Py_ssize_t *dst_shape, Py_ssize_t *dst_strides,
+ int max_ndim, int ndim,
+ Py_ssize_t *input_shape, Py_ssize_t *input_strides,
+ bint *p_broadcast) nogil:
+ cdef Py_ssize_t i
+ cdef int dim_offset = max_ndim - ndim
+
+ for i in range(ndim):
+ src_extent = input_shape[i]
+ dst_extent = dst_shape[i + dim_offset]
+
+ dst_strides[i] = input_strides[i]
+
+ if src_extent != dst_extent:
+ if src_extent == 1:
+ p_broadcast[0] = True
+ dst_strides[i] = 0
+ elif dst_extent == 1:
+ dst_shape[i + dim_offset] = src_extent
+ else:
+ __pyx_err_extents(i, dst_shape[i], input_shape[i])
############### BufferFormatFromTypeInfo ###############
cdef extern from *:
View
138 tests/run/elementwise.pyx
@@ -4,6 +4,22 @@
cimport numpy as np
import numpy as np
+__test__ = {}
+
+def testcase(f):
+ assert f.__doc__, f
+ __test__[f.__name__] = f.__doc__
+ return f
+
+def testcase_like(similar_func):
+ def decorator(wrapper_func):
+ assert similar_func.__doc__
+ wrapper_func.__doc__ = similar_func.__doc__.replace(
+ similar_func.__name__, wrapper_func.__name__)
+ return testcase(wrapper_func)
+ return decorator
+
+@testcase
def test_simple_binop_assign(int[:] m):
"""
>>> a = np.arange(10, dtype='i')
@@ -13,6 +29,7 @@ def test_simple_binop_assign(int[:] m):
m[:] = m * m
return m
+@testcase
def test_simple_binop_assign_contig(int[::1] m):
"""
>>> a = np.arange(10, dtype='i')
@@ -22,6 +39,7 @@ def test_simple_binop_assign_contig(int[::1] m):
m[:] = m * m
return m
+@testcase
def test_simple_binop_assign_2d(int[:, :] m):
"""
>>> a2d = np.arange(100, dtype='i').reshape(10, 10)
@@ -40,6 +58,7 @@ def test_simple_binop_assign_2d(int[:, :] m):
m[:] = m + m + m
return m
+@testcase
def test_simple_binop_assign_contig_2d(int[:, :] m):
"""
>>> a2d = np.arange(100, dtype='i').reshape(10, 10)
@@ -58,6 +77,7 @@ def test_simple_binop_assign_contig_2d(int[:, :] m):
m[:] = m + m + m
return m
+@testcase
def test_typedef(np.int32_t[:] m):
"""
>>> np.asarray(test_typedef(np.arange(10, dtype=np.int32)))
@@ -72,17 +92,121 @@ cdef fused fused_dtype_t:
double complex
object
-def test_arbitrary_dtypes(fused_dtype_t[:] m):
+@testcase
+def test_arbitrary_dtypes(fused_dtype_t[:] m1, fused_dtype_t[::1] m2):
"""
- >>> test_arbitrary_dtypes(np.arange(10, dtype='l'))
+ >>> def operands(dtype):
+ ... return np.arange(10, dtype=dtype), np.arange(10, dtype=dtype)
+ ...
+
+ >>> test_arbitrary_dtypes(*operands('l'))
array([ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27])
- >>> test_arbitrary_dtypes(np.arange(10, dtype=np.longdouble))
+
+ >>> test_arbitrary_dtypes(*operands(dtype=np.longdouble))
array([ 0.0, 3.0, 6.0, 9.0, 12.0, 15.0, 18.0, 21.0, 24.0, 27.0], dtype=float128)
- >>> test_arbitrary_dtypes(np.arange(10, dtype=np.complex128) + 1.2j)
+
+ >>> ops = operands(np.complex128)
+ >>> test_arbitrary_dtypes(ops[0] + 1.2j, ops[1] + 1.2j)
array([ 0.+3.6j, 3.+3.6j, 6.+3.6j, 9.+3.6j, 12.+3.6j, 15.+3.6j,
18.+3.6j, 21.+3.6j, 24.+3.6j, 27.+3.6j])
- >>> test_arbitrary_dtypes(np.arange(10, dtype=np.object))
+
+ >>> test_arbitrary_dtypes(*operands(dtype=np.object))
array([0, 3, 6, 9, 12, 15, 18, 21, 24, 27], dtype=object)
"""
- m[:] = m + m + m
- return np.asarray(m)
+ m1[:] = m1 + m1 + m1
+ m2[:] = m2 + m2 + m2
+ assert np.all(np.asarray(m1) == np.asarray(m2)) or np.allclose(m1, m2)
+ return np.asarray(m1)
+
+class UniqueObject(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __add__(self, other):
+ return UniqueObject(self.value + other.value)
+
+ def __eq__(self, other):
+ return self.value == other.value
+
+ def __str__(self):
+ return str(self.value)
+
+class UniqueObjectInplace(UniqueObject):
+ def __add__(self, other):
+ self.value += other.value
+ return self
+
+def object_range(n, shape=None, cls=UniqueObject):
+ result = np.array([cls(i) for i in range(n)], dtype=np.object)
+ if shape:
+ result = result.reshape(shape)
+ return result
+
+def test_broadcasting(fused_dtype_t[:] m1, fused_dtype_t[:, :] m2):
+ """
+ >>> def operands(dtype):
+ ... return np.arange(10, dtype=dtype), np.arange(100, dtype=dtype).reshape(10, 10)
+ ...
+
+ >>> test_broadcasting(*operands('l'))
+ array([[ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18],
+ [ 10, 12, 14, 16, 18, 20, 22, 24, 26, 28],
+ [ 20, 22, 24, 26, 28, 30, 32, 34, 36, 38],
+ [ 30, 32, 34, 36, 38, 40, 42, 44, 46, 48],
+ [ 40, 42, 44, 46, 48, 50, 52, 54, 56, 58],
+ [ 50, 52, 54, 56, 58, 60, 62, 64, 66, 68],
+ [ 60, 62, 64, 66, 68, 70, 72, 74, 76, 78],
+ [ 70, 72, 74, 76, 78, 80, 82, 84, 86, 88],
+ [ 80, 82, 84, 86, 88, 90, 92, 94, 96, 98],
+ [ 90, 92, 94, 96, 98, 100, 102, 104, 106, 108]])
+
+ >>> test_broadcasting(*operands(np.complex128))
+ array([[ 0.+0.j, 2.+0.j, 4.+0.j, 6.+0.j, 8.+0.j, 10.+0.j,
+ 12.+0.j, 14.+0.j, 16.+0.j, 18.+0.j],
+ [ 10.+0.j, 12.+0.j, 14.+0.j, 16.+0.j, 18.+0.j, 20.+0.j,
+ 22.+0.j, 24.+0.j, 26.+0.j, 28.+0.j],
+ [ 20.+0.j, 22.+0.j, 24.+0.j, 26.+0.j, 28.+0.j, 30.+0.j,
+ 32.+0.j, 34.+0.j, 36.+0.j, 38.+0.j],
+ [ 30.+0.j, 32.+0.j, 34.+0.j, 36.+0.j, 38.+0.j, 40.+0.j,
+ 42.+0.j, 44.+0.j, 46.+0.j, 48.+0.j],
+ [ 40.+0.j, 42.+0.j, 44.+0.j, 46.+0.j, 48.+0.j, 50.+0.j,
+ 52.+0.j, 54.+0.j, 56.+0.j, 58.+0.j],
+ [ 50.+0.j, 52.+0.j, 54.+0.j, 56.+0.j, 58.+0.j, 60.+0.j,
+ 62.+0.j, 64.+0.j, 66.+0.j, 68.+0.j],
+ [ 60.+0.j, 62.+0.j, 64.+0.j, 66.+0.j, 68.+0.j, 70.+0.j,
+ 72.+0.j, 74.+0.j, 76.+0.j, 78.+0.j],
+ [ 70.+0.j, 72.+0.j, 74.+0.j, 76.+0.j, 78.+0.j, 80.+0.j,
+ 82.+0.j, 84.+0.j, 86.+0.j, 88.+0.j],
+ [ 80.+0.j, 82.+0.j, 84.+0.j, 86.+0.j, 88.+0.j, 90.+0.j,
+ 92.+0.j, 94.+0.j, 96.+0.j, 98.+0.j],
+ [ 90.+0.j, 92.+0.j, 94.+0.j, 96.+0.j, 98.+0.j, 100.+0.j,
+ 102.+0.j, 104.+0.j, 106.+0.j, 108.+0.j]])
+
+ >>> result1 = test_broadcasting(object_range(10), object_range(100, (10, 10)))
+ >>> result1
+ array([[0, 2, 4, 6, 8, 10, 12, 14, 16, 18],
+ [10, 12, 14, 16, 18, 20, 22, 24, 26, 28],
+ [20, 22, 24, 26, 28, 30, 32, 34, 36, 38],
+ [30, 32, 34, 36, 38, 40, 42, 44, 46, 48],
+ [40, 42, 44, 46, 48, 50, 52, 54, 56, 58],
+ [50, 52, 54, 56, 58, 60, 62, 64, 66, 68],
+ [60, 62, 64, 66, 68, 70, 72, 74, 76, 78],
+ [70, 72, 74, 76, 78, 80, 82, 84, 86, 88],
+ [80, 82, 84, 86, 88, 90, 92, 94, 96, 98],
+ [90, 92, 94, 96, 98, 100, 102, 104, 106, 108]], dtype=object)
+
+ >>> result2 = test_broadcasting(object_range(10, cls=UniqueObjectInplace),
+ ... object_range(100, (10, 10), cls=UniqueObjectInplace))
+ >>> np.all(result1 == result2)
+ True
+ """
+ m2[...] = m2 + m1
+ return np.asarray(m2)
+
+testcase(test_broadcasting)
+
+def test_broadcasting_c_contig(fused_dtype_t[::1] m1, fused_dtype_t[:, ::1] m2):
+ return test_broadcasting(m1, m2)
+
+
+testcase_like(test_broadcasting)(test_broadcasting_c_contig)
Please sign in to comment.
Something went wrong with that request. Please try again.