Skip to content

Commit

Permalink
Introduce Dart language support (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
MattConflitti committed Feb 24, 2020
1 parent 3086e9a commit 3c431f2
Show file tree
Hide file tree
Showing 18 changed files with 687 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ env:
- TEST=API
- TEST=E2E LANG="c_lang or python or java or go_lang or javascript or php"
- TEST=E2E LANG="c_sharp or visual_basic or powershell"
- TEST=E2E LANG="r_lang"
- TEST=E2E LANG="r_lang or dart"

before_install:
- bash .travis/setup.sh
Expand Down
8 changes: 8 additions & 0 deletions .travis/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,11 @@ if [[ $LANG == *"php"* ]]; then
sudo apt-get update
sudo apt-get install --no-install-recommends -y php
fi

# Install Dart. (https://dart.dev/get-dart)
if [[ $LANG == *"dart"* ]]; then
sudo sh -c 'wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -'
sudo sh -c 'wget -qO- https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list'
sudo apt-get update
sudo apt-get install --no-install-recommends -y dart
fi
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ RUN apt-get update && \
add-apt-repository ppa:deadsnakes/ppa && \
wget -q https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \
dpkg -i packages-microsoft-prod.deb && \
wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
wget -qO- https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list && \
apt-get update && \
apt-get install --no-install-recommends -y \
gcc \
Expand All @@ -20,7 +22,8 @@ RUN apt-get update && \
dotnet-sdk-3.0 \
powershell \
r-base \
php && \
php \
dart && \
rm -rf /var/lib/apt/lists/*

WORKDIR /m2cgen
Expand Down
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
include LICENSE
recursive-include m2cgen VERSION.txt
recursive-include m2cgen linear_algebra.*
recursive-include m2cgen tanh.bas
recursive-include m2cgen tanh.*
global-exclude *.py[cod]
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![PyPI Version](https://img.shields.io/pypi/v/m2cgen.svg?logo=pypi&logoColor=white)](https://pypi.org/project/m2cgen)
[![Downloads](https://pepy.tech/badge/m2cgen)](https://pepy.tech/project/m2cgen)

**m2cgen** (Model 2 Code Generator) - is a lightweight library which provides an easy way to transpile trained statistical models into a native code (Python, C, Java, Go, JavaScript, Visual Basic, C#, PowerShell, R, PHP).
**m2cgen** (Model 2 Code Generator) - is a lightweight library which provides an easy way to transpile trained statistical models into a native code (Python, C, Java, Go, JavaScript, Visual Basic, C#, PowerShell, R, PHP, Dart).

* [Installation](#installation)
* [Supported Languages](#supported-languages)
Expand All @@ -28,6 +28,7 @@ pip install m2cgen

- C
- C#
- Dart
- Go
- Java
- JavaScript
Expand Down
2 changes: 2 additions & 0 deletions m2cgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
export_to_powershell,
export_to_r,
export_to_php,
export_to_dart,
)

__all__ = [
Expand All @@ -24,6 +25,7 @@
export_to_powershell,
export_to_r,
export_to_php,
export_to_dart,
]

with open(os.path.join(os.path.dirname(os.path.realpath(__file__)),
Expand Down
1 change: 1 addition & 0 deletions m2cgen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"powershell": (m2cgen.export_to_powershell, ["indent", "function_name"]),
"r": (m2cgen.export_to_r, ["indent", "function_name"]),
"php": (m2cgen.export_to_php, ["indent", "function_name"]),
"dart": (m2cgen.export_to_dart, ["indent", "function_name"]),
}


Expand Down
24 changes: 24 additions & 0 deletions m2cgen/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,30 @@ def export_to_php(model, indent=4, function_name="score"):
return _export(model, interpreter)


def export_to_dart(model, indent=4, function_name="score"):
"""
Generates a Dart code representation of the given model.
Parameters
----------
model : object
The model object that should be transpiled into code.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.
Returns
-------
code : string
"""
interpreter = interpreters.DartInterpreter(
indent=indent,
function_name=function_name,
)
return _export(model, interpreter)


def _export(model, interpreter):
assembler_cls = assemblers.get_assembler_cls(model)
model_ast = assembler_cls(model).assemble()
Expand Down
2 changes: 2 additions & 0 deletions m2cgen/interpreters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .powershell.interpreter import PowershellInterpreter
from .r.interpreter import RInterpreter
from .php.interpreter import PhpInterpreter
from .dart.interpreter import DartInterpreter

__all__ = [
JavaInterpreter,
Expand All @@ -20,4 +21,5 @@
PowershellInterpreter,
RInterpreter,
PhpInterpreter,
DartInterpreter,
]
Empty file.
40 changes: 40 additions & 0 deletions m2cgen/interpreters/dart/code_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import contextlib

from m2cgen.interpreters.code_generator import CLikeCodeGenerator


class DartCodeGenerator(CLikeCodeGenerator):

scalar_type = "double"
vector_type = "List<double>"

def __init__(self, *args, **kwargs):
super(DartCodeGenerator, self).__init__(*args, **kwargs)

def add_function_def(self, name, args, is_vector_output):
return_type = self._get_var_declare_type(is_vector_output)
function_def = return_type + " " + name + "("
function_def += ",".join([
self._get_var_declare_type(is_vector) + " " + n
for is_vector, n in args])
function_def += ") {"
self.add_code_line(function_def)
self.increase_indent()

@contextlib.contextmanager
def function_definition(self, name, args, is_vector_output):
self.add_function_def(name, args, is_vector_output)
yield
self.add_block_termination()

def vector_init(self, values):
return "[" + ", ".join(values) + "]"

def _get_var_declare_type(self, is_vector):
return (
self.vector_type if is_vector
else self.scalar_type)

def add_dependency(self, dep):
dep_str = "import '" + dep + "';"
self.prepend_code_line(dep_str)
69 changes: 69 additions & 0 deletions m2cgen/interpreters/dart/interpreter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import os

from m2cgen import ast
from m2cgen.interpreters import mixins
from m2cgen.interpreters import utils
from m2cgen.interpreters.interpreter import ToCodeInterpreter
from m2cgen.interpreters.dart.code_generator import DartCodeGenerator


class DartInterpreter(ToCodeInterpreter,
mixins.LinearAlgebraMixin,
mixins.BinExpressionDepthTrackingMixin):

supported_bin_vector_ops = {
ast.BinNumOpType.ADD: "addVectors",
}

supported_bin_vector_num_ops = {
ast.BinNumOpType.MUL: "mulVectorNumber",
}

bin_depth_threshold = 465

exponent_function_name = "exp"
power_function_name = "pow"
tanh_function_name = "tanh"

with_tanh_expr = False

def __init__(self, indent=4, function_name="score", *args, **kwargs):
self.indent = indent
self.function_name = function_name

cg = DartCodeGenerator(indent=indent)
super(DartInterpreter, self).__init__(cg, *args, **kwargs)

def interpret(self, expr):
self._cg.reset_state()
self._reset_reused_expr_cache()

args = [(True, self._feature_array_name)]

with self._cg.function_definition(
name=self.function_name,
args=args,
is_vector_output=expr.output_size > 1):
last_result = self._do_interpret(expr)
self._cg.add_return_statement(last_result)

if self.with_linear_algebra:
filename = os.path.join(
os.path.dirname(__file__), "linear_algebra.dart")
self._cg.add_code_lines(utils.get_file_content(filename))

# Use own tanh function in order to be compatible with Dart
if self.with_tanh_expr:
filename = os.path.join(
os.path.dirname(__file__), "tanh.dart")
self._cg.add_code_lines(utils.get_file_content(filename))

if self.with_math_module:
self._cg.add_dependency("dart:math")

return self._cg.code

def interpret_tanh_expr(self, expr, **kwargs):
self.with_tanh_expr = True
return super(
DartInterpreter, self).interpret_tanh_expr(expr, **kwargs)
14 changes: 14 additions & 0 deletions m2cgen/interpreters/dart/linear_algebra.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
List<double> addVectors(List<double> v1, List<double> v2) {
List<double> result = new List<double>(v1.length);
for (int i = 0; i < v1.length; i++) {
result[i] = v1[i] + v2[i];
}
return result;
}
List<double> mulVectorNumber(List<double> v1, double num) {
List<double> result = new List<double>(v1.length);
for (int i = 0; i < v1.length; i++) {
result[i] = v1[i] * num;
}
return result;
}
7 changes: 7 additions & 0 deletions m2cgen/interpreters/dart/tanh.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
double tanh(double x) {
if (x > 22.0)
return 1.0;
if (x < -22.0)
return -1.0;
return ((exp(2*x) - 1)/(exp(2*x) + 1));
}
2 changes: 2 additions & 0 deletions tests/e2e/executors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from tests.e2e.executors.powershell import PowershellExecutor
from tests.e2e.executors.r import RExecutor
from tests.e2e.executors.php import PhpExecutor
from tests.e2e.executors.dart import DartExecutor

__all__ = [
JavaExecutor,
Expand All @@ -20,4 +21,5 @@
PowershellExecutor,
RExecutor,
PhpExecutor,
DartExecutor,
]
63 changes: 63 additions & 0 deletions tests/e2e/executors/dart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import os
import string

from m2cgen import assemblers, interpreters
from tests import utils
from tests.e2e.executors import base

EXECUTOR_CODE_TPL = """
${model_code}
void main(List<String> args) {
List<double> input_ = args.map((x) => double.parse(x)).toList();
${print_code}
}
"""

EXECUTE_AND_PRINT_SCALAR = """
double res = score(input_);
print(res);
"""

EXECUTE_AND_PRINT_VECTOR = """
List<double> res = score(input_);
print(res.join(" "));
"""


class DartExecutor(base.BaseExecutor):

executor_name = "score"

def __init__(self, model):
self.model = model
self.interpreter = interpreters.DartInterpreter()

assembler_cls = assemblers.get_assembler_cls(model)
self.model_ast = assembler_cls(model).assemble()

self._dart = "dart"

def predict(self, X):
file_name = os.path.join(self._resource_tmp_dir,
"{}.dart".format(self.executor_name))
exec_args = [self._dart,
file_name,
*map(str, X)]
return utils.predict_from_commandline(exec_args)

def prepare(self):
if self.model_ast.output_size > 1:
print_code = EXECUTE_AND_PRINT_VECTOR
else:
print_code = EXECUTE_AND_PRINT_SCALAR

model_code = self.interpreter.interpret(self.model_ast)
executor_code = string.Template(EXECUTOR_CODE_TPL).substitute(
model_code=model_code,
print_code=print_code)

executor_file_name = os.path.join(
self._resource_tmp_dir, "{}.dart".format(self.executor_name))
with open(executor_file_name, "w") as f:
f.write(executor_code)
2 changes: 2 additions & 0 deletions tests/e2e/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
POWERSHELL = pytest.mark.powershell
R = pytest.mark.r_lang
PHP = pytest.mark.php
DART = pytest.mark.dart
REGRESSION = pytest.mark.regr
CLASSIFICATION = pytest.mark.clf

Expand Down Expand Up @@ -125,6 +126,7 @@ def classification_binary_random(model):
(executors.PowershellExecutor, POWERSHELL),
(executors.RExecutor, R),
(executors.PhpExecutor, PHP),
(executors.DartExecutor, DART),
],
# These models will be tested against each language specified in the
Expand Down

0 comments on commit 3c431f2

Please sign in to comment.