Initial Release
. * . * . .
* . ____ _ ___ _ . *
. / ___| / \ |_ _| / \ * .
* . | | _ / _ \ | | / _ \ . *
. | |_| | / ___ \ | | / ___ \ . *
* . \____| /_/ \_\ |___| /_/ \_\ . *
. * . . * . . *
D R 3 E P O C H P H O T O M E T R Y . V A R I A B I L I T Y
_____________________________________________________________________________
| Find every source whose BP or RP flux changed by more than 100% over the |
| Gaia DR3 observation window. InterSystems IRIS + Embedded Python. |
|_____________________________________________________________________________|
A submission to the 1st InterSystems Programming Challenge
(contest #47).
The application reads the Gaia DR3 epoch-photometry archive, and for every
source_id it computes how much each band’s flux varied across all valid
observations:
max_flux - min_flux percentage_change = ------------------- x 100 (computed per band) min_flux
result = max( percentage_change_BP , percentage_change_RP )
Invalid samples (NaN, null, empty) are ignored. Every source whose
percentage_change exceeds 100% is written to the output, one record per
line:
source_id , bp_min_flux , bp_max_flux , rp_min_flux , rp_max_flux , percentage_change
On the official 20-file benchmark this scans 75,068 sources and reports
57,099 variable ones.
.-=========================================================================-.
|| T W O E N G I N E S , O N E A N S W E R ( b y t e - i d e n t ) ||
'-=========================================================================-'
| Engine | Entry point | Optimized for | Warm time |
|---|---|---|---|
| Polars (default) | src/gaia.py |
Code Golf, clarity | ~1.2 s |
| libdeflate + C scan (opt-in) | src/gaia_fast.py + .c |
Benchmarking | ~1.0 s |
Both engines produce identical output to the byte. The default is a compact,
readable polars pipeline; the opt-in engine drops to a hand-written C kernel for
the fastest honest run we could measure.
Prerequisites: git
and Docker Desktop.
git clone https://github.com/isc-nmitchko/ISC-Programming-Challenge-GAIA.git .
docker compose up --build -d
The build installs polars into IRIS Embedded Python, pre-compiles the optional C
kernel, and compiles RunScript.mac. Run the challenge exactly as the judges do:
docker compose exec iris iris session iris -U USER
USER> do ^RunScript
Matched sources: 57099
Elapsed time: 1.5 seconds
The answer is written to data/out/result.csv:
10655814178816,23.783341359526982,157841.99293824387,35.634526749900566,1566.8893953334514,663566.1794160244
10892037246720,203.7002183789311,666.4385312062207,332.5918636164385,661.7290497218023,227.16633124392905
...
The input files are gzip-compressed ECSV (a CSV body behind a ~365-line #
YAML header). Each row is one source; bp_flux and rp_flux are quoted arrays
such as "[1820.8,2013.8,NaN,...]". Only three of the ~47 columns matter:
source_id, bp_flux, rp_flux.
src/gaia.py) *.csv.gz --(polars Rust gzip)--> read 3 cols --> per-file min/max -.
*.csv.gz --(polars Rust gzip)--> read 3 cols --> per-file min/max --+--> concat
*.csv.gz --(polars Rust gzip)--> read 3 cols --> per-file min/max -' |
(16 worker threads, largest file first) v
percentage_change -> filter>100 -> CSV
Each file is read on its own thread, with polars decompressing gzip natively in
Rust. NaN is stripped at the string level so the float cast yields null (which
list.min/list.max already skip), and each worker reduces its file to five
scalar columns before the cross-file concat. Roughly a dozen meaningful lines.
src/gaia_fast.py, src/gaiascan.c) .--------------------- gaiascan.so (OpenMP, GIL released) ---------------------.
| *.gz --> libdeflate_gzip_decompress --> single-pass two-column min/max scan |
'-------------------------------------------------------------------------------'
| struct-of-arrays (no per-element Python)
v
polars: NaN -> null -> percentage_change -> filter > 100 -> write_csv
For each file, in parallel across all cores with the GIL released, the kernel:
ISIZE field (no reallocation).#strtod, tracking four running min/max doubles. NoIt is engineered to never fail: the .so is pre-built at image build, lazily
recompiled at first run if the dev volume mount shadows it, and falls back to the
pure-polars engine if gcc/libdeflate are unavailable.
RunScript.mac imports the default engine. To benchmark the C kernel, change one
line:
; Set gaia = ##class(%SYS.Python).Import("gaia") ; polars (default)
Set gaia = ##class(%SYS.Python).Import("gaia_fast") ; libdeflate + C
This solution was built by measuring iterations. Every change had to be both
faster and identical to the previous answer, verified each round
against a fixed oracle.
10.87s ################################################## serial polars
2.76s ############# parallel reads (16 threads)
1.69s ######## polars-native Rust gzip
1.53s ####### removed per-row list.eval
1.20s ###### largest-first + per-file reduce
1.02s ##### C kernel + libdeflate (the floor)
gzip module not data processing with polars. Parallelizing the reads, thenisal (1.66 s) and cramjam (2.45 s) - both lost toOMP_NUM_THREADS=16 (the core count) was optimal, and-march=native gave no gain (the work lives inside precompiled libdeflate), so-O3 build with no SIGILL risk on the judge’s CPU. ,---------------------------------------------------------------------------.
| The job is decompression-bound. ~1.0s is the floor, and the measurement |
| proves it: nothing reads 1.54 GB of gzip faster without changing the |
| problem (e.g. precomputing a cache, which we deliberately did not do). |
'---------------------------------------------------------------------------'
The subtle contract honored by every iteration: NaN, null, and empty samples
are invalid; a band with no valid values yields a null min/max that
max_horizontal ignores, so a source appears only if a real band swung past
100%. After every change we verified:
wc -l result.csv -> 57099
awk -F, 'NF!=6 || $6<=100' result.csv -> (no rows)
diff <(sort oracle) <(sort result.csv) -> 0 lines (byte-identical)
src/
gaia.py default engine - pure polars, code-golf friendly
gaia_fast.py opt-in engine - libdeflate + C kernel, polars fallback
gaiascan.c the native decompress + single-pass scan kernel
RunScript.mac benchmark entry point invoked by do ^RunScript
data/
in/ the 20 EpochPhotometry_*.csv.gz benchmark files
out/result.csv the answer - 57,099 variable sources
Dockerfile IRIS + Embedded Python; installs polars, builds gaiascan.so
docker-compose.yml
%SYS.Pythondocker compose up --build reproduces everything. "We are made of star-stuff." - and, measured carefully, of about one second.
Input data: ESA Gaia DR3 epoch photometry. Column definitions:
https://gea.esac.esa.int/archive/documentation/GDR3/Gaia_archive/chap_datamodel/sec_dm_photometry/ssec_dm_epoch_photometry.html.
How to cite Gaia: https://gea.esac.esa.int/archive/documentation/credits.html.