Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coefficient Strengthening #14

Merged
merged 40 commits into from
Feb 1, 2021
Merged

Coefficient Strengthening #14

merged 40 commits into from
Feb 1, 2021

Conversation

Anhtu07
Copy link
Contributor

@Anhtu07 Anhtu07 commented Dec 31, 2020

  • Add coefficient strengthening for presolve

! Empty rows/columns are now stored with empty arrays for nzind and nzval

test/runtests.jl Outdated Show resolved Hide resolved
@@ -0,0 +1,99 @@
function maximal_activity(row::RowOrCol{T}, j::Int, ucol::Vector{T}, lcol::Vector{T})::T where {T}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this function doing exactly? Are you trying to compute the maximum activity of a given row?

If so, your implementation is not correct: there may be some variables that have been removed from the problem.
You need to explicitly check this with ps.colflag.

I suggest you use the code that's currently in lp/forcing_row:

# Implied row bounds
row = ps.pb0.arows[i]
l_ = u_ = zero(T)
for (j, aij) in zip(row.nzind, row.nzval)
    ps.colflag[j] || continue
    if aij < zero(T)
        l_ += aij * ps.ucol[j]
        u_ += aij * ps.lcol[j]
    else
        l_ += aij * ps.lcol[j]
        u_ += aij * ps.ucol[j]
    end

    isfinite(l_) || isfinite(u_) || break  # infinite bounds
end

You need access to ps (the PresolveData struct), and the row row you're interested in (or its index i).
At the end of this loop, you get l_ and u_ which are the lower and upper activity bounds on row i.
I you want to skip variable j0 in the computation, just update the check to

(ps.colflag[j] && j != j0) || continue

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To @mtanneau's point, a short docstring above the method definition would be helpful to understand what this function does.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to move the code snippet from forcing_row to a separate util.jl file.

src/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
src/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
src/presolve.jl Outdated Show resolved Hide resolved
src/presolve.jl Outdated
@@ -414,6 +415,9 @@ function presolve!(ps::PresolveData{T}) where {T}
# Round the bounds of integer variables are integers.
round_integer_bounds!(ps)

# Coeficient strengthening
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Coeficient strengthening
# Coefficient strengthening

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

src/presolve.jl Outdated
Perform coefficient strengthening for integer/binary variables
on every constraints.

Called once at the very beginning of the presolve procedure.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Called once at the very beginning of the presolve procedure.
Called once at the beginning of the presolve procedure.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

function single_row_strengthening(row::RowOrCol{T}, b::T, m::Int, j::Int, ucol::Vector{T}, lcol::Vector{T}) where {T}
# perform coef strengthening for one constraints of the from a'x < b
# and return new a' and b'
flag = false # wether there is an update
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two spaces after the end of the line + typo

Suggested change
flag = false # wether there is an update
flag = false # whether there is an update


m = findfirst(isequal(j), row.nzind)
if m == nothing || (lrow > -inf && urow < inf)
continue #skipping ranged constraints and
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
continue #skipping ranged constraints and
continue # skipping ranged constraints and

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and?


# update collumns of A in problem data
# and update the sparse matrix in the case where the new coefficient is close to 0
if abs(new_coef) <= eps(T)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use a drop tolerance here.
cc @joehuchette

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. The eps(T) should be a configurable tolerance in PresolveOptions. Also, this should likely be a separate DropSmallCoefficients Rule.

@mtanneau
Copy link
Owner

mtanneau commented Dec 31, 2020

There is some work to do before this can be merged.
Here are the main points I want to improve:

  • Correctly handle variables/rows that have been deactivated.
    I understand that, currently, the routine is only called at the beginning of presolve, but this may change.
  • If it's absolutely needed, introduce two new parameters in PresolveOptions:
    • drop tolerance for rounding small coefficients to zero
    • "infinity" threshold for rounding large values to infinity
  • Performance improvements.
    I'm not looking for lightning speeds, but I've seen a lot of room for improvements (both time and memory-wise).
    The goal is that the routine can be run in O(nz), where nz is the number of non-zero coefficients in A.
    • Go row-by-row instead of column-by-column:
      1. For each row, compute its activity bounds.
        At the same time, check that at least one variable is integer, otherwise move to next row.
      2. For each variable that appears in the row, perform coefficient strengthening, if applicable. This can be done in-place.
        If a coefficient is strengthened, I think it's possible to immediately update the row's activity bounds, before moving to the next variable.
      3. If some coefficients do end up being rounded to zero, the non-zero counters for said row and column must be updated
    • Avoid (deep) copies. We shouldn't need any.
  • Make the code work for range constraints.
    All constraints are stored as range constraints, which simply denote a'x <= u and -a'x <= -l.
    Thus, it should be possible to treat both cases in a single pass on each row. I'm 99.9% sure there won't be any conflict.
  • Add some documentation.

