From 8f8bc33eb5df89ad9d19dfe6478585b8db0f1d56 Mon Sep 17 00:00:00 2001 From: Qing Date: Mon, 3 Dec 2018 22:35:31 -0800 Subject: [PATCH 1/8] add initial commit --- .../core/src/main/scala/org/apache/mxnet/NDArray.scala | 1 + .../core/src/main/scala/org/apache/mxnet/Symbol.scala | 1 + .../core/src/test/scala/org/apache/mxnet/NDArraySuite.scala | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala index 125958150b72..a745645a5372 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala @@ -35,6 +35,7 @@ import scala.ref.WeakReference @AddNDArrayFunctions(false) object NDArray extends NDArrayBase { implicit def getFirstResult(ret: NDArrayFuncReturn): NDArray = ret(0) + implicit def someWrapper[A](noSome : A) : Option[A] = Option(noSome) private val logger = LoggerFactory.getLogger(classOf[NDArray]) private val functions: Map[String, NDArrayFunction] = initNDArrayModule() diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/Symbol.scala b/scala-package/core/src/main/scala/org/apache/mxnet/Symbol.scala index 29885fc723cd..5d4366845fab 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/Symbol.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/Symbol.scala @@ -841,6 +841,7 @@ object Symbol extends SymbolBase { private val functions: Map[String, SymbolFunction] = initSymbolModule() private val bindReqMap = Map("null" -> 0, "write" -> 1, "add" -> 3) + implicit def someWrapper[A](noSome : A) : Option[A] = Option(noSome) val api = SymbolAPI val random = SymbolRandomAPI diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala index 7992a0ed867b..970ab786fc31 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala @@ -593,4 +593,10 @@ class NDArraySuite extends FunSuite with BeforeAndAfterAll with Matchers { assert(rnd.shape === Shape(1, 2, 3, 4)) assert(rnd2.shape === Shape(3, 4)) } + + test("Generated api") { + val arr = NDArray.ones(Shape(1, 2), dtype = DType.Float64) + NDArray.api.norm(arr, Some(0), out = arr) + val result = NDArray.api.dot(arr, arr) + } } From f76fabc32c4c61facc82e49358f3a31bea025af3 Mon Sep 17 00:00:00 2001 From: Qing Date: Tue, 4 Dec 2018 11:14:32 -0800 Subject: [PATCH 2/8] update image classifier as well --- .../infer/src/main/scala/org/apache/mxnet/infer/Classifier.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Classifier.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Classifier.scala index cf55bc10d97e..5208923275f6 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Classifier.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Classifier.scala @@ -126,7 +126,6 @@ class Classifier(modelPathPrefix: String, }) val predictResult = predictResultPar.toArray - var result: ListBuffer[IndexedSeq[(String, Float)]] = ListBuffer.empty[IndexedSeq[(String, Float)]] From 884e9ba04db28100d19197d13d03fffa6c1cd76f Mon Sep 17 00:00:00 2001 From: Qing Date: Tue, 11 Dec 2018 13:04:01 -0800 Subject: [PATCH 3/8] create Util class make Some conversion --- .../apache/mxnet/util/SomeConversion.scala | 22 ++++++++++++++++++ .../scala/org/apache/mxnet/NDArraySuite.scala | 3 ++- .../org/apache/mxnet/GeneratorBase.scala | 2 +- .../apache/mxnet/utils/CToScalaUtils.scala | 23 +++++++++++-------- .../scala/org/apache/mxnet/MacrosSuite.scala | 5 ++-- 5 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 scala-package/core/src/main/scala/org/apache/mxnet/util/SomeConversion.scala diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/util/SomeConversion.scala b/scala-package/core/src/main/scala/org/apache/mxnet/util/SomeConversion.scala new file mode 100644 index 000000000000..1d12f7c5e08e --- /dev/null +++ b/scala-package/core/src/main/scala/org/apache/mxnet/util/SomeConversion.scala @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.mxnet.util + +object SomeConversion { + implicit def someWrapper[A](noSome : A) : Option[A] = Option(noSome) +} diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala index 970ab786fc31..a55cfb79d2ee 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala @@ -595,8 +595,9 @@ class NDArraySuite extends FunSuite with BeforeAndAfterAll with Matchers { } test("Generated api") { + import org.apache.mxnet.util.SomeConversion._ val arr = NDArray.ones(Shape(1, 2), dtype = DType.Float64) - NDArray.api.norm(arr, Some(0), out = arr) + NDArray.api.norm(arr, ord = 0, out = arr) val result = NDArray.api.dot(arr, arr) } } diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala index 1c2c4fd704b3..ec24f19dfc14 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala @@ -96,7 +96,7 @@ private[mxnet] abstract class GeneratorBase { else if (isSymbol) "org.apache.mxnet.Symbol" else "org.apache.mxnet.NDArray" val typeAndOption = - CToScalaUtils.argumentCleaner(argName, argType, family) + CToScalaUtils.argumentCleaner(argName, argType, family, isJava) Arg(argName, typeAndOption._1, argDesc, typeAndOption._2) } val returnType = diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala index 2fd8b2e73c7a..2346046353dc 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala @@ -18,23 +18,26 @@ package org.apache.mxnet.utils private[mxnet] object CToScalaUtils { - + private val javaType = Array("java.lang.Float", "java.lang.Integer", + "java.lang.Long", "java.lang.Double", "java.lang.Boolean") + private val scalaType = Array("Float", "Int", "Long", "Double", "Boolean") // Convert C++ Types to Scala Types def typeConversion(in : String, argType : String = "", argName : String, - returnType : String) : String = { + returnType : String, isJava : Boolean) : String = { val header = returnType.split("\\.").dropRight(1) + val types = if (isJava) javaType else scalaType in match { case "Shape(tuple)" | "ShapeorNone" => s"${header.mkString(".")}.Shape" case "Symbol" | "NDArray" | "NDArray-or-Symbol" => returnType case "Symbol[]" | "NDArray[]" | "NDArray-or-Symbol[]" | "SymbolorSymbol[]" => s"Array[$returnType]" - case "float" | "real_t" | "floatorNone" => "java.lang.Float" - case "int" | "intorNone" | "int(non-negative)" => "java.lang.Integer" - case "long" | "long(non-negative)" => "java.lang.Long" - case "double" | "doubleorNone" => "java.lang.Double" + case "float" | "real_t" | "floatorNone" => types(0) + case "int" | "intorNone" | "int(non-negative)" => types(1) + case "long" | "long(non-negative)" => types(2) + case "double" | "doubleorNone" => types(3) case "string" => "String" - case "boolean" | "booleanorNone" => "java.lang.Boolean" + case "boolean" | "booleanorNone" => types(4) case "tupleof" | "tupleof" | "tupleof<>" | "ptr" | "" => "Any" case default => throw new IllegalArgumentException( s"Invalid type for args: $default\nString argType: $argType\nargName: $argName") @@ -54,7 +57,7 @@ private[mxnet] object CToScalaUtils { * @return (Scala_Type, isOptional) */ def argumentCleaner(argName: String, argType : String, - returnType : String) : (String, Boolean) = { + returnType : String, isJava : Boolean) : (String, Boolean) = { val spaceRemoved = argType.replaceAll("\\s+", "") var commaRemoved : Array[String] = new Array[String](0) // Deal with the case e.g: stype : {'csr', 'default', 'row_sparse'} @@ -72,9 +75,9 @@ private[mxnet] object CToScalaUtils { s"""expected "optional" got ${commaRemoved(1)}""") require(commaRemoved(2).startsWith("default="), s"""expected "default=..." got ${commaRemoved(2)}""") - (typeConversion(commaRemoved(0), argType, argName, returnType), true) + (typeConversion(commaRemoved(0), argType, argName, returnType, isJava), true) } else if (commaRemoved.length == 2 || commaRemoved.length == 1) { - val tempType = typeConversion(commaRemoved(0), argType, argName, returnType) + val tempType = typeConversion(commaRemoved(0), argType, argName, returnType, isJava) val tempOptional = tempType.equals("org.apache.mxnet.Symbol") (tempType, tempOptional) } else { diff --git a/scala-package/macros/src/test/scala/org/apache/mxnet/MacrosSuite.scala b/scala-package/macros/src/test/scala/org/apache/mxnet/MacrosSuite.scala index 4404b0885d57..4069bba25220 100644 --- a/scala-package/macros/src/test/scala/org/apache/mxnet/MacrosSuite.scala +++ b/scala-package/macros/src/test/scala/org/apache/mxnet/MacrosSuite.scala @@ -36,14 +36,15 @@ class MacrosSuite extends FunSuite with BeforeAndAfterAll { ) val output = List( ("org.apache.mxnet.Symbol", true), - ("java.lang.Integer", false), + ("Int", false), ("org.apache.mxnet.Shape", true), ("String", true), ("Any", false) ) for (idx <- input.indices) { - val result = CToScalaUtils.argumentCleaner("Sample", input(idx), "org.apache.mxnet.Symbol") + val result = CToScalaUtils.argumentCleaner("Sample", input(idx), + "org.apache.mxnet.Symbol", false) assert(result._1 === output(idx)._1 && result._2 === output(idx)._2) } } From 2462433c4bcf1a84dff4e7c0695d818990260eb5 Mon Sep 17 00:00:00 2001 From: Qing Date: Tue, 11 Dec 2018 15:28:37 -0800 Subject: [PATCH 4/8] add test changes --- .../core/src/main/scala/org/apache/mxnet/NDArray.scala | 1 - .../core/src/main/scala/org/apache/mxnet/Symbol.scala | 1 - .../core/src/test/scala/org/apache/mxnet/NDArraySuite.scala | 5 +++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala index a745645a5372..125958150b72 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/NDArray.scala @@ -35,7 +35,6 @@ import scala.ref.WeakReference @AddNDArrayFunctions(false) object NDArray extends NDArrayBase { implicit def getFirstResult(ret: NDArrayFuncReturn): NDArray = ret(0) - implicit def someWrapper[A](noSome : A) : Option[A] = Option(noSome) private val logger = LoggerFactory.getLogger(classOf[NDArray]) private val functions: Map[String, NDArrayFunction] = initNDArrayModule() diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/Symbol.scala b/scala-package/core/src/main/scala/org/apache/mxnet/Symbol.scala index 5d4366845fab..29885fc723cd 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/Symbol.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/Symbol.scala @@ -841,7 +841,6 @@ object Symbol extends SymbolBase { private val functions: Map[String, SymbolFunction] = initSymbolModule() private val bindReqMap = Map("null" -> 0, "write" -> 1, "add" -> 3) - implicit def someWrapper[A](noSome : A) : Option[A] = Option(noSome) val api = SymbolAPI val random = SymbolRandomAPI diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala index a55cfb79d2ee..5e60267646f8 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala @@ -597,7 +597,8 @@ class NDArraySuite extends FunSuite with BeforeAndAfterAll with Matchers { test("Generated api") { import org.apache.mxnet.util.SomeConversion._ val arr = NDArray.ones(Shape(1, 2), dtype = DType.Float64) - NDArray.api.norm(arr, ord = 0, out = arr) - val result = NDArray.api.dot(arr, arr) + val arr2 = NDArray.ones(Shape(1), dtype = DType.Float64) + NDArray.api.norm(arr, ord = 1, out = arr2) + val result = NDArray.api.dot(arr2, arr2) } } From ba8c84c0cc4d6a5e08931dd7e3987f6133876f7b Mon Sep 17 00:00:00 2001 From: Qing Date: Thu, 13 Dec 2018 10:49:16 -0800 Subject: [PATCH 5/8] adress Comments --- .../scala/org/apache/mxnet/NDArraySuite.scala | 5 ++++ .../apache/mxnet/utils/CToScalaUtils.scala | 25 +++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala index 5e60267646f8..51d65f91e26b 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala @@ -595,6 +595,11 @@ class NDArraySuite extends FunSuite with BeforeAndAfterAll with Matchers { } test("Generated api") { + // Without SomeConversion + val arr3 = NDArray.ones(Shape(1, 2), dtype = DType.Float64) + val arr4 = NDArray.ones(Shape(1), dtype = DType.Float64) + val arr5 = NDArray.api.norm(arr3, ord = Some(1), out = Some(arr4)) + // With SomeConversion import org.apache.mxnet.util.SomeConversion._ val arr = NDArray.ones(Shape(1, 2), dtype = DType.Float64) val arr2 = NDArray.ones(Shape(1), dtype = DType.Float64) diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala index 2346046353dc..86646b8eeb9e 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala @@ -18,9 +18,18 @@ package org.apache.mxnet.utils private[mxnet] object CToScalaUtils { - private val javaType = Array("java.lang.Float", "java.lang.Integer", - "java.lang.Long", "java.lang.Double", "java.lang.Boolean") - private val scalaType = Array("Float", "Int", "Long", "Double", "Boolean") + private val javaType = Map( + "float" -> "java.lang.Float", + "int" -> "java.lang.Integer", + "long" -> "java.lang.Long", + "double" -> "java.lang.Double", + "bool" -> "java.lang.Boolean") + private val scalaType = Map( + "float" -> "Float", + "int" -> "Int", + "long" -> "Long", + "double" -> "Double", + "bool" -> "Boolean") // Convert C++ Types to Scala Types def typeConversion(in : String, argType : String = "", argName : String, @@ -32,12 +41,12 @@ private[mxnet] object CToScalaUtils { case "Symbol" | "NDArray" | "NDArray-or-Symbol" => returnType case "Symbol[]" | "NDArray[]" | "NDArray-or-Symbol[]" | "SymbolorSymbol[]" => s"Array[$returnType]" - case "float" | "real_t" | "floatorNone" => types(0) - case "int" | "intorNone" | "int(non-negative)" => types(1) - case "long" | "long(non-negative)" => types(2) - case "double" | "doubleorNone" => types(3) + case "float" | "real_t" | "floatorNone" => types("float") + case "int" | "intorNone" | "int(non-negative)" => types("int") + case "long" | "long(non-negative)" => types("long") + case "double" | "doubleorNone" => types("double") case "string" => "String" - case "boolean" | "booleanorNone" => types(4) + case "boolean" | "booleanorNone" => types("bool") case "tupleof" | "tupleof" | "tupleof<>" | "ptr" | "" => "Any" case default => throw new IllegalArgumentException( s"Invalid type for args: $default\nString argType: $argType\nargName: $argName") From 6a6416156ac611fd6e5a6400f98721cdd63eaeb7 Mon Sep 17 00:00:00 2001 From: Qing Date: Thu, 13 Dec 2018 16:45:54 -0800 Subject: [PATCH 6/8] fix the spacing problem --- .../org/apache/mxnet/utils/CToScalaUtils.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala index 86646b8eeb9e..57c4cfba10b7 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/utils/CToScalaUtils.scala @@ -19,17 +19,17 @@ package org.apache.mxnet.utils private[mxnet] object CToScalaUtils { private val javaType = Map( - "float" -> "java.lang.Float", - "int" -> "java.lang.Integer", - "long" -> "java.lang.Long", + "float" -> "java.lang.Float", + "int" -> "java.lang.Integer", + "long" -> "java.lang.Long", "double" -> "java.lang.Double", - "bool" -> "java.lang.Boolean") + "bool" -> "java.lang.Boolean") private val scalaType = Map( - "float" -> "Float", - "int" -> "Int", - "long" -> "Long", + "float" -> "Float", + "int" -> "Int", + "long" -> "Long", "double" -> "Double", - "bool" -> "Boolean") + "bool" -> "Boolean") // Convert C++ Types to Scala Types def typeConversion(in : String, argType : String = "", argName : String, From 8bd6e46a28b10266fd93cf1c311ef0ee66ba922a Mon Sep 17 00:00:00 2001 From: Qing Date: Fri, 14 Dec 2018 15:51:36 -0800 Subject: [PATCH 7/8] fix generator base --- .../macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala index ec24f19dfc14..498c4e943669 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala @@ -191,7 +191,7 @@ private[mxnet] trait RandomHelpers { // unify call targets (random_xyz and sample_xyz) and unify their argument types private def unifyRandom(func: Func, isSymbol: Boolean): Func = { var typeConv = Set("org.apache.mxnet.NDArray", "org.apache.mxnet.Symbol", - "java.lang.Float", "java.lang.Integer") + "Float", "Int") func.copy( name = func.name.replaceAll("(random|sample)_", ""), From c1fcc95f18b72161290baab7d24bde7f4983297e Mon Sep 17 00:00:00 2001 From: Qing Date: Thu, 20 Dec 2018 16:02:37 -0800 Subject: [PATCH 8/8] change name to Option --- .../mxnet/util/{SomeConversion.scala => OptionConversion.scala} | 2 +- .../core/src/test/scala/org/apache/mxnet/NDArraySuite.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename scala-package/core/src/main/scala/org/apache/mxnet/util/{SomeConversion.scala => OptionConversion.scala} (97%) diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/util/SomeConversion.scala b/scala-package/core/src/main/scala/org/apache/mxnet/util/OptionConversion.scala similarity index 97% rename from scala-package/core/src/main/scala/org/apache/mxnet/util/SomeConversion.scala rename to scala-package/core/src/main/scala/org/apache/mxnet/util/OptionConversion.scala index 1d12f7c5e08e..2cf453ac3d18 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/util/SomeConversion.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/util/OptionConversion.scala @@ -17,6 +17,6 @@ package org.apache.mxnet.util -object SomeConversion { +object OptionConversion { implicit def someWrapper[A](noSome : A) : Option[A] = Option(noSome) } diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala index 51d65f91e26b..2db9ff11b374 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/NDArraySuite.scala @@ -600,7 +600,7 @@ class NDArraySuite extends FunSuite with BeforeAndAfterAll with Matchers { val arr4 = NDArray.ones(Shape(1), dtype = DType.Float64) val arr5 = NDArray.api.norm(arr3, ord = Some(1), out = Some(arr4)) // With SomeConversion - import org.apache.mxnet.util.SomeConversion._ + import org.apache.mxnet.util.OptionConversion._ val arr = NDArray.ones(Shape(1, 2), dtype = DType.Float64) val arr2 = NDArray.ones(Shape(1), dtype = DType.Float64) NDArray.api.norm(arr, ord = 1, out = arr2)