Skip to content

Add MRT format writer to cds.py [GSoC21a] - #11897

Merged
hamogu merged 38 commits into
astropy:mainfrom
Suyog7130:final-cds-writer
Aug 30, 2021
Merged

hamogu merged 38 commits into
astropy:mainfrom
Suyog7130:final-cds-writer

Conversation

@Suyog7130

@Suyog7130 Suyog7130 commented Jun 27, 2021 •

Copy link
Copy Markdown
Contributor

Description

Discussion continued from #11835.

This Pull Request is part of the 2021 Google Summer of Code work I am doing together with mentors @aaryapatil and @hamogu.
It primarily aims to address issue #11257.

Checklist for package maintainer(s)

This checklist is meant to remind the package maintainer(s) who will
review this pull request of some common things to look for. This list
is not exhaustive.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see "When to rebase and squash commits".
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the Extra CI label.
  • Is a change log needed? If yes, did the change log check pass? If no, add the no-changelog-entry-needed label.
  • Is a milestone set? Milestone must be set but astropy-bot check might be missing; do not let the green checkmark fool you.
  • If the milestone set requires a backport to release branch(es), apply the appropriate backport-X.Y.x label(s) before merge.

these together write the CDS Data Table without the ReadMe header.
[skip ci]
this is done via `test_write_data` func in `test_cds.py`
[skip ci]
Comment thread astropy/io/ascii/cds.py Resolved
@bsipocz bsipocz added this to the v5.0 milestone Jun 29, 2021

@hamogu hamogu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I left a number of very specific comments. In some cases, I tried to type code, but I did not check that everything actually works like I've written it. Maybe you want to add tests first and then try those simplifications? The bottom line is that this code is still pretty complex and can probably be shortened by half. As I said on the call, we don't have to save lines of code (LOC) just to prove we can do it, but shorter code is often also more readable, because the reader does not have to jump up and down to find where functions are defined and what they do. That in turn makes is easier to review and to maintain.

There might be more - I have not gone through the string formatting part yet. I think much of the code that I think is too long comes from pycds, keep that in mind when reading that code in the future!

Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Outdated
"""
String formatter for a column containing Float values.
"""
if col.hasNull: # optimized (2x more speed)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is premature optimization. Columns are numpy arrays and col.max() will do. To account for columns that are fully masked you can do something like:

col.max = col.max()
if np.is_masked(col.max):
    col.max = None

[At least I think I understand the logic of this bit of code. I'm not sure. But that fact alone shows that it might be too complex.]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think the way this part of the code works is something as follows.

  • The max/min values have to be found in column containing Null values.
  • If the min/max values themselves are masked, then we want to assign None to them instead of the random masked value (or NaN).
  • So, the null values in the column are filled with a large negative number -999999 (a large positive number 999999). If all the values in the column are Null values, then there won't be any number greater (smaller) than this filled value, and thus, the col.max (col.min) value will be set as None.

I looked up this page for more info: https://docs.astropy.org/en/stable/table/masking.html

I am not sure of the way in which these steps above optimize the process, since checking Min/Max will anyway depend on how well numpy functions are optimized. Maybe the default Min/Max checking is time consuming for columns with a very large number of values, in the worst case scenario and the above steps ensure lesser time?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The numpy code itself is probably written in C (much on numpy internal are), so I'm very surprised if this Python is much faster. Maybe it is. The point is that we first want to get a code that working and that we understand. "I think this works something as follows" is not good enough :-) So, I suggest to use a very simple solutions (like my three lines instead of > dozen lines with two different code path) at this point. If there is an if..else we need to write tests for both cases etc. It's just not worth it unless we know that this part of the code is a bottle neck for speed.

@Suyog7130 Suyog7130 Jul 6, 2021 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay. I have already implemented that in _set_column_val_limits() function.
The optimization is probably good only for extremely large tables. The current implementation poses no problems for now.

Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Outdated

for i, col in enumerate(self.cols):
# check if column is MaskedColumn
if isinstance(col, MaskedColumn):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if isinstance(col, MaskedColumn):
col.hasNull = isinstance(col, MaskedColumn)

And then delete the three lines below this. The fact that this test is only one line long also means that we don't need the hasNull attribute at all. Instead, we can go through the code and replace every occurrence of
if col.hasNull with if isinstance(col, MaskedColumn). Not much longer, but save us an extra attribute.
[At least, that's true until we have cases with hasNull that are not instances of MaskedColumn. Not sure aobut mixin columns with e.g. coordinates. So maybe it's better to leave hasNull for now, but the check to set True or False can still be one line only.

@Suyog7130 Suyog7130 Jul 5, 2021 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay. I am saving col.has_null for the time being.

Suyog7130 added 5 commits July 3, 2021 16:50
including `test_write_ByteByByte_units`
[skip ci]
Also got the `cdsdict` argument passing working. The user can pass it using `ascii.write(Writer=ascii.Cds, cdsdict={})` or `table.write(format='cds', cdsdict={})`.
[skip ci]
and for ByteByByte description of columns with Null values
[skip ci]
@pep8speaks

pep8speaks commented Jul 5, 2021 •

Copy link
Copy Markdown

Hello @Suyog7130 👋! It looks like you've made some changes in your pull request, so I've checked the code again for style.

There are no PEP8 style issues with this pull request - thanks! 🎉

Comment last updated at 2021-08-30 08:32:11 UTC

@Suyog7130

Copy link
Copy Markdown
Contributor Author

Line 61:10: W291 trailing whitespace
Line 76:27: W291 trailing whitespace
Line 84:52: W291 trailing whitespace
Line 86:61: W291 trailing whitespace
Line 87:60: W291 trailing whitespace
Line 88:56: W291 trailing whitespace
Line 146:52: W291 trailing whitespace
Line 147:59: W291 trailing whitespace
Line 148:49: W291 trailing whitespace
Line 149:60: W291 trailing whitespace
Line 150:56: W291 trailing whitespace
Line 152:56: W291 trailing whitespace

Note that all these codestyle suggestions are for the lines containing the table string with is matched with the obtained output in the tests. A way not to get these messages would be to use a list of lines instead of the whole table represented as a single string.

@hamogu

hamogu commented Jul 5, 2021

Copy link
Copy Markdown
Member

Yeah, that's annoying. Unfortunately, we'll have to find some way around that, because it makes the code style check fail and we can't merge with failing tests. There are several ways to solve this: (1) what you suggest, (2) just remove the trailing white space and in the assert statement, compare something like this line.rstrip() = expected (3) tell flake8 that this one time it's OK. Add # noqa: W291 to the end of the line. Unfortunately, I don't know on top of my head where to add that since this is a multiple line string (and if you simply put # noqa: W291 in every line the Python will think that that is part of your string). Probably in the last line?

@aaryapatil aaryapatil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Left a few comments - quite a few of them are related to codestyle. I will look at the tests in more detail later since they are failing.

Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py
fmt = self.__splitFloatFormat(str(rec))

# if value is in Scientific notation
if fmt[4] is True:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you explain to me the logic behind this if-else? I wonder if this could be simplified further, but would be better to first understand what your motive here is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is essentially a counter-measure for calculating format and width of columns containing values both in Float (Decimal format) and the Scientific notation. Like in the column s of the following table.

names e d s i
HD81809 1E-7 22.25608 +2 67
HD103095 -31.6e5 +27.2500 -9E34 -30
  • First, by default, the Float column is designated as having the decimal notation, fformat = 'F'
  • Then we go through the column, record by record, to find the exact format and the width required for it.
  • If, upon splitting the Float string, it's found that the current value is in the Scientific notation, the column format designation is changed to 'E'.
  • If the previous value was in the Decimal format, the column format would have been 'F'. If yes, then the maxSize, maxPrec and maxDec variables are set to default as well. This is done so that the column formatting happens according the value in the Scientific notation, regardless of any other values in the Decimal format.
  • If the current value is not in Scientific notation and the column is marked with 'E' (which means one of the previous values was in the Scientific notation), this particular record is simply skipped, in order to give formatting precedence to the value in Scientific notation.

All this helps correctly writing the above table as,

HD81809  1e-07  22.25608   2e+00  67
HD103095 -3e+06 27.25000  -9e+34 -30

With the ByteByByte being,

 Bytes Format Units  Label     Explanations
--------------------------------------------------------------------------------
 1- 8   A8     ---    names    Description of names             
10-14   E5.1   ---    e       [-3160000.0/0.01] Description of e
16-23   F8.5   ---    d       [22.25/27.25] Description of d    
25-31   E7.1   ---    s       [-9e+34/2.0] Description of s     
33-35   I3     ---    i       [-30/67] Description of i       

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But we do know the format - we write the data ourselves. We get to decide if we output in scientific notation or not - the relevant Outputter of the class that does the writing will format the numbers. I wonder if we should make use of this knowledge instead of turning it to strings and then inspecting the string to find what function we used to make that substitution.

On the other hand, I also wonder if this is the best use of our time. Since this seems to work, maybe we should move on to other steps (e.g. coordinates) and come back to possible simplify this in the end.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This fails in this case:

t = Table()
t['b'] = [2.123456789, 2e20]
ascii.write(t, format='cds')

================================================================================
Byte-by-byte Description of file: table.dat
--------------------------------------------------------------------------------
 Bytes Format Units  Label     Explanations
--------------------------------------------------------------------------------
 1- 5  E5.1   ---    b       [2.12/2e+20] Description of b
--------------------------------------------------------------------------------
Notes:
--------------------------------------------------------------------------------
2e+00
2e+20

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note that this is the expected behaviour, so that code block is not failing strictly speaking.

If any column has atleast one value in exponential notation, the intent is to convert all other values of the column to this format. The number of decimal places in the integer part is governed by the number of digits after decimal of the original value given in exponential notation. Trailing zeros after decimal are discounted.
Thus your example above. t['b'] = [2.123456789, 2.00e20] would also output the table.

If it is required that the output table has a set number of digits after decimal for all column values, then the original value in exponential notation should have digits, without trailing zeroes, after the decimal in the integer part. So,

t['b'] = [2.123456789, 2.45e20]
ascii.write(t, format='cds')

================================================================================
Byte-by-byte Description of file: table.dat
--------------------------------------------------------------------------------
 Bytes Format Units  Label     Explanations
--------------------------------------------------------------------------------
 1- 8  E8.3   ---    b       [2.12/2.45e+20] Description of b
--------------------------------------------------------------------------------
Notes:
--------------------------------------------------------------------------------
2.12e+00
2.45e+20

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This would not be the expected behavior for most users. Silently dropping precision based on an undocumented / tricky rule like this will eventually lead to a bug report if this gets widely used. Consider the very different behavior of two apparently similar cases:

In [18]: t['b'] = [2.123456789, 2.45e15]
    ...: ascii.write(t, format='cds')
    ...: 
Title:
Authors:
Table:
================================================================================
Byte-by-byte Description of file: table.dat
--------------------------------------------------------------------------------
 Bytes Format Units  Label     Explanations
--------------------------------------------------------------------------------
 1-26  F26.9  ---    b       [2.12/2450000000000000.0] Description of b
--------------------------------------------------------------------------------
Notes:
--------------------------------------------------------------------------------
               2.123456789
2450000000000000.000000000

In [19]: t['b'] = [2.123456789, 2.45e16]
    ...: ascii.write(t, format='cds')
    ...: 
Title:
Authors:
Table:
================================================================================
Byte-by-byte Description of file: table.dat
--------------------------------------------------------------------------------
 Bytes Format Units  Label     Explanations
--------------------------------------------------------------------------------
 1- 8  E8.3   ---    b       [2.12/2.45e+16] Description of b
--------------------------------------------------------------------------------
Notes:
--------------------------------------------------------------------------------
2.12e+00
2.45e+16

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah, I see. Thank you for pointing out this discrepancy in the behaviour.
This can probably be addressed by opting for highest precision.
Would be good to address it separately. So, needs an issue.

Comment thread astropy/io/ascii/cds.py
else:
# string formatter
if col.hasNull:
mcol = col

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are you trying to create a copy here? You are using the mask-filled columnmcol to create a coltmp Column later. mcol = col could cause issues if you want a true copy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nope, copy of the column is not needed. The temporary coltmp is only to fill empty string in place of Null values for String columns, so that col.dtype.str returns proper column dtype.

Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
@Suyog7130

Copy link
Copy Markdown
Contributor Author

Two of the CI checks that fail are related to running Tests in Python 3.7
Tests do run successfully for Python 3.8, which is the version I am working with in my Virtual Environment.
I don't know what's amiss, maybe some syntax change?

@Suyog7130

Copy link
Copy Markdown
Contributor Author

(2) just remove the trailing white space and in the assert statement, compare something like this line.rstrip() = expected

Since our aim to check the column widths of the written table as well, I think stripping the whitespace won't be a good option.

@hamogu

hamogu commented Jul 6, 2021

Copy link
Copy Markdown
Member

Don't worry about the CI tests at the moment. The failures are not related to your PR it's something with the install and testing packages. Sometimes that's intermittent (e.g. the pypi server is down for a short time and the dependencies can't be installed), or changes in the test infrastructure (e.g. a new pytest version was released that has a bug in it). We'll have ot get the tsts passing before merging, but we don't have to worry about it right now.

@Suyog7130

Copy link
Copy Markdown
Contributor Author

Yeah, that's annoying. Unfortunately, we'll have to find some way around that, because it makes the code style check fail and we can't merge with failing tests. There are several ways to solve this: (1) what you suggest, (2) just remove the trailing white space and in the assert statement, compare something like this line.rstrip() = expected (3) tell flake8 that this one time it's OK. Add # noqa: W291 to the end of the line. Unfortunately, I don't know on top of my head where to add that since this is a multiple line string (and if you simply put # noqa: W291 in every line the Python will think that that is part of your string). Probably in the last line?

This appears to be an open issue on Pycodestyle (PyCQA/pycodestyle#376).
I can though use # flake8: noqa: E291 to ignore the E291 codestyle check for the entire file. This works.
See: https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html#ignoring-entire-files

Comment thread astropy/io/ascii/tests/test_cds.py Resolved
@taldcroft

Copy link
Copy Markdown
Member

I'm not following any details, but I'd like make sure it is understood that basic code style requirements need to be met prior to merging. In particular:

  • No camelCase anywhere. For example tableDescription should be table_description.
  • Module level variables as SCREAMING_SNAKE_CASE. E.g. BYTE_BY_BYTE_TEMPLATE not ByteByByteTemplate.
  • Regular variables, functions, methods are lower_case_underscore_separated, never camelCase or CapsCase.
  • Private methods start with one underscore not two, e.g. def _split_float_format instead of def __splitFloatFormat.
  • No inline blocks like if sign: col.meta.size += 1
  • Avoid one-line variable names, but especially l that looks like 1.

It's not too early to also be writing proper doc strings for every function since this makes the review easier for @aaryapatil and @hamogu.

Thanks in advance for maintaining the code style (and adhering to modern standards that improve readability) in io.ascii!

@dhomeier dhomeier linked an issue Jul 16, 2021 that may be closed by this pull request
@Suyog7130

Suyog7130 commented Aug 21, 2021 •

Copy link
Copy Markdown
Contributor Author

Apparently I misinterpreted the origin of the FutureWarnings assuming they stemmed from some internal inconsistency in numpy. Strange that they disappeared with still newer numpy versions, but maybe that means that the new behaviour is already silently followed.

They also pass when we have ignore:Conversion of the second argument:FutureWarning

What happens if you explicitly specify the new interpretation, i.e. np.dtype(int).type instead of int and corresponding for object?

This solved the problem.

@Suyog7130

Copy link
Copy Markdown
Contributor Author

Yayyyyyyy....!!!!!!
All tests pass now! 😁
@aaryapatil, @hamogu, I think this PR is ready for final approval now.

@dhomeier

Copy link
Copy Markdown
Contributor

Excellent! The ignore:...:numpy should no longer be needed then.

@taldcroft taldcroft left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some more comments. The biggest one is about always using .info attributes so the code works with mixin columns like Quantity.

Comment thread docs/io/ascii/write.rst Resolved
Comment thread astropy/io/ascii/cds.py
dtype=[str]*5)

# Iterate over the columns to write Byte-By-Byte rows.
for i, col in enumerate(self.cols):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This large block in the loop should be factored out to a function.

Don't use a variable name like i over such a long context. If someone reading this code sees an i 80 lines later it's hard to know where it was defined and even harder to search for i.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think i is fine for a counting variable - it's what most people would expect when they see i or j. However, I don't feel strongly about this and would also be OK with a more explicit name like col_num.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Using i is fine for a loop that can be easily seen in one screenful, while in this case the loop blocks are quite long.

While I'm looking at the code, the giant block in the write method could be done without any index variable by making a list new_cols = [] at the top, then looping over columns and processing each column. For the special cases then some transformation of the column happens (possibly turning one column into two or more), but for the rest you just append the original column. Then at the end self.cols = new_cols. This way:

  • You don't need an index
  • You preserve the original column order. The current code always puts any coordinate columns at the end of the column list.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yup, I have also thought of doing this.
Since, the output of the tests will change because of different column sequence, it would be better to address this separately. So, needs an issue.

Comment thread astropy/io/ascii/cds.py Resolved
Comment thread docs/io/ascii/write.rst Resolved
Comment thread docs/io/ascii/write.rst Resolved
Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py
# attribute. In those cases, a ``Unknown`` column label is put, indicating that
# such columns can be better formatted with some manipulation before calling
# the CDS/MRT writer.
if col.name is None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In general, you should never be accessing attributes like col.name or col.description. Those only work for Column and MaskedColumn, but modern "mixin-safe" code needs to always get/set the info attributes like col.info.name or col.info.description. Right now the CDS writer is failing for a simple QTable because of this:

qt = QTable()
qt['a'] = [1,2] * u.m
ascii.write(qt, format='cds')
Title:
Authors:
Table:
================================================================================
Byte-by-byte Description of file: table.dat
--------------------------------------------------------------------------------
 Bytes Format Units  Label     Explanations
--------------------------------------------------------------------------------
1-3  F3.1   m      Unknown [1.0/2.0] Description of Unknown
--------------------------------------------------------------------------------
Notes:
--------------------------------------------------------------------------------
1.0
2.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Points like this are exactly why we asked you, @taldcroft, to have a look! I'm not using (or writing code for) QTables.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When I check col.name and col.info.name for the qt above, both of them are set to None. Whereas, qt.info() gives out a float64 m Quantity.

This may possibly be because the Basereader.write func in core.py takes the table columns and saves them as a list in header.cols using table.columns.values().

For a Table, t.columns.values() is odict_values([<Column name='b' dtype='float64' length=2> 2.123456789 2.45e+20])
For a QTable, qt.columns.values() is odict_values([<Quantity [1., 2.] m>])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We had not foreseen that QTables may not work with the code. Thank you for your valuable comment. :)
Looks like this may need some work. So, needs an issue.