[EDIT: dropping range constraints]

@@ -0,0 +1,99 @@
function maximal_activity(row::RowOrCol{T}, j::Int, ucol::Vector{T}, lcol::Vector{T})::T where {T}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To @mtanneau's point, a short docstring above the method definition would be helpful to understand what this function does.

@@ -0,0 +1,99 @@
function maximal_activity(row::RowOrCol{T}, j::Int, ucol::Vector{T}, lcol::Vector{T})::T where {T}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to move the code snippet from forcing_row to a separate util.jl file.

end

function single_row_strengthening(row::RowOrCol{T}, b::T, m::Int, j::Int, ucol::Vector{T}, lcol::Vector{T}) where {T}
# perform coef strengthening for one constraints of the from a'x < b
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# perform coef strengthening for one constraints of the from a'x < b
# perform coefficient strengthening for one constraint of the form a'x \leq b

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the mathematical definition for the coefficient strengthening you are doing? Can you include it in the docstring?

a = deepcopy(row.nzval)
if a[m] > 0
d = b - maximal_activity(row, j, ucol, lcol) - a[m]*(ucol[j]-1)
if a[m] >= d && d > 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small suggestion: These can be chained as a[m] >= d > 0

src/mip/coefficient_strengthening.jl Show resolved Hide resolved
row.nzval = -a
ps.lrow[i] = -b

# update collumns of A in problem data
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# update collumns of A in problem data
# update columns of A in problem data

