starhash 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- starhash/__init__.py +1 -0
- starhash/core.py +194 -0
- starhash/data/__init__.py +0 -0
- starhash/data/astronames_boost.txt +226 -0
- starhash/data/combolist.txt +7971 -0
- starhash/data/eff_largewordlist.txt +7777 -0
- starhash-1.0.0.dist-info/METADATA +109 -0
- starhash-1.0.0.dist-info/RECORD +11 -0
- starhash-1.0.0.dist-info/WHEEL +4 -0
- starhash-1.0.0.dist-info/entry_points.txt +2 -0
- starhash-1.0.0.dist-info/licenses/LICENSE +14 -0
starhash/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
starhash/core.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from importlib.resources import files
|
|
3
|
+
from importlib.resources.abc import Traversable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import rich_click as click
|
|
7
|
+
from click import Context, FloatRange
|
|
8
|
+
from ff3 import FF3Cipher
|
|
9
|
+
from healpy import ang2pix, nside2npix, nside2resol, pix2ang
|
|
10
|
+
from rich.logging import RichHandler
|
|
11
|
+
|
|
12
|
+
from starhash import __version__
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Reference implementation details
|
|
17
|
+
STARHASH_KEY = b"starhash!"
|
|
18
|
+
STARHASH_TWEAK = b"opensource"
|
|
19
|
+
HEALPIX_NSIDE = 65536
|
|
20
|
+
WORD_SEPARATOR = "-"
|
|
21
|
+
|
|
22
|
+
# Paths to bundled wordlists
|
|
23
|
+
EFF_LARGEWORDLIST_PATH = files("starhash.data").joinpath("eff_largewordlist.txt")
|
|
24
|
+
ASTROWORDLIST_PATH = files("starhash.data").joinpath("astronames_boost.txt")
|
|
25
|
+
COMBOLIST_PATH = files("starhash.data").joinpath("combolist.txt")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def setup_logger(level: int | str = logging.DEBUG) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Add a rich-formatted logger for development purposes with color output.
|
|
31
|
+
"""
|
|
32
|
+
logger.setLevel(level=level)
|
|
33
|
+
|
|
34
|
+
handler = RichHandler(
|
|
35
|
+
rich_tracebacks=True,
|
|
36
|
+
tracebacks_show_locals=True,
|
|
37
|
+
markup=True,
|
|
38
|
+
show_time=True,
|
|
39
|
+
show_level=True,
|
|
40
|
+
show_path=True,
|
|
41
|
+
)
|
|
42
|
+
handler.setLevel(level=level)
|
|
43
|
+
|
|
44
|
+
formatter = logging.Formatter(fmt="%(message)s", datefmt="[%Y-%m-%d %H:%M:%S]")
|
|
45
|
+
handler.setFormatter(formatter)
|
|
46
|
+
|
|
47
|
+
logger.addHandler(handler)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class StarHash:
|
|
51
|
+
"""Class to generate human-readable names from astronomical coordinates."""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
key: bytes = STARHASH_KEY,
|
|
56
|
+
tweak: bytes = STARHASH_TWEAK,
|
|
57
|
+
healpix_nside: int = HEALPIX_NSIDE,
|
|
58
|
+
wordlist_path: Path | Traversable = COMBOLIST_PATH,
|
|
59
|
+
k_words: int = 3,
|
|
60
|
+
init_logger: bool = False,
|
|
61
|
+
) -> None:
|
|
62
|
+
self.word_separator = WORD_SEPARATOR
|
|
63
|
+
self.wordlist: list[str] = []
|
|
64
|
+
self.k_words = k_words
|
|
65
|
+
self.wordlist_path = wordlist_path
|
|
66
|
+
self.num_words: int = 0
|
|
67
|
+
self.healpix_nside = healpix_nside
|
|
68
|
+
self.healpix_resol = nside2resol(self.healpix_nside, arcmin=True)
|
|
69
|
+
self.npix = nside2npix(self.healpix_nside)
|
|
70
|
+
|
|
71
|
+
# Set size of padding appropriate to max healpix idx
|
|
72
|
+
self.padding_length = len(str(self.npix))
|
|
73
|
+
|
|
74
|
+
if init_logger:
|
|
75
|
+
setup_logger(logging.DEBUG)
|
|
76
|
+
|
|
77
|
+
self.coverage = 0
|
|
78
|
+
logger.debug(
|
|
79
|
+
"healpix grid properties: %i pixels with size %.1f arcsec",
|
|
80
|
+
self.npix,
|
|
81
|
+
self.healpix_resol * 60,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Pad key to correct length
|
|
85
|
+
self.key = key.ljust(32, b"\x00").hex()
|
|
86
|
+
|
|
87
|
+
# Tweak must be 8 bytes long
|
|
88
|
+
self.tweak = tweak[:8].hex()
|
|
89
|
+
|
|
90
|
+
self.cipher = FF3Cipher(key=self.key, tweak=self.tweak)
|
|
91
|
+
self.load_wordlist()
|
|
92
|
+
|
|
93
|
+
def load_wordlist(self) -> None:
|
|
94
|
+
"""Load the word list specified in the class."""
|
|
95
|
+
with self.wordlist_path.open() as f:
|
|
96
|
+
self.wordlist = f.read().splitlines()
|
|
97
|
+
self.num_words = len(self.wordlist)
|
|
98
|
+
logger.debug("%s unique words in word list", self.num_words)
|
|
99
|
+
self.coverage = pow(self.num_words, self.k_words) / self.npix
|
|
100
|
+
logger.debug("Grid coverage factor: %.3f x", self.coverage)
|
|
101
|
+
|
|
102
|
+
if self.coverage < 1:
|
|
103
|
+
raise IncompleteHEALPIXCoverageError
|
|
104
|
+
|
|
105
|
+
def coordinate_to_words(self, ra: float, dec: float) -> str:
|
|
106
|
+
idx = ang2pix(self.healpix_nside, ra, dec, lonlat=True)
|
|
107
|
+
stridx = str(idx).zfill(self.padding_length)
|
|
108
|
+
|
|
109
|
+
idx_encrypted = int(self.cipher.encrypt(stridx))
|
|
110
|
+
|
|
111
|
+
# Repeated modulo division recovers the indices
|
|
112
|
+
words: list[str] = []
|
|
113
|
+
temp = idx_encrypted
|
|
114
|
+
for _ in range(self.k_words):
|
|
115
|
+
words.append(self.wordlist[temp % self.num_words])
|
|
116
|
+
|
|
117
|
+
temp //= self.num_words
|
|
118
|
+
|
|
119
|
+
return self.word_separator.join(words)
|
|
120
|
+
|
|
121
|
+
def words_to_coordinate(self, input_word_str: str) -> tuple[float, float]:
|
|
122
|
+
indices = [self.wordlist.index(w) for w in input_word_str.split(self.word_separator)]
|
|
123
|
+
|
|
124
|
+
# Undo the modulo procedure above to recover the original index
|
|
125
|
+
idx_encrypted = sum(word_idx * (self.num_words**i) for i, word_idx in enumerate(indices))
|
|
126
|
+
|
|
127
|
+
stridx_encrypted = str(idx_encrypted).zfill(self.padding_length)
|
|
128
|
+
stridx_decrypted = self.cipher.decrypt(stridx_encrypted)
|
|
129
|
+
idx = int(stridx_decrypted)
|
|
130
|
+
|
|
131
|
+
if idx > self.npix:
|
|
132
|
+
raise InvalidHEALPIXIndexError
|
|
133
|
+
|
|
134
|
+
ra, dec = pix2ang(self.healpix_nside, idx, lonlat=True)
|
|
135
|
+
|
|
136
|
+
return ra, dec
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def collate_wordlist() -> list[str]:
|
|
140
|
+
wordlist = []
|
|
141
|
+
with EFF_LARGEWORDLIST_PATH.open() as f:
|
|
142
|
+
logger.debug("Loading EFF wordlist")
|
|
143
|
+
|
|
144
|
+
for line in f.read().splitlines():
|
|
145
|
+
if "#" not in line:
|
|
146
|
+
try:
|
|
147
|
+
wordlist.append(line.split("\t")[1].replace(" ", "").replace("-", " "))
|
|
148
|
+
except IndexError:
|
|
149
|
+
logger.exception(line)
|
|
150
|
+
continue
|
|
151
|
+
with ASTROWORDLIST_PATH.open() as f:
|
|
152
|
+
logger.debug("Loading astronomical wordlist")
|
|
153
|
+
wordlist.extend(f.read().splitlines())
|
|
154
|
+
|
|
155
|
+
return sorted(set(wordlist))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class IncompleteHEALPIXCoverageError(Exception):
|
|
159
|
+
"""The chosen StarHash parameters do not fully cover the chosen HEALPIX grid."""
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class InvalidHEALPIXIndexError(Exception):
|
|
163
|
+
"""The given coordinates map to an invalid HEALPIX index."""
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@click.group(context_settings={"show_default": True})
|
|
167
|
+
@click.version_option(__version__, prog_name="starhash-cli")
|
|
168
|
+
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging output")
|
|
169
|
+
@click.pass_context
|
|
170
|
+
def cli(ctx: Context, verbose: bool) -> None:
|
|
171
|
+
ctx.obj = StarHash(init_logger=verbose)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@cli.command()
|
|
175
|
+
@click.option("--ra", "-r", type=FloatRange(min=0, max=360), required=True)
|
|
176
|
+
@click.option("--dec", "-d", type=FloatRange(min=-90, max=90), required=True)
|
|
177
|
+
@click.pass_obj
|
|
178
|
+
def get_name_from_coord(sh: StarHash, ra: float, dec: float) -> None:
|
|
179
|
+
"""Get name corresponding to ra, dec."""
|
|
180
|
+
name = sh.coordinate_to_words(ra, dec)
|
|
181
|
+
click.echo(name)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@cli.command()
|
|
185
|
+
@click.argument("name", type=str)
|
|
186
|
+
@click.pass_obj
|
|
187
|
+
def get_coord_from_name(sh: StarHash, name: str) -> None:
|
|
188
|
+
"""Get ra and dec corresponding to name."""
|
|
189
|
+
ra, dec = sh.words_to_coordinate(name)
|
|
190
|
+
click.echo(f"{ra} {dec}")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
cli()
|
|
File without changes
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
paynegaposchkin
|
|
2
|
+
swannleavitt
|
|
3
|
+
cannon
|
|
4
|
+
fleming
|
|
5
|
+
burbidge
|
|
6
|
+
faber
|
|
7
|
+
rubin
|
|
8
|
+
bellburnell
|
|
9
|
+
herschel
|
|
10
|
+
messier
|
|
11
|
+
galileo
|
|
12
|
+
kepler
|
|
13
|
+
copernicus
|
|
14
|
+
brahe
|
|
15
|
+
newton
|
|
16
|
+
halley
|
|
17
|
+
hale
|
|
18
|
+
shapley
|
|
19
|
+
baade
|
|
20
|
+
sagan
|
|
21
|
+
hoyle
|
|
22
|
+
hawking
|
|
23
|
+
chandrasekhar
|
|
24
|
+
eddington
|
|
25
|
+
bethe
|
|
26
|
+
gamow
|
|
27
|
+
hubble
|
|
28
|
+
zwicky
|
|
29
|
+
penrose
|
|
30
|
+
flare
|
|
31
|
+
burst
|
|
32
|
+
grb
|
|
33
|
+
tidal
|
|
34
|
+
disruption
|
|
35
|
+
accretion
|
|
36
|
+
disk
|
|
37
|
+
jet
|
|
38
|
+
outflow
|
|
39
|
+
wind
|
|
40
|
+
shock
|
|
41
|
+
wave
|
|
42
|
+
pulse
|
|
43
|
+
period
|
|
44
|
+
phase
|
|
45
|
+
cycle
|
|
46
|
+
precession
|
|
47
|
+
nutation
|
|
48
|
+
libration
|
|
49
|
+
moon
|
|
50
|
+
sun
|
|
51
|
+
star
|
|
52
|
+
planet
|
|
53
|
+
asteroid
|
|
54
|
+
comet
|
|
55
|
+
meteor
|
|
56
|
+
meteorite
|
|
57
|
+
bolide
|
|
58
|
+
pulsar
|
|
59
|
+
quasar
|
|
60
|
+
blazar
|
|
61
|
+
magnetar
|
|
62
|
+
neutron
|
|
63
|
+
mercury
|
|
64
|
+
venus
|
|
65
|
+
earth
|
|
66
|
+
mars
|
|
67
|
+
jupiter
|
|
68
|
+
saturn
|
|
69
|
+
uranus
|
|
70
|
+
neptune
|
|
71
|
+
pluto
|
|
72
|
+
ceres
|
|
73
|
+
vesta
|
|
74
|
+
pallas
|
|
75
|
+
juno
|
|
76
|
+
haumea
|
|
77
|
+
makemake
|
|
78
|
+
eris
|
|
79
|
+
sedna
|
|
80
|
+
orcus
|
|
81
|
+
quaoar
|
|
82
|
+
titan
|
|
83
|
+
europa
|
|
84
|
+
io
|
|
85
|
+
ganymede
|
|
86
|
+
callisto
|
|
87
|
+
enceladus
|
|
88
|
+
triton
|
|
89
|
+
charon
|
|
90
|
+
phobos
|
|
91
|
+
deimos
|
|
92
|
+
oberon
|
|
93
|
+
miranda
|
|
94
|
+
ariel
|
|
95
|
+
umbriel
|
|
96
|
+
titania
|
|
97
|
+
rhea
|
|
98
|
+
iapetus
|
|
99
|
+
dione
|
|
100
|
+
tethys
|
|
101
|
+
goto
|
|
102
|
+
zwicky
|
|
103
|
+
hubble
|
|
104
|
+
rubin
|
|
105
|
+
webb
|
|
106
|
+
wise
|
|
107
|
+
gaia
|
|
108
|
+
tess
|
|
109
|
+
kepler
|
|
110
|
+
spitzer
|
|
111
|
+
cheops
|
|
112
|
+
herschel
|
|
113
|
+
planck
|
|
114
|
+
chandra
|
|
115
|
+
fermi
|
|
116
|
+
swift
|
|
117
|
+
integral
|
|
118
|
+
rosat
|
|
119
|
+
nustar
|
|
120
|
+
atlas
|
|
121
|
+
panstarrs
|
|
122
|
+
asassn
|
|
123
|
+
ogle
|
|
124
|
+
macho
|
|
125
|
+
decam
|
|
126
|
+
lsst
|
|
127
|
+
subaru
|
|
128
|
+
keck
|
|
129
|
+
gemini
|
|
130
|
+
magellan
|
|
131
|
+
vlt
|
|
132
|
+
lamost
|
|
133
|
+
orion
|
|
134
|
+
ursa
|
|
135
|
+
draco
|
|
136
|
+
cygnus
|
|
137
|
+
aquila
|
|
138
|
+
lyra
|
|
139
|
+
cassiopeia
|
|
140
|
+
andromeda
|
|
141
|
+
perseus
|
|
142
|
+
cepheus
|
|
143
|
+
hercules
|
|
144
|
+
bootes
|
|
145
|
+
virgo
|
|
146
|
+
leo
|
|
147
|
+
gemini
|
|
148
|
+
cancer
|
|
149
|
+
taurus
|
|
150
|
+
aries
|
|
151
|
+
scorpius
|
|
152
|
+
sagittarius
|
|
153
|
+
capricornus
|
|
154
|
+
aquarius
|
|
155
|
+
pisces
|
|
156
|
+
libra
|
|
157
|
+
ophiuchus
|
|
158
|
+
serpens
|
|
159
|
+
corona
|
|
160
|
+
pegasus
|
|
161
|
+
auriga
|
|
162
|
+
canis
|
|
163
|
+
major
|
|
164
|
+
minor
|
|
165
|
+
lupus
|
|
166
|
+
centaurus
|
|
167
|
+
crux
|
|
168
|
+
carina
|
|
169
|
+
vela
|
|
170
|
+
puppis
|
|
171
|
+
hydra
|
|
172
|
+
crater
|
|
173
|
+
corvus
|
|
174
|
+
columba
|
|
175
|
+
lepus
|
|
176
|
+
eridanus
|
|
177
|
+
phoenix
|
|
178
|
+
tucana
|
|
179
|
+
grus
|
|
180
|
+
pavo
|
|
181
|
+
indus
|
|
182
|
+
galaxy
|
|
183
|
+
spiral
|
|
184
|
+
elliptical
|
|
185
|
+
irregular
|
|
186
|
+
lenticular
|
|
187
|
+
barred
|
|
188
|
+
andromeda
|
|
189
|
+
milky
|
|
190
|
+
way
|
|
191
|
+
magellanic
|
|
192
|
+
cloud
|
|
193
|
+
whirlpool
|
|
194
|
+
sombrero
|
|
195
|
+
pinwheel
|
|
196
|
+
triangulum
|
|
197
|
+
centaurus
|
|
198
|
+
virgo
|
|
199
|
+
fornax
|
|
200
|
+
sculptor
|
|
201
|
+
pegasus
|
|
202
|
+
exoplanet
|
|
203
|
+
szyzgy
|
|
204
|
+
culmination
|
|
205
|
+
opposition
|
|
206
|
+
ouamuamua
|
|
207
|
+
borisov
|
|
208
|
+
desi
|
|
209
|
+
sloan
|
|
210
|
+
astrolabe
|
|
211
|
+
sextant
|
|
212
|
+
quadrant
|
|
213
|
+
alien
|
|
214
|
+
transit
|
|
215
|
+
conjunction
|
|
216
|
+
parallax
|
|
217
|
+
hypatia
|
|
218
|
+
loeb
|
|
219
|
+
aberration
|
|
220
|
+
albedo
|
|
221
|
+
altitude
|
|
222
|
+
annulus
|
|
223
|
+
apex
|
|
224
|
+
apogee
|
|
225
|
+
arcsecond
|
|
226
|
+
asterism
|