wells-group / asimov-contact Goto Github PK
View Code? Open in Web Editor NEWContact models for ASiMoV
License: MIT License
Contact models for ASiMoV
License: MIT License
Hello, I have one last query:
I'm using the repository to simulate a nanoindentation test on the cell wall of wood (S2 layer), where I incorporate a hygroviscoelastic constitutive model for the wood material and a linear model for the indented tip.
In relation to this, I have two questions:
Is there any work related to the Nitsche method of this repository in large deformations?
The constitutive model I am using is formulated for large deformations. Therefore, to incorporate it into the repository, I would need to linearize it to work with small deformations. My question at this point is: do they know of any method to achieve compatibility in communication between a material formulated for large deformations and a contact model that operates in small deformations?
Any other comments related to the work I am doing are welcome, as well as any limitations they may identify.
Too many boundaries can intersect with the ray, and therefore tons of wrong contacts can be detected
The if/else
clause for surf 1 in:
https://github.com/Wells-Group/asimov-contact/blob/main/python/tests/test_projection.py#L55-L58
seems to be doing something undesired, as it marks the same cells irregardless of which surface we are computing the projection to
Currently we have
Related to this: Facets are currently saved as vector of facet indices (_facet_0, _facet_1) and as cell-facet pairs (_cell_facet_pairs_0, _cell_facet_pairs_1). Are both needed?
We should use the lessons learned in:
FEniCS/dolfinx#1937
FEniCS/dolfinx#1948
for our basis function tabulation, as this currently will be the bottleneck of any computation.
if args.radius > 0.8 / args.time_steps:
args.radius = 0.8 / args.time_steps
In the current code, the input types of the assembly kernels are closely related to the kernel signature in DOLFINx, i.e.
contact_kernel_fn unbiased_rhs
= [=](std::vector<std::vector<PetscScalar>>& b, const double* c,
const double* w, const double* coordinate_dofs,
const int* entity_local_index, const std::size_t num_links)
The only difference here is that we do not have quadrature permutations
as input, and instead we send in number of connected cells (num_links
). However, using double*
for input requires us to convert data back to 2D structures, which seems unnecessary and adds extra operations.
@garth-wells what do you think? as we are not aiming to put these kernels inside the DOLFINx
framework, would it be more sensible to use xtl::span
for c
and w
, and and xt::xtensor<double, 2>&
for coordinate_dofs, as this is what we need to compute jacobians.
Fix bug in 'create_contact_mesh' for higher order geometries
Implement contact kernels using gap function relying on closest point projection for the deformed body.
Max_links was a way to give the kernel information about how many cells a single facet can be connected to.
We should rather let max_links=num_quadrature_points_per_facet
and send in additional information to the assemblers as to which matrix each quadrature points relates to.
This would massively simplify the packing of test functions (and their corresponding gradients), and can be computed whenever we create the distance_map (which will be at packing).
Translate function "create_contact_mesh" into C++.
Dear all,
Currently, I am implementing a viscoelastic linear model (Bleyer) which utilizes a mixed functional space. To gradually integrate it, I am solely focusing on creating the mixed space W and working with its vector space W.sub(0).
Upon using W.sub(0), I encounter the following error:
ValueError: Contact kernel not supported for spaces with value size!=1
Additionally, I would like to inquire if there is a version of the demos without kernel optimization. I understand they are written in C++, which hinders my access to details such as those involved in kernel generation.
I have provided a reproducible demo_nitsche_unbiased example for your review.
model (Bleyer): https://comet-fenics.readthedocs.io/en/latest/demo/viscoelasticity/linear_viscoelasticity.html
Para que el código se muestre correctamente en Markdown, necesitas envolverlo en bloques de código, que se indican con tres comillas invertidas (```). También puedes especificar el lenguaje de programación para un resaltado de sintaxis adecuado. Aquí está tu código con el formato correcto:
import argparse
import sys
import numpy as np
import ufl
from dolfinx import default_scalar_type, log
from dolfinx.common import timed, Timer, TimingType, list_timings, timing
from dolfinx.fem import (Constant, dirichletbc, form, Function, Expression, FunctionSpace,
VectorFunctionSpace, locate_dofs_topological)
from dolfinx.fem.petsc import create_vector, assemble_vector, assemble_matrix, set_bc, apply_lifting
from dolfinx.graph import adjacencylist
from dolfinx.io import XDMFFile, VTXWriter
from dolfinx.mesh import locate_entities_boundary, GhostMode, meshtags
from mpi4py import MPI
from petsc4py.PETSc import InsertMode, ScatterMode # type: ignore
from dolfinx_contact.helpers import (epsilon, lame_parameters, sigma_func,
weak_dirichlet)
from dolfinx_contact.meshing import (convert_mesh, create_box_mesh_2D,
create_box_mesh_3D,
create_circle_circle_mesh,
create_circle_plane_mesh,
create_cylinder_cylinder_mesh,
create_sphere_plane_mesh)
from dolfinx_contact.general_contact.contact_problem import ContactProblem, FrictionLaw
from dolfinx_contact.parallel_mesh_ghosting import create_contact_mesh
from dolfinx_contact.helpers import rigid_motions_nullspace_subdomains
from dolfinx_contact.newton_solver import NewtonSolver
from dolfinx_contact.cpp import ContactMode
import os
from typing import Union
from dolfinx import cpp
if __name__ == "__main__":
desc = "Nitsche's method for two elastic bodies using custom assemblers"
parser = argparse.ArgumentParser(description=desc,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("--theta", default=1., type=float, dest="theta",
help="Theta parameter for Nitsche, 1 symmetric, -1 skew symmetric, 0 Penalty-like",
choices=[1., -1., 0.])
parser.add_argument("--gamma", default=10, type=float, dest="gamma",
help="Coercivity/Stabilization parameter for Nitsche condition")
parser.add_argument("--quadrature", default=5, type=int, dest="q_degree",
help="Quadrature degree used for contact integrals")
parser.add_argument("--problem", default=1, type=int, dest="problem",
help="Which problem to solve: 1. Flat surfaces, 2. One curved surface, 3. Two curved surfaces",
choices=[1, 2, 3])
parser.add_argument("--order", default=1, type=int, dest="order",
help="Order of mesh geometry", choices=[1, 2, 3])
_3D = parser.add_mutually_exclusive_group(required=False)
_3D.add_argument('--3D', dest='threed', action='store_true',
help="Use 3D mesh", default=False)
_timing = parser.add_mutually_exclusive_group(required=False)
_timing.add_argument('--timing', dest='timing', action='store_true',
help="List timings", default=False)
_ksp = parser.add_mutually_exclusive_group(required=False)
_ksp.add_argument('--ksp-view', dest='ksp', action='store_true',
help="List ksp options", default=False)
_simplex = parser.add_mutually_exclusive_group(required=False)
_simplex.add_argument('--simplex', dest='simplex', action='store_true',
help="Use triangle/tet mesh", default=False)
_strain = parser.add_mutually_exclusive_group(required=False)
_strain.add_argument('--strain', dest='plane_strain', action='store_true',
help="Use plane strain formulation", default=False)
parser.add_argument("--E", default=1e3, type=np.float64, dest="E",
help="Youngs modulus of material")
parser.add_argument("--nu", default=0.1, type=np.float64, dest="nu", help="Poisson's ratio")
parser.add_argument("--disp", default=0.2, type=np.float64, dest="disp",
help="Displacement BC in negative y direction")
parser.add_argument("--radius", default=0.5, type=np.float64, dest="radius",
help="Search radius for ray-tracing")
parser.add_argument("--load_steps", default=4, type=np.int32, dest="nload_steps",
help="Number of steps for gradual loading")
parser.add_argument("--res", default=0.1, type=np.float64, dest="res",
help="Mesh resolution")
parser.add_argument("--friction", default=0.0, type=np.float64, dest="fric",
help="Friction coefficient for Tresca friction")
parser.add_argument("--outfile", type=str, default=None, required=False,
help="File for appending results", dest="outfile")
_lifting = parser.add_mutually_exclusive_group(required=False)
_lifting.add_argument('--lifting', dest='lifting', action='store_true',
help="Apply lifting (strong enforcement of Dirichlet condition",
default=False)
_raytracing = parser.add_mutually_exclusive_group(required=False)
_raytracing.add_argument('--raytracing', dest='raytracing', action='store_true',
help="Use raytracing for contact search.",
default=False)
_coulomb = parser.add_mutually_exclusive_group(required=False)
_coulomb.add_argument('--coulomb', dest='coulomb', action='store_true',
help="Use coulomb friction kernel. This requires --friction=[nonzero float value].",
default=False)
# Parse input arguments or set to defualt values
args = parser.parse_args()
# Current formulation uses bilateral contact
threed = args.threed
problem = args.problem
nload_steps = args.nload_steps
simplex = args.simplex
mesh_dir = "code_utils/tutoriales/results/meshes"
if not os.path.exists(mesh_dir):
os.makedirs(mesh_dir)
triangle_ext = {1: "", 2: "6", 3: "10"}
tetra_ext = {1: "", 2: "10", 3: "20"}
hex_ext = {1: "", 2: "27"}
quad_ext = {1: "", 2: "9", 3: "16"}
line_ext = {1: "", 2: "3", 3: "4"}
if args.order > 2:
raise NotImplementedError("More work in DOLFINx (SubMesh) required for this to work.")
# Load mesh and create identifier functions for the top (Displacement condition)
# and the bottom (contact condition)
if threed:
displacement = np.array([[0, 0, -args.disp], [0, 0, 0]])
if problem == 1:
outname = "code_utils/tutoriales/results/tu8/problem2_3D_simplex" if simplex else "code_utils/tutoriales/results/tu8/problem2_3D_hex"
fname = f"{mesh_dir}/sphere"
create_sphere_plane_mesh(filename=f"{fname}.msh", order=args.order, res=args.res)
convert_mesh(fname, fname, gdim=3)
with XDMFFile(MPI.COMM_WORLD, f"{fname}.xdmf", "r") as xdmf:
mesh = xdmf.read_mesh()
domain_marker = xdmf.read_meshtags(mesh, name="cell_marker")
tdim = mesh.topology.dim
mesh.topology.create_connectivity(tdim - 1, tdim)
facet_marker = xdmf.read_meshtags(mesh, name="facet_marker")
dirichlet_bdy_1 = 2
contact_bdy_1 = 1
contact_bdy_2 = 8
dirichlet_bdy_2 = 7
else:
displacement = np.array([[0, -args.disp], [0, 0]])
if problem == 1:
outname = "code_utils/tutoriales/results/tu8/problem2_2D_simplex" if simplex else "code_utils/tutoriales/results/tu8/problem2_2D_quads"
fname = f"{mesh_dir}/problem2_2D_simplex" if simplex else f"{mesh_dir}/problem2_2D_quads"
create_circle_plane_mesh(filename=f"{fname}.msh", quads=not simplex,
res=args.res, order=args.order, r=0.3, gap=0.1, height=0.1, length=1.0)
convert_mesh(fname, f"{fname}.xdmf", gdim=2)
with XDMFFile(MPI.COMM_WORLD, f"{fname}.xdmf", "r") as xdmf:
mesh = xdmf.read_mesh()
domain_marker = xdmf.read_meshtags(mesh, name="cell_marker")
tdim = mesh.topology.dim
mesh.topology.create_connectivity(tdim - 1, tdim)
facet_marker = xdmf.read_meshtags(mesh, name="facet_marker")
dirichlet_bdy_1 = 8
contact_bdy_1 = 10
contact_bdy_2 = 6
dirichlet_bdy_2 = 4
if mesh.comm.size > 1:
mesh, facet_marker, domain_marker = create_contact_mesh(
mesh, facet_marker, domain_marker, [contact_bdy_1, contact_bdy_2], 2.0)
ncells = mesh.topology.index_map(tdim).size_local
indices = np.array(range(ncells), dtype=np.int32)
values = mesh.comm.rank * np.ones(ncells, dtype=np.int32)
process_marker = meshtags(mesh, tdim, indices, values)
process_marker.name = "process_marker"
domain_marker.name = "cell_marker"
facet_marker.name = "facet_marker"
with XDMFFile(mesh.comm, f"{mesh_dir}/test.xdmf", "w") as xdmf:
xdmf.write_mesh(mesh)
xdmf.write_meshtags(domain_marker, mesh.geometry)
xdmf.write_meshtags(facet_marker, mesh.geometry)
# Solver options
ksp_tol = 1e-10
newton_tol = 1e-7
newton_options = {"relaxation_parameter": 1,
"atol": newton_tol,
"rtol": newton_tol,
"convergence_criterion": "residual",
"max_it": 200,
"error_on_nonconvergence": True}
# petsc_options = {"ksp_type": "preonly", "pc_type": "lu"}
petsc_options = {
"matptap_via": "scalable",
"ksp_type": "cg",
"ksp_rtol": ksp_tol,
"ksp_atol": ksp_tol,
"pc_type": "gamg",
"pc_mg_levels": 3,
"pc_mg_cycles": 1, # 1 is v, 2 is w
"mg_levels_ksp_type": "chebyshev",
"mg_levels_pc_type": "jacobi",
"pc_gamg_type": "agg",
"pc_gamg_coarse_eq_limit": 1000,
"pc_gamg_agg_nsmooths": 1,
"pc_gamg_threshold": 0.015,
"pc_gamg_square_graph": 2,
"pc_gamg_reuse_interpolation": True,
"ksp_norm_type": "unpreconditioned"
}
# Pack mesh data for Nitsche solver
dirichlet_vals = [dirichlet_bdy_1, dirichlet_bdy_2]
contact = [(1, 0), (0, 1)]
data = np.array([contact_bdy_1, contact_bdy_2], dtype=np.int32)
offsets = np.array([0, 2], dtype=np.int32)
surfaces = adjacencylist(data, offsets)
# Function, TestFunction, TrialFunction and measures
V = ufl.VectorElement("P", mesh.ufl_cell(), 1)
Q = ufl.TensorElement("DG", mesh.ufl_cell(), 1) # , shape = (3,3)
W = FunctionSpace(mesh, ufl.MixedElement([V, Q]))
s = Function(W)
(u, epsv) = ufl.split(s)
s_old = Function(W)
(du, epsv_old) = ufl.split(s_old)
s_ = ufl.TestFunction(W)
(v, epsv_) = ufl.split(s_)
s_t = ufl.TrialFunction(W)
(w, epsv_t) = ufl.split(s_t)
# V = VectorFunctionSpace(mesh, ("Lagrange", args.order))
# u = Function(V)
# du = Function(V)
# v = ufl.TestFunction(V)
# w = ufl.TrialFunction(V)
dx = ufl.Measure("dx", domain=mesh, subdomain_data=domain_marker)
ds = ufl.Measure("ds", domain=mesh, subdomain_data=facet_marker)
# Compute lame parameters
E = args.E
nu = args.nu
mu_func, lambda_func = lame_parameters(args.plane_strain)
mu = mu_func(E, nu)
lmbda = lambda_func(E, nu)
sigma = sigma_func(mu, lmbda)
# Create variational form without contact contributions
F = ufl.inner(sigma(u), epsilon(v)) * dx
# Nitsche parameters
gamma = args.gamma
theta = args.theta
gdim = mesh.geometry.dim
bcs = []
bc_fns = []
for k in range(displacement.shape[0]):
d = displacement[k, :]
tag = dirichlet_vals[k]
g = Constant(mesh, default_scalar_type(tuple(d[i] for i in range(gdim))))
bc_fns.append(g)
def weak_dirichlet(F: ufl.Form, u: Function,
f: Union[Function, Constant], sigma, gamma, theta, ds):
V = s.function_space
# V = W.sub(0) # I also use this
# v = F.arguments()[0]
mesh = V.mesh
V2 = FunctionSpace(mesh, ("DG", 0))
tdim = mesh.topology.dim
ncells = mesh.topology.index_map(tdim).size_local
h = Function(V2)
h_vals = cpp.mesh.h(mesh._cpp_object, mesh.topology.dim, np.arange(0, ncells, dtype=np.int32))
h.x.array[:ncells] = h_vals[:]
n = ufl.FacetNormal(mesh)
F += - ufl.inner(sigma(u) * n, v) * ds\
- theta * ufl.inner(sigma(v) * n, u - f) * \
ds + gamma / h * ufl.inner(u - f, v) * ds
return F
F = weak_dirichlet(F, u, g, sigma, E * gamma * args.order**2, theta, ds(tag))
F = ufl.replace(F, {u: u + du})
J = ufl.derivative(F, du, w)
# compiler options to improve performance
cffi_options = ["-Ofast", "-march=native"]
jit_options = {"cffi_extra_compile_args": cffi_options,
"cffi_libraries": ["m"]}
# compiled forms for rhs and tangen system
F_compiled = form(F, jit_options=jit_options)
J_compiled = form(J, jit_options=jit_options)
log.set_log_level(log.LogLevel.WARNING)
num_newton_its = np.zeros(nload_steps, dtype=int)
num_krylov_its = np.zeros(nload_steps, dtype=int)
newton_time = np.zeros(nload_steps, dtype=np.float64)
solver_outfile = args.outfile if args.ksp else None
V0 = FunctionSpace(mesh, ("DG", 0))
mu0 = Function(V0)
lmbda0 = Function(V0)
fric = Function(V0)
mu0.interpolate(lambda x: np.full((1, x.shape[1]), mu))
lmbda0.interpolate(lambda x: np.full((1, x.shape[1]), lmbda))
fric.interpolate(lambda x: np.full((1, x.shape[1]), args.fric))
if args.raytracing:
search_mode = [ContactMode.Raytracing for _ in range(len(contact))]
else:
search_mode = [ContactMode.ClosestPoint for _ in range(len(contact))]
# create contact solver
contact_problem = ContactProblem([facet_marker], surfaces, contact, mesh, args.q_degree, search_mode)
if args.coulomb:
friction_law = FrictionLaw.Coulomb
else:
friction_law = FrictionLaw.Tresca
contact_problem.generate_contact_data(friction_law, W.sub(0), {"u": u, "du": du, "mu": mu0,
"lambda": lmbda0, "fric": fric},
E * gamma * args.order**2, theta)
# define functions for newton solver
def compute_coefficients(x, coeffs):
size_local = V.dofmap.index_map.size_local
bs = V.dofmap.index_map_bs
du.x.array[:size_local * bs] = x.array_r[:size_local * bs]
du.x.scatter_forward()
contact_problem.update_contact_data(du)
@timed("~Contact: Assemble residual")
def compute_residual(x, b, coeffs):
b.zeroEntries()
b.ghostUpdate(addv=InsertMode.INSERT,
mode=ScatterMode.FORWARD)
with Timer("~~Contact: Contact contributions (in assemble vector)"):
contact_problem.assemble_vector(b, V)
with Timer("~~Contact: Standard contributions (in assemble vector)"):
assemble_vector(b, F_compiled)
# Apply boundary condition
if len(bcs) > 0:
apply_lifting(
b, [J_compiled], bcs=[bcs], x0=[x], scale=-1.0)
b.ghostUpdate(addv=InsertMode.ADD,
mode=ScatterMode.REVERSE)
if len(bcs) > 0:
set_bc(b, bcs, x, -1.0)
@timed("~Contact: Assemble matrix")
def compute_jacobian_matrix(x, a_mat, coeffs):
a_mat.zeroEntries()
with Timer("~~Contact: Contact contributions (in assemble matrix)"):
contact_problem.assemble_matrix(a_mat, V)
with Timer("~~Contact: Standard contributions (in assemble matrix)"):
assemble_matrix(a_mat, J_compiled, bcs=bcs)
a_mat.assemble()
# create vector and matrix
a_mat = contact_problem.create_matrix(J_compiled)
b = create_vector(F_compiled)
# Set up snes solver for nonlinear solver
newton_solver = NewtonSolver(mesh.comm, a_mat, b, contact_problem.coeffs)
# Set matrix-vector computations
newton_solver.set_residual(compute_residual)
newton_solver.set_jacobian(compute_jacobian_matrix)
newton_solver.set_coefficients(compute_coefficients)
# Set rigid motion nullspace
null_space = rigid_motions_nullspace_subdomains(V, domain_marker, np.unique(
domain_marker.values), num_domains=len(np.unique(domain_marker.values)))
newton_solver.A.setNearNullSpace(null_space)
# Set Newton solver options
newton_solver.set_newton_options(newton_options)
# Set Krylov solver options
newton_solver.set_krylov_options(petsc_options)
# initialise vtx writer
u.name = "u"
vtx = VTXWriter(mesh.comm, f"{outname}_{nload_steps}_step.bp", [u], "bp4")
vtx.write(0)
for i in range(nload_steps):
for k, g in enumerate(bc_fns):
if len(bcs) > 0:
g.value[:] = displacement[k, :] / nload_steps
else:
g.value[:] = (i + 1) * displacement[k, :] / nload_steps
timing_str = f"~Contact: {i+1} Newton Solver"
with Timer(timing_str):
n, converged = newton_solver.solve(du, write_solution=True)
num_newton_its[i] = n
du.x.scatter_forward()
u.x.array[:] += du.x.array[:]
contact_problem.update_contact_detection(u)
a_mat = contact_problem.create_matrix(J_compiled)
a_mat.setNearNullSpace(null_space)
newton_solver.set_petsc_matrix(a_mat)
du.x.array[:] = 0.1 * du.x.array[:]
contact_problem.update_contact_data(du)
vtx.write(i + 1)
vtx.close()
Hello, recently I was using the asimov-contact repository without any issues, but due to an error on my computer, I had to perform a fresh installation. Unfortunately, it's not working as before, and it's generating the following errors when running the demos.
This is running on WSL in an environment. Below are the installation steps in case I've made any mistakes.
sudo apt-get update
conda create -n contact
conda activate contact
conda install -c conda-forge fenics-dolfinx mpich pyvista
conda install -c conda-forge pybind11
conda install -c conda-forge cmake
conda install -c conda-forge pocl
conda install -c conda-forge oclgrind
conda install -c conda-forge intel-compute-runtime
conda install -c conda-forge beignet
conda install -c conda-forge ocl-icd-system
conda install -c conda-forge scipy
pip install pytest
conda update pip
conda update setuptools
python3 -m pip install numpy
pip install gmsh
git clone https://github.com/Wells-Group/asimov-contact.git
cd asimov-contact/cpp
mkdir build
cd build
export CC=/usr/bin/gcc
export CXX=/usr/bin/g++
cmake -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX ..
make -j4
make install
cd ../../python
python3 -m pip install -v .
conda list
We can test the packing of derivatives of test functions by:
N
number of functions, the i
th function is 1 in its ith entry, 0 everywhere else.grad(u_i)
at quadrature points.Currently diverging with multiple load steps
Code crashes if the if statement is removed in this line: https://github.com/Wells-Group/asimov-contact/blob/main/python/dolfinx_contact/one_sided/nitsche_rigid_surface.py#L182
Potential issues in having many submeshes (duplication of mpi comms).
Should rather extract facets.
Ray-tracing currently only works if gdim = tdim
Reported in PR #129
Remains unresolved
This will allow to update the geometry used for determining the gap without changing the original mesh.
This is needed for gap functions that depend on u in a non-linear fashion.
Use Surface_A and Surface_B as opposed to 0 and 1, as this is always varying from paper to paper (some start with 1 as index), and also makes the code harder to read.
Dear all,
I'm currently working on a self-contact problem (see figure). To do this, I've constructed this problem where C1 and C2 are the surfaces that come into contact and u=a, u=0 are the Dirichlet conditions applied to the surfaces opposite the contact surface.
The code runs, but does not converge. I'm confused because I'm not sure I fully understood the concept of self-contact in this article (http://dx.doi.org/10.1016/j.cma.2017.07.015). In fact, self-contact is dealt with implicitly.
Looking at the code in the demos, I noticed that there are always two distinct geometries, but I'm wondering how to exploit this for a self-contact test.
Your help in understanding how to implement this problem would be a real stepping stone for me.
Thanks in advance.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.