Skip to content

Add eachTriangle #95

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

Merged
merged 25 commits into from
Feb 16, 2018
Merged

Add eachTriangle #95

merged 25 commits into from
Feb 16, 2018

Conversation

jmh530
Copy link
Contributor

@jmh530 jmh530 commented Sep 26, 2017

After some of the work on eachUploPair I was thinking that it might make sense to specialize a version for upper and lower triangles. This would be useful for implementing functions similar to tril and triu from Matlab/Numpy in numir.

I'm not sure this is the most efficient version, but at the moment it gets the job done and works on rectangular matrices the way I would expect.

@@ -1884,3 +1885,760 @@ size_t countImpl(alias fun, Slices...)(Slices slices)
while(!slices[0].empty);
return ret;
}

private template eachLowerTriangle(alias fun, size_t k = 1,
bool BeyondMainDiagonal = false)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be better to have eachLowerTriangle(alias fun,ptrdiff_t diagonalOffset) if I am understanding this correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's more-or-less how it originally was, but I thought the code was a little simpler when I had done it this way.

unittest
{
import mir.ndslice.allocation: slice;
import mir.ndslice.topology: iota, canonical;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to combine these essentially duplicate tests with an alias seq?
Something like

import mir.ndslice.topology: iota, canonical, universal;
/*static*/ foreach(type; AliasSeq!(identity, canonical, universal)
{
    auto m = iota([3, 3], 1).slice.type;
    m.eachLowerTriangle!((ref a) {a = 0; }, 0);
    assert(m == [
        [0, 2, 3],
        [0, 0, 6],
        [0, 0, 0]]);

Copy link
Member

Choose a reason for hiding this comment

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

Yes, please. I like unittests, but it is nice to keep them smaller with the same coverage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@thewilsonator The way I was able to get it to work looks like

version(unittest)
{
    T identity(T)(T x) @property
    {
        return x;
    }
}

unittest
{
    import mir.ndslice.allocation: slice;
    import mir.ndslice.topology: iota, canonical, universal;
    import std.meta: AliasSeq;
    
    void test(alias func)()
    {
        // 1 2 3
        // 4 5 6
        // 7 8 9
        auto m = func(iota([3, 3], 1).slice);
        m.eachUpper!((ref a) {a = 0; }, 0);
        assert(m == [
            [0, 0, 0],
            [4, 0, 0],
            [7, 8, 0]]);
    }
    
    static foreach(type; AliasSeq!(identity, canonical, universal))
    {
        test!type;
    }
}

@9il
Copy link
Member

9il commented Sep 26, 2017

I'm not sure this is the most efficient version, but at the moment it gets the job done and works on rectangular matrices the way I would expect.

eachUploPair at master should be slightly faster then current implementation of eachTriangle. Furthermore, if one use a lambda with refs (ref u, ref l) then it will be optimised well (without refs for primitive types too). Plus, eachUploPair!((ref u, ref l){ ... }) has clearer API.

I do not think we need this API. Maybe docs for eachUploPair!((ref u, ref l){ ... }) can be updated.

@jmh530
Copy link
Contributor Author

jmh530 commented Sep 26, 2017

@9il A few reasons I wrote this up: 1) eachUploPair only works for square matrices. This works for rectangular. 2) While eachUploPair works well for the main diagonal or one off the main diagonal, it is a little awkward to express further shifts in the diagonal. I honestly didn't even try it.

This function makes it very easy to implement the triu and tril functions as they are in Matlab/Numpy. They are currently not implemented in numir. I think it would be more difficult with eachUploPair, even if eachUploPair worked for rectangular matrices.

@9il
Copy link
Member

9il commented Sep 26, 2017

OK, I see.
Please compress unittests where possible.

What about eachUpper and eachLower names?

@jmh530
Copy link
Contributor Author

jmh530 commented Sep 26, 2017 via email

@9il
Copy link
Member

9il commented Sep 26, 2017

I can also add specialization for the square cases where
eachUploPair can be used.

Probably implementation should look like calls of eachUploPair and eachImpl

@9il
Copy link
Member

9il commented Sep 26, 2017

@jmh530 BeyondMainDiagonal looks useless (popFront!0 / popFront!1 can be used instead).

Possibly eachUpper and eachLower should accept multiple arguments as each do.

@jmh530
Copy link
Contributor Author

jmh530 commented Sep 26, 2017

@9i With respect to BeyondMainDiagonal I was planning to make the adjustment suggested by @thewilsonator above.

@9il
Copy link
Member

9il commented Sep 26, 2017

@9i With respect to BeyondMainDiagonal I was planning to make the adjustment suggested by @thewilsonator above.

LGTM

@codecov-io
Copy link

codecov-io commented Sep 27, 2017

Codecov Report

Merging #95 into master will increase coverage by 1.38%.
The diff coverage is 98.88%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #95      +/-   ##
==========================================
+ Coverage   95.24%   96.62%   +1.38%     
==========================================
  Files          45       30      -15     
  Lines        6599     5456    -1143     
==========================================
- Hits         6285     5272    -1013     
+ Misses        314      184     -130
Impacted Files Coverage Δ
source/mir/ndslice/package.d 92.25% <ø> (ø) ⬆️
source/mir/ndslice/algorithm.d 99.09% <98.88%> (-0.09%) ⬇️
source/mir/ndslice/internal.d 57.69% <0%> (-1.96%) ⬇️
source/mir/ndslice/chunks.d 95.62% <0%> (-0.68%) ⬇️
source/mir/ndslice/iterator.d 95.12% <0%> (-0.6%) ⬇️
source/mir/ndslice/topology.d 99.28% <0%> (-0.55%) ⬇️
source/mir/functional.d 96.34% <0%> (-0.35%) ⬇️
source/mir/ndslice/concatenation.d 99.03% <0%> (-0.01%) ⬇️
source/mir/math/sum.d 99.4% <0%> (-0.01%) ⬇️
... and 33 more

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 3cd51f4...d95cf16. Read the comment docs.

@thewilsonator
Copy link
Contributor

LGTM. Resolve the conflict and make sure the CI passes. Wr.t the latter I'm not sure that LDC has static foreach yet, you may have to remove that, either way it will work without it.


version(unittest)
{
T identity(T)(T x)
Copy link
Member

Choose a reason for hiding this comment

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

no need in global declaration. can be replaced with lambda

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't see how to do this with lambdas without making it more complicated. The lambdas I was trying weren't inferring the return type correctly, so then I tried making it a template and it just got uglier. I just put the identity function in each of the unittests instead of the version.

fun = A function
k = adjustment to diagonals (default = 1)
+/
template eachLower(alias fun, ptrdiff_t k = 1)
Copy link
Member

@9il 9il Sep 27, 2017

Choose a reason for hiding this comment

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

why not to make k a runtime parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will make it a run-time parameter. The reason I hadn't is because I couldn't think of a time when I wanted to use triu or tril without knowing at compile-time how I was going to use it. Since this is more general than those functions, it probably makes sense.

As an aside, it would be much easier if in D you could have some parameters be available in run-time or compile-time.

static if (__traits(isSame, naryFun!fun, fun))
{
void eachLower(SliceKind kind, Iterator)
(Slice!(kind, [2], Iterator) matrix)
Copy link
Member

Choose a reason for hiding this comment

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

Multiple arguments is required for many algorithms (the same way as each). For example, for triu implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't fully understand this, but it is late and I will try to look at it again with fresh eyes.


void eachLowerImpl(T)(T matrix)
{
void eachLowerImpl_i(U)(U e, size_t i)
Copy link
Member

Choose a reason for hiding this comment

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

No non-static inner functions plz

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

(Slice!(kind, [2], Iterator) matrix)
{
immutable(size_t) m = matrix.length!0;
immutable(size_t) n = matrix.length!1;
Copy link
Member

Choose a reason for hiding this comment

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

Oh, lets replace the implementation with single one loop and without eachUploPair calls (sorry for wrong recommendation before, found algo issues with it now). We should be careful about code time generation and object code size.

x x x x < | 2D eachImpl
x x x x < |
x x x x < || Loop over 1D eachImpl
  x x x < ||
    x x < ||
      x < ||

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I have implemented this in the latest commit.

if (i < (n + k))
e[0..(i - k + 1)].eachImpl!fun;
else
e.eachImpl!fun;
Copy link
Member

Choose a reason for hiding this comment

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

eachImpl probably will be inlined, so the following code will be two times smaller (in terms of object code size):

eachImpl!fun(i < n + k ? e[0..(i - k + 1)] : e);

EDIT: generally we should not have conditions in the loop. So dismiss this comment.

@jmh530 jmh530 force-pushed the jmh530-eachTriangle branch from 125e60b to 4ceeea6 Compare September 27, 2017 03:36
@jmh530
Copy link
Contributor Author

jmh530 commented Sep 28, 2017

@9il I think I've got a handle on how to make it work for multiple slices now, but still a little work to do.

@jmh530 jmh530 force-pushed the jmh530-eachTriangle branch from 95c66bf to 4eadd12 Compare October 5, 2017 00:13
@jmh530
Copy link
Contributor Author

jmh530 commented Oct 5, 2017

@9il I've submitted some updates. The only fail is on DMD nightly. Let me know if you want additional changes.

@PetarKirov
Copy link

The problem on Travis CI doesn't appear related to your pull request - the installer failed:

source "$(CURL_USER_AGENT="$CURL_USER_AGENT" bash install.sh dmd-nightly --activate)"
Warning: No gpg tool found to verify downloads.
curl: (56) Recv failure: Operation timed out
/Users/travis/.travis/job_stages: line 57: : No such file or directory

foreach(ref slice; slices)
slice.popFront!0;
i++;
} while (i < k);
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this loop can be replaced with slice.popFrontExactly!0(k)

val = i - k + 1;
static if (slices[0].shape.length == 1)
mixin("fun(" ~
frontSelectFrontOf!(Slices.length, "val") ~ ");");
Copy link
Member

Choose a reason for hiding this comment

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

please use single line here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does mir follow a line length restriction? I normally use 80.

frontSelectFrontOf!(Slices.length, "val") ~ ");");
else
mixin(".eachImpl!fun(" ~
frontSelectFrontOf!(Slices.length, "val") ~ ");");
Copy link
Member

Choose a reason for hiding this comment

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

ditto

selectFrontOf!(Slices.length, "val") ~ ");");
do
{
foreach(ref slice; slices)
Copy link
Member

Choose a reason for hiding this comment

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

ditto popFrontExactly


static if ((Inputs.length > 1) && (isIntegral!(Inputs[$ - 1])))
{
k = inputs[$ - 1];
Copy link
Member

Choose a reason for hiding this comment

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

sizediff_t k = inputs[$ - 1];

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's sizediff_t?

alias slices = inputs[0..($ - 1)];
}
else
{
Copy link
Member

Choose a reason for hiding this comment

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

enum sizediff_t k = 1;

@@ -166,6 +166,7 @@ $(TR $(TDNW $(SUBMODULE algorithm)
$(SUBREF algorithm, cmp)
$(SUBREF algorithm, count)
$(SUBREF algorithm, each)
$(SUBREF algorithm, eachTriangle)
Copy link
Member

Choose a reason for hiding this comment

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

names was changed

function applied.
+/
void eachLower(Inputs...)(Inputs inputs)
if (Inputs.length)
Copy link
Member

Choose a reason for hiding this comment

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

Needs batter contraints:
(Inputs.length > 1) && (isIntegral!(Inputs[$ - 1])) || Inputs.length

if (k < 0)
{
val = -k;
static if (slices[0].shape.length == 1)
Copy link
Member

Choose a reason for hiding this comment

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

do we have 1D case?

import std.meta : allSatisfy;
import std.traits : isIntegral;
import mir.ndslice.traits : isMatrix;
import mir.ndslice.slice : Slice;
Copy link
Member

Choose a reason for hiding this comment

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

Is something from this imports are global in the module?

//| 5 6 7 8 |
//| 9 10 11 12 |
auto m = iota([3, 4], 1).slice;
m.eachUpper!((ref a) {a = 0; })(-2);
Copy link
Member

@9il 9il Oct 24, 2017

Choose a reason for hiding this comment

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

m.eachUpper!"a = 0" should work too. Please use it since it does not generate template bloat for each difinition.

@jmh530
Copy link
Contributor Author

jmh530 commented Feb 7, 2018

@9il ...and I just realized that there were changes requested here. Sorry about that. I'll try to get back to this.

@jmh530 jmh530 force-pushed the jmh530-eachTriangle branch from 8ac2135 to a4557b4 Compare February 16, 2018 03:33
@jmh530
Copy link
Contributor Author

jmh530 commented Feb 16, 2018

@9il I've made some updates. Travis-CI is failing on ldc-1.7.0 for some reason, but otherwise looks fine.

@9il
Copy link
Member

9il commented Feb 16, 2018

awesome!

@9il 9il merged commit 402bcd2 into libmir:master Feb 16, 2018
@jmh530 jmh530 deleted the jmh530-eachTriangle branch February 16, 2018 13:02
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