Skip to content

Conversation

@SaVoAMP
Copy link
Contributor

@SaVoAMP SaVoAMP commented Feb 27, 2022

Pull Request Checklist

Below is a simple checklist but please do not hesitate to ask for assistance!

  • Fork, clone, and checkout the newest version of the code
  • Create a new branch
  • Make necessary code changes
  • Install black (i.e., python -m pip install black or conda install -c conda-forge black)
  • Install flake8 (i.e., python -m pip install flake8 or conda install -c conda-forge flake8)
  • Install pytest-cov (i.e., python -m pip install pytest-cov or conda install -c conda-forge pytest-cov)
  • Run black . in the root stumpy directory
  • Run flake8 . in the root stumpy directory
  • Run ./setup.sh && ./test.sh in the root stumpy directory
  • Reference a Github issue (and create one if one doesn't already exist)
    Add Unit Tests for mmotifs and aamp_mmotifs #552

@SaVoAMP SaVoAMP changed the title Unit test [WIP] Unit test Feb 27, 2022
@seanlaw
Copy link
Contributor

seanlaw commented Feb 27, 2022

@SaVoAMP Before pushing your commits, please remember to execute black . && flake8 .

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Feb 27, 2022

./test_mstump.py:93:1: D103 Missing docstring in public function
./test_mstump.py:111:1: D103 Missing docstring in public function
./test_mstump.py:130:1: D103 Missing docstring in public function
./test_mstump.py:141:1: D103 Missing docstring in public function
./test_mstump.py:155:1: D103 Missing docstring in public function
./test_mstump.py:166:1: D103 Missing docstring in public function
./test_mstump.py:182:1: D103 Missing docstring in public function
./test_mstump.py:194:1: D103 Missing docstring in public function
./test_mstump.py:210:1: D103 Missing docstring in public function
./test_mstump.py:216:1: D103 Missing docstring in public function
./test_mstump.py:227:1: D103 Missing docstring in public function
./test_mstump.py:241:1: D103 Missing docstring in public function
./test_mstump.py:252:1: D103 Missing docstring in public function
./test_mstump.py:267:1: D103 Missing docstring in public function
./test_mstump.py:284:1: D103 Missing docstring in public function
./test_mstump.py:304:1: D103 Missing docstring in public function
./test_mstump.py:317:1: D103 Missing docstring in public function
./test_mstump.py:338:1: D103 Missing docstring in public function
./test_mstump.py:359:1: D103 Missing docstring in public function
./test_scrump.py:27:1: D103 Missing docstring in public function
./test_scrump.py:44:1: D103 Missing docstring in public function
./test_scrump.py:61:1: D103 Missing docstring in public function
./test_scrump.py:79:1: D103 Missing docstring in public function
./test_scrump.py:95:1: D103 Missing docstring in public function
./test_scrump.py:102:1: D103 Missing docstring in public function
./test_scrump.py:136:1: D103 Missing docstring in public function
./test_scrump.py:170:1: D103 Missing docstring in public function
./test_scrump.py:205:1: D103 Missing docstring in public function
./test_scrump.py:239:1: D103 Missing docstring in public function
./test_scrump.py:277:1: D103 Missing docstring in public function
./test_scrump.py:315:1: D103 Missing docstring in public function
./test_scrump.py:343:1: D103 Missing docstring in public function
./test_scrump.py:371:1: D103 Missing docstring in public function
./test_scrump.py:412:1: D103 Missing docstring in public function
./test_scrump.py:457:1: D103 Missing docstring in public function
./test_scrump.py:486:1: D103 Missing docstring in public function
./test_scrump.py:515:1: D103 Missing docstring in public function
./test_scrump.py:544:1: D103 Missing docstring in public function
./test_scrump.py:580:1: D103 Missing docstring in public function
./test_scrump.py:621:1: D103 Missing docstring in public function
./test_scrump.py:662:1: D103 Missing docstring in public function

I am not sure what to do because I get many problems when running flake8 .

@codecov-commenter
Copy link

codecov-commenter commented Feb 27, 2022

Codecov Report

Merging #553 (7684825) into main (d59c286) will increase coverage by 0.95%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #553      +/-   ##
==========================================
+ Coverage   98.93%   99.89%   +0.95%     
==========================================
  Files          74       80       +6     
  Lines       10732    11192     +460     
==========================================
+ Hits        10618    11180     +562     
+ Misses        114       12     -102     
Impacted Files Coverage Δ
tests/test_motifs.py 100.00% <ø> (ø)
stumpy/aamp_mmotifs.py 100.00% <100.00%> (+100.00%) ⬆️
stumpy/aamp_motifs.py 100.00% <100.00%> (ø)
stumpy/mmotifs.py 100.00% <100.00%> (+100.00%) ⬆️
stumpy/motifs.py 100.00% <100.00%> (ø)
tests/test_aamp_mmotifs.py 100.00% <100.00%> (ø)
tests/test_mmotifs.py 100.00% <100.00%> (ø)
stumpy/core.py 100.00% <0.00%> (ø)
tests/naive.py 100.00% <0.00%> (ø)
stumpy/maamp.py 100.00% <0.00%> (ø)
... and 14 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 d59c286...7684825. Read the comment docs.

@seanlaw
Copy link
Contributor

seanlaw commented Feb 27, 2022

@SaVoAMP Can you try running those commands in the root STUMPY directory instead on inside the tests/ directory?

There is a file called .flake8 in the root directory that helps ignore these errors

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Feb 27, 2022

Ohh, sorry, that was pretty stupid! I probably should not make pull requests while having a headache 😆

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Feb 27, 2022

I tried to write the first testing function. I'm not sure whether I did it as expected 🤔

Never mind, I see the problem 😆

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Feb 27, 2022

I think we have got a problem with mmotifs:

 query_matches = match(
            Q=T[subspace_k, motif_idx : motif_idx + m],
            T=T[subspace_k],
            M_T=M_T[subspace_k],
            Σ_T=Σ_T[subspace_k],
            max_matches=max_matches,
            max_distance=max_distance,
            atol=atol,
            normalize=normalize,
            p=p,
        )

        query_length = len(query_matches)

        if len(query_matches) > min_neighbors:
            motif_distances.append(query_matches[:, 0])
            motif_indices.append(query_matches[:, 1])
            motif_subspaces.append(subspace_k)
            motif_mdls.append(mdls)

I think we need something like query_matches.size instead of len(query_matches) since query_matches is an array.
I just noticed that I didn't get the expected results while trying to construct the test example:

I have got array([[0.0, 3]], dtype=object) for query_matches and when writing query_length = len(query_matches) I get 1 as result but I should get 2. Thus the if-statement gets skipped and I get wrong results.
Probably it will be enough to set if len(query_matches) >= min_neighbors:, but it is confusing that the length of the array is to short. So when setting array([[0.0, 3, 5]], dtype=object) I get a length of 2 instead of three.

You don't have to look at the test_mmotifs.py script or why the tests currently fail. I am aware of the cause and will try to fix it as soon as I am sure that mmotifs() is doing what it is supposed to do.

@seanlaw
Copy link
Contributor

seanlaw commented Feb 28, 2022

I have got array([[0.0, 3]], dtype=object) for query_matches and when writing query_length = len(query_matches) I get 1 as result but I should get 2.

Based on array([[0.0, 3]], dtype=object), I would agree that the length is 1 as expected. len(query_matches) sounds correct to me. Why would you expect to get 2? Please let me know if there's anything I can do to help.

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Feb 28, 2022

I'm sorry for the confusion!

The problem is that I have min_neighbors=1. Shouldn't this mean that I should get at least one motif pair without the other matches? Or is the max_distance parameter superior?

image
I would expect that the nearest neighbor of the motif on position 3 would also be included with the distance 0.045 on position 10. I set max_distance=None and the default computation gives me 0.0 for max_distance which is pretty, pretty small and only includes the self match.
Therefore I get different subspace results when calling stumpy.subspace() after I computed the matrix profile and found the motif_idx and the nearest_neighbor_idx and calling mmotifs().

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Feb 28, 2022

I get exit code 5 on pytest which means, there are no tests to run.

I don't see any assertions (using the assert keyword) as seen in the documentation of pytest in your code.

I tried to use your code but I still get the same error message. What am I missing?

@seanlaw
Copy link
Contributor

seanlaw commented Feb 28, 2022

The problem is that I have min_neighbors=1. Shouldn't this mean that I should get at least one motif pair without the other matches? Or is the max_distance parameter superior?

Based on the order of the code, I see that min_neighbors is not used in query_matches (which is expected). Thus, I believe that max_distance takes precedence over min_neighbors. Can you tell me what the standard deviation and mean are for P is in this case? I'm guessing that the default of 2 stddevs below the mean might be a negative value in this case and so the evaluation clips the max_distance to be no smaller than the smallest value in P. That's just a hunch though.

So, with min_neighbors=1 and max_distance=0.0, the only thing that can get returned would be the self-match/trivial-match and any other perfect matches, which I am guessing (the latter) does not exist in you data. Thus, there is only one neighbor (the self-match) and so, naturally, if len(query_matches) > min_neighbors: resolves to False and nothing gets returned.

I'm sorry for the confusion!

Also, please do not feel the need to apologize. I get confused by this stuff as well and we are all here to learn/help/exchange ideas. Open source is a team sport and we should support each other!

@seanlaw
Copy link
Contributor

seanlaw commented Feb 28, 2022

I get exit code 5 on pytest which means, there are no tests to run.
I don't see any assertions (using the assert keyword) as seen in the documentation of pytest in your code.
I tried to use your code but I still get the same error message. What am I missing?

Let me take a look.

@seanlaw
Copy link
Contributor

seanlaw commented Feb 28, 2022

I get exit code 5 on pytest which means, there are no tests to run.
I don't see any assertions (using the assert keyword) as seen in the documentation of pytest in your code.
I tried to use your code but I still get the same error message. What am I missing?

I think I understand what the issue is after looking at the unit tests that are being executed here:

============================= test session starts ==============================
platform linux -- Python 3.7.12, pytest-7.0.1, pluggy-1.0.0
rootdir: /home/runner/work/stumpy/stumpy, configfile: pytest.ini
collected 7 items

tests/test_aamp_motifs.py .......                                        [100%]

============================== 7 passed in 1.09s ===============================
============================= test session starts ==============================
platform linux -- Python 3.7.12, pytest-7.0.1, pluggy-1.0.0
rootdir: /home/runner/work/stumpy/stumpy, configfile: pytest.ini
collected 0 items

============================ no tests ran in 0.00s =============================
Error: pytest encountered exit code 5
Error: Process completed with exit code 5.

This is consistent with what you are observing. However, it wasn't clear which test file was experiencing an exit code 5 though the test file that last succeeded was for tests/test_aamp_motifs.py. Since this is a unit test (and not coverage testing), I took a look at the test_unit section to see what the next test was after tests/test_aamp_motifs.py:

tests/test_aamp_motifs.py
check_errs $?
pytest -x -W ignore::RuntimeWarning -W ignore::DeprecationWarning tests/test_aamp_mmotifs.py
check_errs $?

Okay, so the next test is tests/test_aamp_mmotifs.py and when I look inside that file I see that it is empty, which is then consistent with your observation of:

I get exit code 5 on pytest which means, there are no tests to run.

Because there are, indeed, no tests to run inside of tests/test_aamp_mmotifs.py. I'm guessing that if you add a simple dummy test inside of tests/test_aamp_mmotifs.py like:

def increment(x):
    return x + 1


def test_answer():
    assert increment(3) == 4

Then you should be able to overcome this pytest exit 5 error. With your tests/test_aamp_mmotifs.py file being empty, you can probably reproduce the same exit 5 error by changing the test_custom function to:

pytest -x -W ignore::RuntimeWarning -W ignore::DeprecationWarning tests/test_aamp_mmotifs.py

and then doing

./setup.sh && test.sh custom

On a side note, I do see that all 4 tests for tests/test_mmotifs.py have passed (you'll need to scroll up to see it).

Now, to address your comment:

I don't see any assertions (using the assert keyword) as seen in the documentation of pytest in your code.

Yes, it is true that I don't explicitly use the assert keyword in my tests. This is because numpy arrays are especially tricky (i.e., not only do you need to compare each element between two arrays but you need to also consider things like the shape and the dtype, etc) to compare with each other using plain vanilla assert and we perform a ton of comparisons between numpy arrays in STUMPY. So, instead, numpy provides a very nice set of testing tools that will handle a lot of this for us. Thus, in place of Python's assert which is typically used for comparing single (scalar-like) values, we use numpy.testing assertions. Here is a simple example (I have only written this down from the top of my head so I hope it has minimal errors):

import numpy as np
import numpy.testing as npt

def naive_increment(x, incr):
    x = x.copy()
    for i in range(x):
        x[i] += incr
    return x


def test_answer():
    ref = np.array([3, 4, 5, 6, 7, 8, 9, 10, 11, 12], dtype=np.int64)
    x = np.arange(10)
    cmp = naive_increment(x, 3)
    npt.assert_almost_equal(cmp, ref)

Notice that we have a reference ref that is basically the "expected outcome" and then the thing that we are comparing to, cmp. To compare the two numpy arrays, we use numpy.testing (aliased to npt here to save us from typing) and then we assert that the two arrays are "almost equal" via npt.assert_almost_equal(cmp, ref). We typically use assert_almost_equal (which is overkill here for integers) since floating point values will have poorer precision and so we allow for some slight differences but not a lot.

In fact, you can combine both sets of numpy assertions and Python assert into one test file if you like:

import numpy as np
import numpy.testing as npt

def increment(x, incr):
    x = x.copy()
    for i in range(x):
        x[i] += incr
    return x


def test_answer():
    ref = np.array([3, 4, 5, 6, 7, 8, 9, 10, 11, 12], dtype=np.int64)
    x = np.arange(10)
    cmp = increment(x, 3)
    npt.assert_almost_equal(cmp, ref)


def increment(x):
    return x + 1


def test_answer():
    assert increment(3) == 4

Please let me know if I can help fill any knowledge/understanding gaps.

@seanlaw
Copy link
Contributor

seanlaw commented Feb 28, 2022

Awesome! It looks like all tests are passing now (even the dummy one). Now, you can focus on writing the tests for coverage:

Name                     Stmts   Miss  Cover   Missing
------------------------------------------------------
stumpy/aamp_mmotifs.py      51     44    14%   108-195
stumpy/core.py             407      1    99%   4[78](https://github.com/TDAmeritrade/stumpy/runs/5361908563?check_suite_focus=true#step:8:78)
stumpy/mmotifs.py           51      6    88%   144, 157, 170-171, 182, 207
------------------------------------------------------
TOTAL                    10684     51    99%

Please ignore the missing coverage for stumpy/core.py. I just pushed some new code that fixes that (so please merge the newest addition at your earliest convenience)

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Feb 28, 2022

Alright, perfect!

Would you mind taking a look at my first test function in test_mmotifs.py before I start with the other tests? I would appreciate any feedback as I'm pretty unconfident with all of this.

@seanlaw
Copy link
Contributor

seanlaw commented Feb 28, 2022

Would you mind taking a look at my first test function in test_mmotifs.py before I start with the other tests? I would appreciate any feedback as I'm pretty unconfident with all of this.

Yes, please give me some time to review



@pytest.mark.parametrize("Q, T", test_data)
def test_match(Q, T):
Copy link
Contributor

Choose a reason for hiding this comment

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

Since match is technically in the motifs.py module, I feel like this should get moved into the tests/test_motifs.py test file

def test_motifs_multidimensional_one_motif_all_dimensions():
# Find the two dimensional motif pair

# Arrange
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please tell me how you came up with this set of 4 arrays? Did you determine them all by hand (i.e., without invoking stumpy.mmotifs)?

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 was using already covered functions to obtain them. Is this ok?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that should be fine. However, if for some reason those functions are not covered then we should also cover them first.

T, P, I, max_distance=np.inf, max_matches=2, k=1
)

# Assert
Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, if the tests pass then you are on the right track! The goal here is to compare your manual calculations (without calling the mmotifs() function) with what the mmotifs() function would produce.

Then, you try to test all possible combinations of inputs by hand (note your expected manual outputs) and then see if the function you are testing also produces the same expected outputs. If not then there is something wrong with your function.

After that, the goal is to think about nasty edge cases and to write tests to ensure that we are handling those edge cases properly. We don't have to catch everything (it's impossible to) but at least think about how a user might use/abuse the function and hopefully we test and make sure that we consider all reasonable (i.e., rationally obvious) use cases and have good coverage on those.

Also, as you may have noticed, the goal of having a "naive" implementation means that you don't have to do things manually and, instead, it allows you to try random inputs of different small arrays (in addition to a pre-determined array with known outputs). The only difference between the naive implementation vs your implementation should be speed (because the naive implementation would use a bunch of slow for-loops but is easier to read and follow versus faster algorithms are usually VERY HARD to read/follow since they often employ hyper specialized tricks).

@seanlaw
Copy link
Contributor

seanlaw commented Mar 1, 2022

Alright, perfect!

Would you mind taking a look at my first test function in test_mmotifs.py before I start with the other tests? I would appreciate any feedback as I'm pretty unconfident with all of this.

@SaVoAMP I've left a few comments but, seriously, (no surprise) you're doing a great job! You'll be teaching me about unit testing soon :)

@seanlaw
Copy link
Contributor

seanlaw commented Mar 12, 2022

@SaVoAMP Can you please provide me with the set of input parameters for mmotifs that you believe should allow you to enter the break condition?

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Mar 13, 2022

image
I'm able to get to break if I place a print-function before it (without doing anything else).
I only call the function test_motifs_multidimensional_more_motifs_when_cutoffs_is_set() in test_mmotifs.py. Once motif_value=np.inf the if-condition is fulfilled, thus I traverse into the condition and reach the break. Without the print it's just jumping over the break keyword, but with it I'm able to get to the break. Nonetheless the break is working even though I can't reach that exact line directly. The only problem is that this line doesn't pass the coverage tests, neither in test_mmotifs.py nor in test_aamp_mmotifs.py.

@seanlaw
Copy link
Contributor

seanlaw commented Mar 13, 2022

@SaVoAMP In this rare case, please add # pragma: no cover:

if (
            motif_value > cutoffs[k]
            or not np.isfinite(motif_value)
            or (isinstance(max_distance, float) and motif_value > max_distance)
        ):  # pragma: no cover
            break

I don't usually recommend this but this is an exceptional case that requires it.

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Mar 13, 2022

====================== 1271 passed in 2050.37s (0:34:10) =======================
Name    Stmts   Miss  Cover   Missing
-------------------------------------
-------------------------------------
TOTAL   10871      0   100%

76 files skipped due to complete coverage.

It looks as if everything is covered now.

@seanlaw
Copy link
Contributor

seanlaw commented Mar 13, 2022

It looks as if everything is covered now.

Awesome! Maybe you can focus on the tutorial now and I will find some time to thoroughly review the tests and code changes. Thank you for all of your efforts!

Copy link
Contributor

@seanlaw seanlaw left a comment

Choose a reason for hiding this comment

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

@SaVoAMP I think things look pretty good. I provided some comments for you to consider. All comments for test_aamp_mmoitfs.py should also apply to test_mmotifs.py. Please, let me know if you have any questions, comments, or concerns.

@seanlaw
Copy link
Contributor

seanlaw commented Mar 15, 2022

@SaVoAMP Thank you for putting in all of this work. I hope that I'm not coming across as too "nit-picky" (I probably am 😄). However, since you had expressed that you are relatively new to (some?) of this software development process, I wanted to at least provide the appropriate feedback and "thinking" regarding best practices. While I could have accepted your PR "as is", I didn't want to take away your opportunity to learn, try, and gain more "real world" exposure on the topic. As you can see, this part is often more art than science but I'm sure you can understand why it takes me a long time (working by myself) to release new features as I care deeply about maintaining the high quality of that last Kilometer. Things like unit testing and documentation are often the hardest part of the development process that gets heavily neglected because it isn't glamorous work (like adding a new feature) and you've really excelled! However, unit testing and documentation are an integral (if not the most important) part of open source software so please know that you are really helping STUMPY uphold its standards as well as its commitment to its community! Thank you and I appreciate your patience

Copy link
Contributor

@seanlaw seanlaw left a comment

Choose a reason for hiding this comment

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

@SaVoAMP We are very, very close to having your PR merged! Just a few minor comments and suggestions for you to consider.

]


def test_aamp_mmotifs_default_parameters():
Copy link
Contributor

Choose a reason for hiding this comment

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

Perfect!

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if you notice this but after we take out the chunk of code for T, the test function(s) are all super simple and it is easy to digest what is happening. This little bit of refactoring, cleans up the code and everything can be read without scrolling. This is exactly how a "good" unit test should look and this is about as close to "perfection" as I could expect, which makes my job as a reviewer really, really easy. The rest of the unit tests look equally as great too! Excellent work!

"Future Sean" thanks you as well. 😄

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Mar 15, 2022

Thank you for your support!

However, since you had expressed that you are relatively new to (some?) of this software development process, I wanted to at least provide the appropriate feedback and "thinking" regarding best practices.

Yes, as I've already mentioned these are my first pull requests ever. I started my major without any prior experience with computers (I didn't even had one and bought my first computer ever in the third semester 😆) as my specialization in "high school" was social work. And although my current major included some computer science modules (C# Programming, data structures and algorithms, very basic software engineering without git) the focus is still on Math and Physics, we are only required to do those modules to be able to do numerical simulations with tools like COMSOL Multiphysics and the like.

Since the start of my mandatory apprenticeship and successive bachelors thesis at the Fraunhofer Institute I'm working on data science projects and had to learn Python, Linux and git from scratch over the last months.

Therefore I especially value your professional and detailed feedback. (I also brush up my english skills along the way 😄)

@seanlaw
Copy link
Contributor

seanlaw commented Mar 15, 2022

Yes, as I've already mentioned these are my first pull requests ever. I started my major without any prior experience with computers (I didn't even had one and bought my first computer ever in the third semester 😆) as my specialization in "high school" was social work. And although my current major included some computer science modules (C# Programming, data structures and algorithms, very basic software engineering without git) the focus is still on Math and Physics, we are only required to do those modules to be able to do numerical simulations with tools like COMSOL Multiphysics and the like.

😮 🤯

I hope you realize that you are well on your way in your professional career! You've picked things up really, really quickly and, if this is your first time, I can only imagine how successful you will be when all of this becomes second nature to you. 😄 I've given you only a bit of remote guidance and you've been resourceful and proactive. It's been a pleasure working with you!

I also brush up my english skills along the way

I was actually thinking about this the other day. Your English is excellent and it is not lost on me that you are needing to push through the language barrier in addition to learning brand new software development skills as well as balancing your responsibilities as a student. Really amazing! I sincerely appreciate your dedication. I hope that this isn't a huge distraction/stress for you.

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Mar 15, 2022

Though I must admit, I can't resist the temptation to use the DeepL translator a little too often 😄
Also I seem to be quite a workaholic as well, but I think that it's necessary because otherwise I wouldn't be able to keep up with the other students and the workload in general.
Since state-run universities are completely free in Germany (even getting paid for college is commonplace, since eligibility almost doesn't depend on GPA, college debt isn't really a thing here) a good portion of students tend to put in less hours and I get the chance to catch up.

I'm jokingly referring to this since I was surprised about you answering my questions on weekends. 😆

@seanlaw
Copy link
Contributor

seanlaw commented Mar 15, 2022

I'm jokingly referring to this since I was surprised about you answering my questions on weekends.

I completely understand. STUMPY is a labor of love and 100% volunteer and I can't expect other people to care more about it than I do. Having said that, my unsolicited advice is "don't be like me" 😅 . As a former academic (scientific researcher), I have accrued some really bad work habits and you should not use me as a "good example". I am lucky that I love STUMPY and so I don't feel that this is "work". But you must find your balance and figure out what is important to you and what you are passionate about. I am grateful for people like you who wish to contribute as I cannot do all of this myself!

@seanlaw
Copy link
Contributor

seanlaw commented Mar 16, 2022

@SaVoAMP Not trying to rush you but will you let me know when it is appropriate for me to review this PR again?

@SaVoAMP
Copy link
Contributor Author

SaVoAMP commented Mar 16, 2022

Ohh, I'm sorry! I was just about to ask you the same question 😆
I've already tried to change what you suggested. It's in the last commit.

@seanlaw
Copy link
Contributor

seanlaw commented Mar 16, 2022

Great! Glad I asked. I will find some time to review it

@seanlaw seanlaw merged commit 5e4a493 into stumpy-dev:main Mar 16, 2022
@seanlaw
Copy link
Contributor

seanlaw commented Mar 16, 2022

@SaVoAMP Congratulations on another wonderful PR being merged. Thank you for this wonderful contribution!

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.

3 participants