/* Copyright 2019 The TensorFlow Authors. 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.
==============================================================================*/
#include "tensorflow/core/kernels/data/tensor_dataset_op.h"

#include "tensorflow/core/kernels/data/dataset_test_base.h"

namespace tensorflow {
namespace data {
namespace {

constexpr char kNodeName[] = "tensor_dataset";

class TensorDatasetOpTest : public DatasetOpsTestBase {
 protected:
  // Creates a new TensorDataset op kernel.
  Status CreateTensorDatasetKernel(
      DataTypeVector dtypes, std::vector<PartialTensorShape> shapes,
      std::unique_ptr<OpKernel> *tensor_dataset_kernel) {
    std::vector<string> components;
    components.reserve(dtypes.size());
    for (int i = 0; i < dtypes.size(); i++) {
      components.emplace_back(
          strings::StrCat(TensorDatasetOp::kComponents, "_", i));
    }
    node_def_ = test::function::NDef(
        kNodeName, name_utils::OpName(TensorDatasetOp::kDatasetType),
        components,
        {{TensorDatasetOp::kToutput_types, dtypes},
         {TensorDatasetOp::kOutputShapes, shapes}});
    TF_RETURN_IF_ERROR(CreateOpKernel(node_def_, tensor_dataset_kernel));
    return Status::OK();
  }

  // Creates a new TensorDataset op kernel context.
  Status CreateTensorDatasetContext(OpKernel *const tensor_dataset_kernel,
                                    gtl::InlinedVector<TensorValue, 4> *inputs,
                                    std::unique_ptr<OpKernelContext> *context) {
    TF_RETURN_IF_ERROR(CheckOpKernelInput(*tensor_dataset_kernel, *inputs));
    TF_RETURN_IF_ERROR(
        CreateOpKernelContext(tensor_dataset_kernel, inputs, context));
    return Status::OK();
  }

 private:
  NodeDef node_def_;
};

struct TestCase {
  std::vector<Tensor> components;
  std::vector<Tensor> expected_outputs;
  DataTypeVector expected_output_dtypes;
  std::vector<PartialTensorShape> expected_output_shapes;
  int64 expected_cardinality;
  std::vector<int> breakpoints;
};

// Test case 1: test a dataset that represents a single tuple of plain tensors.
TestCase PlainTensorsTestCase() {
  return {/*components*/
          {CreateTensor<int64>(TensorShape({}), {1}),
           CreateTensor<int64>(TensorShape({1, 3}), {1, 2, 3}),
           CreateTensor<double>(TensorShape({}), {37.0}),
           CreateTensor<tstring>(TensorShape({1, 2}), {"a", "b"})},
          /*expected_outputs*/
          {CreateTensor<int64>(TensorShape({}), {1}),
           CreateTensor<int64>(TensorShape({1, 3}), {1, 2, 3}),
           CreateTensor<double>(TensorShape({}), {37.0}),
           CreateTensor<tstring>(TensorShape({1, 2}), {"a", "b"})},
          /*expected_output_dtypes*/
          {DT_INT64, DT_INT64, DT_DOUBLE, DT_STRING},
          /*expected_output_shapes*/
          {PartialTensorShape({}), PartialTensorShape({1, 3}),
           PartialTensorShape({}), PartialTensorShape({1, 2})},
          /*expected_cardinality*/ 1,
          /*breakpoints*/ {0, 1, 2}};
}

// Test case 2: test a dataset that represents a tuple of nested tensors.
TestCase NestedTensorsTestCase() {
  return {
      /*components*/
      {CreateTensor<Variant>(
           TensorShape({}),
           {CreateTensor<double>(TensorShape({2, 2}), {1.0, 2.0, 3.0, 4.0})}),
       CreateTensor<Variant>(
           TensorShape({}),
           {CreateTensor<tstring>(TensorShape({1, 2}), {"a", "b"})}),
       CreateTensor<int64>(TensorShape({1, 3}), {1, 2, 3})},
      /*expected_outputs*/
      {CreateTensor<Variant>(
           TensorShape({}),
           {CreateTensor<double>(TensorShape({2, 2}), {1.0, 2.0, 3.0, 4.0})}),
       CreateTensor<Variant>(
           TensorShape({}),
           {CreateTensor<tstring>(TensorShape({1, 2}), {"a", "b"})}),
       CreateTensor<int64>(TensorShape({1, 3}), {1, 2, 3})},
      /*expected_output_dtypes*/
      {DT_VARIANT, DT_VARIANT, DT_INT64},
      /*expected_output_shapes*/
      {PartialTensorShape({}), PartialTensorShape({}),
       PartialTensorShape({1, 3})},
      /*expected_cardinality*/ 1,
      /*breakpoints*/ {0, 1, 2}};
}

class ParametrizedTensorDatasetOpTest
    : public TensorDatasetOpTest,
      public ::testing::WithParamInterface<TestCase> {};

TEST_P(ParametrizedTensorDatasetOpTest, GetNext) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = GetParam();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  std::unique_ptr<IteratorContext> iterator_context;
  TF_ASSERT_OK(
      CreateIteratorContext(tensor_dataset_context.get(), &iterator_context));
  std::unique_ptr<IteratorBase> iterator;
  TF_ASSERT_OK(tensor_dataset->MakeIterator(iterator_context.get(), "Iterator",
                                            &iterator));
  bool end_of_sequence = false;
  std::vector<Tensor> out_tensors;
  while (!end_of_sequence) {
    TF_EXPECT_OK(iterator->GetNext(iterator_context.get(), &out_tensors,
                                   &end_of_sequence));
  }
  EXPECT_EQ(out_tensors.size(), test_case.expected_outputs.size());
  for (int i = 0; i < out_tensors.size(); ++i) {
    if (out_tensors[i].dtype() == DT_VARIANT) {
      // Currently `ExpectEqual()` does not support the variant tensor
      // yet, so we manually cast the variant to numeric/string tensor.
      const Tensor *output = out_tensors[i].scalar<Variant>()().get<Tensor>();
      const Tensor *expected_output =
          test_case.expected_outputs[i].scalar<Variant>()().get<Tensor>();
      TF_EXPECT_OK(ExpectEqual(*output, *expected_output));
    } else {
      TF_EXPECT_OK(ExpectEqual(out_tensors[i], test_case.expected_outputs[i]));
    }
  }
}

TEST_F(TensorDatasetOpTest, DatasetTypeString) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = PlainTensorsTestCase();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  EXPECT_EQ(tensor_dataset->type_string(),
            name_utils::OpName(TensorDatasetOp::kDatasetType));
}

TEST_F(TensorDatasetOpTest, DatasetNodeName) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = PlainTensorsTestCase();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  EXPECT_EQ(tensor_dataset->node_name(), kNodeName);
}

