@tensorflow/tfjs-core
Version:
Hardware-accelerated JavaScript library for machine intelligence
721 lines • 138 kB
JavaScript
/**
* @license
* Copyright 2017 Google LLC. All Rights Reserved.
* 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.
* =============================================================================
*/
import * as tf from '../index';
import { ALL_ENVS, describeWithFlags } from '../jasmine_util';
import { expectArraysClose } from '../test_util';
function generateCaseInputs(totalSizeTensor, totalSizeFilter) {
const inp = new Array(totalSizeTensor);
const filt = new Array(totalSizeFilter);
for (let i = 0; i < totalSizeTensor; i++) {
inp[i] = i + 1;
}
for (let i = 0; i < totalSizeFilter; i++) {
filt[i] = i + 1;
}
return { input: inp, filter: filt };
}
describeWithFlags('conv2d', ALL_ENVS, () => {
it('x=[1,4,4,1] f=[1,1,1,3] s=2 d=1 p=same', async () => {
const inputDepth = 1;
const inputShape = [4, 4, inputDepth];
const outputDepth = 3;
const fSize = 1;
const pad = 'same';
const stride = [2, 2];
const x = tf.tensor3d([
10, 30, 50, 70, 20, 40, 60, 80, -10, -30, -50, -70, -20, -40, -60, -80
], inputShape);
const w = tf.tensor4d([1, 0.5, 1], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad);
expectArraysClose(await result.data(), [10, 5, 10, 50, 25, 50, -10, -5, -10, -50, -25, -50]);
});
it('x=[2,2,2,2] f=[1,1,2,2] s=1 d=1 p=0', async () => {
const inputDepth = 2;
const inShape = [2, 2, 2, inputDepth];
const outputDepth = 2;
const fSize = 1;
const pad = 0;
const stride = 1;
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], inShape);
const w = tf.tensor4d([-1, 1, -2, 0.5], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad);
expect(result.shape).toEqual([2, 2, 2, 2]);
const expected = [-5, 2, -11, 5, -17, 8, -23, 11, -29, 14, -35, 17, -41, 20, -47, 23];
expectArraysClose(await result.data(), expected);
});
it('x=[2,2,1] f=[1,1,1,2] s=1 d=1 p=0', async () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 1;
const pad = 0;
const stride = 1;
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.tensor4d([2], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad);
expectArraysClose(await result.data(), [2, 4, 6, 8]);
});
it('x=[3,3,2] f=[2,2,2,1] s=1 d=1 p=valid', async () => {
const pad = 'valid';
const stride = 1;
const x = tf.tensor3d([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90], [3, 3, 2]);
const w = tf.tensor4d([.1, .2, .3, .4, .5, .6, .7, .8], [2, 2, 2, 1]);
const result = tf.conv2d(x, w, stride, pad);
const resultData = await result.data();
expect(result.shape).toEqual([2, 2, 1]);
expectArraysClose(resultData, new Float32Array([25.6, 53.5, 157.0, 220.9]));
});
it('x=[2,2,2,1] f=[1,1,1,1] s=1 d=1 p=0', async () => {
const inputDepth = 1;
const inShape = [2, 2, 2, inputDepth];
const outputDepth = 1;
const fSize = 1;
const pad = 0;
const stride = 1;
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8], inShape);
const w = tf.tensor4d([2], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad);
expect(result.shape).toEqual([2, 2, 2, 1]);
const expected = [2, 4, 6, 8, 10, 12, 14, 16];
expectArraysClose(await result.data(), expected);
});
it('x=[2,1,2,2] f=[1,1,1,1] s=1 d=1 p=0 NCHW', async () => {
const inputDepth = 1;
const inShape = [2, inputDepth, 2, 2];
const outputDepth = 1;
const fSize = 1;
const pad = 0;
const stride = 1;
const dataFormat = 'NCHW';
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8], inShape);
const w = tf.tensor4d([2], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat);
expect(result.shape).toEqual([2, 1, 2, 2]);
const expected = [2, 4, 6, 8, 10, 12, 14, 16];
expectArraysClose(await result.data(), expected);
});
it('x=[4,2,1] f=[4,2,1,1] s=1 d=1 p=same', async () => {
const inputDepth = 1;
const outputDepth = 1;
const pad = 'same';
const stride = 1;
const dataFormat = 'NHWC';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4, 5, 6, 7, 8], [4, 2, inputDepth]);
const w = tf.tensor4d([3, 1, 5, 0, 2, 7, 8, 9], [4, 2, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([4, 2, 1]);
expectArraysClose(resultData, [133, 66, 200, 102, 108, 58, 56, 58]);
});
it('x=[4,2,1] f=[4,2,1,1] s=1 d=1 p=explicit', async () => {
const inputDepth = 1;
const outputDepth = 1;
const pad = [[0, 0], [1, 2], [0, 1], [0, 0]];
const stride = 1;
const dataFormat = 'NHWC';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4, 5, 6, 7, 8], [4, 2, inputDepth]);
const w = tf.tensor4d([3, 1, 5, 0, 2, 7, 8, 9], [4, 2, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([4, 2, 1]);
expectArraysClose(resultData, [133, 66, 200, 102, 108, 58, 56, 58]);
});
it('x=[2,2,1] f=[2,2,1,1] s=1 d=1 p=same', async () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 2;
const pad = 'same';
const stride = 1;
const dataFormat = 'NHWC';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.tensor4d([3, 1, 5, 0], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([2, 2, 1]);
expectArraysClose(resultData, new Float32Array([20, 26, 13, 12]));
});
it('x=[1,2,2] f=[2,2,1,1] s=1 d=1 p=same NCHW', async () => {
const inputDepth = 1;
const inputShape = [inputDepth, 2, 2];
const outputDepth = 1;
const fSize = 2;
const pad = 'same';
const stride = 1;
const dataFormat = 'NCHW';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.tensor4d([3, 1, 5, 0], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([1, 2, 2]);
expectArraysClose(resultData, [20, 26, 13, 12]);
});
it('x=[4,2,2] f=[1,1,4,4] s=1 d=1 p=same NCHW', async () => {
// Skip tensorflow backend due to NCHW not supported.
if (tf.getBackend() === 'tensorflow') {
return;
}
const inputDepth = 4;
const inputShape = [inputDepth, 2, 2];
const outputDepth = 4;
const fSize = 1;
const pad = 'same';
const stride = 1;
const dataFormat = 'NCHW';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], inputShape);
const w = tf.tensor4d([3, 3, 3, 3, 1, 1, 1, 1, 5, 5, 5, 5, 0, 0, 0, 0], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([4, 2, 2]);
expectArraysClose(resultData, [9, 18, 27, 36, 9, 18, 27, 36, 9, 18, 27, 36, 9, 18, 27, 36]);
});
it('x=[3,2,2] f=[1,1,3,4] s=1 d=1 p=same NCHW', async () => {
// Skip tensorflow backend due to NCHW not supported.
if (tf.getBackend() === 'tensorflow') {
return;
}
const inputDepth = 3;
const inputShape = [inputDepth, 2, 2];
const outputDepth = 4;
const fSize = 1;
const pad = 'same';
const stride = 1;
const dataFormat = 'NCHW';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], inputShape);
const w = tf.tensor4d([3, 3, 3, 3, 1, 1, 1, 1, 5, 5, 5, 5], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([4, 2, 2]);
expectArraysClose(resultData, [9, 18, 27, 36, 9, 18, 27, 36, 9, 18, 27, 36, 9, 18, 27, 36]);
});
it('x=[2,2,2,2] f=[1,1,4,4] s=1 d=1 p=same NCHW', async () => {
// Skip tensorflow backend due to NCHW not supported.
if (tf.getBackend() === 'tensorflow') {
return;
}
const inputDepth = 2;
const inputShape = [2, inputDepth, 2, 2];
const outputDepth = 2;
const fSize = 1;
const pad = 'same';
const stride = 1;
const dataFormat = 'NCHW';
const dilation = 1;
const x = tf.tensor4d([1, 3, 5, 7, 2, 4, 6, 8, 9, 11, 13, 15, 10, 12, 14, 16], inputShape);
const w = tf.tensor4d([-1, 1, -2, 0.5], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([2, 2, 2, 2]);
expectArraysClose(resultData, [-5, -11, -17, -23, 2, 5, 8, 11, -29, -35, -41, -47, 14, 17, 20, 23]);
});
it('x=[4,2,2] f=[2,2,4,4] s=1 d=1 p=same NCHW', async () => {
// Skip tensorflow backend due to NCHW not supported.
if (tf.getBackend() === 'tensorflow') {
return;
}
const inputDepth = 4;
const inputShape = [inputDepth, 2, 2];
const outputDepth = 4;
const fSize = 2;
const pad = 'same';
const stride = 1;
const dataFormat = 'NCHW';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], inputShape);
const w = tf.tensor4d([
3, 3, 3, 3, 1, 1, 1, 1, 5, 5, 5, 5, 0, 0, 0, 0, 3, 3, 3, 3, 1, 1,
1, 1, 5, 5, 5, 5, 0, 0, 0, 0, 3, 3, 3, 3, 1, 1, 1, 1, 5, 5, 5, 5,
0, 0, 0, 0, 3, 3, 3, 3, 1, 1, 1, 1, 5, 5, 5, 5, 0, 0, 0, 0,
], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([4, 2, 2]);
expectArraysClose(resultData, [90, 54, 63, 36, 90, 54, 63, 36, 90, 54, 63, 36, 90, 54, 63, 36]);
});
it('x=[1,2,2] f=[2,2,1,1] s=1 d=1 p=explicit NCHW', async () => {
const inputDepth = 1;
const inputShape = [inputDepth, 2, 2];
const outputDepth = 1;
const fSize = 2;
const pad = [[0, 0], [0, 0], [0, 1], [0, 1]];
const stride = 1;
const dataFormat = 'NCHW';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.tensor4d([3, 1, 5, 0], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([1, 2, 2]);
expectArraysClose(resultData, [20, 26, 13, 12]);
});
it('x=[2,2,2] f=[2,2,2,1] s=1 d=1 p=same NCHW', async () => {
const inputDepth = 2;
const inputShape = [inputDepth, 2, 2];
const outputDepth = 1;
const fSize = 2;
const pad = 'same';
const stride = 1;
const dataFormat = 'NCHW';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4, 5, 6, 7, 8], inputShape);
const w = tf.tensor4d([3, 1, 5, 0, 0, 5, 1, 3], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([1, 2, 2]);
expectArraysClose(resultData, [81, 52, 36, 20]);
});
it('x=[2,1,2,2] f=[2,2,1,1] s=1 d=1 p=same NCHW', async () => {
const inputDepth = 1;
const inputShape = [2, inputDepth, 2, 2];
const outputDepth = 1;
const fSize = 2;
const pad = 'same';
const stride = 1;
const dataFormat = 'NCHW';
const dilation = 1;
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8], inputShape);
const w = tf.tensor4d([3, 1, 5, 0], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const resultData = await result.data();
expect(result.shape).toEqual([2, 1, 2, 2]);
expectArraysClose(resultData, [20, 26, 13, 12, 56, 58, 29, 24]);
});
it('x=[2,2,1] f=[2,2,1,1] s=1 d=1 p=0', async () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 2;
const pad = 0;
const stride = 1;
const dataFormat = 'NHWC';
const dilation = 1;
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.tensor4d([3, 1, 5, 0], [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
expectArraysClose(await result.data(), [20]);
});
it('x=[4,4,1] f=[2,2,1,1] s=1 d=2 p=0', async () => {
const inputDepth = 1;
const inputShape = [4, 4, inputDepth];
const outputDepth = 1;
const fSize = 2;
const fSizeDilated = 3;
const pad = 0;
const stride = 1;
const dataFormat = 'NHWC';
const dilation = 2;
const noDilation = 1;
const x = tf.tensor3d([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], inputShape);
const w = tf.tensor4d([3, 1, 5, 2], [fSize, fSize, inputDepth, outputDepth]);
// adding a dilation rate is equivalent to using a filter
// with 0s for the dilation rate
const wDilated = tf.tensor4d([3, 0, 1, 0, 0, 0, 5, 0, 2], [fSizeDilated, fSizeDilated, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad, dataFormat, dilation);
const expectedResult = tf.conv2d(x, wDilated, stride, pad, dataFormat, noDilation);
expect(result.shape).toEqual(expectedResult.shape);
expectArraysClose(await result.data(), await expectedResult.data());
expect(result.shape).toEqual(expectedResult.shape);
expect(result.dtype).toBe(expectedResult.dtype);
});
it('x=[1,3,6,1] f=[2,2,1,1] s=[1,2] d=1 p=valid', async () => {
const inputDepth = 1;
const inputShape = [1, 3, 6, inputDepth];
const outputDepth = 1;
const fSize = 2;
const pad = 'valid';
const stride = [1, 2];
const inputs = generateCaseInputs(1 * 3 * 6 * inputDepth, fSize * fSize);
const x = tf.tensor4d(inputs.input, inputShape);
const w = tf.tensor4d(inputs.filter, [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad);
expectArraysClose(await result.data(), [58.0, 78.0, 98.0, 118.0, 138.0, 158.0]);
});
it('x=[1,8,8,16] f=[3,3,16,1] s=[2,2] d=1 p=same', async () => {
const inputDepth = 16;
const xSize = 8;
const inputShape = [1, xSize, xSize, inputDepth];
const outputDepth = 1;
const fSize = 3;
const pad = 'same';
const stride = [2, 2];
// TODO(annxingyuan): Make this test work with large inputs using
// generateCaseInputs https://github.com/tensorflow/tfjs/issues/3143
const inputData = [];
for (let i = 0; i < xSize * xSize * inputDepth; i++) {
inputData.push(i % 5);
}
const wData = [];
for (let i = 0; i < fSize * fSize * inputDepth * outputDepth; i++) {
wData.push(i % 5);
}
const x = tf.tensor4d(inputData, inputShape);
const w = tf.tensor4d(wData, [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad);
expect(result.shape).toEqual([1, 4, 4, 1]);
expectArraysClose(await result.data(), new Float32Array([
854, 431, 568, 382, 580, 427, 854, 288, 431, 568, 580,
289, 285, 570, 285, 258
]));
});
it('x=[1,8,8,3] f=[3,3,3,4] s=[2,2] d=1 p=same', async () => {
const inputDepth = 3;
const xSize = 8;
const inputShape = [1, xSize, xSize, inputDepth];
const outputDepth = 4;
const fSize = 3;
const pad = 'same';
const stride = [2, 2];
// TODO(annxingyuan): Make this test work with large inputs using
// generateCaseInputs https://github.com/tensorflow/tfjs/issues/3143
const inputData = [];
for (let i = 0; i < xSize * xSize * inputDepth; i++) {
inputData.push(i % 5);
}
const wData = [];
for (let i = 0; i < fSize * fSize * inputDepth * outputDepth; i++) {
wData.push(i % 5);
}
const x = tf.tensor4d(inputData, inputShape);
const w = tf.tensor4d(wData, [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad);
expect(result.shape).toEqual([1, 4, 4, 4]);
expectArraysClose(await result.data(), new Float32Array([
104, 125, 126, 102, 133, 126, 104, 57, 137, 102, 57, 112, 64,
40, 76, 92, 116, 53, 110, 142, 50, 104, 133, 137, 104, 125,
126, 102, 83, 88, 78, 33, 133, 126, 104, 57, 137, 102, 57,
112, 116, 53, 110, 142, 37, 76, 100, 99, 33, 68, 83, 88,
70, 83, 76, 64, 92, 88, 64, 40, 51, 44, 27, 50
]));
});
it('x=[1,8,8,3] f=[3,3,3,4] s=[2,2] d=1 p=valid', async () => {
const inputDepth = 3;
const xSize = 8;
const inputShape = [1, xSize, xSize, inputDepth];
const outputDepth = 4;
const fSize = 3;
const pad = 'valid';
const stride = [2, 2];
const inputData = [];
for (let i = 0; i < xSize * xSize * inputDepth; i++) {
inputData.push(i % 5);
}
const wData = [];
for (let i = 0; i < fSize * fSize * inputDepth * outputDepth; i++) {
wData.push(i % 5);
}
const x = tf.tensor4d(inputData, inputShape);
const w = tf.tensor4d(wData, [fSize, fSize, inputDepth, outputDepth]);
const result = tf.conv2d(x, w, stride, pad);
expect(result.shape).toEqual([1, 3, 3, 4]);
expectArraysClose(await result.data(), new Float32Array([
104, 125, 126, 102, 133, 126, 104, 57, 137, 102, 57, 112,
116, 53, 110, 142, 50, 104, 133, 137, 104, 125, 126, 102,
133, 126, 104, 57, 137, 102, 57, 112, 116, 53, 110, 142
]));
});
it('x=[1,2,2,3] f=[1,1] s=2 p=1 fractional outputs default rounding', async () => {
const inputDepth = 3;
const inShape = [1, 2, 2, inputDepth];
const outputDepth = 1;
const fSize = 1;
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], inShape);
const w = tf.tensor4d([2, 2, 1], [fSize, fSize, inputDepth, outputDepth]);
const pad = [[0, 0], [1, 1], [1, 1], [0, 0]];
const strides = 2;
const result = tf.conv2d(x, w, strides, pad);
expect(result.shape).toEqual([1, 2, 2, 1]);
expectArraysClose(await result.data(), [0, 0, 0, 54]);
});
it('throws when x is not rank 3', () => {
const inputDepth = 1;
const outputDepth = 1;
const fSize = 2;
const pad = 0;
const stride = 1;
// tslint:disable-next-line:no-any
const x = tf.tensor2d([1, 2, 3, 4], [2, 2]);
const w = tf.tensor4d([3, 1, 5, 0], [fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad)).toThrowError();
});
it('throws when weights is not rank 4', () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const pad = 0;
const stride = 1;
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
// tslint:disable-next-line:no-any
const w = tf.tensor3d([3, 1, 5, 0], [2, 2, 1]);
expect(() => tf.conv2d(x, w, stride, pad)).toThrowError();
});
it('throws when x depth does not match weight depth', () => {
const inputDepth = 1;
const wrongInputDepth = 5;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 2;
const pad = 0;
const stride = 1;
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.randomNormal([fSize, fSize, wrongInputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad)).toThrowError();
});
it('throws when x depth does not match weight depth NCHW', () => {
const inputDepth = 1;
const wrongInputDepth = 5;
const inputShape = [inputDepth, 2, 2];
const outputDepth = 1;
const fSize = 2;
const pad = 0;
const stride = 1;
const dataFormat = 'NCHW';
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.randomNormal([fSize, fSize, wrongInputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad, dataFormat)).toThrowError();
});
it('throws when dimRoundingMode is set and pad is same', () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 2;
const pad = 'same';
const stride = 1;
const dataFormat = 'NHWC';
const dilation = 1;
const dimRoundingMode = 'round';
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.randomNormal([fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad, dataFormat, dilation, dimRoundingMode))
.toThrowError();
});
it('throws when dimRoundingMode is set and pad is valid', () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 2;
const pad = 'valid';
const stride = 1;
const dataFormat = 'NHWC';
const dilation = 1;
const dimRoundingMode = 'round';
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.randomNormal([fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad, dataFormat, dilation, dimRoundingMode))
.toThrowError();
});
it('throws when dimRoundingMode is set and pad is a non-integer number', () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 2;
const pad = 1.2;
const stride = 1;
const dataFormat = 'NHWC';
const dilation = 1;
const dimRoundingMode = 'round';
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.randomNormal([fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad, dataFormat, dilation, dimRoundingMode))
.toThrowError();
});
it('throws when dimRoundingMode is set and pad is explicit by non-integer ' +
'number', () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 2;
const pad = [[0, 0], [0, 2.1], [1, 1], [0, 0]];
const stride = 1;
const dataFormat = 'NHWC';
const dilation = 1;
const dimRoundingMode = 'round';
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.randomNormal([fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad, dataFormat, dilation, dimRoundingMode))
.toThrowError();
});
it('throws when stride is less than or equal to 0', async () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 1;
const pad = 0;
const stride = [1, 0];
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.tensor4d([2], [fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad)).toThrowError();
});
it('throws when dilation is less than or equal to 0', async () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 1;
const pad = 0;
const stride = 1;
const dataFormat = 'NHWC';
const dilation = [1, 0];
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.tensor4d([2], [fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad, dataFormat, dilation))
.toThrowError();
});
it('throws when both stride and dilation are greater than 1', () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const outputDepth = 1;
const fSize = 2;
const pad = 0;
const stride = [2, 1];
const dataFormat = 'NHWC';
const dilation = [1, 2];
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
const w = tf.tensor4d([3, 1, 5, 0], [fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad, dataFormat, dilation))
.toThrowError();
});
it('gradient with clones input=[3,3,1] f=[2,2,1,1] s=1 p=0', async () => {
const inputDepth = 1;
const outputDepth = 1;
const inputShape = [3, 3, inputDepth];
const filterSize = 2;
const stride = 1;
const pad = 0;
const filterShape = [filterSize, filterSize, inputDepth, outputDepth];
const filter = tf.ones(filterShape);
const x = tf.tensor3d([1, 2, 3, 4, 5, 6, 7, 8, 9], inputShape);
const dy = tf.tensor3d([3, 1, 2, 0], [2, 2, 1]);
const grads = tf.grads((x, filter) => x.clone().conv2d(filter.clone(), stride, pad).clone());
const [dx, dfilter] = grads([x, filter], dy);
expect(dx.shape).toEqual(x.shape);
expectArraysClose(await dx.data(), [3, 4, 1, 5, 6, 1, 2, 2, 0]);
expect(dfilter.shape).toEqual(filterShape);
expectArraysClose(await dfilter.data(), [13, 19, 31, 37]);
});
it('gradient x=[2,3,3,1] f=[2,2,1,1] s=1 p=0', async () => {
const inputDepth = 1;
const outputDepth = 1;
const inputShape = [2, 3, 3, inputDepth];
const filterSize = 2;
const stride = 1;
const pad = 0;
const filterShape = [filterSize, filterSize, inputDepth, outputDepth];
const filter = tf.ones(filterShape);
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9], inputShape);
const dy = tf.tensor4d([3, 1, 2, 0, 3, 1, 2, 0], [2, 2, 2, 1]);
const grads = tf.grads((x, filter) => x.conv2d(filter, stride, pad));
const [dx, dfilter] = grads([x, filter], dy);
expect(dx.shape).toEqual(x.shape);
expectArraysClose(await dx.data(), [3, 4, 1, 5, 6, 1, 2, 2, 0, 3, 4, 1, 5, 6, 1, 2, 2, 0]);
expect(dfilter.shape).toEqual(filterShape);
expectArraysClose(await dfilter.data(), [13 * 2, 19 * 2, 31 * 2, 37 * 2]);
});
it('gradient x=[1,1,3,3] f=[2,2,1,1] s=1 p=0 NCHW', async () => {
const inputDepth = 1;
const outputDepth = 1;
const inputShape = [1, inputDepth, 3, 3];
const filterSize = 2;
const stride = 1;
const pad = 0;
const dataFormat = 'NCHW';
const filterShape = [filterSize, filterSize, inputDepth, outputDepth];
const filter = tf.ones(filterShape);
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9], inputShape);
const dy = tf.tensor4d([3, 1, 2, 0], [1, 1, 2, 2]);
const grads = tf.grads((x, filter) => x.conv2d(filter, stride, pad, dataFormat));
const [dx, dfilter] = grads([x, filter], dy);
expect(dx.shape).toEqual(x.shape);
expectArraysClose(await dx.data(), [3, 4, 1, 5, 6, 1, 2, 2, 0]);
expect(dfilter.shape).toEqual(filterShape);
expectArraysClose(await dfilter.data(), [13, 19, 31, 37]);
});
it('gradient x=[2,1,3,3] f=[2,2,1,1] s=1 p=0 NCHW', async () => {
const inputDepth = 1;
const outputDepth = 1;
const inputShape = [2, inputDepth, 3, 3];
const filterSize = 2;
const stride = 1;
const pad = 0;
const dataFormat = 'NCHW';
const filterShape = [filterSize, filterSize, inputDepth, outputDepth];
const filter = tf.ones(filterShape);
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9], inputShape);
const dy = tf.tensor4d([3, 1, 2, 0, 3, 1, 2, 0], [2, 1, 2, 2]);
const grads = tf.grads((x, filter) => x.conv2d(filter, stride, pad, dataFormat));
const [dx, dfilter] = grads([x, filter], dy);
expect(dx.shape).toEqual(x.shape);
expectArraysClose(await dx.data(), [3, 4, 1, 5, 6, 1, 2, 2, 0, 3, 4, 1, 5, 6, 1, 2, 2, 0]);
expect(dfilter.shape).toEqual(filterShape);
expectArraysClose(await dfilter.data(), [26, 38, 62, 74]);
});
it('throws when passed x as a non-tensor', () => {
const inputDepth = 1;
const outputDepth = 1;
const fSize = 1;
const pad = 0;
const stride = 1;
const w = tf.tensor4d([2], [fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d({}, w, stride, pad))
.toThrowError(/Argument 'x' passed to 'conv2d' must be a Tensor/);
});
it('throws when passed filter as a non-tensor', () => {
const inputDepth = 1;
const inputShape = [2, 2, inputDepth];
const pad = 0;
const stride = 1;
const x = tf.tensor3d([1, 2, 3, 4], inputShape);
expect(() => tf.conv2d(x, {}, stride, pad))
.toThrowError(/Argument 'filter' passed to 'conv2d' must be a Tensor/);
});
it('throws when input is int32', async () => {
const inputDepth = 2;
const inShape = [2, 2, 2, inputDepth];
const outputDepth = 2;
const fSize = 1;
const pad = 0;
const stride = 1;
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], inShape, 'int32');
const w = tf.tensor4d([-1, 1, -2, 0.5], [fSize, fSize, inputDepth, outputDepth]);
expect(() => tf.conv2d(x, w, stride, pad))
.toThrowError(/Argument 'x' passed to 'conv2d' must be float32/);
});
it('throws when filter is int32', async () => {
const inputDepth = 2;
const inShape = [2, 2, 2, inputDepth];
const outputDepth = 2;
const fSize = 1;
const pad = 0;
const stride = 1;
const x = tf.tensor4d([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], inShape);
const w = tf.tensor4d([-1, 1, -2, 0.5], [fSize, fSize, inputDepth, outputDepth], 'int32');
expect(() => tf.conv2d(x, w, stride, pad))
.toThrowError(/Argument 'filter' passed to 'conv2d' must be float32/);
});
it('accepts a tensor-like object', async () => {
const pad = 0;
const stride = 1;
const x = [[[1], [2]], [[3], [4]]]; // 2x2x1
const w = [[[[2]]]]; // 1x1x1x1
const result = tf.conv2d(x, w, stride, pad);
expectArraysClose(await result.data(), [2, 4, 6, 8]);
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udjJkX3Rlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi90ZmpzLWNvcmUvc3JjL29wcy9jb252MmRfdGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLEtBQUssRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUMvQixPQUFPLEVBQUMsUUFBUSxFQUFFLGlCQUFpQixFQUFDLE1BQU0saUJBQWlCLENBQUM7QUFDNUQsT0FBTyxFQUFDLGlCQUFpQixFQUFDLE1BQU0sY0FBYyxDQUFDO0FBRy9DLFNBQVMsa0JBQWtCLENBQUMsZUFBdUIsRUFBRSxlQUF1QjtJQUMxRSxNQUFNLEdBQUcsR0FBRyxJQUFJLEtBQUssQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUN2QyxNQUFNLElBQUksR0FBRyxJQUFJLEtBQUssQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUV4QyxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsZUFBZSxFQUFFLENBQUMsRUFBRSxFQUFFO1FBQ3hDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0tBQ2hCO0lBQ0QsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLGVBQWUsRUFBRSxDQUFDLEVBQUUsRUFBRTtRQUN4QyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztLQUNqQjtJQUVELE9BQU8sRUFBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUMsQ0FBQztBQUNwQyxDQUFDO0FBRUQsaUJBQWlCLENBQUMsUUFBUSxFQUFFLFFBQVEsRUFBRSxHQUFHLEVBQUU7SUFDekMsRUFBRSxDQUFDLHdDQUF3QyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ3RELE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQztRQUNyQixNQUFNLFVBQVUsR0FBNkIsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ2hFLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQztRQUN0QixNQUFNLEtBQUssR0FBRyxDQUFDLENBQUM7UUFDaEIsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDO1FBQ25CLE1BQU0sTUFBTSxHQUFxQixDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUV4QyxNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUNqQjtZQUNFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFO1NBQ3ZFLEVBQ0QsVUFBVSxDQUFDLENBQUM7UUFDaEIsTUFBTSxDQUFDLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBRTVFLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFFNUMsaUJBQWlCLENBQ2IsTUFBTSxNQUFNLENBQUMsSUFBSSxFQUFFLEVBQ25CLENBQUMsRUFBRSxFQUFFLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQzVELENBQUMsQ0FBQyxDQUFDO0lBRUgsRUFBRSxDQUFDLHFDQUFxQyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ25ELE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQztRQUNyQixNQUFNLE9BQU8sR0FBcUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUN4RSxNQUFNLFdBQVcsR0FBRyxDQUFDLENBQUM7UUFDdEIsTUFBTSxLQUFLLEdBQUcsQ0FBQyxDQUFDO1FBQ2hCLE1BQU0sR0FBRyxHQUFHLENBQUMsQ0FBQztRQUNkLE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQztRQUVqQixNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUNqQixDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3RFLE1BQU0sQ0FBQyxHQUNILEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBRTNFLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDNUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzNDLE1BQU0sUUFBUSxHQUNWLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLENBQUMsRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRXpFLGlCQUFpQixDQUFDLE1BQU0sTUFBTSxDQUFDLElBQUksRUFBRSxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQ25ELENBQUMsQ0FBQyxDQUFDO0lBRUgsRUFBRSxDQUFDLG1DQUFtQyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ2pELE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQztRQUNyQixNQUFNLFVBQVUsR0FBNkIsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ2hFLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQztRQUN0QixNQUFNLEtBQUssR0FBRyxDQUFDLENBQUM7UUFDaEIsTUFBTSxHQUFHLEdBQUcsQ0FBQyxDQUFDO1FBQ2QsTUFBTSxNQUFNLEdBQUcsQ0FBQyxDQUFDO1FBRWpCLE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNoRCxNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBRXBFLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFFNUMsaUJBQWlCLENBQUMsTUFBTSxNQUFNLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3ZELENBQUMsQ0FBQyxDQUFDO0lBRUgsRUFBRSxDQUFDLHVDQUF1QyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ3JELE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQztRQUNwQixNQUFNLE1BQU0sR0FBRyxDQUFDLENBQUM7UUFFakIsTUFBTSxDQUFDLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FDakIsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUMvRCxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNmLE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBRXRFLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFFNUMsTUFBTSxVQUFVLEdBQUcsTUFBTSxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDdkMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDeEMsaUJBQWlCLENBQUMsVUFBVSxFQUFFLElBQUksWUFBWSxDQUFDLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzlFLENBQUMsQ0FBQyxDQUFDO0lBRUgsRUFBRSxDQUFDLHFDQUFxQyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ25ELE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQztRQUNyQixNQUFNLE9BQU8sR0FBcUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUN4RSxNQUFNLFdBQVcsR0FBRyxDQUFDLENBQUM7UUFDdEIsTUFBTSxLQUFLLEdBQUcsQ0FBQyxDQUFDO1FBQ2hCLE1BQU0sR0FBRyxHQUFHLENBQUMsQ0FBQztRQUNkLE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQztRQUVqQixNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3pELE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUM7UUFFcEUsTUFBTSxNQUFNLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxHQUFHLENBQUMsQ0FBQztRQUM1QyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDM0MsTUFBTSxRQUFRLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFOUMsaUJBQWlCLENBQUMsTUFBTSxNQUFNLENBQUMsSUFBSSxFQUFFLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDbkQsQ0FBQyxDQUFDLENBQUM7SUFFSCxFQUFFLENBQUMsMENBQTBDLEVBQUUsS0FBSyxJQUFJLEVBQUU7UUFDeEQsTUFBTSxVQUFVLEdBQUcsQ0FBQyxDQUFDO1FBQ3JCLE1BQU0sT0FBTyxHQUFxQyxDQUFDLENBQUMsRUFBRSxVQUFVLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQ3hFLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQztRQUN0QixNQUFNLEtBQUssR0FBRyxDQUFDLENBQUM7UUFDaEIsTUFBTSxHQUFHLEdBQUcsQ0FBQyxDQUFDO1FBQ2QsTUFBTSxNQUFNLEdBQUcsQ0FBQyxDQUFDO1FBQ2pCLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQztRQUUxQixNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3pELE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUM7UUFFcEUsTUFBTSxNQUFNLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFDeEQsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzNDLE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRTlDLGlCQUFpQixDQUFDLE1BQU0sTUFBTSxDQUFDLElBQUksRUFBRSxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQ25ELENBQUMsQ0FBQyxDQUFDO0lBRUgsRUFBRSxDQUFDLHNDQUFzQyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ3BELE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQztRQUNyQixNQUFNLFdBQVcsR0FBRyxDQUFDLENBQUM7UUFDdEIsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDO1FBQ25CLE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQztRQUNqQixNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUM7UUFDMUIsTUFBTSxRQUFRLEdBQUcsQ0FBQyxDQUFDO1FBRW5CLE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUM7UUFDcEUsTUFBTSxDQUFDLEdBQ0gsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsVUFBVSxFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUM7UUFFM0UsTUFBTSxNQUFNLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRWxFLE1BQU0sVUFBVSxHQUFHLE1BQU0sTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3ZDLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3hDLGlCQUFpQixDQUFDLFVBQVUsRUFBRSxDQUFDLEdBQUcsRUFBRSxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3RFLENBQUMsQ0FBQyxDQUFDO0lBRUgsRUFBRSxDQUFDLDBDQUEwQyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ3hELE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQztRQUNyQixNQUFNLFdBQVcsR0FBRyxDQUFDLENBQUM7UUFDdEIsTUFBTSxHQUFHLEdBQ0wsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBb0MsQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxDQUFDLENBQUM7UUFDakIsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDO1FBQzFCLE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBQztRQUVuQixNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDO1FBQ3BFLE1BQU0sQ0FBQyxHQUNILEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLFVBQVUsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBRTNFLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLFVBQVUsRUFBRSxRQUFRLENBQUMsQ0FBQztRQUVsRSxNQUFNLFVBQVUsR0FBRyxNQUFNLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN2QyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUN4QyxpQkFBaUIsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUN0RSxDQUFDLENBQUMsQ0FBQztJQUVILEVBQUUsQ0FBQyxzQ0FBc0MsRUFBRSxLQUFLLElBQUksRUFBRTtRQUNwRCxNQUFNLFVBQVUsR0FBRyxDQUFDLENBQUM7UUFDckIsTUFBTSxVQUFVLEdBQTZCLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNoRSxNQUFNLFdBQVcsR0FBRyxDQUFDLENBQUM7UUFDdEIsTUFBTSxLQUFLLEdBQUcsQ0FBQyxDQUFDO1FBQ2hCLE1BQU0sR0FBRyxHQUFHLE1BQU0sQ0FBQztRQUNuQixNQUFNLE1BQU0sR0FBRyxDQUFDLENBQUM7UUFDakIsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDO1FBQzFCLE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBQztRQUVuQixNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFDaEQsTUFBTSxDQUFDLEdBQ0gsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsV0FBVyxDQUFDLENBQUMsQ0FBQztRQUV2RSxNQUFNLE1BQU0sR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxVQUFVLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFFbEUsTUFBTSxVQUFVLEdBQUcsTUFBTSxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDdkMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDeEMsaUJBQWlCLENBQUMsVUFBVSxFQUFFLElBQUksWUFBWSxDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3BFLENBQUMsQ0FBQyxDQUFDO0lBRUgsRUFBRSxDQUFDLDJDQUEyQyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ3pELE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQztRQUNyQixNQUFNLFVBQVUsR0FBNkIsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQ2hFLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQztRQUN0QixNQUFNLEtBQUssR0FBRyxDQUFDLENBQUM7UUFDaEIsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDO1FBQ25CLE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQztRQUNqQixNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUM7UUFDMUIsTUFBTSxRQUFRLEdBQUcsQ0FBQyxDQUFDO1FBRW5CLE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNoRCxNQUFNLENBQUMsR0FDSCxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBRXZFLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLFVBQVUsRUFBRSxRQUFRLENBQUMsQ0FBQztRQUVsRSxNQUFNLFVBQVUsR0FBRyxNQUFNLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN2QyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUN4QyxpQkFBaUIsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2xELENBQUMsQ0FBQyxDQUFDO0lBRUgsRUFBRSxDQUFDLDJDQUEyQyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQ3pELHFEQUFxRDtRQUNyRCxJQUFJLEVBQUUsQ0FBQyxVQUFVLEVBQUUsS0FBSyxZQUFZLEVBQUU7WUFDcEMsT0FBTztTQUNSO1FBQ0QsTUFBTSxVQUFVLEdBQUcsQ0FBQyxDQUFDO1FBQ3JCLE1BQU0sVUFBVSxHQUE2QixDQUFDLFVBQVUsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDaEUsTUFBTSxXQUFXLEdBQUcsQ0FBQyxDQUFDO1FBQ3RCLE1BQU0sS0FBSyxHQUFHLENBQUMsQ0FBQztRQUNoQixNQUFNLEdBQUcsR0FBRyxNQUFNLENBQUM7UUFDbkIsTUFBTSxNQUFNLEdBQUcsQ0FBQyxDQUFDO1FBQ2pCLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQztRQUMxQixNQUFNLFFBQVEsR0FBRyxDQUFDLENBQUM7UUFFbkIsTUFBTSxDQUFDLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FDakIsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNsRSxNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUNqQixDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUNoRCxDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUM7UUFFN0MsTUFBTSxNQUFNLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRWxFLE1BQU0sVUFBVSxHQUFHLE1BQU0sTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3ZDLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3hDLGlCQUFpQixDQUNiLFVBQVUsRUFDVixDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDcEUsQ0FBQyxDQUFDLENBQUM7SUFFSCxFQUFFLENBQUMsMkNBQTJDLEVBQUUsS0FBSyxJQUFJLEVBQUU7UUFDekQscURBQXFEO1FBQ3JELElBQUksRUFBRSxDQUFDLFVBQVUsRUFBRSxLQUFLLFlBQVksRUFBRTtZQUNwQyxPQUFPO1NBQ1I7UUFDRCxNQUFNLFVBQVUsR0FBRyxDQUFDLENBQUM7UUFDckIsTUFBTSxVQUFVLEdBQTZCLENBQUMsVUFBVSxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNoRSxNQUFNLFdBQVcsR0FBRyxDQUFDLENBQUM7UUFDdEIsTUFBTSxLQUFLLEdBQUcsQ0FBQyxDQUFDO1FBQ2hCLE1BQU0sR0FBRyxHQUFHLE1BQU0sQ0FBQztRQUNuQixNQUFNLE1BQU0sR0FBRyxDQUFDLENBQUM7UUFDakIsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDO1FBQzFCLE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBQztRQUVuQixNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUN4RSxNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUNqQixDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQ3BDLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsV0FBVyxDQUFDLENBQUMsQ0FBQztRQUU3QyxNQUFNLE1BQU0sR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxVQUFVLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFFbEUsTUFBTSxVQUFVLEdBQUcsTUFBTSxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDdkMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDeEMsaUJBQWlCLENBQ2IsVUFBVSxFQUNWLENBQUMsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFB