-
Notifications
You must be signed in to change notification settings - Fork 10
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
Add SourceField
and subclasses, rework Source
#405
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #405 +/- ##
==========================================
+ Coverage 75.97% 76.54% +0.57%
==========================================
Files 64 65 +1
Lines 7820 7965 +145
==========================================
+ Hits 5941 6097 +156
+ Misses 1879 1868 -11 ☔ View full report in Codecov by Sentry. |
dad621f
to
428f959
Compare
SourceField
and subclasses, rework Source
This pickle functionality wasn't used anywhere Keep it around for now in case I was mistakten. To be replaces with a YAML-based solution in the future... Early raise... No more np.all
Fix broken cube test, cube stuff and plotting Minor stuff and more units, fix failing tests
428f959
to
613454d
Compare
Could you please summarize the idea behind these changes so it is recorded somewhere? I didn't follow the full discussion at Waidhofen, so I'll trust you on that. |
Added this information as a module docstring in |
Class diagram from the module docstring, because it doesn't yet render anywhere else: classDiagram
class SourceField{+field}
class SpectrumSourceField{+spectra}
class HDUSourceField{+wcs}
SourceField <|-- SpectrumSourceField
SourceField <|-- HDUSourceField
SpectrumSourceField <|-- TableSourceField
SpectrumSourceField <|-- ImageSourceField
HDUSourceField <|-- ImageSourceField
HDUSourceField <|-- CubeSourceField
|
ScopeSim_Templates is now broken:
Could you please either make this PR (even more) backwards compatible or otherwise create a sibling-PR for ScopeSim_Templates? |
Did you verify that everything in the IRDB functions with the new SourceField classes? |
I gathered from the Waidhofen meeting that we (later) want to support more parametric sources. E.g. an 'image' that might not have a pixel array associated with it. How would that fit in this scheme? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @teutoburg! ScopeSim keeps getting better! The decoupling between field/spectra/meta was pretty painful to work with (e.g. that [{}]
assert...), now it is much better and more extensible.
Please judge for yourself how to deal with my comments. There are many, but most of them are pretty minor, and some of the larger ones might be postponed.
scopesim/effects/ter_curves.py
Outdated
if isinstance(fld, CubeSourceField): | ||
fld.field = apply_throughput_to_cube(fld.field, thru) | ||
continue | ||
|
||
for isp, spec in fld.spectra.items(): | ||
fld.spectra[isp] = combine_two_spectra( | ||
spec, thru, "multiply", wave_min, wave_max) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this a bit risky, as we haven't checked whether fld
has a spectra
attribute. Maybe
if isinstance(fld, CubeSourceField): | |
fld.field = apply_throughput_to_cube(fld.field, thru) | |
continue | |
for isp, spec in fld.spectra.items(): | |
fld.spectra[isp] = combine_two_spectra( | |
spec, thru, "multiply", wave_min, wave_max) | |
if isinstance(fld, CubeSourceField): | |
fld.field = apply_throughput_to_cube(fld.field, thru) | |
elif isinstance(fld, SpectrumSourceField): | |
fld.spectra = { | |
isp: combine_two_spectra(spec, thru, "multiply", wave_min, wave_max) | |
for isp, spec in fld.spectra.items() | |
} |
def _write_stream(self, stream: TextIO) -> None: | ||
raise NotImplementedError("Subclasses should implement this.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment on Source.__repr__()
. I believe _write_stream()
is not necessary.
if (isinstance(spectra, Iterable) and | ||
not isinstance(spectra, np.ndarray)): | ||
if all(isinstance(spec, SourceSpectrum) for spec in spectra): | ||
yield from spectra |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really like this refactoring, because the code is more complex and the yield statements indicate that it works with generators but it does not, because the all()
would have used up everything. E.g.
from itertools import count, takewhile
numbers = takewhile(lambda x: x< 20, count(10))
def convert(spectra):
def get_list():
if all(isinstance(spec, int) for spec in spectra):
yield from spectra
return list(get_list())
list_of_numbers = convert(numbers)
print(f"{list_of_numbers=}")
will print
list_of_numbers=[]
I think it is better to simplify this by converting the input to a list and remove the yields.
|
||
if isinstance(wave_min, u.Quantity): | ||
wave_min = wave_min.to(u.Angstrom).value | ||
else: | ||
wave_min *= 1E4 | ||
|
||
if isinstance(wave_max, u.Quantity): | ||
wave_max = wave_max.to(u.Angstrom).value | ||
else: | ||
wave_max *= 1E4 | ||
# Note: Assuming um if given as float. | ||
wave_min = (wave_min << u.um << u.Angstrom).value | ||
wave_max = (wave_max << u.um << u.Angstrom).value | ||
# Note: There appear to be some float shenanigans going on here, but | ||
# rounding produces an error in the spectrum evaluation. Not sure what's | ||
# going on, maybe it's fine as-is. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably NOT fine as it is. The problem is:
>>> (1 << u.um << u.Angstrom).value
9999.999999999998
The 1E4
conversion was ugly, but correct.
It might be that in this particular case everything is fine, but I think we should not rely on that.
This incorrect conversion also causes all kinds of problems with synphot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've reconsidered. We should make this change.
This unit conversion could indeed go wrong. However, it already would go wrong if fractional wavelengths are used, e.g. wavemin=1333.3
. Our code should not depend on float values being integer.
This means that even if we would hardcode this 1E4 here (and similarly elsewhere), that the code will still be broken in general. So we should not use such hacks and instead just let astropy handle it, as you did.
Per extension, it should not cause any problems if a value of 1000.0 becomes 999.9999999999 or 1000.000000001:
- Any (synphot) code that computes overlaps should be resilient to such rounding errors and just ignore it.
- Any plotting/printing code should just print 1000.0. (At least for UX, not for debugging.)
Perhaps we should find some floating point numbers that you cannot faithfully multiply/divide by (e.g.) 1E4. Maybe one number that rounds up and one that rounds down. Then we should use those in the basic_instrument test package to detect any errors that such a conversion causes. Or maybe have some test irdb packages that uses weird units like wavelengths in parsec and such.
Oh I'm being stupid, I didn't notice AstarVienna/ScopeSim_Templates#91 and I was on a branch so |
Co-authored-by: Hugo Buddelmeijer <[email protected]>
Co-authored-by: Hugo Buddelmeijer <[email protected]>
Some code (mostly in ScopeSim_Templates) relies on single-field sources exposing their field meta as the Source's meta. If there's more than one field, the metas should always be accessed through the fields to avoid ambiguity. This (hacky) solution now seems to work and is certainly better than silently always returning just the first field's meta. Also removed the setter for meta. It's a dict, so if it needs to be updated, just do that. It should never be replaced (== overwritten!) anyway.
Some
minormajor refactoring in vague preparation of scopesim-targets...Main changes
SourceField
and subclasses: As discussed in the Waidhofen hackathon, this family of classes will now hold the source fields as part of theSource
class. Different subclasses exist for different kinds of fields (table, image, cube). This produced a bit of a rat-tail of changes elsewhere, but should ultimately move us forward.Source.spectra
(now just a collection of allfield.spectra
) adict
. This was a rather minimal change with limited need to adapt elsewhere, but is a lot more explicit on which spectra are referenced where, instead of hoping the the indices of the previouslist
don't get mixed up when changing stuff in the source.Other changes
Source
class.Further notes
Ignore all the force pushes, which resulted from rebasing...
This needs 3.10, so waiting for that...