C = T[1, 1, 1, 1]
lc = T[0, 1, 0, 2]
uc = T[1, 5//2, 1, 3]
lr = T[-1e30, -1e30]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update for new Inf standard



@test ps.urow == T[5, 9//2]
@test ps.lrow == T[-1e30, -1e30]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

C = T[1, 1, 1, 1]
lc = T[0, 0, 0, -3]
uc = T[2, 1, 2, 10^30]
lr = T[-1e30, -1e30]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again

lc = T[-4, -3//2, 9, 0, -1]
uc = T[2, 1, 15, 1, 1]
lr = T[2, -1e30]
ur = T[1e30, 13/3]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again

@joehuchette
Copy link
Collaborator

My concern about ranged constraints is that if you apply the strengthening to both sides you will end up with distinct linear pieces, which would require us adding extra constraints to our representation. Is this not the case? If it is, is it worth the added complexity?

@mtanneau
Copy link
Owner

My concern about ranged constraints is that if you apply the strengthening to both sides you will end up with distinct linear pieces, which would require us adding extra constraints to our representation. Is this not the case? If it is, is it worth the added complexity?

Good point :)
For a row l <= a'x <= u, I guess this also raises the question of whether the coefficient strengthening w.r.t a'x <= l is valid w.r.t l <= a'x.

  • If the answer is no, we can skip range constraints.
  • If the answer is yes, we have 2 options:
    1. Choose either side and do the coefficient strengthening w.r.t that side only.
    2. If there's no conflict, try both strengthenings and keep the best (where "best" is TBD)

If it's too much hassle at this point we can drop it.

@joehuchette
Copy link
Collaborator

@Anhtu07: Thoughts?

My initial (offline) suggestion was to punt completely on range constraints for the time being; I hadn't considered the "yes" branch. It's worth thinking about.

@Anhtu07
Copy link
Contributor Author

Anhtu07 commented Dec 31, 2020

I think there will be conflict most of the time when using CS on ranged constraints. For example, consider
3 <= 2x + 3y <= 6, 0<= x <=1 and y is integral, y<=2, using CS w.r.t 2x + 3y <=6 will get 2x + 2y <= 4, but 3<= 2x + 2y isn't valid.
Intuitively, when using CS, we decrease/increase some coefficient toward 0, and thus the lower/upper need to be updated as well

@mtanneau
Copy link
Owner

there will be conflict most of the time when using CS on ranged constraints

All right, I'm convinced. Let's skip range-constraints.

- using Inf instead of 1e30
@codecov-io
Copy link

codecov-io commented Jan 1, 2021

Codecov Report

Merging #14 (3e7fdc3) into master (c317b25) will increase coverage by 5.78%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #14      +/-   ##
==========================================
+ Coverage   46.83%   52.62%   +5.78%     
==========================================
  Files          14       15       +1     
  Lines         901     1011     +110     
==========================================
+ Hits          422      532     +110     
  Misses        479      479              
Impacted Files Coverage Δ
src/mip/coefficient_strengthening.jl 100.00% <100.00%> (ø)
src/presolve.jl 47.25% <100.00%> (+1.83%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c317b25...72cb1ad. Read the comment docs.

@@ -52,9 +52,9 @@ function coefficient_strengthening!(ps::PresolveData{T}, j::Int, inf::T = T(1e30
urow = ps.urow[i]

m = findfirst(isequal(j), row.nzind)
if m == nothing || (lrow > -inf && urow < inf)
if m == nothing || (lrow > -Inf && urow < Inf)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to ensure type-stability

Suggested change
if m == nothing || (lrow > -Inf && urow < Inf)
if m == nothing || (lrow > T(-Inf) && urow < T(Inf))

continue #skipping ranged constraints and
elseif urow < inf
elseif urow < Inf
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elseif urow < Inf
elseif urow < T(Inf)

@@ -73,7 +73,7 @@ function coefficient_strengthening!(ps::PresolveData{T}, j::Int, inf::T = T(1e30
ps.pb0.acols[j].nzval[k] = new_coef
end
end
elseif lrow > -inf
elseif lrow > -Inf
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elseif lrow > -Inf
elseif lrow > T(-Inf)

- remove deepcopy
- perform coefficient strengthening row by row instead of col by col
- no longer using findfirst, which may cost nzlog(nz) worst-case time complexity
- Update way in which the maximal activity of a row except variable j is compute
- skip row with no integer variable
- set rowflag/colflag
- update #nonzeros in each row/col
- add docs
Co-authored-by: mtanneau <9593025+mtanneau@users.noreply.github.com>
Perform coefficient strengthening on each row and on each integer variables

In particular, for the i-th constraints of the form
aᵢ x ⩽ bᵢ and let xⱼ be a integer/binary variable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End sentence with a period.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

aᵢⱼ ≔ aᵢⱼ - d
bᵢ ≔ bᵢ - duⱼ
Also perform coefficient strengthening on constraints of the form aᵢ x ⩾ cᵢ
with similar update rule
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End with a period

Also perform coefficient strengthening on constraints of the form aᵢ x ⩾ cᵢ
with similar update rule

Skip ranged constraints, i.e, cᵢ ⩽ aᵢ x ⩽ bᵢ
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make a complete sentence (add "We" at beginning, end with a period).


Skip ranged constraints, i.e, cᵢ ⩽ aᵢ x ⩽ bᵢ

The new coefficients are stored by updating ps.pb0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Period

where Mᵢⱼ is maximal activity of the i-th row without considering xⱼ
and uⱼ is upper bound of xⱼ, then
aᵢⱼ ≔ aᵢⱼ - d
bᵢ ≔ bᵢ - duⱼ
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently reads as an identity; I would rephrase to make clear that this is the transformation you apply.

Suggested change
bᵢ ≔ bᵢ - duⱼ
and uⱼ is upper bound of xⱼ, then we transform
aᵢⱼ <- aᵢⱼ - d
bᵢ <- bᵢ - duⱼ

return new_coef, new_bound
end

function lowerbound_strengthening!(ps::PresolveData{T}, i::Int, j_index::Int, j::Int, min_act) where {T}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually modify ps? If not, remove the !. (And similarly for upperbound)

end
#update sup
if coef > 0
sup = sup - (coef - new_coef) * ps.ucol[j]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sup = sup - (coef - new_coef) * ps.ucol[j]
sup -= (coef - new_coef) * ps.ucol[j]

if coef > 0
sup = sup - (coef - new_coef) * ps.ucol[j]
else
sup = sup - (coef - new_coef) * ps.lcol[j]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sup = sup - (coef - new_coef) * ps.lcol[j]
sup -= (coef - new_coef) * ps.lcol[j]

end
#update inf
if coef > 0
inf = inf - (coef - new_coef) * ps.lcol[j]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inf = inf - (coef - new_coef) * ps.lcol[j]
inf -= (coef - new_coef) * ps.lcol[j]

if coef > 0
inf = inf - (coef - new_coef) * ps.lcol[j]
else
inf = inf - (coef - new_coef) * ps.ucol[j]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inf = inf - (coef - new_coef) * ps.ucol[j]
inf -= (coef - new_coef) * ps.ucol[j]

- add one more test
- edit docs (period and complete sentence)
- using += and -=
Perform coefficient strengthening on each row and on each integer variables

In particular, for the i-th constraints of the form
aᵢ x ⩽ bᵢ and let xⱼ be a integer/binary variable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

If a column is reduced to 0 after this step, it is set to be inactive

"""
function maximal_activity(row::Row{T}, ucol::Vector{T}, lcol::Vector{T})::T where {T}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

end
end
sup = ucol[nonzero_index]'pos_coef + lcol[nonzero_index]'neg_coef
return T(sup)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

a[m] = a[m] - d
b = b - d*ucol[j]
flag = true
function minimal_activity(row::Row{T}, ucol::Vector{T}, lcol::Vector{T})::T where {T}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

Comment on lines 45 to 46
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

end

function zero_coefficient_strengthening!(ps::PresolveData{T}) where {T}
# perform coefficient stregthening but if there is a coefficient is reduced to 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump


function zero_coefficient_strengthening!(ps::PresolveData{T}) where {T}
# perform coefficient stregthening but if there is a coefficient is reduced to 0
# it is still kept in the ps.pb0.arows and ps.pb0.acols
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

Comment on lines 113 to 114
sup = maximal_activity(row, ps.ucol, ps.lcol)
inf = minimal_activity(row, ps.ucol, ps.lcol)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

src/presolve.jl Outdated
@@ -414,6 +415,9 @@ function presolve!(ps::PresolveData{T}) where {T}
# Round the bounds of integer variables are integers.
round_integer_bounds!(ps)

# Coeficient strengthening
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

src/presolve.jl Outdated
Perform coefficient strengthening for integer/binary variables
on every constraints.

Called once at the very beginning of the presolve procedure.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump

@joehuchette
Copy link
Collaborator

@Anhtu07 There are some outstanding comments (I think) that I bumped. You can see them if you click the "Files Changed" tab up top.

Copy link
Owner

@mtanneau mtanneau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing: check whether rows/columns are active or not

src/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
src/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
src/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
src/mip/coefficient_strengthening.jl Show resolved Hide resolved
Anhtu07 and others added 2 commits January 30, 2021 17:55
Co-authored-by: mtanneau <9593025+mtanneau@users.noreply.github.com>
Co-authored-by: mtanneau <9593025+mtanneau@users.noreply.github.com>
src/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
src/presolve.jl Outdated
coefficient_strengthening!(ps::PresolveData)

Perform coefficient strengthening for integer/binary variables
on every constraints.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
on every constraints.
in every constraint.

return new_coef, new_bound
end

function zero_coefficient_strengthening!(ps::PresolveData{T}) where {T}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this naming. It sounds like you're only applying coefficient strengthening on zero coefficients (maybe?). I would just call this _coefficient_strengthenening!.

@@ -0,0 +1,178 @@
"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The location of this docstring is a bit odd--usually it would go immediately before (no extra newline) before a function it's describing. Here, it's above maximal_activity, but that's just really a helper function.

If a row/column is reduced to 0 after this step, it will be set to be inactive.
"""

function maximal_activity(ps::PresolveData{T}, i::Int)::T where {T}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving maximal_activity and minimal_activity to util.jl.

Copy link
Owner

@mtanneau mtanneau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're reaching the end!
@Anhtu07 you will be the record holder for the longest PR :)

I have checked all the source code. Except for the removal of empty rows/columns, everything looks good. I have not checked the underlying math.
I have also not checked the correctness of tests, I trust you know what you're doing.

test/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
test/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
test/mip/coefficient_strengthening.jl Show resolved Hide resolved
test/mip/coefficient_strengthening.jl Show resolved Hide resolved
test/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
test/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
test/mip/coefficient_strengthening.jl Outdated Show resolved Hide resolved
src/presolve.jl Outdated Show resolved Hide resolved
src/presolve.jl Outdated Show resolved Hide resolved
src/presolve.jl Outdated Show resolved Hide resolved
Anhtu07 and others added 3 commits January 31, 2021 09:22
Co-authored-by: mtanneau <9593025+mtanneau@users.noreply.github.com>
Co-authored-by: Joey Huchette <joehuchette@gmail.com>
- move maximal_activity/ minimal_activity to util
- remove coefficient_strengthening! from presolve.jl
- deleting spaces
@Anhtu07
Copy link
Contributor Author

Anhtu07 commented Jan 31, 2021

@mtanneau Thanks for letting me know, I will put that on my CV :) Trust me, future PR will not take as long

you can review it one more time now.

Copy link
Owner

@mtanneau mtanneau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Given the large number of commits in this PR, I'd prefer we squash and merge, and clean the commit history when doing so.

@joehuchette joehuchette merged commit e29763f into mtanneau:master Feb 1, 2021
@joehuchette
Copy link
Collaborator

Thanks for your hard work on this one, @Anhtu07 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants