limTOD.cstbeam — using CST Studio far-field exports
The sibling of limTOD.uvbeam for the other format a measured or
simulated horn arrives in: CST Microwave Studio far-field ASCII exports,
one file per frequency. Needs only healpy and scipy, both base
dependencies — there is no extra to install.
from limTOD.cstbeam import cst_beam_func
from limTOD import TODSim
sim = TODSim(beam_func=cst_beam_func("~/beams/HornDryGround"),
sky_func=..., ...)
Three entry points
Function |
Gives you |
|---|---|
|
one file as |
|
|
|
a |
cst_beam_func validates the directory and the options at construction,
not at the first channel, and caches each file’s HEALPix resampling across
calls — a sweep of 200 channels over a 61-file directory parses each file once
rather than hundreds of times.
Conventions
Stated because getting one wrong returns a finite, correctly shaped, wrong beam.
Theta. CST’s Theta is measured from the model’s +z axis and maps
directly onto the HEALPix colatitude: the boresight sits at the pole, which is
what limTOD’s beam maps mean by beam-local.
The quantity. Abs(Dir.) is total directivity in dBi — a power quantity.
Maps come back as \(10^{\mathrm{dBi}/10}\), which is the \(B\) of
\(\int B T / \int B\). Nothing is normalized here. Divide by your own
quadrature \(\int B\) downstream; that is the only way the band limit cancels
exactly.
Frequency is in MHz throughout, as elsewhere in limTOD, and is read from
the trailing number of each filename’s stem — HornDry70.5.txt is 70.5 MHz.
Interpolation between bracketing files is linear in linear power.
Extrapolation is refused: a beam invented outside the simulated band is not a
beam.
Phi, which the file does not contain
CST’s Phi is measured from the model’s +x axis. limTOD’s beam-map
phi = 0 is carried to the direction of increasing elevation. Which physical
direction the CST +x axis points is a fact about how the horn was built and
mounted, and it is not in the export — so it cannot be recovered here.
Two degrees of freedom are exposed instead:
Option |
Meaning |
|---|---|
|
the CST azimuth that lands on the beam-map |
|
|
The defaults are the identity mapping, which is an assumption to check against the as-built horn, not a result. For a beam with real azimuthal structure the handedness is not a detail — RHINO’s horn varies by 30–60 % around the \(\theta = 30^\circ\) ring, and getting the sense backwards mirrors that structure into the wrong half of the sky while leaving every integral, every peak and every azimuthally-symmetric diagnostic unchanged.
Note
Unlike limTOD.uvbeam, whose azimuth convention is fixed by
pyuvdata and is therefore locked numerically by a three-way orientation test,
this one cannot be locked: the information is not in the file. What the tests
do lock is that the knobs act correctly — that phi_sense is a reflection
about phi = 0 rather than a relabelling, and that phi0_deg is a rotation
that conserves the integral.
What a CST export looks like
Two header lines, then rows on a regular grid with theta running fastest inside each phi block:
Theta [deg.] Phi [deg.] Abs(Dir.)[dBi] Abs(Theta)[dBi] ...
--------------------------------------------------------------
0.000 0.000 1.41421356000000e+01 ...
2.000 0.000 1.41205511000000e+01 ...
Reshaping phi-fastest gives a correctly-shaped array with the samples
transposed — a beam, just not this one — so the reader checks that the rows
fill the complete grid they span and raises ValueError if they do not.
API
CST Studio far-field exports -> HEALPix beam maps.
The sibling of limTOD.uvbeam for the other format a measured or
simulated horn arrives in: CST Microwave Studio far-field ASCII exports, one
file per frequency. Three entry points:
read_cst_farfield()— one file into a regular(theta, phi)grid.cst_beam_maps()— a directory of them onto HEALPix maps in limTOD’s beam-map convention (boresight at the pole, RING ordering), interpolated in frequency.cst_beam_func()— the same wrapped as abeam_func(freq=..., nside=...)callable satisfyinglimTOD.TODSim’s contract, so a CST horn drops straight into a simulation.
Needs only healpy and scipy, both base dependencies — there is no extra
to install for this module.
Conventions
Stated because getting one wrong returns a finite, correctly shaped, wrong beam.
Theta. CST’s
Thetais measured from the model’s+zaxis and maps directly onto the HEALPix colatitude: the boresight sits at the pole, which is what limTOD’s beam maps mean by beam-local.The quantity.
Abs(Dir.)is total directivity in dBi — a POWER quantity. Maps come back as10 ** (dBi / 10), which is theBofint(B T) / int(B). Nothing is normalized here; ask the consumer to divide by its own quadratureint(B), which is the only way the band limit cancels exactly.Phi, which is not derivable from the file. CST’s
Phiis measured from the model’s+xaxis; limTOD’s beam-mapphi = 0is carried to the direction of increasing elevation. Which physical direction the CST+xaxis points is a fact about how the horn was built and mounted, and it is not in the export — so it cannot be recovered here.phi0_degandphi_senseexpose the two degrees of freedom (an offset and a handedness). Their defaults are the identity mapping, which is an assumption to check against the as-built horn, not a result. For a beam with real azimuthal structure the handedness is not a detail: RHINO’s horn varies by 30-60 % around thetheta = 30deg ring, so getting it backwards mirrors that structure into the wrong half of the sky while leaving every integral, every peak and every symmetric diagnostic unchanged.Unlike
limTOD.uvbeam, whose azimuth convention is fixed by pyuvdata and is therefore locked numerically by a test, this one cannot be: the file does not contain the information. What the tests here lock instead is that the knobs do the right thing — thatphi_senseis a reflection aboutphi = 0rather than a relabelling, and thatphi0_degis a rotation that conserves the integral.Frequency. In MHz throughout, as elsewhere in limTOD, and read from the trailing number of each filename’s stem:
HornDry70.5.txtis 70.5 MHz. Interpolation between bracketing files is linear in linear power. Extrapolation is refused — a beam invented outside the simulated band is not a beam.
- limTOD.cstbeam.read_cst_farfield(path)[source]
Read one CST far-field export into a regular
(theta, phi)grid.- Parameters:
path (str or Path) – A CST Studio far-field ASCII export: two header lines, then
Theta Phi Abs(Dir.) ...rows on a regular grid.- Returns:
theta_deg (ndarray, shape (n_theta,))
phi_deg (ndarray, shape (n_phi,))
directivity (ndarray, shape (n_theta, n_phi)) – Linear power,
10 ** (dBi / 10)— not dB.
- Raises:
ValueError – If the rows do not fill a complete regular grid. An incomplete export would otherwise reshape into a plausible-looking beam with the samples in the wrong places.
- Return type:
- limTOD.cstbeam.cst_frequency_table(directory, *, suffix='.txt')[source]
Map frequency [MHz] to file for a directory of CST exports.
The frequency is the trailing number of the stem, in MHz —
HornDry70.5.txtis 70.5. Files whose stem does not end in a number are ignored, so aREADME.txtalongside the exports is harmless.
- limTOD.cstbeam.cst_beam_maps(directory, freq_MHz, *, nside, suffix='.txt', phi0_deg=0.0, phi_sense='ccw', _cache=None)[source]
Sample a directory of CST exports onto HEALPix maps at given frequencies.
- Parameters:
directory (str or Path) – Directory of per-frequency CST exports (see
cst_frequency_table()).freq_MHz (array_like, shape (n_freq,)) – Output frequencies [MHz].
nside (int) – HEALPix resolution of the output maps (RING ordering).
suffix (str) – File extension of the exports.
phi0_deg (float) – CST azimuth that lands on the beam-map
phi = 0meridian.phi_sense ({"ccw", "cw"}) – Whether CST azimuth increases with beam-map
phi. See the module docstring: this is a fact about the horn, not about the file._cache (Dict | None)
- Returns:
Linear-power beam maps, unnormalized.
- Return type:
ndarray, shape (n_freq, 12 * nside ** 2)
- Raises:
ValueError – On an unknown
phi_sense, or a requested frequency outside the range the directory covers.
Notes
Only the files actually bracketing a requested frequency are read: a production directory holds dozens, each a 65k-row parse.
- limTOD.cstbeam.cst_beam_func(directory, *, suffix='.txt', phi0_deg=0.0, phi_sense='ccw')[source]
Wrap a directory of CST exports as a limTOD
beam_func.The returned callable satisfies
limTOD.TODSim’s contract,beam_func(freq=..., nside=...) -> (npix,)withfreqin MHz, so a CST horn drops straight into a simulation:sim = TODSim(beam_func=cst_beam_func(horn_dir), sky_func=..., ...)
Chromatic by construction: each channel interpolates between the two bracketing exports.
Configuration errors — an unreadable directory, an unknown
phi_sense— surface here rather than at the first call, and the per-file HEALPix resampling is cached across calls, so a simulation sweeping 200 channels through a 61-file directory parses each file once instead of hundreds of times.