Giter Club home page Giter Club logo

epigraph's Introduction

Epigraph is a modern C++ interface to formulate and solve linear, quadratic and second order cone problems. It makes use of Eigen types and operator overloading for straightforward problem formulation.

Features

  • Flexible and intuitive way to formulate LPs, QPs and SOCPs
  • Dynamic parameters that can be changed without re-formulating the problem
  • Automatically clean up the problem and remove unused variables
  • Print the problem formulation and solver data for inspection

Dependencies

Supported Solvers

The solvers are included as submodules for convenience. Note that some solvers have more restrictive licenses which automatically override the Epigraph license when activated. Pass the listed argument to cmake during configuration to enable the solvers.

QP

  • OSQP -DENABLE_OSQP=TRUE. Apache-2.0 License.

SOCP

  • ECOS -DENABLE_ECOS=TRUE. GPLv3 License.

Usage

CMake

To use Epigraph with a cmake project, simply enable the desired solvers, include the subdirectory and link the library.

set(ENABLE_OSQP TRUE)
set(ENABLE_ECOS TRUE)
add_subdirectory(Epigraph)
target_link_libraries(my_library epigraph)

Documentation

While the example below is likely enough to get you started, more explanation is provided below. The full reference can be found here.

Example

#include "epigraph.hpp"

#include <iostream>

// This example solves the portfolio optimization problem in QP form

using namespace cvx;

int main()
{
    size_t n = 5; // Assets
    size_t m = 2; // Factors

    // Set up problem data.
    double gamma = 0.5;          // risk aversion parameter
    Eigen::VectorXd mu(n);       // vector of expected returns
    Eigen::MatrixXd F(n, m);     // factor-loading matrix
    Eigen::VectorXd D(n);        // diagonal of idiosyncratic risk
    Eigen::MatrixXd Sigma(n, n); // asset return covariance

    mu.setRandom();
    F.setRandom();
    D.setRandom();

    mu = mu.cwiseAbs();
    F = F.cwiseAbs();
    D = D.cwiseAbs();
    Sigma = F * F.transpose();
    Sigma.diagonal() += D;

    // Formulate QP.
    OptimizationProblem qp;

    // Declare variables with...
    // addVariable(name) for scalars,
    // addVariable(name, rows) for vectors and
    // addVariable(name, rows, cols) for matrices.
    VectorX x = qp.addVariable("x", n);

    // Available constraint types are equalTo(), lessThan(), greaterThan() and box()
    qp.addConstraint(greaterThan(x, 0.));
    qp.addConstraint(equalTo(x.sum(), 1.));

    // Make mu dynamic in the cost function so we can change it later
    qp.addCostTerm(x.transpose() * par(gamma * Sigma) * x - dynpar(mu).dot(x));

    // Print the problem formulation for inspection
    std::cout << qp << "\n";

    // Create and initialize the solver instance.
    osqp::OSQPSolver solver(qp);

    // Print the canonical problem formulation for inspection
    std::cout << solver << "\n";

    // Solve problem and show solver output
    const bool verbose = true;
    solver.solve(verbose);

    std::cout << "Solver message:  " << solver.getResultString() << "\n";
    std::cout << "Solver exitcode: " << solver.getExitCode() << "\n";

    // Call eval() to get the variable values
    std::cout << "Solution:\n" << eval(x) << "\n";

    // Update data
    mu.setRandom();
    mu = mu.cwiseAbs();

    // Solve again
    // OSQP will warm start automatically
    solver.solve(verbose);

    std::cout << "Solution after changing the cost function:\n" << eval(x) << "\n";
}

See the tests for more examples, including the same problem in SOCP form.

Variables

Variables are created by directly adding them to a problem:

    OptimizationProblem qp;
    cvx::Scalar scalar_var = qp.addVariable("x");
    cvx::VectorX vector_var = qp.addVariable("x", n);
    cvx::MatrixX matrix_var = qp.addVariable("x", n, m);

They contain the solution values after the problem has been solved successfully. Those values can be retrieved with the eval function, or by casting the value to a double.

    double scalar_sol = cvx::eval(scalar_var);
    Eigen::VectorXd vector_sol = cvx::eval(vector_var);
    Eigen::MatrixXd matrix_sol = cvx::eval(matrix_var);

Parameters

There are three kinds of parameters:

Constant

A value that can't be changed after instantiating the solver. Use the par function to turn scalars or Eigen types into constant parameters.

Dynamic

A value that can be changed after instantiating the solver. Use the dynpar function to turn scalars or Eigen types into dynamic parameters. Internally, this stores a pointer to the original data and will fetch the data each time the problem is solved. Important: Do not move or let this data go out of scope before the solver instance.

Operation

This parameter type is created when using the operations +, -, * or / with dynamic parameters. This records the operations and will later execute them again to build the new problem based on the changed dynamic parameters. Using said operations with constant parameters will again yield constant parameters and not result in any additional computations.

Problem Formulation

The following terms may be passed to the constraint functions:

Function Allowed expressions
equalTo() Affine == Affine
lessThan() Affine <= Affine or Norm2 + Affine <= Affine (SOCP)
greaterThan() Affine >= Affine or Affine >= Norm2 + Affine (SOCP)
box() Affine <= Affine <= Affine
addCostTerm() Affine (SOCP) or QuadForm + Affine (QP)

With the following expressions:

Expression Form
Affine p1 * x1 + p2 * x2 + ... + c
Norm2 (Affine1^2 + Affine2^2 + ...)^(1/2)
QuadForm x' * P * x where P is Hermitian

epigraph's People

Contributors

benjaminnavarro avatar embersarc 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

epigraph's Issues

The time for adding constrain is much longer than solving the qp problem in the test_mpc_qp.cpp.

using namespace cvx;

#include

TEST_CASE("MPC QP")
{
// This test solves an MPC problem with quadratic programming.

size_t T = 700; // time horizon

Eigen::MatrixXd A(2, 2);
A << 2, -1, 1, 0.2;
Eigen::MatrixXd B(2, 1);
B << 1, 0;
Eigen::VectorXd x0(2);
x0 << 3, 1;

OptimizationProblem qp;

// Create variables
MatrixX x = qp.addVariable("x", 2, T + 1);
MatrixX u = qp.addVariable("u", 1, T);

// Dynamics
for (size_t t = 0; t < T; t++)
{
    qp.addConstraint(equalTo(x.col(t + 1), par(A) * x.col(t) + par(B) * u.col(t)));
}

// State and control limits
qp.addConstraint(box(-5., x, 5.));

// Equivalent to
// qp.addConstraint(box(-2., u, 2.));
qp.addConstraint(greaterThan(u, -2.));
qp.addConstraint(lessThan(u, 2.));

// Boundary constraints
qp.addConstraint(equalTo(x.col(0), par(x0)));
qp.addConstraint(equalTo(x.col(T), 0.));

// Cost function
qp.addCostTerm(x.squaredNorm() + u.squaredNorm());

// Print the problem for inspection

// Create and initialize the solver instance.
osqp::OSQPSolver solver(qp);

solver.setAlpha(1.);
clock_t start,finish;
start = clock();
// Solve the problem and get the solution
solver.solve(false);
finish = clock();
std::cout<<"runtime is "<<((double)(finish-start))/CLOCKS_PER_SEC<<std::endl;
Eigen::MatrixXd x_sol = eval(x);
Eigen::MatrixXd u_sol = eval(u);

// Check solution
for (size_t t = 0; t < T; t++)
{
    const Eigen::VectorXd x_solution = x_sol.col(t + 1);
    const Eigen::VectorXd x_propagate = A * x_sol.col(t) + B * u_sol.col(t);
    const double max_error = (x_propagate - x_solution).cwiseAbs().maxCoeff();
    REQUIRE(max_error == Approx(0.).margin(1e-5));
}
REQUIRE(x_sol.maxCoeff() <= Approx(5.).margin(1e-3));
REQUIRE(x_sol.minCoeff() >= Approx(-5.).margin(1e-3));
REQUIRE(u_sol.maxCoeff() <= Approx(2.).margin(1e-3));
REQUIRE(u_sol.minCoeff() >= Approx(-2.).margin(1e-3));

}

Takes too much time to add Constraints.

Hi,

I am using the QP solver of this library for my application. I Have almost 1000 variables in my actual application and it was taking too much time to add EqualTo constraint. I was not sure if it got stuck somewhere, so I started again with 320 variables with a similar EqualTo constraint. I realized that solver is working but it is taking too much time, more than an hour to add cost function and constraints. I am not sure if I am missing something over here or is it normal for this library to take this much amount of time?

Thank you.

Conan package

I wanted to give Epigraph a try and since I get my other dependencies using Conan I thought it would be nice to make a Conan package for Epigraph.

So I tried to make one but I stumble on a few problems due to how the project is organized and how its dependencies are managed:

  1. Use of Git submodules: to follow the Conan philosophy it would be better to have the solvers as Conan packages rather than submodules. For this, Epigraph needs a way to rely on find_pakage() for the solvers rather than pulling them directly. A CMake option could make this possible.
  2. Include paths: the solvers' headers are included like #include <solver/include/file.h> which is non standard and doesn't respect how these files are installed (<prefix>/include/solver/file.h) which requires #include <solver/file.h>. Also, Epigraph doesn't use a subfolder for its headers which might lead to clashes, i.e #include <epigraph/variable.hpp> would be better than #include <variable.hpp>
  3. No CMake install rules for Epigraph: running the CMake install target will properly install the solvers (headers + libs) but not Epigraph itself.

For now I'll use Epigraph as a submodule even if it's not ideal but if you are interested in Conan support, I'd encourage you to address the issues I raised. Basically, you need to make sure that Epigraph can be compiled using system dependencies (relying on find_package) and that it installs itself properly inside CMAKE_INSTALL_PREFIX when running the install target. If this works, I can make the Conan packages for Epigraph and its solvers (if they don't have one already).

ubuntu 18.04 make error

/usr/include/eigen3/Eigen/src/SparseCore/SparseMatrix.h: In instantiation of ‘Eigen::SparseMatrix<_Scalar, _Flags, _StorageIndex>::Scalar& Eigen::SparseMatrix<_Scalar, _Flags, _StorageIndex>::insertBackUncompressed(Eigen::Index, Eigen::Index) [with _Scalar = cvx::internal::Parameter; int _Options = 1; _StorageIndex = int; Eigen::SparseMatrix<_Scalar, _Flags, _StorageIndex>::Scalar = cvx::internal::Parameter; Eigen::Index = long int]’:
/usr/include/eigen3/Eigen/src/SparseCore/SparseMatrix.h:941:57: required from ‘void Eigen::internal::set_from_triplets(const InputIterator&, const InputIterator&, SparseMatrixType&, DupFunctor) [with InputIterator = __gnu_cxx::__normal_iterator<Eigen::Tripletcvx::internal::Parameter, std::vector<Eigen::Tripletcvx::internal::Parameter > >; SparseMatrixType = Eigen::SparseMatrixcvx::internal::Parameter; DupFunctor = Eigen::internal::scalar_sum_op<cvx::internal::Parameter, cvx::internal::Parameter>]’
/usr/include/eigen3/Eigen/src/SparseCore/SparseMatrix.h:995:92: required from ‘void Eigen::SparseMatrix<_Scalar, _Flags, _StorageIndex>::setFromTriplets(const InputIterators&, const InputIterators&) [with InputIterators = __gnu_cxx::__normal_iterator<Eigen::Tripletcvx::internal::Parameter
, std::vector<Eigen::Tripletcvx::internal::Parameter > >; _Scalar = cvx::internal::Parameter; int _Options = 0; _StorageIndex = int]’
/home/gelinhe/test_ep/Epigraph/src/wrappers/socpWrapperBase.cpp:145:66: required from here
/usr/include/eigen3/Eigen/src/SparseCore/SparseMatrix.h:896:31: error: no match for ‘operator=’ (operand types are ‘Eigen::internal::CompressedStorage<cvx::internal::Parameter, int>::Scalar {aka cvx::internal::Parameter}’ and ‘int’)
return (m_data.value(p) = 0);
~~~~~~~~~~~~~~~~~^~~~
In file included from /home/gelinhe/test_ep/Epigraph/include/expressions.hpp:8:0,
from /home/gelinhe/test_ep/Epigraph/include/constraint.hpp:8,
from /home/gelinhe/test_ep/Epigraph/include/problem.hpp:8,
from /home/gelinhe/test_ep/Epigraph/include/wrappers/wrapperBase.hpp:3,
from /home/gelinhe/test_ep/Epigraph/include/wrappers/socpWrapperBase.hpp:3,
from /home/gelinhe/test_ep/Epigraph/src/wrappers/socpWrapperBase.cpp:1:
/home/gelinhe/test_ep/Epigraph/include/parameter.hpp:76:15: note: candidate: cvx::internal::Parameter& cvx::internal::Parameter::operator=(const cvx::internal::Parameter&)
class Parameter
^~~~~~~~~
/home/gelinhe/test_ep/Epigraph/include/parameter.hpp:76:15: note: no known conversion for argument 1 from ‘int’ to const cvx::internal::Parameter&’
/home/gelinhe/test_ep/Epigraph/include/parameter.hpp:76:15: note: candidate: cvx::internal::Parameter& cvx::internal::Parameter::operator=(cvx::internal::Parameter&&)
/home/gelinhe/test_ep/Epigraph/include/parameter.hpp:76:15: note: no known conversion for argument 1 from ‘int’ to cvx::internal::Parameter&&’
Epigraph/CMakeFiles/epigraph.dir/build.make:206: recipe for target 'Epigraph/CMakeFiles/epigraph.dir/src/wrappers/socpWrapperBase.cpp.o' failed
make[2]: *** [Epigraph/CMakeFiles/epigraph.dir/src/wrappers/socpWrapperBase.cpp.o] Error 1
CMakeFiles/Makefile2:85: recipe for target 'Epigraph/CMakeFiles/epigraph.dir/all' failed
make[1]: *** [Epigraph/CMakeFiles/epigraph.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2

Buffer overflow on OSQPSolver destruction

Using AddressSanitizer to investigate a crash in my code, I found that it is likely linked to a buffer overflow during the destruction of an OSQPSolver instance.

Here is the AddressSanitizer output on the MPC example:

=================================================================
==89629==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x613000001538 at pc 0x56517fe1dfa8 bp 0x7fff7a9d96b0 sp 0x7fff7a9d96a0
READ of size 8 at 0x613000001538 thread T0
    #0 0x56517fe1dfa7 in Eigen::internal::handmade_aligned_free(void*) /home/idhuser/.conan/data/eigen/3.3.7/conan/stable/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/eigen3/Eigen/src/Core/util/Memory.h:98
    #1 0x56517fe1e012 in Eigen::internal::aligned_free(void*) /home/idhuser/.conan/data/eigen/3.3.7/conan/stable/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/eigen3/Eigen/src/Core/util/Memory.h:179
    #2 0x56517fe31e97 in void Eigen::internal::conditional_aligned_free<true>(void*) /home/idhuser/.conan/data/eigen/3.3.7/conan/stable/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/eigen3/Eigen/src/Core/util/Memory.h:230
    #3 0x7fc9b2be63aa in void Eigen::internal::conditional_aligned_delete_auto<cvx::internal::Parameter, true>(cvx::internal::Parameter*, unsigned long) /usr/include/eigen3/Eigen/src/Core/util/Memory.h:416
    #4 0x7fc9b2be4b90 in Eigen::DenseStorage<cvx::internal::Parameter, -1, -1, 1, 0>::~DenseStorage() /usr/include/eigen3/Eigen/src/Core/DenseStorage.h:542
    #5 0x7fc9b2be46a9 in Eigen::PlainObjectBase<Eigen::Matrix<cvx::internal::Parameter, -1, 1, 0, -1, 1> >::~PlainObjectBase() /usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:98
    #6 0x7fc9b2be46c5 in Eigen::Matrix<cvx::internal::Parameter, -1, 1, 0, -1, 1>::~Matrix() /usr/include/eigen3/Eigen/src/Core/Matrix.h:178
    #7 0x7fc9b2c0dbe7 in cvx::internal::QPWrapperBase::~QPWrapperBase() /home/idhuser/prog/umrob/hmee325/nao-project/robot-control/src/Epigraph/solvers/wrappers/include/qpWrapperBase.hpp:8
    #8 0x7fc9b2c0db64 in cvx::osqp::OSQPSolver::~OSQPSolver() /home/idhuser/prog/umrob/hmee325/nao-project/robot-control/src/Epigraph/solvers/wrappers/src/osqpWrapper.cpp:140
    #9 0x56517fe1c833 in main /home/idhuser/prog/umrob/hmee325/nao-project/robot-control/apps/tests/main.cpp:51
    #10 0x7fc9b196a001 in __libc_start_main (/usr/lib/libc.so.6+0x27001)
    #11 0x56517fe199fd in _start (/home/idhuser/prog/umrob/hmee325/nao-project/robot-control/build/bin/tests+0x11a9fd)

0x613000001538 is located 8 bytes to the left of 368-byte region [0x613000001540,0x6130000016b0)
allocated by thread T0 here:
    #0 0x7fc9b2d0e83a in __interceptor_realloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:164
    #1 0x7fc9b2be4147 in Eigen::internal::aligned_realloc(void*, unsigned long, unsigned long) /usr/include/eigen3/Eigen/src/Core/util/Memory.h:194
    #2 0x7fc9b2bed81e in void* Eigen::internal::conditional_aligned_realloc<true>(void*, unsigned long, unsigned long) (/home/idhuser/prog/umrob/hmee325/nao-project/robot-control/build/lib/libepigraph.so+0x10f81e)
    #3 0x7fc9b2bec80d in cvx::internal::Parameter* Eigen::internal::conditional_aligned_realloc_new_auto<cvx::internal::Parameter, true>(cvx::internal::Parameter*, unsigned long, unsigned long) /usr/include/eigen3/Eigen/src/Core/util/Memory.h:396
    #4 0x7fc9b2bea51e in Eigen::DenseStorage<cvx::internal::Parameter, -1, -1, 1, 0>::conservativeResize(long, long, long) /usr/include/eigen3/Eigen/src/Core/DenseStorage.h:548
    #5 0x7fc9b2be7752 in Eigen::internal::conservative_resize_like_impl<Eigen::Matrix<cvx::internal::Parameter, -1, 1, 0, -1, 1>, Eigen::Matrix<cvx::internal::Parameter, -1, 1, 0, -1, 1>, true>::run(Eigen::DenseBase<Eigen::Matrix<cvx::internal::Parameter, -1, 1, 0, -1, 1> >&, long) /usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:993
    #6 0x7fc9b2be571c in Eigen::PlainObjectBase<Eigen::Matrix<cvx::internal::Parameter, -1, 1, 0, -1, 1> >::conservativeResize(long) /usr/include/eigen3/Eigen/src/Core/PlainObjectBase.h:434
    #7 0x7fc9b2bf3fa6 in cvx::internal::QPWrapperBase::addVariable(cvx::internal::Variable&) /home/idhuser/prog/umrob/hmee325/nao-project/robot-control/src/Epigraph/solvers/wrappers/src/qpWrapperBase.cpp:198
    #8 0x7fc9b2bf29ee in cvx::internal::QPWrapperBase::QPWrapperBase(cvx::OptimizationProblem&) /home/idhuser/prog/umrob/hmee325/nao-project/robot-control/src/Epigraph/solvers/wrappers/src/qpWrapperBase.cpp:24
    #9 0x7fc9b2c0ccce in cvx::osqp::OSQPSolver::OSQPSolver(cvx::OptimizationProblem&) /home/idhuser/prog/umrob/hmee325/nao-project/robot-control/src/Epigraph/solvers/wrappers/src/osqpWrapper.cpp:6
    #10 0x56517fe1c228 in main /home/idhuser/prog/umrob/hmee325/nao-project/robot-control/apps/tests/main.cpp:51
    #11 0x7fc9b196a001 in __libc_start_main (/usr/lib/libc.so.6+0x27001)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/idhuser/.conan/data/eigen/3.3.7/conan/stable/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/eigen3/Eigen/src/Core/util/Memory.h:98 in Eigen::internal::handmade_aligned_free(void*)
Shadow bytes around the buggy address:
  0x0c267fff8250: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c267fff8260: fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c267fff8270: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c267fff8280: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c267fff8290: fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa
=>0x0c267fff82a0: fa fa fa fa fa fa fa[fa]00 00 00 00 00 00 00 00
  0x0c267fff82b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c267fff82c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c267fff82d0: 00 00 00 00 00 00 fa fa fa fa fa fa fa fa fa fa
  0x0c267fff82e0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c267fff82f0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc

From my tests, it seems to always be linked to memory allocated during a call to addVariable() at different locations in the constructor.

It started blowing up for me when, instead of recreating the solver at each iteration because the problem might have changed, I kept an std::optional<cvx::osqp::OSQPSolver> around and only (re)created the solver when the problem is updated. I think the difference is that in the first case the solver was allocated on the stack always at a different location and so the memory corruption of the previous destruction was not visible, but in the second case, the same memory region was reused and so might be affected by the corruption.

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.