Commit d5c22c1f authored by Adam Procter's avatar Adam Procter

Add test generator (WIP)

parent 3c469863
......@@ -170,6 +170,7 @@ set(MULTI_TEST_SRC
backend_test.in.cpp
backend_unary_elementwise.in.cpp
convolution_test.in.cpp
dyn_replace_slice_test.in.cpp
dyn_slice_test.in.cpp
dynamic.in.cpp
)
......
This source diff could not be displayed because it is too large. You can view the blob instead.
#!/usr/bin/env python
# ******************************************************************************
# Copyright 2017-2019 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ******************************************************************************
#
# Test case generator for DynReplaceSlice op.
#
# TODO(amprocte): refactor to use parameterized gtests.
# TODO(amprocte): de-duplicate lots of code in generate_dyn_slice_ref.py.
#
import sys
import numpy as np
def make_iterable(x):
try:
_ = iter(x)
except TypeError as _:
return [x]
return x
def print_lb_values(slices):
slices = make_iterable(slices)
strs = []
for sl in slices:
try:
x = int(sl)
strs.append(str(x))
except TypeError as _:
if isinstance(sl, slice) and sl.start is not None:
strs.append(str(sl.start))
else:
strs.append('0')
return ','.join(strs)
def print_ub_values(slices):
slices = make_iterable(slices)
strs = []
for sl in slices:
if isinstance(sl, slice) and sl.stop is not None:
strs.append(str(sl.stop))
else:
strs.append('0')
return ','.join(strs)
def print_stride_values(slices):
slices = make_iterable(slices)
strs = []
for sl in slices:
if isinstance(sl, slice) and sl.step is not None:
strs.append(str(sl.step))
else:
strs.append('1')
return ','.join(strs)
def print_lb_mask_axes(slices):
slices = make_iterable(slices)
mask_strs = []
i = 0
for sl in slices:
if isinstance(sl, slice) and sl.start is None:
mask_strs.append(str(i))
i += 1
return ','.join(mask_strs)
def print_ub_mask_axes(slices):
slices = make_iterable(slices)
mask_strs = []
i = 0
for sl in slices:
if isinstance(sl, slice) and sl.stop is None:
mask_strs.append(str(i))
i += 1
return ','.join(mask_strs)
def print_new_mask_axes(slices):
slices = make_iterable(slices)
mask_strs = []
i = 0
for sl in slices:
if sl is None:
mask_strs.append(str(i))
i += 1
return ','.join(mask_strs)
def print_shrink_mask_axes(slices):
slices = make_iterable(slices)
mask_strs = []
i = 0
for sl in slices:
try:
_ = int(sl)
mask_strs.append(str(i))
except TypeError as _:
pass
i += 1
return ','.join(mask_strs)
def print_ellipsis_mask_axes(slices):
slices = make_iterable(slices)
mask_strs = []
i = 0
for sl in slices:
if sl is Ellipsis:
mask_strs.append(str(i))
i += 1
return ','.join(mask_strs)
def np_dt_to_c(dtype):
if dtype=='int8':
return 'int8_t'
elif dtype=='uint8':
return 'uint8_t'
elif dtype=='int16':
return 'int16_t'
elif dtype=='uint16':
return 'uint16_t'
elif dtype=='int32':
return 'int32_t'
elif dtype=='uint32':
return 'uint32_t'
elif dtype=='int64':
return 'int64_t'
elif dtype=='uint64':
return 'uint64_t'
elif dtype=='float16':
return 'float16'
elif dtype=='float32':
return 'float'
elif dtype=='float64':
return 'double'
elif dtype=='bool':
return 'char'
else:
raise ValueError('Unsupported numpy data type: %s' % dtype)
def np_dt_to_ng(dtype):
if dtype=='int8':
return 'element::i8'
elif dtype=='uint8':
return 'element::u8'
elif dtype=='int16':
return 'element::i16'
elif dtype=='uint16':
return 'element::u16'
elif dtype=='int32':
return 'element::i32'
elif dtype=='uint32':
return 'element::u32'
elif dtype=='int64':
return 'element::i64'
elif dtype=='uint64':
return 'element::u64'
elif dtype=='float16':
return 'element::f16'
elif dtype=='float32':
return 'element::f32'
elif dtype=='float64':
return 'element::f64'
elif dtype=='bool':
return 'element::boolean'
else:
raise ValueError('Unsupported numpy data type: %s' % dtype)
def print_values(values):
values = make_iterable(values)
strs = []
for v in values:
strs.append(str(v))
return ','.join(strs)
def print_shape(dims):
dims = make_iterable(dims)
strs = []
for d in dims:
strs.append(str(d))
return 'Shape{' + ','.join(strs) + '}'
def print_slice(sl):
if sl is None:
return 'newaxis'
elif sl is Ellipsis:
return "..."
elif isinstance(sl, slice):
s = ''
if sl.start is not None:
s += str(sl.start)
s += ':'
if sl.stop is not None:
s += str(sl.stop)
if sl.step is not None:
s += ':'
s += str(sl.step)
return s
else:
return str(sl)
def print_slices(slices):
slices = make_iterable(slices)
strs = []
for sl in slices:
strs.append(print_slice(sl))
return '[' + ','.join(strs) + ']'
#
# Class to intercept __setitem__ operations and write an nGraph C++ test case.
# The generated test case will ensure that the output is identical to what
# would be produced by numpy. Specifically, the numpy (and equivalent C++)
# it will generate a "linspaced" array of the appropriate shape and dtype, and
# attempt to overwrite it with the "value" argument of __setitem__. If the
# value is None, it will auto-generate a replacement value of the appropriate
# shape.
#
# We will attempt to catch any exceptions raised by numpy's __setitem__, and
# generate a test that checks for erroring behavio.
#
# Example usage:
#
# w = ReplaceSliceTestWriter(stream=sys.stdout)
#
# # behave as if writing into a 4x5x6 input array of data type int32
# w.set_shape(4,5,6)
# w.set_dtype('int32')
#
# # generate test cases for various behaviors, writing C++ code to sys.stdout
# w[0,:,:] = np.ones(shape=(5,6), dtype='int32')
# w[0,:,:] = None # test will auto-generate something of shape (5,6)
# w[...,-1:-3,:] = np.zeros(shape=(4,0,6), dtype='int32')
#
# # generate test cases for some erroring behaviors, writing C++ code to
# # sys.stdout
# w[1,2,3,4] = 0 # too many indices
# w[7] = np.ones(shape=(5,6)) # index out of bounds
# w[1,1] = [2] # shape mismatch between slice and
# # replacement (NOTE: this example is
# # actually legal in np because it would
# # auto-broadcast, but not in nGraph.)
#
class ReplaceSliceTestWriter:
def __init__(self, shape=(), dtype='int32', stream=sys.stdout):
self._shape = shape
self._dtype = dtype
self._stream = stream
self._test_counter = 0
def __setitem__(self, slices, value):
self.write_test(slices, value)
def write_test(self, slices, value, value_shape=None):
# Generate some linspaced input data.
data_in = np.linspace(0,np.prod(self._shape)-1,np.prod(self._shape),dtype=self._dtype).reshape(self._shape)
failure_reasons = []
if value_shape is None:
try:
slice_shape = data_in.__getitem__(slices).shape
except Exception:
failure_reasons.append('numpy getitem failed')
slice_shape = ()
if value_shape is None:
value_shape = slice_shape
# If `value` is None, we'll auto-generate some data. This will only
# work if value_shape is specified, OR if the slices are legal for the
# input.
#
# Generated value is linspaced, starting where data_in left off.
if value is None:
value = np.linspace(np.prod(self._shape), np.prod(self._shape) + np.prod(value_shape) - 1, np.prod(value_shape), dtype=self._dtype).reshape(value_shape)
else:
value = np.array(value)
# numpy allows autobroadcast of the replacement to match the slice
# shape, but we don't, so we would expect failure in that case
if value.shape != slice_shape:
failure_reasons.append('slice shape and replacement shape do not match')
self._stream.write('\n')
self._stream.write('// slices are: %s\n' % print_slices(slices))
self._stream.write('// dtype is: %s\n' % self._dtype)
self._stream.write('// input shape is: %s\n' % print_shape(self._shape))
self._stream.write('// slice shape is: %s\n' % print_shape(slice_shape))
self._stream.write('// replacement shape is: %s\n' % print_shape(value.shape))
# If numpy fails for any reason, we expect failure.
try:
data_out = data_in
data_out.__setitem__(slices, value)
except Exception:
failure_reasons.append('numpy setitem failed')
# numpy allows implicit data type conversion, but we don't, so we
# expect failure if dtypes do not match.
if value.dtype != self._dtype:
failure_reasons.append('dtype mismatch')
if failure_reasons != []:
self._stream.write('// failure is expected (%s)\n'
'NGRAPH_TEST(${BACKEND_NAME}, dyn_replace_slice_%d)\n'
'{\n'
' check_failure<%s,%s>\n'
' (%s,\n'
' %s,\n'
' %s,\n'
' %s,\n'
' std::vector<int64_t>{%s},\n'
' std::vector<int64_t>{%s},\n'
' std::vector<int64_t>{%s},\n'
' AxisSet{%s},\n'
' AxisSet{%s},\n'
' AxisSet{%s},\n'
' AxisSet{%s},\n'
' AxisSet{%s},\n'
' std::vector<%s>{%s});\n'
'}\n'
% (', '.join(failure_reasons),
self._test_counter,
np_dt_to_c(self._dtype),
np_dt_to_c(value.dtype),
np_dt_to_ng(self._dtype),
np_dt_to_ng(value.dtype),
print_shape(data_in.shape),
print_shape(value.shape),
print_lb_values(slices),
print_ub_values(slices),
print_stride_values(slices),
print_lb_mask_axes(slices),
print_ub_mask_axes(slices),
print_new_mask_axes(slices),
print_shrink_mask_axes(slices),
print_ellipsis_mask_axes(slices),
np_dt_to_c(value.dtype), print_values(value.reshape(-1))))
else:
self._stream.write('// expected output shape is %s\n'
'NGRAPH_TEST(${BACKEND_NAME}, dyn_replace_slice_%d)\n'
'{\n'
' check_success<%s>\n'
' (%s,\n'
' %s,\n'
' %s,\n'
' std::vector<int64_t>{%s},\n'
' std::vector<int64_t>{%s},\n'
' std::vector<int64_t>{%s},\n'
' AxisSet{%s},\n'
' AxisSet{%s},\n'
' AxisSet{%s},\n'
' AxisSet{%s},\n'
' AxisSet{%s},\n'
' %s,\n'
' std::vector<%s>{%s},\n'
' std::vector<%s>{%s});\n'
'}\n'
% (print_shape(data_out.shape),
self._test_counter,
np_dt_to_c(self._dtype),
np_dt_to_ng(self._dtype),
print_shape(data_in.shape),
print_shape(value.shape),
print_lb_values(slices),
print_ub_values(slices),
print_stride_values(slices),
print_lb_mask_axes(slices),
print_ub_mask_axes(slices),
print_new_mask_axes(slices),
print_shrink_mask_axes(slices),
print_ellipsis_mask_axes(slices),
print_shape(data_out.shape),
np_dt_to_c(self._dtype), print_values(data_out.reshape(-1)),
np_dt_to_c(value.dtype), print_values(value.reshape(-1))))
self._test_counter += 1
def set_shape(self,shape):
self._shape = shape
def set_dtype(self,dtype):
self._dtype = dtype
def write_header(f):
f.write('''\
//*****************************************************************************
// Copyright 2017-2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//*****************************************************************************
// !!!!!!!!!!!!!! THIS FILE IS AUTOGENERATED OUTSIDE OF THE BUILD PROCESS !!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DO NOT EDIT THIS FILE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// DO NOT EDIT THIS FILE. If you want to add new tests, you should edit
// test/ref_generators/generate_dyn_replace_slice_ref.py and regenerate this file.
//
// To regenerate:
//
// $ cd <ngraph source dir>/test
// $ ./update_dyn_replace_slice_reference.sh
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DO NOT EDIT THIS FILE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!! THIS FILE IS AUTOGENERATED OUTSIDE OF THE BUILD PROCESS !!!!!!!!!!!!!!
//
// clang-format off
#include <algorithm>
#include <cmath>
#include "gtest/gtest.h"
#include "ngraph/ngraph.hpp"
#include "util/test_tools.hpp"
#include "util/autodiff/numeric_compare.hpp"
#include "util/all_close_f.hpp"
#include "util/test_control.hpp"
using namespace std;
using namespace ngraph;
static string s_manifest = "${MANIFEST}";
template <typename Targ, typename Treplace>
void check_failure(const element::Type& input_element_type,
const element::Type& replacement_element_type,
const Shape& input_shape,
const Shape& replacement_shape,
const std::vector<int64_t>& lb_values,
const std::vector<int64_t>& ub_values,
const std::vector<int64_t>& strides_values,
const AxisSet& lb_mask,
const AxisSet& ub_mask,
const AxisSet& new_mask,
const AxisSet& shrink_mask,
const AxisSet& ellipsis_mask,
const std::vector<Treplace>& replacement_values)
{
#if 0
auto arg = std::make_shared<op::Parameter>(input_element_type, input_shape);
auto lb = std::make_shared<op::Parameter>(element::i64, Shape{lb_values.size()});
auto ub = std::make_shared<op::Parameter>(element::i64, Shape{ub_values.size()});
auto strides = std::make_shared<op::Parameter>(element::i64, Shape{strides_values.size()});
std::vector<Targ> input_values(shape_size(input_shape));
std::iota(input_values.begin(), input_values.end(), static_cast<Targ>(0));
EXPECT_ANY_THROW({
auto slice = std::make_shared<op::DynSlice>(arg, lb, ub, strides, lb_mask, ub_mask, new_mask, shrink_mask, ellipsis_mask);
auto f = std::make_shared<Function>(NodeVector{slice}, ParameterVector{arg, lb, ub, strides});
auto backend = runtime::Backend::create("${BACKEND_NAME}",true);
auto ex = backend->compile(f);
auto input_arg = backend->create_tensor(input_element_type, input_shape);
auto input_lb = backend->create_tensor(element::i64, Shape{lb_values.size()});
auto input_ub = backend->create_tensor(element::i64, Shape{ub_values.size()});
auto input_strides = backend->create_tensor(element::i64, Shape{strides_values.size()});
copy_data(input_arg, input_values);
copy_data(input_lb, lb_values);
copy_data(input_ub, ub_values);
copy_data(input_strides, strides_values);
auto output = backend->create_dynamic_tensor(input_element_type, PartialShape::dynamic());
ex->call_with_validate({output}, {input_arg, input_lb, input_ub, input_strides});
});
#endif
}
template <typename T>
void check_success(const element::Type& input_element_type,
const Shape& input_shape,
const Shape& replacement_shape,
const std::vector<int64_t>& lb_values,
const std::vector<int64_t>& ub_values,
const std::vector<int64_t>& strides_values,
const AxisSet& lb_mask,
const AxisSet& ub_mask,
const AxisSet& new_mask,
const AxisSet& shrink_mask,
const AxisSet& ellipsis_mask,
const Shape& expected_output_shape,
const std::vector<T>& expected_values,
const std::vector<T>& replacement_values)
{
auto arg = std::make_shared<op::Parameter>(input_element_type, input_shape);
auto repl = std::make_shared<op::Parameter>(input_element_type, replacement_shape);
auto lb = std::make_shared<op::Parameter>(element::i64, Shape{lb_values.size()});
auto ub = std::make_shared<op::Parameter>(element::i64, Shape{ub_values.size()});
auto strides = std::make_shared<op::Parameter>(element::i64, Shape{strides_values.size()});
std::vector<T> input_values(shape_size(input_shape));
std::iota(input_values.begin(), input_values.end(), static_cast<T>(0));
auto rsl = std::make_shared<op::DynReplaceSlice>(arg, repl, lb, ub, strides, lb_mask, ub_mask, new_mask, shrink_mask, ellipsis_mask);
auto f = std::make_shared<Function>(NodeVector{rsl}, ParameterVector{arg, repl, lb, ub, strides});
auto backend = runtime::Backend::create("${BACKEND_NAME}",true);
auto ex = backend->compile(f);
auto input_arg = backend->create_tensor(input_element_type, input_shape);
auto input_repl = backend->create_tensor(input_element_type, replacement_shape);
auto input_lb = backend->create_tensor(element::i64, Shape{lb_values.size()});
auto input_ub = backend->create_tensor(element::i64, Shape{ub_values.size()});
auto input_strides = backend->create_tensor(element::i64, Shape{strides_values.size()});
copy_data(input_arg, input_values);
copy_data(input_repl, replacement_values);
copy_data(input_lb, lb_values);
copy_data(input_ub, ub_values);
copy_data(input_strides, strides_values);
auto output = backend->create_dynamic_tensor(input_element_type, PartialShape::dynamic());
ex->call_with_validate({output}, {input_arg, input_repl, input_lb, input_ub, input_strides});
EXPECT_EQ(output->get_element_type(), input_element_type);
EXPECT_EQ(output->get_shape(), expected_output_shape);
auto output_values = read_vector<T>(output);
EXPECT_EQ(output_values, expected_values);
}
''')
def write_footer(f):
f.write('''\
// clang-format on
''')
def main():
if len(sys.argv) < 2:
sys.stderr.write('Output filename is required\n')
sys.exit(1)
f = open(sys.argv[1], 'w')
write_header(f)
t = ReplaceSliceTestWriter(stream=f)
t.set_shape((4,))
for dt in ['int32','int64','float32','uint32']:
t.set_dtype(dt)
t[np.newaxis,3:0:-1] = None
t[...] = None
t[1:3] = None
t[2] = None
t[3:0:-2] = None
t[3::-2] = None
t[4::-2] = None
t[5::-2] = None
t[-9000:-8000:2] = None
t[-9000:8000:2] = None
t[-5:5:2] = None
t[np.newaxis] = None
t[np.newaxis,np.newaxis] = None
t[np.newaxis,np.newaxis,...,np.newaxis] = None
# Some tests with incorrect replacement shapes
t[2] = np.ones(shape=(2,2), dtype=dt)
t.set_shape((5,))
t[3:0:-2] = None
t[0:3:2] = None
t[0:4:2] = None
t[0:5:2] = None
t[0:6:2] = None
t[0:100:2] = None
t[4:0:-2] = None
t[4:0:-3] = None
t[3:2:1] = None
t[4::-2] = None
#
# A couple of tests for negative-stride slicing. The issue we want to
# be on the lookout for is this:
#
# [ORIGINAL]
# 01234567
# ..1..0.. [5:0:-3] # suppose we start with this, want to convert
# _____ # to pos stride. suppose that our stride is
# # "uneven" wrt the slicing region, i.e. the
# # start-to-end distance is not an even
# # multiple of the strides (e.g. here: we get
# # elements 5 and 2.)
#
# [INCORRECT]
# 01234567
# .0..1... [1:6:3] # if we just reverse the sign of the stride
# _____ # and flip the start/end indices while
# # traversing, we will get out the wrong
# # elements. (e.g. here: we get elements 1 and
# # 4, which are not what we want.)
#
# [CORRECT]
# 01234567
# ..0..1.. [2:6:3] # the correct thing to do is to adjust the
# ____ # start of our reversed slice to be the last
# # element that is *actually* touched by the
# # original negative striding, not the
# # boundary of the region. (e.g. here: we get
# # elements 2 and 5, which are what we want.)
#
# There's some logic to do this transformation in DynElimination, but
# it feels a bit delicate.
#
t.set_shape((8,))
t[5:2:-3] = None
t[5:1:-3] = None
t[5:0:-3] = None
t[5::-3] = None
t[6:3:-3] = None
t[6:2:-3] = None
t[6:1:-3] = None
t[6::-3] = None
t[7:1:-3] = None
t[7:0:-3] = None
t[7::-3] = None
t.set_dtype('int32')
t[80000] = None # error expected (shrink-axis OOB)
t[-80000] = None # error expected (shrink-axis OOB)
t[:,:] = None # error expected (too many indices)
t[0:0:0] = None # error expected (stride==0)
t[0:1:0] = None # error expected (stride==0)
t[0:2:0] = None # error expected (stride==0)
t[::0] = None # error expected (stride==0)
t.set_shape((2,3,4))
t.set_dtype('int32')
# Test with incorrect DT
t[...] = np.ones(shape=(2,3,4), dtype='float32')
# Test some cases where auto-broadcast would be required
t[...] = np.ones(shape=(1,3,4), dtype='int32')
t[...] = np.ones(shape=(3,4), dtype='int32')
t[0,...,0] = np.ones(shape=(1), dtype='int32')
for dt in ['int32','int64','float32','uint32']:
t.set_dtype(dt)
t[1,np.newaxis] = None
t[-1,-1,np.newaxis] = None
t.set_shape((2,4,6,8,2,2,2))
for dt in ['int32','int64','float32','uint32']:
t.set_dtype(dt)
t[0:,:4,2:6:2,7:3:-2,np.newaxis,...,1] = None
t.set_dtype('int32')
t[...,...] = None # error expected (too many ellipses)
write_footer(f)
f.close()
if __name__ == "__main__":
main()
#!/bin/bash
# ******************************************************************************
# Copyright 2017-2019 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ******************************************************************************
declare THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
python ${THIS_SCRIPT_DIR}/ref_generators/generate_dyn_replace_slice_ref.py ${THIS_SCRIPT_DIR}/dyn_replace_slice_test.in.cpp
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment