GithubHelp home page GithubHelp logo

sambayless / monosat Goto Github PK

View Code? Open in Web Editor NEW
105.0 13.0 29.0 44.44 MB

MonoSAT - An SMT solver for Monotonic Theories

License: MIT License

C++ 80.82% Python 6.23% CMake 0.59% Java 9.79% Scala 0.08% C 0.76% Cython 1.74%
sat smt graph

monosat's Introduction

MonoSAT

Build Status

MonoSAT is a SAT Modulo Theory solver for monotonic theories, over Booleans and bitvectors. It supports a wide set of graph predicates (including reachability, shortest paths, maximum s-t flow, minimum spanning tree, and acyclicity constraints). MonoSAT supports reasoning about graphs that are either directed or undirected (including graphs that may have cycles). Edges may (optionally) have constant or Bitvector weights. MonoSAT also has experimental support for constraints on finite state machines - see FiniteStateMachines for details.

MonoSAT can be used from the command line, or as a Python 3 library. See the installation instructions below; see also the tutorial.

To see further examples of use cases for MonoSAT, and details on the (very simple) input file format that MonoSAT accepts, see FORMAT.

Building

MonoSAT requires CMake (version 2.7 or higher).

From the root directory, build and install MonoSAT with:

$cmake .
$make
$sudo make install

MonoSAT requires C++11 support, zlib, and GMP >= 5.1.3. Tested on Ubuntu 14.04 and 16.04, with g++ 4.8.2 or higher, and with Clang 3.5. The Python library requires Python 3.3+.

If you get compilation errors along the lines of "error: could not convert ‘x’ from ‘__gmp_expr<__mpq_struct [1], __mpq_struct [1]>’ to ‘bool’", then you likely need to install a more recent version of GMP, with C++ support enabled.

If you build MonoSAT without using the provided cmake/makefiles, it is critically important to compile with NDEBUG set (i.e., -DNDEBUG), as otherwise many very expensive debugging assertions will be enabled.

Building on OSX with brew

You will need to first install GMP:

$brew install gmp

MonoSAT will, by default, compile statically linked binaries and dynamically linked libraries. In most cases, you can then continue the cmake installation as above.

However, in some cases brew might not add the GMP libraries to the library path, in which case MonoSAT compilation may fail during linking. You can check where brew installed GMP with:

$brew --prefix gmp

If brew installed GMP to /opt/local/lib/gmp, then you may need to modify the MonoSAT compilation instructions as follows:

$cmake .
$DYLD_LIBRARY_PATH=/opt/local/lib LIBRARY_PATH=/opt/local/lib make
$sudo make install

Building on FreeBSD (Only tested on FreeBSD 12)

You will need to first install GMP, which you can do via pkg or ports:

$pkg install gmp

If you intend to include the Java library (see below), you'll also need a JDK and you'll need to set your JAVA_HOME environment variable:

$pkg install openjdk8
$export JAVA_HOME=/usr/local/openjdk8

Installing the Python library

To install the Python library (system-wide) on your system's default Python version:

$cmake -DPYTHON=ON .
$make
$sudo make install

MonoSAT also has support for Cython bindings, which are about 30% faster but require you to have a wokring installation of cython:

$cmake -DPYTHON=ON -DCYTHON=ON .
$make
$sudo make install

To install MonoSAT in an alternative python version (or in a virtual env or a non-standard location), first build MonoSAT as normal, then cd into 'src/monosat/api/python', and then use setup.py to install the Python library manually, eg:

$cd src/monosat/api/python
$sudo python3.6 setup.py install -f

Above, -f ensures that if you have previously installed a version of the monosat library, it will be overwritten cleanly with the new version.

See the tutorial and tutorial.py for instructions on using the Python library.

Compiling the Java Library

To compile the Java library, you need to build MonoSAT with Java bindings enabled. You will also need an installed JDK, version 1.8 or higher:

$cmake -DJAVA=ON .
$make

This should generate monosat.jar in MonoSAT's root directory. To use MonoSAT from Java, you will need to include the jar in your classpath. You will also need to ensure that Java can find MonoSAT's dynamic library, for example:

java -Djava.library.path=path/to/libmonosat.so -cp path/to/monosat.jar mypacakge.MyMainClass

On OSX, you would instead use path/to/libmonosat.dylib

See Tutorial.java for instructions on using the Java library.

Command-Line Usage

The recommended way to use MonoSAT is as a library, via the Python or Java bindings. However, it is also possible to call MonoSAT from the command line. MonoSAT is based on MiniSat 2, and supports many of the same calling conventions:

$monosat [-witness|-witness-file=filename] input_file.gnf

Where input_file.gnf is a file in GNF format (a very simple extension of DIMACS CNF format to support graph, bitvector, and finite state machine predicates). Use -witness to print the solution (if one exists) to stdout, or -witness-file to save it to file.

MonoSAT includes a very large set of configuration options - most of which you should stay away from unless you know what you are doing or want to explore the internals of MonoSAT (also, some of those configuration options might lead to buggy behaviour). Two options that often have a large impact on performance are -decide-theories and -conflict-min-cut:

$monosat -decide-theories -conflict-min-cut input_file.gnf

The -decide-theories option will cause the solver to make heuristic decisions that try to satisfy the various SMT predicates, which will often lead to improved performance, but can be pathologically bad in some common cases, and so is disabled by default. -conflict-min-cut will cause the solver to use a much slower, but more aggressive, clause learning strategy for reachability predicates; it may be useful for small, dificult instances.

MonoSAT implements a generalization of the circuit routing heuristics described in Routing Under Constraints; you can activate them using the '-ruc' command line option. Thse can greatly improve performance on instances that use multiple reachability constraints. See examples/python/routing/router.py for an example usage.

Source Overview

MonoSAT is written in C++. Core SAT solver functionality is in the core/ and simp/ directories; in particular, note core/Config.cpp, which is a central listing of all the configuration options available to MonoSAT.

The graph and finite state machine theory solvers can be found in graph/ and fsm/. Many of the graph algorithsms used by MonoSAT are collected in dgl/ (for 'Dynamic Graph Library').

dgl/ incldudes C++ implementations of several dynamic graph algorithms (as well as some more common graph algorithms), and is well-optimized for medium sized (<20,000 nodes, < 100,000 edges), sparse graphs. The algorithms in dgl are designed for the case where the set of possible edges (and nodes) is fixed and known in advance (or only changes infrequently), and from that fixed set of possible edges many subsets of edges will subsequently be selected to be included in or excluded from the graph. 'dgl' supports templated edge weights and edge capacities, and has been tested successfully with integers, floats, and GMP arbitrary precision rationals.

Algirthms implemented in 'dgl/' include:

Licensing

The majority of MonoSAT is released under the MIT license (as documented in individual source files). However, by default MonoSAT links some GPLv2 sources (found in src/monosat/dgl/alg/dyncut/). If built with these sources, the resulting binary is licensed as a whole under the GPLv2.

MonoSAT can be built without including any GPL licensed sources, in which case it retains the MIT license. To build MonoSAT without using GPL sources, use: ''' $cmake -DGPL=OFF '''

Acknowledgements

MonoSAT was made possible by the use of several open-source projects, including the afore-mentioned MiniSat, as well as a high-performance dynamic maximum-flow algorithm by Pushmeet Kohli and Philip Torr, Emil Stefanov's implementation of Disjoint Sets, and a Link-Cut Tree implementation by Daniel Sleator.

Publications using MonoSAT

If you would like your publication listed here, please contact Sam Bayless.

If you want to cite MonoSAT, please cite our 2015 AAAI paper:

@article{monosat2015,
  author	= {Sam Bayless and Noah Bayless and Holger H. Hoos and Alan J. Hu},
  title		= {{SAT Modulo Monotonic Theories}},
  booktitle	= {Proceedings of the 29th AAAI Conference on Artificial Intelligence},
  year		= {2015}
}

Further References

monosat's People

Contributors

acairncross avatar agithubuserseva avatar bkocik avatar copumpkin avatar dsoko2 avatar hirse avatar jo285317 avatar lamestllama avatar nickf0211 avatar sambayless avatar seanmcl avatar xoolive avatar yasoob avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

monosat's Issues

Undirected acyclicity constraint bevahiour

It seems that having any undirected edges in a graph causes it to be cyclic. Is this the intended behaviour/is there another way to express the acyclicity constraint that I want?

>>> from monosat import *
>>> g = Graph()
>>> n1, n2 = [g.addNode() for _ in range(2)]
>>> e1 = g.addUndirectedEdge(n1, n2)
>>> Assert(g.acyclic(directed=False))
>>> Assert(e1)
>>> Solve()
False

Possibly flawed assumption in CMakeLists.txt?

As mentioned in the last ticket, I'm packaging monosat and its language libraries for nixpkgs. In doing the java portion, I noticed that you have:

WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/monosat/api/java/"

Which assumes that CMAKE_BINARY_DIR contains your source code. If you're doing an in-source build, that's generally true, but it's not required and in my case the two don't correspond, so cmake is unhappy and fails at that step.

Question: Clang compiler warnings

Just curious on this one:

Building monosat on OSX with clang, I get thousands upon thousands of compiler warnings. Most are not so disturbing, but some are a little scary, e.g.

/usr/local/monosat/src/monosat/core/Solver.h:262:142: warning: format specifies type 'long' but the argument has type 'uint64_t' (aka 'unsigned long long') [-Wformat]
                        printf("swapping counter clears/total, max swapping counter value:     %ld/%ld, %ld\n",stats_max_swap_count,stats_swapping_conflict_count,stats_swapping_resets);

Are compiler warnings something that you'd like to eliminate eventually, or will they permanently be there?

Soundness bug with connected components

I am seeing a soundness bug with connected compontents, where it marks the connected components expression and its negation as both true.

Example:

from monosat import *

import numpy as np

def print_districts(districts):
    print("districts:")
    for row in range(districts.shape[0]):
        for col in range(districts.shape[1]):
            for d in range(districts.shape[2]):
                if districts[row, col, d].value():
                    print(f"{d:02}", end=" ")
        print()


def solve_gerrymander(grid, num_districts):

    district_size = grid.shape[0] * grid.shape[1] // num_districts
    if district_size * num_districts != grid.shape[0] * grid.shape[1]:
        print("Error: Need evenly divisible districts")
        return
    exact_victory = np.sum(grid) == (district_size // 2 + 1) * (num_districts // 2 + 1)
    if not exact_victory:
        print(
            "Warning: this solver may be more efficient if the gerrymandering must be solved exactly."
        )

    districts = np.fromfunction(
        np.vectorize(lambda r, c, d: Var()),
        (grid.shape[0], grid.shape[1], num_districts),
        dtype=object)

    graphs = [Graph() for _ in range(num_districts)]
    nodes = np.fromfunction(
        np.vectorize(lambda r, c, d: graphs[d].addNode()),
        (grid.shape[0], grid.shape[1], num_districts),
        dtype=object)

    # Each square is in exactly one district
    for row in range(grid.shape[0]):
        for col in range(grid.shape[1]):
            AssertEqualPB([districts[row, col, d] for d in range(num_districts)], 1)

    # Each district is of the right size.
    for d in range(num_districts):
        squares_contained = [
            districts[row, col, d]
            for row in range(grid.shape[0])
            for col in range(grid.shape[1])
        ]
        AssertEqualPB(squares_contained, district_size)

    for row in range(grid.shape[0]):
        for col in range(grid.shape[1]):
            for dr, dc in [[1, 0], [0, 1]]:
                other_r = row + dr
                other_c = col + dc
                if other_c < 0 or other_r < 0 or other_r >= grid.shape[
                    0] or other_c >= grid.shape[1]:
                    continue
                for d in range(num_districts):
                    edge = graphs[d].addUndirectedEdge(nodes[row, col, d], nodes[other_r, other_c, d])
                    AssertEq(edge, And(districts[row, col, d], districts[other_r, other_c, d]))

    ccs = []
    ccns = []
    # All graphs must have exactly N-district size+1 connected components
    for d in range(num_districts):
        ccs.append(graphs[d].connectedComponentsGreaterThan((num_districts-1) * district_size))
        ccns.append(Not(graphs[d].connectedComponentsGreaterThan((num_districts-1) * district_size)))
        Assert(ccs[-1])
        Assert(ccns[-1])

    # A majority of districts must be won.
    districts_won = []
    for d in range(num_districts):
        votes = []
        for row in range(grid.shape[0]):
            for col in range(grid.shape[1]):
                if grid[row, col]:
                    votes.append(districts[row, col, d])
        if exact_victory:
            # everything must be gerrymandered perfectly to solve.
            AssertOr(EqualPB(votes, 0),
                     EqualPB(votes, district_size // 2 + 1))
        else:
            districts_won.append(GreaterEqPB(votes, district_size // 2 + 1))
    if exact_victory:
        # everything must be gerrymandered perfectly to solve.
        AssertEqualPB(districts_won, num_districts // 2 + 1)
    else:
        AssertGreaterEqPB(districts_won, num_districts // 2 + 1)

    result = Solve()
    if result:
        print("SAT")
        for d in range(num_districts):
            print(d, ccs[d].value())
            print(d, ccns[d].value())
        print_districts(districts)
    else:
        print("UNSAT")


def broken_example():
    grid = np.array([[False, False, False, False, True],
                     [False, False, True, False, True],
                     [True, False, True, False, True],
                     [False, False, False, False, True],
                     [True, False, True, True, True]])
    solve_gerrymander(grid, 5)

Output:

SAT
0 True
0 True
1 True
1 True
2 True
2 True
3 True
3 True
4 True
4 True
districts:
01 00 04 01 04 
01 01 03 02 04 
03 00 03 01 02 
04 00 00 03 04 
02 02 02 00 03 

Note how I assert a constraint and its negation, but it still says SAT, and it evaluates both the constraint and its negation as true (all the "True"s printed out)

"Bad solution" error with acyclic property and self edge

I ran into the error message below. I minimized a reproducing test case by hand, and it seems to be related to the combination of an acyclic property being asserted, along with a self edge in the GNF. The error is sensitive to other changes as well, presumably because it winds up investigating a good solution first in those cases.

Error in solution of graph theory 0, in detector 0 (Cycle Detector)
Unexpected error:
BAD SOLUTION: failure in graph theory detector
p cnf 12 8
1 0
12 9 0
digraph 3 11 0
edge 0 2 2 9
edge 0 2 1 12
acyclic 0 1

Bug in addNode()

When a new node is added, there is a check for whether that name is already assigned to a node. However, this is done against the keys of self.names instead of the values.

  def addNode(self, name=None):
        ...
        if name is None:
            name = str(n)
        else:
            name = str(name)

        if name is not None and name in self.names:  <-----------
            raise ValueError("Node %s already exists" % (str(name)))

        self.names[n] = name
        self.nodemap[name] = n
        self.out_edge_map.append(dict())
        self.in_edge_map.append(dict())

        return n

Integration with Z3?

The interface and design of monosat seems similar to Z3. Is there any chance that you would support using monosat as a theory within Z3, to allow solving problems that have a mixture of graph constraints and other constraints that are better handled by Z3?

monosat.aiger?

Hi! This project is awesome. Thanks for the great work!

I'm packaging monosat in nixpkgs, should handle some of the more annoying issues with pypi and other packaging not handling the native dependencies.

While packaging the python module, I noticed that one of its tests seems to refer to a .aiger module that I can't find anywhere in the source. Did something get removed or refactored?

======================================================================
ERROR: cnf (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: cnf
Traceback (most recent call last):
  File "/nix/store/89ammdpygm0cx2y4184x4kfvsncc0dsr-python3-3.6.5/lib/python3.6/unittest/loader.py", line 153, in loadTestsFromName
    module = __import__(module_name)
  File "/private/tmp/nix-build-python3.6-monosat-1nx3wh3.drv-0/source/src/monosat/api/python/monosat/cnf.py", line 24, in <module>
    from monosat.aiger import *
ModuleNotFoundError: No module named 'monosat.aiger'

You can find it here.

Edit: the monosat.graphcircuit module referenced right below it also seems to be an orphan. Is that cnf module still used?

Can't build on OSX

I'm having trouble building on OSX. I've brew installed gmp and cmake. It makes it all the way to the linking step, but then can't find my gmp (I think)

[ 47%] Linking CXX executable monosat
/usr/local/Cellar/cmake/3.11.0/bin/cmake -E cmake_link_script CMakeFiles/monosat_static.dir/link.txt --verbose=1
/usr/local/bin/g++-7  -fopenmp  -DNO_GMP -std=c++11 -Wno-unused-variable -Wno-unused-but-set-variable   -Wno-sign-compare  -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -O3 -DNDEBUG -DNDEBUG -O3 -Wl,-search_paths_first -Wl,-headerpad_max_install_names  CMakeFiles/monosat_static.dir/src/monosat/Main.cc.o  -o monosat libmonosat.a -lz -lm -lgmpxx -lgmp
Undefined symbols for architecture x86_64:
  "operator<<(std::basic_ostream<char, std::char_traits<char> >&, __mpq_struct const*)", referenced from:
      dgl::DynamicGraph<__gmp_expr<__mpq_struct [1], __mpq_struct [1]> >::setEdgeWeight(int, __gmp_expr<__mpq_struct [1], __mpq_struct [1]> const&) in Main.cc.o
      dgl::DynamicGraph<__gmp_expr<__mpq_struct [1], __mpq_struct [1]> >::addEdge(int, int, int, __gmp_expr<__mpq_struct [1], __mpq_struct [1]>) in Main.cc.o
      std::basic_ostream<char, std::char_traits<char> >& operator<< <2u, __gmp_expr<__mpq_struct [1], __mpq_struct [1]> >(std::basic_ostream<char, std::char_traits<char> >&, ConvexPolygon<2u, __gmp_expr<__mpq_struct [1], __mpq_struct [1]> >&) in Main.cc.o
      std::basic_ostream<char, std::char_traits<char> >& operator<< <2u, __gmp_expr<__mpq_struct [1], __mpq_struct [1]> >(std::basic_ostream<char, std::char_traits<char> >&, Point<2u, __gmp_expr<__mpq_struct [1], __mpq_struct [1]> > const&) in Main.cc.o
      Monosat::BVTheorySolver<__gmp_expr<__mpq_struct [1], __mpq_struct [1]> >::newComparison(Monosat::Comparison, int, __gmp_expr<__mpq_struct [1], __mpq_struct [1]> const&, int, bool) in Main.cc.o
      ConvexHullDetector<2u, __gmp_expr<__mpq_struct [1], __mpq_struct [1]> >::checkSatisfied() in Main.cc.o
      Monosat::BVTheorySolver<__gmp_expr<__mpq_struct [1], __mpq_struct [1]> >::writeBounds(int) in Main.cc.o
      ...
  "operator>>(std::basic_istream<char, std::char_traits<char> >&, __mpq_struct*)", referenced from:
      Monosat::GeometryParser<char*, Monosat::SimpSolver, __gmp_expr<__mpq_struct [1], __mpq_struct [1]> >::readConvexHullArea(char*&, Monosat::SimpSolver&) in Main.cc.o
ld: symbol(s) not found for architecture x86_64
collect2: error: ld returned 1 exit status
make[2]: *** [monosat] Error 1
make[1]: *** [CMakeFiles/monosat_static.dir/all] Error 2
make: *** [all] Error 2

I know next to nothing about CMAKE. I tried

$ DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/opt/local/lib make
$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/local/lib make 

Both failed. Any advice?

Thanks!

Use of `_FPU_EXTENDED`/`_FPU_DOUBLE` is x86/x87-specific

Hi,

Main.cc tries to disable the x87 extended precision mode @

newcw = (oldcw & ~_FPU_EXTENDED) | _FPU_DOUBLE;

But this is platform-specific and compilation fails on aarch64 linux:

/build/source/src/monosat/Main.cc:550:27: error: '_FPU_EXTENDED' was not declared in this scope
  550 |         newcw = (oldcw & ~_FPU_EXTENDED) | _FPU_DOUBLE;
      |                           ^~~~~~~~~~~~~
/build/source/src/monosat/Main.cc:550:44: error: '_FPU_DOUBLE' was not declared in this scope
  550 |         newcw = (oldcw & ~_FPU_EXTENDED) | _FPU_DOUBLE;
      |                                            ^~~~~~~~~~~

Full build log: https://hydra.nixos.org/log/gdvq3inxzcyinf032y7vwrdm1lncnw4z-monosat-1.8.0.drv

I've worked around it by patching it out, but it would be better to detect this somehow. Platforms like aarch64 do seem to have an fpu_control.h, possibly for backward compatibility, but they don't seem to have _FPU_EXTENDED or _FPU_DOUBLE defined. So possibly you could actually check for those being defined to enable/disable this block.

Although documentation around this suggests that the C99 standard fenv.h should be used these days in preference to use of fpu_control.h at all, so.. 🤷

getConflictClause returns the negation of assumption literals

Hello, I'm using monosat to solve some problems of graphs, and I am a little confused by the behavior of getConflictClause because the returned value is the negation of those literals:

from monosat import *
g = Graph()
a = g.addNode()
b = g.addNode()
e1 = g.addEdge(a, b)
e2 = g.addEdge(b, a)
x = g.acyclic()
Solve(e1, e2, x)

print(getConflictClause())
# [7, 5, 3]
print([e1, e2, x])
# [2, 4, 6]
print(list(map(Not, [e1, e2, x])))
# [3, 5, 7]

I assume it should return exactly the passed literals? Or is this the intended behavior?

`__local_t` has not been declared

I just tried to compile master, and get the following errors on GCC 4.9

(... compiling Monosat.cpp ...)
In file included from .../gcc-4.9.4/include/c++/4.9.4/ctime:42:0,
                 from ... src/monosat/utils/System.h:50,
                 from ...src/monosat/graph/GraphTheory.h:25,
                 from ...src/monosat/graph/AllPairsDetector.cpp:22:
/usr/include/time.h:226:5: error: ‘__locale_t’ has not been declared
     __locale_t __loc) __THROW;
     ^

Connection to datalog?

With MonoSAT's graph operations, it feels vaguely like there might be a somewhat mechanical transformation from datalog to GNF and MonoSAT, but I haven't fleshed that out. Have you given it much thought, or is there any related work you know of in that space?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.