10b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
20b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
30b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleLicensed under the Apache License, Version 2.0 (the "License");
40b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selleyou may not use this file except in compliance with the License.
50b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleYou may obtain a copy of the License at
60b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
70b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    http://www.apache.org/licenses/LICENSE-2.0
80b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
90b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleUnless required by applicable law or agreed to in writing, software
100b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selledistributed under the License is distributed on an "AS IS" BASIS,
110b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
120b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew SelleSee the License for the specific language governing permissions and
130b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellelimitations under the License.
140b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle==============================================================================*/
150b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <string.h>
160b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <algorithm>
170b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <cmath>
180b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <memory>
190b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <string>
200b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <unordered_map>
210b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include <vector>
220b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
230b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h"
240b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/model.h"
250b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/runtime/types.h"
260b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/contrib/lite/toco/tooling_util.h"
270b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle#include "tensorflow/core/platform/logging.h"
280b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
290b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellenamespace toco {
300b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
310b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Sellebool ResolveConstantUnaryOperator::Run(Model* model, std::size_t op_index) {
320b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  const auto unary_it = model->operators.begin() + op_index;
330b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  const auto* unary_op = unary_it->get();
340b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // Test for unary ops of types that we know how to resolve
3538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  if (unary_op->type != OperatorType::kCast &&
36c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower      unary_op->type != OperatorType::kNeg &&
3738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      unary_op->type != OperatorType::kTensorFlowRsqrt &&
380b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      unary_op->type != OperatorType::kTensorFlowSqrt &&
390b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      unary_op->type != OperatorType::kTensorFlowSquare &&
400b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      unary_op->type != OperatorType::kTensorFlowSum &&
410b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      unary_op->type != OperatorType::kTensorFlowMin &&
420b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      unary_op->type != OperatorType::kTensorFlowMax &&
430b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      unary_op->type != OperatorType::kTensorFlowReshape) {
440b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    return false;
450b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
460b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // Check if the input is a constant parameter.
470b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (!IsConstantParameterArray(*model, unary_op->inputs[0])) {
480b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    return false;
490b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
500b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
510b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // if the unary op involves a tensor required by a rnn state, ignore it
520b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (const auto& rnn_state : model->flags.rnn_states()) {
530b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (unary_op->inputs[0] == rnn_state.back_edge_source_array()) {
540b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      return false;
550b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
560b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (unary_op->inputs[0] == rnn_state.state_array()) {
570b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      return false;
580b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
590b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
600b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
6138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  auto& output_array = model->GetArray(unary_op->outputs[0]);
6238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  if (!output_array.has_shape()) {
6338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    // Yield until the output array dims have been resolved.
6438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    return false;
6538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  }
6638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower
670b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // At the moment we don't want to care about fused activation functions.
680b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // The idea is that we should do the present constants-propagation before
690b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // activation functions get fused.
700b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  if (unary_op->fused_activation_function !=
710b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      FusedActivationFunctionType::kNone) {
720b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    AddMessageF(
730b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        "Not resolving constant %s "
740b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        " because it has a fused activation function",
750b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        LogName(*unary_op));
760b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    return false;
770b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
7838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower
790b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  const auto& input_array = model->GetArray(unary_op->inputs[0]);
800b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // We have already tested above for existence of buffers (synonymous to being
810b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  // a constant param).
820b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  CHECK(input_array.buffer);
8338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  std::vector<DataType<ArrayDataType::kFloat>> const* input_float_data;
8438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  if (unary_op->type == OperatorType::kCast) {
8538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    CastOperator const* cast_op = static_cast<CastOperator const*>(unary_op);
8638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    if (cast_op->dst_data_type != ArrayDataType::kFloat) {
8738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      AddMessageF(
8838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower          "Not resolving constant %s because we currently only support casting "
8938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower          "to float",
9038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower          LogName(*unary_op));
9138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      return false;
9238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    }
9338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    if (cast_op->src_data_type != input_array.buffer->type) {
9438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      AddMessageF(
9538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower          "Not resolving constant %s because cast op source type does not "
9638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower          "match input type",
9738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower          LogName(*unary_op));
9838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    }
9938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  } else {
10038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    if (input_array.buffer->type != ArrayDataType::kFloat) {
10138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      return false;
10238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    }
10338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    input_float_data = &(input_array.GetBuffer<ArrayDataType::kFloat>().data);
1040b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1050b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
10638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  // Create a float buffer on the output array, which are always constant.
1070b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  const Shape& output_shape = output_array.shape();
10838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  const int output_dims_count = output_shape.dimensions_count();
10938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  const int output_buffer_size = RequiredBufferSizeForShape(output_shape);
1100b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  auto& output_float_data =
1110b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      output_array.GetMutableBuffer<ArrayDataType::kFloat>().data;
1120b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  output_float_data.resize(output_buffer_size);
1130b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
11438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  const Shape& input_shape = input_array.shape();
11538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  const int input_buffer_size = RequiredBufferSizeForShape(input_shape);
11638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  if (unary_op->type == OperatorType::kCast) {
11738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    for (int i = 0; i < output_buffer_size; i++) {
11838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      float outval = 0.0f;
11938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      if (input_array.buffer->type == ArrayDataType::kFloat) {
12038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower        outval = static_cast<float>(
12138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower            input_array.GetBuffer<ArrayDataType::kFloat>().data[i]);
12238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      } else if (input_array.buffer->type == ArrayDataType::kUint8) {
12338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower        outval = static_cast<float>(
12438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower            input_array.GetBuffer<ArrayDataType::kUint8>().data[i]);
12538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      } else if (input_array.buffer->type == ArrayDataType::kInt32) {
12638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower        outval = static_cast<float>(
12738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower            input_array.GetBuffer<ArrayDataType::kInt32>().data[i]);
12838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      } else if (input_array.buffer->type == ArrayDataType::kInt64) {
12938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower        outval = static_cast<float>(
13038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower            input_array.GetBuffer<ArrayDataType::kInt64>().data[i]);
13138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      } else {
13238b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower        LOG(FATAL) << "Unsupported cast op input type";
13338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      }
13438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      output_float_data[i] = outval;
13538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    }
13638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower  } else if (unary_op->type == OperatorType::kTensorFlowReshape) {
1370b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    CHECK(input_buffer_size == output_buffer_size);
13838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    memcpy(output_float_data.data(), (*input_float_data).data(),
13938b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower           output_buffer_size * sizeof(output_float_data[0]));
1400b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  } else if (unary_op->type == OperatorType::kTensorFlowSum) {
1410b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    // At the moment only full reduction across all dimensions is supported.
1420b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    float sum = 0.f;
14338b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    for (int i = 0; i < input_buffer_size; i++) {
14438b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      sum += (*input_float_data)[i];
1450b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
146c7e1de032658f7256e53b8a5a6066b140ecb9615A. Unique TensorFlower    for (int i = 0; i < output_buffer_size; ++i) {
147c7e1de032658f7256e53b8a5a6066b140ecb9615A. Unique TensorFlower      output_float_data[i] = sum;
148c7e1de032658f7256e53b8a5a6066b140ecb9615A. Unique TensorFlower    }
1490b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  } else if (unary_op->type == OperatorType::kTensorFlowMin) {
1500b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    // At the moment only full reduction across all dimensions is supported.
1510b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    // TODO(starka): Output should not be padded.
1520b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    for (int i = 0; i < output_dims_count; i++) {
1530b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      CHECK_EQ(output_shape.dims(i), 1);
1540b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
15538b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    float min = (*input_float_data)[0];
15638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    for (int i = 0; i < input_buffer_size; i++) {
15738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      min = std::min(min, (*input_float_data)[i]);
1580b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1590b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    output_float_data[0] = min;
1600b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  } else if (unary_op->type == OperatorType::kTensorFlowMax) {
1610b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    // At the moment only full reduction across all dimensions is supported.
1620b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    // TODO(starka): Output should not be padded.
1630b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    for (int i = 0; i < output_dims_count; i++) {
1640b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      CHECK_EQ(output_shape.dims(i), 1);
1650b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
16638b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    float max = (*input_float_data)[0];
16738b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    for (int i = 0; i < input_buffer_size; i++) {
16838b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      max = std::max(max, (*input_float_data)[i]);
1690b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1700b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    output_float_data[0] = max;
171c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower  } else if (unary_op->type == OperatorType::kNeg ||
172c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower             unary_op->type == OperatorType::kTensorFlowRsqrt ||
1730b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle             unary_op->type == OperatorType::kTensorFlowSqrt ||
1740b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle             unary_op->type == OperatorType::kTensorFlowSquare) {
1750b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    // Element-wise ops. Should have perfectly matching sizes here.
1760b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    for (int i = 0; i < output_dims_count; i++) {
1770b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      CHECK_EQ(output_shape.dims(i), input_shape.dims(i));
1780b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1790b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
18038b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower    for (int i = 0; i < output_buffer_size; i++) {
18138b20f83dbaada96902bdd5b419feb5a8e47395cA. Unique TensorFlower      const float val = (*input_float_data)[i];
1820b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      float outval = 0.f;
183c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower      if (unary_op->type == OperatorType::kNeg) {
184c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower        outval = -val;
185c02147704b1244010a93557de33ad4c64a20a5a5A. Unique TensorFlower      } else if (unary_op->type == OperatorType::kTensorFlowRsqrt) {
1860b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        outval = 1.0f / std::sqrt(val);
1870b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      } else if (unary_op->type == OperatorType::kTensorFlowSqrt) {
1880b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        outval = std::sqrt(val);
1890b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      } else if (unary_op->type == OperatorType::kTensorFlowSquare) {
1900b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        outval = val * val;
1910b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      } else {
1920b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle        LOG(FATAL) << "should not get here.";
1930b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      }
1940b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle      output_float_data[i] = outval;
1950b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
1960b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  } else {
1970b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    LOG(FATAL) << "should not get here.";
1980b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
1990b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  for (const auto& input : unary_op->inputs) {
2000b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    if (CountOpsWithInput(*model, input) == 1) {
201ba4aec48268d02f111cd7e2c2666f4e7b077e68aA. Unique TensorFlower      model->EraseArray(input);
2020b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle    }
2030b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  }
2040b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  AddMessageF("Resolved constant %s to the equivalent constant array",
2050b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle              LogName(*unary_op));
2060b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  model->operators.erase(unary_it);
2070b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle  return true;
2080b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle}
2090b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle
2100b15439f8f0f2d4755587f4096c3ea04cb199d23Andrew Selle}  // namespace toco
211