TEST_F(TensorDatasetOpTest, DatasetOutputDtypes) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = PlainTensorsTestCase();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  EXPECT_EQ(tensor_dataset->output_dtypes(), test_case.expected_output_dtypes);
}

TEST_F(TensorDatasetOpTest, DatasetOutputShapes) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = PlainTensorsTestCase();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  EXPECT_EQ(tensor_dataset->output_shapes().size(),
            test_case.expected_output_shapes.size());
  for (int i = 0; i < test_case.expected_output_shapes.size(); i++) {
    EXPECT_TRUE(test_case.expected_output_shapes[i].IsIdenticalTo(
        tensor_dataset->output_shapes()[i]));
  }
}

TEST_F(TensorDatasetOpTest, Cardinality) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = PlainTensorsTestCase();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  EXPECT_EQ(tensor_dataset->Cardinality(), test_case.expected_cardinality);
}

TEST_P(ParametrizedTensorDatasetOpTest, IteratorOutputDtypes) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = GetParam();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  std::unique_ptr<IteratorContext> iterator_context;
  TF_ASSERT_OK(
      CreateIteratorContext(tensor_dataset_context.get(), &iterator_context));
  std::unique_ptr<IteratorBase> iterator;
  TF_ASSERT_OK(tensor_dataset->MakeIterator(iterator_context.get(), "Iterator",
                                            &iterator));
  EXPECT_EQ(iterator->output_dtypes(), test_case.expected_output_dtypes);
}

TEST_P(ParametrizedTensorDatasetOpTest, IteratorOutputShapes) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = GetParam();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  std::unique_ptr<IteratorContext> iterator_context;
  TF_ASSERT_OK(
      CreateIteratorContext(tensor_dataset_context.get(), &iterator_context));
  std::unique_ptr<IteratorBase> iterator;
  TF_ASSERT_OK(tensor_dataset->MakeIterator(iterator_context.get(), "Iterator",
                                            &iterator));

  EXPECT_EQ(iterator->output_shapes().size(),
            test_case.expected_output_shapes.size());
  for (int i = 0; i < test_case.expected_output_shapes.size(); ++i) {
    EXPECT_TRUE(test_case.expected_output_shapes[i].IsIdenticalTo(
        iterator->output_shapes()[i]));
  }
}

