From 2be5cd88bc8b4ddcdf7c78924a13c1aaa7ea86f9 Mon Sep 17 00:00:00 2001 From: Les De Ridder Date: Wed, 10 Jul 2019 00:46:42 +0200 Subject: [PATCH] Add plots and concat test --- dub.sdl | 1 + dub.selections.json | 3 +- matplotlib-d/.gitignore | 11 +++ matplotlib-d/.travis.yml | 17 ++++ matplotlib-d/LICENSE | 21 +++++ matplotlib-d/README.md | 91 +++++++++++++++++++ matplotlib-d/dub.json | 12 +++ matplotlib-d/examples/.gitignore | 7 ++ matplotlib-d/examples/dub.json | 6 ++ matplotlib-d/examples/source/app.d | 84 +++++++++++++++++ matplotlib-d/python/prebuild.py | 38 ++++++++ matplotlib-d/source/matplotlibd/core/pycall.d | 26 ++++++ .../source/matplotlibd/core/translate.d | 59 ++++++++++++ matplotlib-d/source/matplotlibd/pyplot.d | 50 ++++++++++ source/app.d | 75 +++++++++++---- 15 files changed, 483 insertions(+), 18 deletions(-) create mode 100644 matplotlib-d/.gitignore create mode 100644 matplotlib-d/.travis.yml create mode 100644 matplotlib-d/LICENSE create mode 100644 matplotlib-d/README.md create mode 100644 matplotlib-d/dub.json create mode 100644 matplotlib-d/examples/.gitignore create mode 100644 matplotlib-d/examples/dub.json create mode 100644 matplotlib-d/examples/source/app.d create mode 100644 matplotlib-d/python/prebuild.py create mode 100644 matplotlib-d/source/matplotlibd/core/pycall.d create mode 100644 matplotlib-d/source/matplotlibd/core/translate.d create mode 100644 matplotlib-d/source/matplotlibd/pyplot.d diff --git a/dub.sdl b/dub.sdl index ff9b456..54b2927 100644 --- a/dub.sdl +++ b/dub.sdl @@ -5,6 +5,7 @@ copyright "Copyright © 2019, Les De Ridder" license "NCSA" dependency "emsi_containers" version="~>0.7.0" dependency "collections" version="~>0.1.0" +dependency "matplotlib-d" path="matplotlib-d" -- :/ importPaths "/home/lesderid/repos/d/druntime/src" diff --git a/dub.selections.json b/dub.selections.json index ee734cc..64d9b20 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -3,8 +3,7 @@ "versions": { "collections": "0.1.0", "emsi_containers": "0.7.0", - "mir-algorithm": "3.5.0-alpha09", - "mir-core": "0.3.5", + "matplotlib-d": {"path":"matplotlib-d"}, "stdx-allocator": "2.77.5" } } diff --git a/matplotlib-d/.gitignore b/matplotlib-d/.gitignore new file mode 100644 index 0000000..6bdc428 --- /dev/null +++ b/matplotlib-d/.gitignore @@ -0,0 +1,11 @@ +.dub +docs.json +__dummy.html +*.o +*.obj +__test__*__ +*~ +views/ +build/ +README.html +*.png \ No newline at end of file diff --git a/matplotlib-d/.travis.yml b/matplotlib-d/.travis.yml new file mode 100644 index 0000000..7a5ee5b --- /dev/null +++ b/matplotlib-d/.travis.yml @@ -0,0 +1,17 @@ +os: + - linux + - osx + +language: d + +d: + - ldc2 + - dmd + +before_install: + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get install python-tk; fi + - sudo pip install matplotlib + + +script: + - dub test --compiler=${DC} diff --git a/matplotlib-d/LICENSE b/matplotlib-d/LICENSE new file mode 100644 index 0000000..2f031fd --- /dev/null +++ b/matplotlib-d/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2019 koji-kojiro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/matplotlib-d/README.md b/matplotlib-d/README.md new file mode 100644 index 0000000..2b1a889 --- /dev/null +++ b/matplotlib-d/README.md @@ -0,0 +1,91 @@ +# matplotlib-d + +[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) +[![Build Status](https://travis-ci.org/koji-kojiro/matplotlib-d.svg?branch=master)](https://travis-ci.org/koji-kojiro/matplotlib-d) +[![Dub version](https://img.shields.io/dub/v/matplotlib-d.svg)](https://code.dlang.org/packages/matplotlib-d) +[![Dub download](https://img.shields.io/dub/dt/matplotlib-d.svg)](https://code.dlang.org/packages/matplotlib-d) + +2D Plotting library for D using python and matplotlib. + +## Requirements +- Python +- matplotlib + +`matplotlib-d` uses `python3` by default, thus if you want to use `python2`, set `$MATPLOTLIB_D_PYTHON` to `python2`. + +## Usage +### Installation +To use this package, put the following dependency into your project's dependencies section: + +dub.json: `"matplotlib-d": "~>0.1.4"` +dub.sdl: `dependency "matplotlib-d" version="~>0.1.4"` + + +For small applications or scripts, add following sentence to the head of your script. +```d +#!/usr/bin/env dub +/+ dub.sdl: + name "name_of_your_application" + dependency "matplotlib-d" version="~>0.1.4" ++/ +``` +And excute with `dub run --single`. +For more details, please refer to [the documentation of dub](https://code.dlang.org/getting_started). + +### Syntax +Most pyplot functions are available. +For more details for each functions, please refer to the [documantation of pyplot](http://matplotlib.org/api/pyplot_summary.html). +Describe Python keyword arguments as an associative array with string of keyword name as key. + +- The Python way: +*function(arg1, arg2..., keyword1=kwarg1, keyword2=kwarg2...)* +- The D way: +*function(arg1, arg2..., ["keyword1": kwarg1], ["keyword2": kwarg2])* + + +## Examples + +Simple example: +```d +import std.math; +import std.range; +import std.algorithm; +import plt = matplotlibd.pyplot; + +void main() { + auto x = iota(0, 2.05, 0.05).map!(x => x * PI); + auto y = x.map!(sin); + + plt.plot(x, y, "r-", ["label": "$y=sin(x)$"]); + plt.xlim(0, 2 * PI); + plt.ylim(-1, 1); + plt.legend(); + plt.savefig("simple.png"); + plt.clear(); +} +``` +![Simple example](./examples/simple.png) + +Color plot example: + +```d +import std.range; +import plt = matplotlibd.pyplot; + +void main() { + const n = 100; + auto x = iota(n); + auto y = x[]; + double[n][n] z; + + foreach (i; 0..n) + foreach (j; 0..n) + z[i][j] = i + j; + + plt.contourf(x, y, z, 64, ["cmap": "hsv"]); + plt.colorbar(); + plt.savefig("color.png"); + plt.clear(); +} +``` +![Color plot example](./examples/color.png) diff --git a/matplotlib-d/dub.json b/matplotlib-d/dub.json new file mode 100644 index 0000000..8fd4afd --- /dev/null +++ b/matplotlib-d/dub.json @@ -0,0 +1,12 @@ +{ + "name": "matplotlib-d", + "authors": ["koji-kojiro"], + "description": "2D Plotting library for D using python and matplotlib", + "copyright": "Copyright © 2016-2019, koji-kojiro", + "license": "MIT", + "targetName": "matplotlibd", + "targetType": "staticLibrary", + "targetPath": "build", + "workingDirectory": "build", + "preBuildCommands": ["python3 $PACKAGE_DIR/python/prebuild.py $PACKAGE_DIR"] +} diff --git a/matplotlib-d/examples/.gitignore b/matplotlib-d/examples/.gitignore new file mode 100644 index 0000000..30e9d59 --- /dev/null +++ b/matplotlib-d/examples/.gitignore @@ -0,0 +1,7 @@ +.dub +docs.json +__dummy.html +*.o +*.obj +__test__*__ +examples \ No newline at end of file diff --git a/matplotlib-d/examples/dub.json b/matplotlib-d/examples/dub.json new file mode 100644 index 0000000..512af55 --- /dev/null +++ b/matplotlib-d/examples/dub.json @@ -0,0 +1,6 @@ +{ + "name": "examples", + "dependencies": { + "matplotlib-d": {"version": "~master", "path": "../"} + } +} diff --git a/matplotlib-d/examples/source/app.d b/matplotlib-d/examples/source/app.d new file mode 100644 index 0000000..3bf832c --- /dev/null +++ b/matplotlib-d/examples/source/app.d @@ -0,0 +1,84 @@ +import std.math; +import std.range; +import std.random; +import std.algorithm; +import plt = matplotlibd.pyplot; + +void main() { + simple(); + color(); + polar(); + subplots(); +} + +void bad() { + plt.plot("hoge"); + plt.show(); +} + +void simple() { + auto x = iota(0, 2.05, 0.05).map!(x => x * PI); + auto y = x.map!(sin); + + plt.plot(x, y, "r-", ["label": "$y=sin(x)$"]); + plt.xlim(0, 2 * PI); + plt.ylim(-1, 1); + plt.legend(); + plt.savefig("simple.png"); + plt.clear(); +} + +void color() { + const n = 100; + auto x = iota(n); + auto y = x[]; + double[n][n] z; + + foreach (i; 0..n) + foreach (j; 0..n) + z[i][j] = i + j; + plt.contourf(x, y, z, 64, ["cmap": "hsv"]); + plt.colorbar(); + plt.savefig("color.png"); + plt.clear(); +} + +void subplots() { + auto x = iota(0, 2 * PI + 0.05, 0.05); + + plt.subplot(221); + plt.plot(x, x.map!(sin)); + plt.xlim(0, 2 * PI); + plt.ylim(-1, 1); + + plt.subplot(222); + plt.plot(x, x.map!(cos)); + plt.xlim(0, 2 * PI); + plt.ylim(-1, 1); + + plt.subplot(223); + plt.plot(x, x.map!(i => sin(i) * exp(-0.4 * i))); + plt.xlim(0, 2 * PI); + plt.ylim(-1, 1); + + plt.subplot(224); + plt.plot(x, x.map!(i => cos(i) * exp(-0.4 * i))); + plt.xlim(0, 2 * PI); + plt.ylim(-1, 1); + + plt.savefig("subplots.png"); + plt.clear(); +} + +void polar() { + + auto r = iota(0, 1.001, 0.001); + auto theta = r.map!(i => i * 32 * PI); + auto area = r.map!(i => i * 2000); + + plt.subplot(111, ["projection": "polar"]); + plt.scatter(theta, r, ["c": r], ["s": area], ["cmap": "hsv"], ["alpha": 0.25]); + plt.savefig("polar.png"); + plt.clear(); +} + diff --git a/matplotlib-d/python/prebuild.py b/matplotlib-d/python/prebuild.py new file mode 100644 index 0000000..4fda696 --- /dev/null +++ b/matplotlib-d/python/prebuild.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +def extract_function_names(module): + ''' + extract function names from attributes of 'module'. + ''' + from importlib import import_module + + mod = import_module(module.__name__) + attr_list = dir(mod) + scope = locals() + + def iscallable(name): + ignore_decorators = ['dedent','deprecated','silent_list', 'warn_deprecated'] + return eval('callable(mod.{})'.format(name), scope) and name not in ignore_decorators + + return filter(iscallable, attr_list) + + +def gen_pyplot_functions(dub_root): + ''' + generate 'pyplot_functions.txt' for matplotlibd.pyplot. + ''' + import matplotlib.pyplot + from string import ascii_lowercase + + functions = filter(lambda i: i[0] != '_' or i[0] in ascii_lowercase, + extract_function_names(matplotlib.pyplot)) + + with open(dub_root + "/views/pyplot_functions.txt", "w") as f: + f.write("\n".join(functions)) + + +if __name__ == '__main__': + from sys import argv + gen_pyplot_functions(argv[1]) diff --git a/matplotlib-d/source/matplotlibd/core/pycall.d b/matplotlib-d/source/matplotlibd/core/pycall.d new file mode 100644 index 0000000..a44dcaa --- /dev/null +++ b/matplotlib-d/source/matplotlibd/core/pycall.d @@ -0,0 +1,26 @@ +module matplotlibd.core.pycall; + + +void call(string py_script) { + import std.process: environment, pipeProcess, wait, Redirect; + + auto py_path = environment.get("MATPLOTLIB_D_PYTHON", "python3"); + auto pipes = pipeProcess(py_path, Redirect.stdin | Redirect.stderr); + + pipes.stdin.writeln(py_script); + pipes.stdin.writeln("exit()"); + pipes.stdin.close(); + + auto result = wait(pipes.pid); + + if (result != 0) { + string error; + foreach (line; pipes.stderr.byLine) + error ~= line ~ "\n"; + throw new Exception("\n\nERROR occurred in Python:\n" ~ error); + } +} + +unittest { + call("print(\"Passing!\")\n"); +} diff --git a/matplotlib-d/source/matplotlibd/core/translate.d b/matplotlib-d/source/matplotlibd/core/translate.d new file mode 100644 index 0000000..1819117 --- /dev/null +++ b/matplotlib-d/source/matplotlibd/core/translate.d @@ -0,0 +1,59 @@ +module matplotlibd.core.translate; + +alias immutable bool PyBool; +alias immutable (void*) PyNone; + +PyBool False = false; +PyBool True = true; +PyNone None = null; + + +string d2py(T)(T v) { + import std.format: format; + static if (is(typeof(v) : PyNone)) + return "None"; + + else static if (is(typeof(v) : bool)) + return v ? "True" : "False"; + + else static if (is(typeof(v) : string)) + return format("\"%s\"", v); + + else + return format("%s", v); +} + +unittest { + import std.range: iota; + assert(d2py(None) == "None"); + assert(d2py(null) == "None"); + assert(d2py(True) == "True"); + assert(d2py(true) == "True"); + assert(d2py(False) == "False"); + assert(d2py(false) == "False"); + assert(d2py("Hello!") == "\"Hello!\""); + assert(d2py(5.iota) == "[0, 1, 2, 3, 4]"); +} + + +string parseArgs(Args)(Args args) { + static if (is(typeof(args.keys) : string[])) { + string parsed; + foreach(key; args.byKey) + parsed ~= key ~ "=" ~ d2py(args[key]) ~ ","; + } + else + string parsed = d2py(args) ~ ","; + return parsed; +} + +unittest { + import std.range: iota; + assert(parseArgs(5) == "5,"); + assert(parseArgs(5.iota) == "[0, 1, 2, 3, 4],"); + assert(parseArgs(["test": 5]) == "test=5,"); + assert(parseArgs(["test": "test"]) == "test=\"test\","); + assert(parseArgs(["test": 5.iota]) == "test=[0, 1, 2, 3, 4],"); + assert(parseArgs(["test": false]) == "test=False,"); + assert(parseArgs(["test": False]) == "test=False,"); +} diff --git a/matplotlib-d/source/matplotlibd/pyplot.d b/matplotlib-d/source/matplotlibd/pyplot.d new file mode 100644 index 0000000..5d7949f --- /dev/null +++ b/matplotlib-d/source/matplotlibd/pyplot.d @@ -0,0 +1,50 @@ +module matplotlibd.pyplot; +import matplotlibd.core.pycall; +import matplotlibd.core.translate; + +private: + +string py_script = "import matplotlib.pyplot as plt\n"; + +immutable string plt_funcs = (){ + import std.string: splitLines; + + string[] method_names = import("pyplot_functions.txt").splitLines; + string plt_funcs; + + foreach(name; method_names) { + plt_funcs ~= + "void " ~ name ~ "(T...)(T a)" ~ + "{import std.format: format;" ~ + "string p;if(a.length>0){foreach(i;a){p~=parseArgs(i);}" ~ + "p = p[0..$-1];}py_script~=format(\"plt."~ name ~ "(%s)\n\",p);"; + + if (name == "show" || name == "savefig") + plt_funcs ~= "call(py_script);}\n"; + else + plt_funcs ~= "}\n"; + } + + return plt_funcs; +}(); + + +public: + +import matplotlibd.core.translate: False, True, None; + + +void clear() { + py_script = "import matplotlib.pyplot as plt\n"; +} + +mixin(plt_funcs); + +unittest { + import std.string; + auto script = py_script ~ "plt.plot([1, 2],[2, 4],\"r-\",lw=2)\n"; + plot([1, 2], [2, 4], "r-", ["lw": 2]); + assert(script == py_script); + clear(); + assert(py_script == "import matplotlib.pyplot as plt\n"); +} diff --git a/source/app.d b/source/app.d index 2a35833..0d402c1 100644 --- a/source/app.d +++ b/source/app.d @@ -3,32 +3,38 @@ import util : make, rcarray, StdArray, StdxArray, EMSIArray; import std.stdio : stderr, writeln; import std.datetime : Duration; -void testInsert(Container, int times = 100)() +import std.meta : AliasSeq; + +alias tests = AliasSeq!(testInsert, testInsertDelete, testConcat); +alias containers = AliasSeq!(int[], StdArray!int, EMSIArray!int, rcarray!int, StdxArray!int); +enum times = 100000; + +void testInsert(Container, size_t size = 100)() { auto container = make!Container(); //debug stderr.writeln("Testing inserts on ", typeof(container).stringof); - foreach (i; 0 .. times) + foreach (i; 0 .. size) { container ~= 42; } - assert(container.length == times && container[times - 1] == 42); + assert(container.length == size && container[size - 1] == 42); } -void testInsertDelete(Container, int times = 100)() +void testInsertDelete(Container, size_t size = 100)() { auto container = make!Container(); //debug stderr.writeln("Testing inserts+deletes on ", typeof(container).stringof); - foreach (i; 0 .. times) + foreach (i; 0 .. size) { container ~= 42; } - foreach (i; 0 .. times) + foreach (i; 0 .. size) { static if (is(Container : StdxArray!U, U)) { @@ -47,24 +53,33 @@ void testInsertDelete(Container, int times = 100)() assert(container.length == 0); } -void testConcat(Container, int times = 100)() +void testConcat(Container, size_t size = 100)() { + auto a = make!Container(); + auto b = make!Container(); + foreach (i; 0 .. size / 2) + { + a ~= 1; + b ~= 2; + } + + auto c = a ~ b; + + assert(c.length == size); } -import std.meta : AliasSeq; -alias tests = AliasSeq!(testInsert, testInsertDelete, testConcat); - -auto testContainers(Containers...)(int times = 100000) +auto testContainers(Containers...)() { import std.datetime.stopwatch : benchmark; import std.meta : staticMap; + import std.array : array; Duration[][string] results; static foreach (test; tests) { - results[test.stringof] = benchmark!(staticMap!(test, Containers))(times); + results[test.stringof] = benchmark!(staticMap!(test, Containers))(times).array; } return results; @@ -84,12 +99,40 @@ void printResults(Containers...)(Duration[][string] results) } } +void plotResults(Containers...)(Duration[][string] results) +{ + import plt = matplotlibd.pyplot; + + auto i = 0; + foreach (test, testResults; results) + { + import std.range : iota; + import std.algorithm : map; + import std.array : array; + import std.conv : to; + + auto title = test ~ " (" ~ times.to!string ~ " runs)"; + auto x = iota(testResults.length); + auto height = testResults.map!(d => d.total!"msecs").array; + + string[] names; + foreach (Container; Containers) + { + names ~= Container.stringof; + } + + plt.subplot(results.length, 1, ++i); + plt.bar(x, height); + plt.xticks(x, names); + plt.ylabel("Time (ms)"); + plt.title(title); + } + plt.show(); +} + void main() { - import std.meta : AliasSeq; - - alias containers = AliasSeq!(int[], StdArray!int, StdxArray!int, EMSIArray!int, rcarray!int); - auto results = testContainers!containers; results.printResults!containers; + results.plotResults!containers; }