Comment thread astropy/io/ascii/cds.py
if col.description is not None:
description = col.description
else:
description = "Description of " + col.name

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the default description if not provided should just be blank, or perhaps --- or the column name if there must be something there. The Description of ... is just adding noise but not information.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This strongly encourages users to put in a description for every column though, which I think is the intend of the MRT format. MRT tables will (in the use case that we are working towards, although I'm use users will find a way to abuse that) always be edited by journal staff to put in the proper reference information of the published article, so an pretty obvious "PUT SOEMTHING HERE" can be useful. That said, --- fills the same role and is a little less obtrusive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since, the output of the tests will change when this is done, it would be better to address this separately. So, needs an issue.

Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py Resolved
@taldcroft

Copy link
Copy Markdown
Member

Now that this is getting close to being merged, the flake8 issues can be addressed. @Suyog7130 - if you are out of time then we can leave this as an open issue for cleanup and I'll be happy enough to do it post-merge.

(astropy) ➜  astropy git:(final-cds-writer) flake8 astropy/io/ascii/cds.py --ignore=W503,W504,E402,E731,E721,F541 --max-line-length=100
astropy/io/ascii/cds.py:31:1: F401 'textwrap.fill' imported but unused
astropy/io/ascii/cds.py:41:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:42:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:43:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:44:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:45:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:48:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:49:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:50:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:51:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:52:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:53:1: E128 continuation line under-indented for visual indent
astropy/io/ascii/cds.py:400:32: E226 missing whitespace around arithmetic operator
astropy/io/ascii/cds.py:479:17: E129 visually indented line with same indent as next logical line
astropy/io/ascii/cds.py:509:29: E127 continuation line over-indented for visual indent
astropy/io/ascii/cds.py:522:40: E226 missing whitespace around arithmetic operator
astropy/io/ascii/cds.py:522:61: E226 missing whitespace around arithmetic operator
astropy/io/ascii/cds.py:523:46: E226 missing whitespace around arithmetic operator
astropy/io/ascii/cds.py:523:68: E226 missing whitespace around arithmetic operator
astropy/io/ascii/cds.py:532:34: E226 missing whitespace around arithmetic operator
astropy/io/ascii/cds.py:597:25: E125 continuation line with same indent as next logical line
astropy/io/ascii/cds.py:598:29: E117 over-indented (comment)
astropy/io/ascii/cds.py:599:29: E117 over-indented
astropy/io/ascii/cds.py:630:53: E251 unexpected spaces around keyword / parameter equals
astropy/io/ascii/cds.py:630:55: E251 unexpected spaces around keyword / parameter equals
astropy/io/ascii/cds.py:634:53: E251 unexpected spaces around keyword / parameter equals
astropy/io/ascii/cds.py:634:55: E251 unexpected spaces around keyword / parameter equals
astropy/io/ascii/cds.py:645:17: E265 block comment should start with '# '
astropy/io/ascii/cds.py:681:37: E128 continuation line under-indented for visual indent

Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py
Comment on lines +229 to +237
regfloat = re.compile(r"""(?P<sign> [+-]*)
(?P<ent> [^eE.]+)
(?P<deciPt> [.]*)
(?P<decimals> [0-9]*)
(?P<exp> [eE]*-*)[0-9]*""",
re.VERBOSE)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This expression for floats is not correct. E.g. try it with .2 or ++-+-+5 or +hello5. I think the answer https://stackoverflow.com/questions/385558/extract-float-double-value is correct.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Though I guess to be fair this expression might work for parsing the bits of a string that is known to have been generated by "normal" Python formatting of a float value. But do remember that the format specifier for a column can be quite general, e.g. col['a'].info.format = '++{:.2f}'. The current expression would accept values formatted that way, whereas the right behavior would probably be an exception since a float value is being formatted in a non-float output.

So you might test for a correctly formatted float by doing float(value) in a try/except.

@Suyog7130 Suyog7130 Aug 30, 2021 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This would need some work. So, needs an issue.

Comment thread astropy/io/ascii/cds.py
fmt = self.__splitFloatFormat(str(rec))

# if value is in Scientific notation
if fmt[4] is True:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This fails in this case:

t = Table()
t['b'] = [2.123456789, 2e20]
ascii.write(t, format='cds')

================================================================================
Byte-by-byte Description of file: table.dat
--------------------------------------------------------------------------------
 Bytes Format Units  Label     Explanations
--------------------------------------------------------------------------------
 1- 5  E5.1   ---    b       [2.12/2e+20] Description of b
--------------------------------------------------------------------------------
Notes:
--------------------------------------------------------------------------------
2e+00
2e+20

Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py

for i, col in enumerate(self.cols):
col.width = max([len(vals[i]) for vals in vals_list])
if self.start_line is not None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm confused by this. start_line is not a keyword arg for ascii.write, so I'm wondering what this is doing here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The data part of the CDS table can itself have header lines, esp. when the data is written in a separate file. The title of the Byte-By-Byte for such tables then says that rows till start_line are header lines.
Because such tables are not ubiquitous, adding the start_line keyword to ascii.write for allowing this feature prob skipped my mind.
Should this be added? Else I can remove that if statement.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we leave it in for now.

Comment thread astropy/io/ascii/cds.py Resolved
Comment thread astropy/io/ascii/cds.py
bbblines = StringIO()
bbb.write(bbblines, format='ascii.fixed_width_no_header',
delimiter=' ', bookend=False, delimiter_pad=None,
formats={'Format': '<6s',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The Format and Units can both be longer than 6 characters, so I'm confused about the hardwired 6 here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, they can be. 6 is hardcoded to have a minimum column width when Format and Units have less than 6 characters. This also helps in the Byte-By-Byte mini-table header row alignment.

@Suyog7130

Copy link
Copy Markdown
Contributor Author

Hi @taldcroft,
Thank you very much for your valuable comments.
Hm... Today happens to be the last day to submit student report for GSoC. I had done that a couple of days back. From tomorrow till 30th Aug, it will be the Final Evaluations time.
Since you agree to this, I also think it would be good to merge this now and open an issue to improve upon it. Or perhaps, I can add them in one of the follow PRs that are currently active.

@taldcroft

Copy link
Copy Markdown
Member

@Suyog7130 - I'm OK with merging if you transfer any unresolved issues into one or more new astropy io.ascii issues so we can track them. Good job on this complicated project and thanks for the work!

cc: @hamogu @aaryapatil

@aaryapatil

Copy link
Copy Markdown
Member

Glad to see that all the tests are now passing -- great job @Suyog7130!

Thanks @taldcroft for the comments. I agree that either the issues should be fixed here or any unresolved ones should be tracked. Depending on Suyog's time constraints, we can work around that -- I am happy to help fix unresolved issues post-merge too.

@hamogu

hamogu commented Aug 23, 2021

Copy link
Copy Markdown
Member

@Suyog7130 Nothing is ever fully finished ;-) Great work so far.
I would suggest that you go through the comments that @taldcroft made and decide which ones to address here (in some cases, it's really simple and faster than opening a new issue, e.g. just remove the work "currently" from the docs). In other cases, a new issue is a good solution. If you have time, please open those issues, if not, please just leave a comment to @taldcroft comment that says "needs new issue" and I will open a new issue with description and links to the commits and code. That code change can than be worked into any of the following [GSoc21b/c/d/...] PRs or into a new one.

Once we have resolved @taldcroft comments (either by addressing them or by opening a new issue), I will merge this PR.

taldcroft corrections in docs
remove `ignore...numpy` line from `setup.cfg`
remove first part of int checking
put `i_bbb = lines.index() for some tests
docstring change
change `col.meta.unit` to `col_unit`
change `col.meta.size` to `col.formatted_width`
@Suyog7130

Copy link
Copy Markdown
Contributor Author

I have addressed most of the minor issues, principally the ones not requiring any change in the test outputs, pointed out by @taldcroft, including the flake8 warnings.
For a few others that need more time and rearrange/change the code to a large degree, I have commented needs an issue in the conversation. If @hamogu would be generous to open a linked issue for these, I can work on correcting them in one of the later PRs or a new one.
Rest, I suppose, this PR can be merged.
Thank you very much for all your kind help.

@hamogu hamogu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

All changes have been thoroughly discussed and the main functionality is working. We have a follow up issue (#12130) for any improvements that are left from the comments here.

@hamogu
hamogu merged commit 6ef2ea5 into astropy:main Aug 30, 2021
@hamogu

hamogu commented Aug 30, 2021

Copy link
Copy Markdown
Member

Thanks to @Suyog7130 for great work in the GSOC 2021! This really is a big step forward for writing tables. I'm looking forward t get the other, smaller PRs in soon; let us know if you need any help!

@taldcroft

Copy link
Copy Markdown
Member

🎉

@pllim

pllim commented Aug 30, 2021

Copy link
Copy Markdown
Member

I think +1,257 −12 warrants a what's new entry. 😸

Congrats! 👏

@aaryapatil

aaryapatil commented Sep 2, 2021 •

Copy link
Copy Markdown
Member

Late to the party, but congratulations on merging and successfully completing GSoC 2021 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow writing of Table to cds format

8 participants