TEST_F(TensorDatasetOpTest, IteratorOutputPrefix) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = PlainTensorsTestCase();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  std::unique_ptr<IteratorContext> iterator_context;
  TF_ASSERT_OK(
      CreateIteratorContext(tensor_dataset_context.get(), &iterator_context));
  std::unique_ptr<IteratorBase> iterator;
  TF_ASSERT_OK(tensor_dataset->MakeIterator(iterator_context.get(), "Iterator",
                                            &iterator));

  EXPECT_EQ(iterator->prefix(),
            name_utils::IteratorPrefix("FromTensor", "Iterator"));
}

TEST_P(ParametrizedTensorDatasetOpTest, Roundtrip) {
  int thread_num = 2, cpu_num = 2;
  TF_ASSERT_OK(InitThreadPool(thread_num));
  TF_ASSERT_OK(InitFunctionLibraryRuntime({}, cpu_num));

  const TestCase &test_case = GetParam();
  std::vector<Tensor> components = test_case.components;
  gtl::InlinedVector<TensorValue, 4> inputs;
  for (auto &component : components) {
    inputs.push_back(TensorValue(&component));
  }
  std::unique_ptr<OpKernel> tensor_dataset_kernel;
  TF_ASSERT_OK(CreateTensorDatasetKernel(test_case.expected_output_dtypes,
                                         test_case.expected_output_shapes,
                                         &tensor_dataset_kernel));
  std::unique_ptr<OpKernelContext> tensor_dataset_context;
  TF_ASSERT_OK(CreateTensorDatasetContext(tensor_dataset_kernel.get(), &inputs,
                                          &tensor_dataset_context));
  DatasetBase *tensor_dataset;
  TF_ASSERT_OK(CreateDataset(tensor_dataset_kernel.get(),
                             tensor_dataset_context.get(), &tensor_dataset));
  core::ScopedUnref scoped_unref(tensor_dataset);

  std::unique_ptr<IteratorContext> iterator_ctx;
  TF_ASSERT_OK(
      CreateIteratorContext(tensor_dataset_context.get(), &iterator_ctx));
  std::unique_ptr<IteratorBase> iterator;
  TF_ASSERT_OK(
      tensor_dataset->MakeIterator(iterator_ctx.get(), "Iterator", &iterator));

  std::unique_ptr<SerializationContext> serialization_ctx;
  TF_ASSERT_OK(CreateSerializationContext(&serialization_ctx));
  bool end_of_sequence = false;
  std::vector<Tensor> out_tensors;
  int cur_iteration = 0;
  const std::vector<int> &breakpoints = test_case.breakpoints;
  for (int breakpoint : breakpoints) {
    VariantTensorData data;
    VariantTensorDataWriter writer(&data);
    TF_EXPECT_OK(iterator->Save(serialization_ctx.get(), &writer));
    TF_EXPECT_OK(writer.Flush());
    VariantTensorDataReader reader(&data);
    TF_EXPECT_OK(RestoreIterator(iterator_ctx.get(), &reader, "Iterator",
                                 *tensor_dataset, &iterator));

    while (cur_iteration <= breakpoint) {
      TF_EXPECT_OK(iterator->GetNext(iterator_ctx.get(), &out_tensors,
                                     &end_of_sequence));
      if (!end_of_sequence) {
        EXPECT_EQ(out_tensors.size(), test_case.expected_outputs.size());
        for (int i = 0; i < out_tensors.size(); ++i) {
          if (out_tensors[i].dtype() == DT_VARIANT) {
            // Currently `ExpectEqual()` does not support the variant tensor
            // yet, so we manually cast the variant to numeric/string tensor.
            const Tensor *output =
                out_tensors[i].scalar<Variant>()().get<Tensor>();
            const Tensor *expected_output =
                test_case.expected_outputs[i].scalar<Variant>()().get<Tensor>();
            TF_EXPECT_OK(ExpectEqual(*output, *expected_output));
          } else {
            TF_EXPECT_OK(
                ExpectEqual(out_tensors[i], test_case.expected_outputs[i]));
          }
        }
      }
      cur_iteration++;
    }

    if (breakpoint >= test_case.expected_cardinality) {
      EXPECT_TRUE(end_of_sequence);
    } else {
      EXPECT_FALSE(end_of_sequence);
    }
  }
}

INSTANTIATE_TEST_CASE_P(
    TensorDatasetOpTest, ParametrizedTensorDatasetOpTest,
    ::testing::ValuesIn(std::vector<TestCase>({PlainTensorsTestCase(),
                                               NestedTensorsTestCase()})));

}  // namespace
}  // namespace data
}  // namespace tensorflow


The term vegan didn’t start up until the 1940s, and it’s been a growing trend ever since. Veganism means abstaining from all animal products, especially when it comes to food. Avoiding meat, eggs, dairy, honey and more is becoming more common, even if veganism doesn’t come close to representing a majority. It’s estimated that there are 16 million people in the United States alone that are vegetarian or vegan, and that veganism has grown by 500 percent in the last three years in the US.

Because of these growing numbers, vegetarian and vegan restaurants have been popping up all over the world, and it also means that existing chains are going to have to adapt to keep up with higher demand. The world’s most popular fast food chain, McDonald’s, is among those that are looking to expand into the vegan market.

Currently, there aren’t many vegan options at the majority of McDonald’s locations outside of the side salad, most condiments, buns and drinks, along with bagels and oatmeal. Choosing to go vegan can be difficult if you want to give up the old McDonald’s standbys like the hamburger, but the fast food chain is hoping you don’t have to. Recently, they’ve introduced the McVegan, and they’re testing it in one very particular market.

In Tampere, Finland, McDonald’s offers up the McVegan with a soy-based patty that consists of no animal products. For the toppings, you get pickles, mustard, lettuce, onion and tomato on a sesame seed bun. The french fries in Finland are already vegan, and are now being touted to the public as such. In the United States, you won’t find vegan fries at McDonald’s since they’re fried in hydrolyzed milk.

McDonald’s has said that the specific Finland location is the only one that will be getting the McVegan…for now. After hearing about the news, those in other countries have taken to social media to ask for McDonald’s to make the McVegan a permanent staple of their menu. One user said “Please bring your vegan burger to Seattle. We will eat them all day.” Another added “Bring the McVegan burger to the U.S. plus vegan fries, I’ll guarantee they will sell. Definitely bring me back.”

But why Tampere, Finland? It’s not exactly a small city with more than 223,000 residents, but it seems a bit odd to restrict this test item into a non-global city outside of the United States. The population of vegetarians and vegans in Finland is also quite similar to the United States at six percent. Experts speculate that it’s the ease of Finland’s vegan preparation of french fries that made the logistics much easier than in a United States location.

The quick response that McDonald’s has received has the fast food giant feeling optimistic about introducing more vegan and vegetarian friendly options to their menu. Christoffer Ronnblad is the Marketing Director for McDonald’s in Finland. He said “We are following the reaction with interest and we are happy to receive feedback. That’s the purpose of the trial.” He added that “The vegan hamburger required extensive in-house production development. The (McVegan) steak is soy based. We were really passionate about finding a steak that tastes really good.”

After hearing some of the comments that customers have been giving, Ronnblad said “At least the feedback that we have quickly received and what reception has been online…it has been quite positive. We are going to be positive in this test.” The price point hasn’t been one that’s talked about, leading many to believe that it will end up costing just the same as most of the other gourmet burgers, if not just a bit more. Either way, it’s not going to be a $10 burger if introduced in the United States.

McDonald’s goes through a very long testing process before adding anything to their menu. Even some that were incredibly popular end up getting removed due to high costs, low volume sales and other aspects. You might remember at one point that McDonald’s has had popular items such as the Chicken Selects or the Big ‘n’ Tasty fall by the wayside eventually, while every store carries the signature Big Mac that was added to the menu in 1967.

If the McVegan does get rolled out to stores around the world, will it become the vegan equivalent of the Big Mac? Or will it simply fall flat and become a regional taste that you won’t be able to find in smaller cities. Only time will tell, but the early returns on the McVegan have been a pleasant surprise.


Birmingham complete signing of Ben Foster from Manchester United

19 May 2010 01:52

Viewed : 19

Foster, 27, completed his medical this morning [Wednesday] after deciding that the move offers him the best chance to breathe new life into his club and international career, having narrowly missed out on Fabio Capello's provisional World Cup squad for South Africa.

Although he was selected for successive internationals against Belarus and Brazil by Capello, Foster's club situation he made just nine Premier League starts last season after dropping behind Edwin van der Sar and Tomasz Kuszczak to third choice keeper counted against him in terms of making the England squad. Robert Green, David James and Joe Hart were all selected ahead of him.

Foster leaves Manchester United with warm praise from his United manager Sir Alex Ferguson, who called him the best English goalkeeper in the country. 'I think he deserves the opportunity to do better for himself,' Ferguson said.

'That's the reason we accepted the offer. I feel and I still am very strong about this, I think he's the best English goalkeeper in England.'

Foster becomes Birmingham's first major signing of the summer, and an important one at that. Essentially, he has been brought in as replacement for Hart who has returned to Manchester City after a season long loan.

The United keeper was singled out by Birmingham's goalkeeping coach Dave Watson, and signed off by manager Alex McLeish.

"It looks as if we've got another great acquisition with Ben," said McLeish. "The goalkeeper is a massive position and we've now signed another young hungry goalkeeper who wants to prove himself and live up to his potential. We believe he is of the same kind of standard as Joe

Hart. Ben's situation is similar to Joe's in that he found himself twiddling his thumbs behind another top keeper in Van der Saar. Van der Saar is a world class player and a hard act to follow.

"Ben is ambitious and wants to play regular football and naturally doesn't want to play second fiddle. We think he can make a name for himself and it's up to him to do what Big Joe did."


Q:

How to replace an edge with Gremlin script

I want to look for a vertex, get an edge 'views', remove it, and replace with a new edge between the same vertices.
g.V('uuid','bf4dcbd24e9944319954dec5ad60c658')
.inE('views')
.sideEffect{g.addEdge(it.outV.next(),it.inV.next(),'likes')}
.sideEffect{g.removeEdge(it)}

This works, but is it the best way? Renaming the edge is an option?

A:

You can use sideEffect as you did.  That approach has the positive aspect of being straightforward and easy to understand.  However, my personal preference for "production" Gremlin code is to use explicit Gremlin functions when they are available.  In this case, I see the opportunity to use "link" and "step closures" (https://github.com/tinkerpop/gremlin/wiki/Step-Closures):
gremlin> g = TinkerGraphFactory.createTinkerGraph()
==>tinkergraph[vertices:6 edges:6]
gremlin> g.E
==>e[10][4-created->5]
==>e[7][1-knows->2]
==>e[9][1-created->3]
==>e[8][1-knows->4]
==>e[11][4-created->3]
==>e[12][6-created->3]
gremlin> g.v(1).as('x').outE('knows').as('toRemove').inV.except('x').linkIn('swonk','x').sideEffect{v,m->g.removeEdge(m.toRemove)}
==>v[2]
==>v[4]
gremlin> g.E
==>e[1][1-swonk->4]
==>e[10][4-created->5]
==>e[0][1-swonk->2]
==>e[9][1-created->3]
==>e[11][4-created->3]
==>e[12][6-created->3]

In the above I "rename" all the "knows" edges for g.v(1) to "swonk".


Following its work on stage 1, UK consulting engineer Arup has been appointed to investigate the second stage of the Parramatta Light Rail scheme in Sydney, Australia.

Parramatta Light Rail is one of the New South Wales’ Government’s latest major infrastructure projects, additional to the massive Sydney Metro project, which runs underground.

Opening in 2023, stage 1 is a 12-km two-way overground track from Westmead to Carlingford, via the central business district (CBD) of the growing suburb of Parramatta.

Stage 2 is planned as a 9-km line from Parramatta CBD to Sydney Olympic Park via Ermington, Melrose Park, and Wentworth Point.

According to the NSW government, the Parramatta Light Rail will provide frequent, reliable, ‘turn up and go’ light rail services for the Greater Parramatta region, with around 28,000 people getting on and off every day by 2026.

Arup’s project director Ryan Andriessen said: “We started out as infrastructure technical adviser, assisting in the planning and procurement of Stage 1 which will connect Westmead to Carlingford via the Parramatta CBD and Camellia. Since then, the agency’s trust in our ability to deliver has kept increasing, and they’ve enacted several options to the original contract.

“This has opened up a lot of doors to future opportunities, with contracts that extend through to 2023 to provide on-going technical support for the early, enabling and main works packages for Stage 1, including design reviews and construction phase services.

“Our brief has grown again to include investigations for Stage 2, which will create a branch to Sydney Olympic Park via Ermington and Wentworth Point, continuing to connect communities, culture and commerce.”

Image: Render of Parramatta Light Rail train station (Arup)


Last Children – More Bitter Than Sweet?

Last night when it was bedtime, the kids were watching a show. I told them they could either come upstairs with me now and read books or go right to bed at the end of the show with no books. T opted to watch the show. A looked up at me with wide eyes and said, "Mama, I wanna read books with you. Let's go." Then she grabbed my hand and we began climbing the stairs together.

I let her choose several books and we read them together. It was nice having a little one on one time with my girl without her brother, who to date had never passed up storytime before bed. It melted my heart that she chose me over the television.

Later on, I fell asleep wondering if this is the beginning of something new with T. He is totally into the Wii, iPad and television. He always has been, as much as we let him, but it feels different lately. He is a little boy now, and storytime with mom isn't as cool as television with dad. A preview of things to come, I suppose.

Today we're getting a ton of snow, so we've been knocking little things off the to-do list around the house. Hang that picture. Tidy up the top of the dresser. Fold and put away laundry. Everything found its place today.

As I was tidying up A's room, I realized the top of her dresser is still a changing table in spite of the fact that she has been potty trained for months. I dismantled it and put some books and stuffed animals on it so it wouldn't look so empty.

While I was dismantling things, I decided to take the front of her crib off and convert it into a toddler bed. Another thing we are long overdue for, especially since I have a hard time lifting her in and out of her crib.

When I called her into her room to show her what I had done, she said "Mama, it's just perfect. I love it. Thank you so much." Which was exactly what I needed to hear. Then T came running into the room and gave his approval of the changes. (Mostly, I think, because now it will be less work for him to climb into bed with her in the morning.)

But for some reason, when Ry made his way into the room I got choked up and started to cry a little bit. "If you weren't ready, why did you change everything?" he asked.

"Because she is more than ready, and it's not about me, it's about her," I said.

And I meant it. But still – I am glad that she and I read stories just the two of us last night and that I took a little longer than usual before I lifted her into her crib. And that I stayed in her room for a few extra minutes finding her blankies and kitties hidden under piles of Barbies and princess dresses. Because in a way, I feel like that was the last time I put my baby to bed, even if I didn't know it yet.

Tonight, she will put herself in her bed and get herself up tomorrow morning. And because she is my last child, it is the end of something. It feels incredibly bitter, more bitter than sweet.

I wonder if this is the way it will be for me with her. Which will be nice, in a way, the symmetry of it. With your oldest child, you focus on the "firsts", rather than the "lasts." I can tell you at what age T reached almost all of his milestones. With A, I am more likely to give you a range or tell you if she did something before or after her brother.

Conversely, I haven't noticed a lot of T's "lasts." We moved him into a double bed without a second thought – mostly because he was even more overdue for a bed than A is now. I didn't cry over it once.

It makes me glad there are two of them, a boy and a girl, to balance each other out. Okay, to balance me out.

Let’s Socialize

About Leah

Thirtysomething wife and mama to three kids who are known to the blogosphere as T, A & E. We live in Maine with the kids’ dad, R – who also happens to be the boss of our marketing agency. The fun never ends, I tell you. Currently trying to remember how to take care of a baby, due to the four year age gap between A and E. In my spare time (HA!) I take endless photographs of the tiny humans and sew ill-fitting clothes for A. Me, in three words: always perfectly imperfect.

Recent Posts

Earlier this week, a post appeared in my Facebook newsfeed that said something along the lines of "Thanks, now I know what each of your children look like and what grade they are in." As if I needed a reminder that I'm smack dab in the middle of "Club 30", the prevalance of back to […]

When I found out I was unexpectedly expecting Baby #3, part of me began mourning the loss of the family of four. We had just reached "the sweet spot" with the big kids – everyone was using the toilet on their own, everyone was sleeping through the night, R and I were beginning to remember […]

My biggest little girl is starting kindergarten this week. All summer, people have been asking if we're ready, and all summer we have answered the same way. A says "I'm so excited!", and I say "She's so ready." What I do not say is "I'm not ready." I was not ready to send T, but […]