Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
[2.0.0] - 2026-10-29¶
This release collects the pull requests merged since 1.2.0, #71 to #306, most of them from the fix plan of #99. It fixes the statistics of the individual-star fit, the priors, the population and line-of-sight fits, and the photometric offsets, and it validates the inputs of the public functions. Results differ from 1.2.0 and from the paper (Speagle et al. 2025): see the entries marked (output-changing). Some arguments and attributes are removed, and some inputs that 1.2.0 ignored now raise errors. The migration guide lists each change a user must make.
Fixed: statistical correctness (output-changing)¶
The parallax selection gate no longer removes the true models.
logpost_gridselects models by a weight that includes the parallax. That term was a Gaussian in the flux scale, and it was left out when the parallax SNR was 4 or less. A dwarf favoured by the model prior then removed a reddened giant that the parallax requires. On 120 mock giants at 6-10 kpc with parallax SNR -1.3 to 3.7, the posterior median was below half the true distance for 15.8% (now 2.5%). The term is now the parallax likelihood averaged over each model’s Gaussian in ln(d), by five-node adaptive Gauss-Hermite quadrature, at every SNR. Fits with a parallax can select different models.BruteForcereads the predictions inStarGrid.params. WithStarGrid(models, labels, params), the fit readagewt,fehandlogafromlabelsonly. It dropped the age weights without a warning, and the Galactic prior got nologa.BruteForce.models_labelsnow holds the fields oflabelsandparams, so both forms ofStarGridgive the same fit.apply_agewt=Trueon a grid withoutagewt, such as the Bayestar grid, now raisesValueError; passapply_agewt=False.The grid-spacing correction (
apply_grad) uses only the grid axes.BruteForcetook every field ofStarGrid.labelsas a grid axis. WithStarGrid(models, labels)and the label array ofload_models, as in tutorial 05, it also added a spacing term for each prediction (loga,logl,logt,logg,agewt). For a mock bulge giant at 8.3 kpc these terms favoured a dwarf model by 17.6 in ln over the true model, and on 200 mock bulge giants 88 posteriors missed the true distance entirely. Only the grid inputs now get the term:mini,eep,feh,afeandsmf, andMron a grid withoutmini(the Bayestar grid).The
max_modelscap no longer biases the posterior. Above the cap,logpost_gridkept a weighted sample of models but gave them their full weights, which narrowed the distance posterior by up to 31% and loweredobj_log_evidby up to 0.49. It now keeps model i with probabilitymin(1, c * w_i)by systematic sampling, returns that log-probability asln_incl, andfitdivides each kept model’s weight by it. Fits below the cap do not change.loglike_gridfits each model to its best (scale, A(V), R(V)). The flux-space R(V) step left out the factor A(V) indf/dR(V), so the refinement stopped at a point that gave the R(V) prior too much weight when A(V) > 1. The magnitude-space fit alternated A(V) and R(V) steps and stopped far from its minimum when the two are correlated, so the first cut could drop the best model of a star that no model fits exactly. Both fits now take Gauss-Newton steps in (A(V), R(V)) with the distance profiled out, hold a parameter at its bound while they fit the other, retry a step that does not improve the fit with Levenberg-Marquardt damping, and stop each model when the predicted gain in log-posterior is belowltol, whose default is now1e-3(was3e-2). On 24 Orion stars, master’s best-modellnlwas up to 9.8 too low.The first cut of
loglike_gridkeeps models within a factor1e-6of the best (init_thresh,logl_initthresh), not5e-3. The cut ranks models by their magnitude-space fit, whose chi2 differs from the flux-space fit, so the old cut left models unrefined that carry posterior weight. On 24 Orion stars, the largest total-variation distance between the model weights and those of a fit without the cut falls from 0.106 to 0.0005, at the same run time.fit(apply_dlabels=False)evaluates the Galactic prior without model labels.logpost_gridreplaceddlabels=Nonewith the grid labels, so the option had no effect.logpost_gridand_fitnow default to the grid labels, anddlabels=Nonepasseslabels=Noneto the prior.Dust-map A(V) prior scatter restored:
logp_extinctionagain adds a 0.2 mag systematic scatter in quadrature to the map spread (newscatterargument, default0.2, as in brutus <= 0.8.3; the 1.x refactor had dropped it). Bayestar19 spreads are ~0.02 mag (median), so without it the prior pinned A(V) (and, through the A(V)-distance profile, the distance) far more tightly than the map supports, producing overconfident and occasionally badly wrong distances. Map bins with zero spread now also receive a proper prior instead of falling back to uniform.Dust-map prior is proper in masked bins: where the map is uninformative (reliability-masked distance bins, no coverage), the prior is now the normalized uniform density over
avlim,-ln(avlim[1] - avlim[0]), instead ofln p = 0. The improper value acted as a density of 1 per mag and up-weighted reliability-masked distances relative to reliable ones by an arbitrary, unit-dependent factor.BruteForcepassesavlimto the default dust prior, so its truncation and uniform normalization match the fitted A(V) range (customlndustpriorfunctions keep the 4-argument signature).An integer 0/1
data_maskmasks bands inBruteForce.fitandloglike_grid. It was used as an index array, so the fit used band 0 and repeated band 1 in place of the masked selection. Boolean masks give the same results as before.loglike_gridno longer changes the caller’sdata_mask,av_initorrv_init.loglike_gridnormalizes every model’slnl. The Gaussian constant-0.5 * (Ndim * ln(2 pi) + sum(ln sigma^2))went only to the models that passed the initial cut, so the culled models sat that constant (about 90 log-units for fluxes in maggies) too low. Culled models keep their magnitude-space fit of (scale, A(V), R(V)).logpost_gridtruncates the Monte Carlo draws of A(V), R(V) and distance atavlim,rvlimand (0.001, 1e6) kpc. A draw outside the support now gets zero weight. Before, it was clipped to the bound at full weight: about half of the A(V) draws of an unreddened model sat at exactly 0, and such a model gained up to ln 2 inlnp. This changessamps_red,samps_dred,samps_dist,obj_log_post,obj_log_evidandmc_ess;mc_essis 0 for a model with no draw of nonzero weight.A
functools.partialoflogp_extinctiongets the fit’savlim, as the bare function does, unless the partial bindsavlimitself. A prior such aspartial(logp_extinction, scatter=0.3)was normalized over its default (0, 20) instead of the fitted range.BruteForce.fitskips an object with no valid bands instead of stopping the whole run withValueError. The object is written withmodel_idx = -99,obj_Nbands = 0and NaN values, andfitwarns once with the number of such objects. The output file now closes on every exit path.quantiledrops samples with zero weight before it builds the CDF. A zero-weight sample still got a CDF node, soquantile([0, 1, 100], [0.5, 0.9], weights=[1, 1, 0])gave[0.5, 60.4]instead of[0.5, 1.0]. Negative or non-finite weights, or weights that sum to 0, raiseValueError.An object with no degrees of freedom adds 0 to a cluster fit. With
dim_prior=True,phot_loglikeandchisquare_outlier_loglikegave such an object (Ndim - dof_reduction <= 0, for example all bands NaN)-inf, which made the cluster log-likelihood-inffor every parameter. It now gets 0, the same as withdim_prior=False.truncnorm_logpdfandlogp_imfreturnLOG_ZERO(-1e300) for zero probability, not-inf, as the rest of brutus does. The fused and numpy paths oflogp_galactic_structurenow agree for ages abovemax_age(they gave -1e300 and -inf).truncnorm_logpdfandtruncnorm_pdfaccept numpy scalars and 0-d arrays, sologp_age_from_feh(5.0)no longer raisesTypeError. The valueLOG_ZEROlives inbrutus.utils.math.chisquare_logpdfreturnsLOG_ZERO(-1e300) where the density is zero, not-inf, liketruncnorm_logpdf. Atx == locit now agrees withscipy.stats.chi2.logpdf(-ln 2fordf = 2,+inffordf < 2), and float32 input no longer overflows to-inf.The line-of-sight and cluster log-likelihoods return
LOG_ZERO(-1e300) for a zero probability, not-inf:los_clouds_loglike_samples,kernel_tophat,kernel_gauss,kernel_lorentz,marginalize_isochrone_grid,isochrone_population_loglikeanduniform_outlier_loglike. Withdim_prior=True,phot_loglikegivesLOG_ZEROfor an exact fit (chi2 = 0) with 3 or more degrees of freedom.kernel_gaussandkernel_lorentznormalize overrlimsstably in the tails.kernel_gausstook the mass from anndtrdifference that rounds to 0 for a kernel mean about 8 sigma belowrlimsor 38 sigma above; it now useslog_ndtr.kernel_lorentztook it from anarctandifference that lost its digits for a mean far fromrlims(a relative error of 2e-4 at 1e7 widths).The fused Galactic prior normalizes the age prior with the tail-stable log-mass of the fused extinction prior. It used a floored
erfdifference. The prior changes by at most 1.8e-15 for valid inputs; the fits ofbench/fullfit.pydo not change.logp_extinctionnormalizes its truncated normal stably in the tails. It took the mass onavlimfrom anerfdifference with a1e-300floor, so a map mean far outsideavlim(for example 9 mag withavlim=(0, 6)) gave a log-prior near +631 instead of about +1. It now useslog_ndtrin the numpy path and a mirrorederfcin the fused kernel; both agree withscipy.stats.truncnorm.logpdf.Batched
inverse3(regularize=True)regularizes like the single-matrix path. Its smallest-eigenvalue check cancelled for ill-conditioned matrices and shifted some that needed no shift. The saved results of thebench/fullfit.pyfits do not change.draw_sarand the plots that regenerate draws useavlim=(0, 20), theBruteForce.fitdefault, not(0, 6). This affectscornerplot,dist_vs_redandbin_pdfs_distred. A star with A(V) above 6 mag got every draw replaced by its mean. Withoutspan, the A(V) axis ofdist_vs_redandbin_pdfs_distrednow covers 0 to 20 mag, also for saved draws, so the default bins are 3.3 times wider; passspanfor a narrower range.draw_sarpads a distribution with its mean after 100 passes with no accepted draw, instead of aftermax_attempts.analysis.photometric_offsetsweights the draws of a fitted band by the Gaussian leave-one-out ratio,ln w = chi2_b / 2. The old default (dim_prior=True) used a ratio of chi-square densities, which does not match the posteriorBruteForcesamples, with or withoutlogl_dim_prior. The default offsets change. With a 10% offset injected into a fitted band (400 stars, 1000 draws, 8 seeds), the mean bias fell from 0.0026 to 0.0008.dist_vs_redandbin_pdfs_distredsmooth A(V) by 0.005 of its range by default (0.1 mag over the defaultavlim): the defaultsmoothis(0.015, 0.005)and(0.01, 0.005).load_modelsapplies the main-sequence and binary cuts whateverlabelsholds. The cuts read the file’seepandsmfcolumns. Alabelslist withouteeporsmfskipped the cut: ongrid_mist_v9.h5,labels=['mini', 'feh'], include_postms=Falsereturned 613530 models instead of 143856. A phase cut on a file withouteepnow raisesValueErrorinstead of keeping every model.StellarPop.get_sedsfinds each companion’s EEP on the isochrone’s own EEP grid. It inverted mini(EEP) on the caller’seeponly, so a coarse or truncatedeepdropped companions below its smallest mass and misplaced the others; a singleeepnever got a companion. At [Fe/H] = 0, log(age) = 9.5 andbinary_fraction=0.6, the SDSS g magnitude at EEP 350 is now 14.621 for anyeep, against 14.645 witheep=np.arange(300, 480, 10). With the default cluster grids,isochrone_population_loglikechanges by about 0.01 on a synthetic cluster of 30 stars. Companions that a young isochrone places before the zero-age main sequence are now included.StellarPop.get_sedsreturns NaNparams2for every star whose SED has no companion, for all binary fractions. For0 < binary_fraction < 1, it also returned the parameters of companions that it left out of the SED (belowmini_bound, or with no valid SED).los_clouds_loglike_samplesaverages each star’s likelihood over its finite draws only. It divided by the count of all draws, so NaN draws lowered the star’s inlier term against the outlier term, and a star with only NaN draws addedln(pb) - ln(area), which pulled the outlier fractionpbupward. A draw with a NaN or infinite distance or reddening is now left out, a finite distance outside [0, 1e10) still counts as a zero-likelihood draw, and a star with no finite draw is left out of the sum. With 5 stars, 25 draws each, and one star all NaN, the log-likelihood atpb= 0.01, 0.1 and 0.5 was -8.25, -6.22 and -6.14; it is now -1.85, -2.13 and -3.66, the values without that star.StarEvolTrackfinds a companion’s EEP at the primary’s [α/Fe], with no clamp to the library range, asget_sedsalready used for the companion’s parameters. Tracks without agridpointsEEP axis used [α/Fe] = 0.0. OnMIST_1.2_EEPtrk.h5, results at [α/Fe] = 0 do not change; for 0 < |[α/Fe]| < 1e-5 the companion EEP changes by at most 9e-13 (200 random binaries each at ±3e-6).logp_galactic_structurecomputes in float64 for float32labels. It used thefehandlogafields of a structuredlabelsarray at their own dtype, so a float32 grid gave float32 metallicity and age priors on the numpy path (1000 points or fewer) and, withNUMBA_DISABLE_JIT=1, on the fused path. Both call styles now take the same float64 path. On 500 random points the log-prior changes by at most 2.0e-6 (relative 2.7e-7). A multi-dimensionallabelsmust have the shape ofdists; one of the same size but another shape now raisesValueError, where it could pair distances with the wrong labels.StarGrid.get_sedsandget_predictionsreject queries they cannot answer. A NaN, infinite or out-of-range grid label raisesValueErrorthat names it; before, it returned the edge model with no warning (grid_mist_v9.h5:mini=50andmini=NaNgave themini=2SED). Values within a relative 1e-6 of an axis edge are accepted. Withuse_multilinear=False, an omitted multi-valued axis now raisesValueError, as with the default; before, the KD-tree ignored it.
Changed¶
load_modelskeeps constant grid labels. It returned noafecolumn for a single-afe grid under the default labels, and nosmfcolumn withoutinclude_binaries. Both are now one-value columns, soStarGridcan check a query against them. Fits do not change:BruteForceskips the grid-spacing term of a one-value label, and the default Galactic prior reads onlyfehandloga.hist2dno longer accepts**kwargs. It ignored unknown keywords, so an unknown key incornerplot(hist2d_kwargs=...)had no effect. An unknown keyword now raisesTypeError.cornerplotno longer sets a defaultalphafor the 2-D histograms, whichhist2dnever applied; the figures do not change.StarEvolTrack.get_sedsandIsochrone.get_predictionspass errors of the tracks, the interpolator and the neural network through with their own type.get_sedswrapped a track error inRuntimeError, and turned an error in the primary or companion SED into aRuntimeWarningwith a NaN or single-star SED; the companion EEP search turned errors into a NaN EEP.Isochrone.get_predictionswrapped interpolator errors inRuntimeError.EEPTracksstill rebuilds any cache that it cannot load, and skips the cache write only onOSError.GridGenerator.make_gridhas nodistargument. Grids are always computed at 1 kpc, the distance thatload_models,StarGridandBruteForceassume; a grid made at anotherdistgave distances off by a factor of (dist / 1000)^2. Passingdistnow raisesTypeError.GridGenerator.make_gridraisesValueErrornaming the argument whenav_gridorrv_gridhas a non-finite value or fewer than 2 distinct values, or whenav_wtorrv_wthas a non-finite value or a different shape from its grid. Before, a one-value lattice gave a rank-deficient fit with only aRankWarning, so the reddening coefficients that it could not determine were arbitrary; a NaN marked every model invalid. Tutorial 3 now passes 4 R(V) values.EEPTracksdrops the unused attributesnullandbinwidths. Itsygridattribute holds the unpadded grid (one [α/Fe] layer when the tracks have one [α/Fe] value);xgridandgrid_dimsstill describe the padded grid of the interpolator.StarEvolTrack.get_seds,StarGrid.get_sedsandStarGrid.get_predictionsno longer accept**kwargs. They ignored unknown keywords, so a misspelled argument such asAv=5.0orfe_h=-1gave a wrong result or a misleading error. An unknown keyword now raisesTypeError.subsample_modeis removed fromfit,_fitandlogpost_grid, with its"topk"option, which truncated the posterior.logpost_gridreturns a ninth value,ln_incl, the log-probability that each selected model was kept by themax_modelscap.max_modelsmust be None or a positive integer;fitandlogpost_gridraiseValueErrorotherwise. With the old cap,0kept every model.load_modelsraisesValueErrorforinclude_ms=False, include_postms=Falsebefore it reads the file. It warns with aUserWarningthat lists requestedlabelsabsent from the file.load_offsetswarns for a filter name that is neither inbrutus.FILTERSnor in the file, and its verbose summary prints each offset as a signed percentage, such as+12.34%.Every
fetch_*function takes the file choice first andtarget_dironly as a keyword, for examplefetch_offsets("mist_v9", target_dir=...). A call that passestarget_dirby position, such asfetch_isos("./data"), now fails. Before,fetch_gridsalone took the file choice first, sofetch_offsets("mist_v9")made a link in a new directory./mist_v9. An unknown option raisesValueErrorthat names the argument, the valid options and the given value.Bayestar.get_query_sizeis removed. Nothing called it, and it gave twice the result size for an[l, b]array.ltol_subthreshis removed fromloglike_grid,fitand_fit. Each model’s fit now checks its own convergence.ltolmust be positive.dim_prioris removed fromanalysis.photometric_offsets, which always uses the Gaussian leave-one-out weights. Passing it raisesTypeError.old_offsetsmust be finite and > 0.dim_prioris removed fromplotting.photometric_offsetsandplotting.photometric_offsets_2d, which use the weights ofanalysis.photometric_offsets.PhotometricOffsetsConfigloses four switches.use_vectorized_bootstrapand the per-object bootstrap loop it selected are removed.validate_inputsis removed, so input validation always runs.random_seedis removed: pass a seededrngtophotometric_offsetsinstead (rng=np.random.default_rng(seed)gives the old results ofrandom_seed=seed).progress_intervalis removed, andverbosealone controls the progress messages.logpost_gridtakesreturn_precision=Trueto return the precision matrices of the selected models in place of their covariances.fituses it to invert only the resampled models.The default fit limits are the constants
brutus.utils.math.AVLIM, (0, 20) mag, andRVLIM, (1, 8). Every function that takesavlimorrvlimuses them as its default.import brutusnow raises the originalImportErrorwhen a dependency is missing. Before, it warned with a hiddenImportWarningand left most names undefined.brutus.__version__comes from the installed package metadata, so thebrutus_versionattribute of generated grids follows the installed release. A source tree that is not installed reports"unknown".The top level re-exports every data fetcher and loader:
fetch_nns,fetch_offsets,fetch_tracksandload_offsetsjoinfetch_dustmaps,fetch_grids,fetch_isosandload_models.brutus.coreno longer exports the private_get_seds; import it frombrutus.core.sed_utils.brutus.dust.__version__is removed.StarGridis inbrutus.core.individual.__all__.logp_extinctionno longer acceptsavlim=None, which gave masked map bins an improperln p = 0next to normalized Gaussians in reliable bins. Pass the A(V) support of the fit instead;NoneraisesValueError.brutus.utilsre-exportschisquare_outlier_loglikeanduniform_outlier_loglike. The private, unused_function_wrapperis removed frombrutus.utils.math.Package metadata declares the license as the SPDX expression
MIT(License-Expression: MIT) instead of a license table and classifier. OnlyLICENSEis packaged as a license file;AUTHORS.mdstays in the source distribution. Building from source needssetuptools>=77.BruteForce.loglike_gridandlogpost_gridno longer accept unused**kwargs, so a misspelled keyword such asav_gausraisesTypeError. Before, it was dropped and the default applied.astropy 6.1 or later is a declared dependency (
brutus.dustimports it).Python 3.10 or later and numpy 2.0 or later are required (the floors were 3.9 and 1.19). Python 3.9 is past its end of life, and no astropy release for Python 3.9 supports numpy 2.
StellarPop.get_seds(return_dict=False)andget_seds_smf_gridreturnparamswith shape (Neep, Npred), likeparams2andStarEvolTrack.get_seds. It was the transpose, (Npred, Neep).StellarPopandStarEvolTrackraise the error of the neural network when it fails to load, at construction. Before, they setpredictorto None, andget_sedsraised aRuntimeErrorwithout the cause.FastNNPredictorraisesValueErrorthat names the filters missing fromnnfile, in place of an h5pyKeyError.StellarPopraisesValueErrorwhenisochrone.predictionslacks mini, logl, logt, logg, feh_surf or afe_surf, andStarEvolTrackwhentracks.predictionslacks loga, logl, logt, logg, feh_surf or afe_surf.StellarPop.get_sedscomputes SEDs only withFastNNPredictor.sed_batch. The per-starsedfallback, which ran whensed_batchraised, is removed; such an error now propagates, as does an error in the companions’ parameters, which gave single-star SEDs with a warning.Isochrone.get_predictionsandEEPTracks.get_correctionsshare one correction function, with bitwise-identical results.IsochroneraisesValueErrorfor acorr_paramswithout 4 entries, and at construction for a file without the mini, logt, logl or logg label; it warned and returned uncorrected predictions.StellarPoppasses errors ofIsochrone.get_predictionsthrough, not asRuntimeError.cornerplotdefaultsapplied_parallaxtoNone, which applies the parallax when bothparallaxandparallax_errare given. The defaultTrueraised for saved(dists, reds, dreds)draws given without a parallax, where the flag has no effect.applied_parallax=Truewithout both values now raises only for(scales, avs, rvs, covs_sar)input.dist_vs_redtakesfig=(fig, ax)and draws onax, as the other plotting functions do. Without it, it draws on the current axes, as before.summary_plottakesrstateand passes it tocornerplotandposterior_predictive. The violins of the SED inset came from the global numpy stream, which onlynp.random.seedcould seed.
Fixed¶
brutus’s numba caches are recompiled when any file of the package changes. Numba checked a cached kernel only against its own source file, so a kernel that calls jitted code in another brutus module kept returning results of the old code after that module changed.
StarGrid.get_seds(afe=0.4)ongrid_mist_v9.h5raisesValueError.load_modelsdropped the constantafelabel, so the query ignored it silently and returned the [α/Fe] = 0 result.load_modelsnow keeps the column (see Changed), andStarGridchecks it as a one-value axis. A label that the grid does not have at all warns and is ignored.Isochronekeeps theOSErrororKeyErrorof a failed file load as the cause of itsRuntimeError, so the traceback shows the original error.EEPTrackswrites its cache to a temporary file and renames it. A failed write left an empty or partial*_cachev3.pkl, which every later load read, rejected and rebuilt.StarGridbuilds its grid axes and its KD-tree from finite label values only. A NaN label made a one-value axis count as multi-valued, so omitting it raised, and it made the KD-tree ignore that axis entirely. A grid label with no finite value now raisesValueError.logpost_gridraisesValueErrorwhen only one ofparallaxandparallax_erris given, asloglike_gridandfitdo. It ignored the parallax.logpost_gridraisesValueErrorthat namesdlabelswhen the default Galactic prior gets a plain, not structured,dlabelsarray. It raised aTypeErroraboutNoneType.StarGridkeeps the requestedfiltersorder for a structuredmodelsarray, asload_modelsdoes. It ordered the columns as in the file and dropped unknown names silently; it now warns with the skipped names and raisesValueErrorwhen none exists. For an unstructuredmodelsarray, afilterslist of the wrong length raisesValueError.StarGridno longer warns about missing grid corners that carry zero weight. Ongrid_mist_v9.h5, 96 of 3000 queries on grid nodes warned; the SEDs do not change.StarGrid.get_seds(use_multilinear=False)works on a grid of one model; it raisedAttributeError. On a grid with no grid label (mini,eep,feh,afeorsmf), both neighbour methods raiseValueError; the default method returned the first model for any query. Adistthat is not a finite distance > 0 raisesValueError; a negativedistgave NaN magnitudes.find_nn_file,EEPTracksandIsochronelook for their default data files in the brutus download directory (strato.path, whichASTRO_BRUTUS_DATA_DIRsets) first, then indata/DATAFILES. They ignoredASTRO_BRUTUS_DATA_DIR, so they missed files that thefetch_*functions had downloaded there. A missing default file raisesFileNotFoundErrorthat names the directories and thefetch_*function to run.EEPTracksraisesValueErrornamingpredictionswhen it lacksloga,logl,logtorloggor has an unknown name, andFileNotFoundErrorwith afetch_tracks()hint for a missingmistfile;Isochroneraises it with afetch_isos()hint. It no longer wraps errors from reading the file inRuntimeError.lb2pixreturns -1 for a NaN or infinite longitude, as for an invalid latitude. It returned a valid pixel, soBayestar.querycould give such a star the extinction profile of another sightline.Bayestar.queryraisesValueErrornamingcoordswhen an array input does not have shape(2,)or(Ncoords, 2). A(2, 3)array of[l values, b values]was read row by row, and an(N, 3)array lost its third column.query_galwith a scalarellandbreturns profiles of shape(n_dist,), asquery_equdoes; it returned(1, n_dist).logp_extinctionraisesValueErrorfor a 3D dust map called withoutdistance, andTypeErrorwhendustmap.queryreturns something other than a tuple, list or array of length 2 or 3. Both cases returned the uniform prior with no warning, which turned off the dust prior. A list or array result is now read like a tuple.logp_galactic_structureraisesValueErrorwhenlabelsdoes not have one entry per distance. With JIT on and more than 1000 distances, it read past the end oflabels; otherwise it warned and dropped the metallicity and age priors. A scalar distance withlabelsorfehnow works. The fused kernels of the Galactic and extinction priors no longer catch every error and fall back to numpy with a warning.logp_imfraisesValueErrornaming the argument for amass_minormass_breakthat is not finite and positive, amass_maxthat does not exceedmass_min, or a slope that is not finite. A NaNmass_breakreturned-infeverywhere.inverse3convertsAto float and raisesValueErrorunless its shape is(3, 3)or(N, 3, 3). An integer matrix gave an integer inverse that truncated 0.5 to 0.sample_multivariate_normalconvertsmeanandcovto float and raisesValueErrorwhencovdoes not have shape(Ndist, dim, dim). An integercovshrank the spread of the draws, and a shortcovreturned garbage with JIT on.core.sed_utils.get_sedsraisesValueErrorunlessmag_coeffshas shape(Nmodels, Nbands, 3). With fewer coefficients, the JIT kernel read past the coefficient axis.plotting.posterior_predictivetreats an integer 0/1data_maskas a mask and raisesValueErrorfor a mask of the wrong shape. It plotted band 0 and repeated band 1 in place of the unmasked bands.BruteForce.fitandloglike_gridcheck their input shapes and raiseValueErrorthat names the argument, beforefitcreates its output file. Aparallaxgiven withoutparallax_err, which the fit silently ignored, now raises.logl_initthresh(init_thresh) must be in (0, 1). A parallax with an error of 0 or less counts as no parallax, as in the parallax priors; it stopped the run with an unclear error.BruteForce.logpost_gridraisesValueErrorwhenlnpriorordlabelshas a different length from the likelihood results. Afterloglike_grid(indices=...), the defaultdlabelsgave the Galactic prior the labels of the wrong models.BruteForce.get_sed_gridraisesValueErrorfor anavorrvarray whose length is not the number of models. With JIT on, it read past the end of the array and returned garbage SEDs.isochrone_population_loglikechecks its inputs before it builds the model grid and raisesValueErrorthat names the argument:obs_phot,obs_errandmaskmust be 2-D with one shape,parallaxandparallax_errmust come together with shape (N_objects,), andsmf_gridandeep_gridmust be 1-D, finite and strictly increasing, withsmf_gridin [0, 1]. These inputs returnedLOG_ZEROwith a warning for everytheta. Only athetawith no valid model in the isochrone grid still returnsLOG_ZERO; any other error, such as a misspelled outlier keyword or aValueErrorfrom the model code, now raises.generate_isochrone_population_gridchecks the grids and no longer turns a failed SED slice into a skipped slice.compute_isochrone_cluster_loglikeraisesValueErrorfor aparallaxwithoutparallax_errordistance, which it dropped without a warning.StellarPop.get_sedsraisesValueErrorfor abinary_fractionthat is not finite or lies outside [0, 1], andget_seds_smf_griddoes the same forsmf_values. Such values gave single-star SEDs. An integer or scalareepnow works with binaries; it raisedValueErrorwhen a companion had no valid EEP.los_clouds_priortransformraisesValueErrorunlessuhas 4 + 2 * n entries (n >= 1), all finite. A shorturaised anIndexError, and a NaN entry gave NaN parameters.los_clouds_loglike_samplesraisesValueErrorunlessNdrawsis an integer >= 1 ands0andsare positive.Ndraws=-1silently dropped the last draw,Ndraws=0returned NaN, ands=0failed inside the kernel with a message that did not name the argument.analysis.photometric_offsetscheckssel,weights,prior_meanandprior_stdbefore it generates the model SEDs, and raisesValueErrorthat names the argument.selmust be a bool or 0/1 array of shape(Nobj,): an index array excluded objects with no error.weightsmust be finite and >= 0, andprior_stdfinite and > 0. A scalar prior failed only after the full computation.plotting.photometric_offsetsandphotometric_offsets_2dweight the draws of each band with the leave-one-out weights ofanalysis.photometric_offsets, computed in flux space. They weighted by the likelihood of the other bands alone, which keeps part of the pull of the band on the draws. With a 10% offset injected into a fitted band, the plotted offset differed from the analysis offset by 0.013 mag; it now agrees to 0.0001 mag.dist_vs_redandbin_pdfs_distredaccept a float parallax and parallax error for several objects and apply them to every object, and raiseValueErrorthat names the argument for any shape other than(Nobj,). A float raisedTypeError, and a short array left the last objects without parallax smoothing.bin_pdfs_distredaccepts a tuplespanfor everydist_type, and raisesValueErrorunlessspanhas shape(2, 2);dist_type='scale'and'parallax'raisedTypeError.plotting.photometric_offsetsleaves the panel of a band that no object qualifies for blank and draws the other bands; it raisedIndexError. It raisesValueErrorthat namesbinsunlessbinsis an int or one int per band; a length-2binsraisedTypeError.photometric_offsets_2dreads abinsof lengthNfiltas one entry per band, also with 2 bands. Both raiseValueErrorthat namesweightsunless it has shape(Nobj,)or(Nobj, Nsamps); a(Nsamps,)array was read as per-object weights whenNsamps == Nobj. The titles ofphotometric_offsets_2dgive the offset in fixed point (+12.0%, not1.2e+01%).plotting.photometric_offsets_2ddraws each map over its bin edges. The image extent was the outer bin centres, half a bin too narrow on each side.xspanandyspannow set the range of the bins, as inphotometric_offsets; they relabelled the axes of bins taken over the data range. The last bin includes its right edge, as innp.histogram; the object with the largest x or y was left out of every bin.posterior_predictive,photometric_offsetsandphotometric_offsets_2dlay out only a figure that they create. Withfig=(fig, ax), they calledplt.tight_layout(), which re-laid out the current figure, not the one passed.hist2danddist_vs_redno longer write their defaults into the keyword dicts the caller passes (data_kwargs,contourf_kwargs,contour_kwargs,truth_kwargs). Acontour_kwargsreused across panels kept the colour of the first call.cornerplotandbin_pdfs_distredchoose the input format by the length ofdata, and raiseValueErrorthat namesdataunless it has 3 or 4 arrays.cornerplotaccepts saved draws as lists; it sent them to the(scales, avs, rvs, covs_sar)path, which failed with an unpacking error. With(scales, avs, rvs, covs_sar)input,bin_pdfs_distredgives zero weight to a sample when the priors give zero weight to every draw of it; the binned PDF of its object was NaN.
Performance¶
photometric_offsets_2dgroups the objects of each band into the bins once, instead of scanning every object for each bin. The maps are bitwise identical. With 3000 objects, 5 bands, 20 draws and 50 x 50 bins, a call takes a median 0.54 s instead of 0.71 s (3 alternated runs, 4 CPUs).StarGrid.get_sedsandget_predictionswithuse_multilinear=Falsekeep the scaling of the grid axes with the KD-tree instead of scanning every model per query. Results are bitwise identical. Ongrid_mist_v9.h5with 5 bands, a KD-treeget_sedscall takes a median 0.11 ms instead of 35 ms, and a multilinear call 0.14 ms instead of 0.19 ms (5 interleaved runs, 4 CPUs).GridGenerator.make_gridcomputes each model without a companion once per (mini, eep, feh, afe) and copies it to the othersmf_gridvalues. The saved values are bitwise identical. A 960-model grid with 6smfvalues takes a median 0.78 s instead of 1.29 s (3 interleaved runs).EEPTrackspads a single [α/Fe] value with a broadcast view instead of a copy, and its cache stores the unpadded grid. OnMIST_1.2_EEPtrk.h5, the cache falls from 255 MB to 128 MB, the peak memory of a build from 945 MB to 750 MB and of a cached load from 459 MB to 337 MB. Predictions are bitwise identical. The cache file is now*_cachev3.pkl; the first load rebuilds it.EEPTrackscomputes the age weights with one numpy path (a stablenp.lexsortby track and EEP, thennp.gradientper track) instead of a pandas groupby with a slower loop when pandas is absent. The weights are bitwise identical onMIST_1.2_EEPtrk.h5, and computing them takes a median 0.72 s instead of 1.08 s with pandas (5 interleaved runs).The bootstrap of
analysis.photometric_offsetsdraws each sample by a per-row binary search of the weight CDF, in a jitted helper, instead of comparing every CDF entry. The medians are bitwise identical. With 20,000 objects, 500 draws and 30 replicates, a bootstrap takes 0.33 s instead of 1.66 s (medians of 7 interleaved runs, 4 CPUs).marginalize_isochrone_gridmakes two fewer full-size temporaries, andcompute_isochrone_cluster_loglikeadds the parallax term only when one exists. Results are bitwise identical. With 21000 models and 1000 stars, the marginalization takes a median 0.59 to 0.67 s against 0.70 s on one core (25 interleaved runs, two repeats).cornerplotresamples the regenerated draws with one batch of uniform deviates and a search of each sample’s weight CDF, instead of onerstate.choicecall per sample. The draws are bitwise identical for a seededRandomStateorGenerator. For 2000 samples and 500 draws, the resampling takes 12 ms instead of 35 ms (medians of 7 interleaved runs, 4 CPUs).
Documentation¶
New page “Migrating from 1.x to 2.0” with the old and new usage of each changed argument.
The docstring examples of
core.individual,core.neural_netsandcore.grid_generationrun on the shipped data: binary examples use [α/Fe] = 0 (the MIST tracks have no other value) and filter names are real ones such asPS_gandBessell_V.FastNNPredictor.sedgives the input ranges ofnn_c3k.h5(R(V) in [2, 5], [α/Fe] in [-0.2, 0.6]) and says that magnitudes are in each survey’s native system, not AB._get_sedsgives the meaning and units ofrvecsanddrvecs.The
GridGeneratordocstrings give the default grid size (4,093,100 labels before masking, not ~300,000), drop the unmeasured run times and memory figures, say that cost is linear inlen(av_grid) * len(rv_grid), and give themini_gridandav_griddefaults that the code uses.New SVG logo, with light and dark variants, and a favicon. The isochrone “S” is drawn as model-grid points coloured by temperature, over a faint posterior across the grid.
The documentation site moves to the Furo theme and Markdown (MyST) pages, with a brutus colour style and a new landing page.
Rewritten getting-started pages and method overview, with schematics of the forward model, the fit and the distance-extinction degeneracy, and a References page.
Rewritten stellar models and model grid pages. The EEP values of the main sequence are corrected, and the coverage of the default grid (0.5 to 2.0 solar masses, [Fe/H] -3.0 to +0.45) is stated.
The data module page says that only the
fetch_*functions download files, and where the loaders look for them. It describes the model grid as a precomputed SED grid, adds the 1.1 GB EEP track file and its cache, and corrects the storage totals.The tutorials keep their data requirements in one table in
tutorial_utils.py, which each setup cell checks, and leave file sizes to the installation page, which now lists the 13 MB Bayestar grid. Tutorial 5 no longer downloads the grid, the offsets and the dust map when it runs, and Tutorials 7 and 8 save their figures totutorials/plots/like the others, not to the working directory.The
Bayestar.querydocstring states thatav_meanandav_stdkeep the storage dtype of the file (float16 forbayestar2019_v1.h5), andquery_equstates that it ignores the distanced.The
los_dustdocstrings state that the outlier model is uniform in reddening overrlims, independent of distance, and thats_paramssets the kernel width as a fraction of therlimsrange, not in percent.Tutorial 1 and the
Isochrone.get_predictionsdocstring use the MIST primary EEPs: the main sequence is EEP 202 to 454, the subgiant and red giant branches 454 to 605, and core helium burning 631 to 707.The population docstrings give units (distance in pc), allow a per-star
cluster_prob, describe the grid’s model selection as the code does it, nameEEPTracks, and useafe=0.0in their examples: the shipped isochrone file has only [α/Fe] = 0, and other values give NaN.The
cornerplotdocstring says thatlabelsare field names ofparamsand also the axis text. Thehist2ddocstring documentssmoothand drops its “internal function” note. Thedist_vs_reddocstring gives theebvdefault asNone.The
loglike_gridandlogpost_griddocstrings document every parameter and each value they return, with shapes and units. Theget_sed_griddocstring gives the distance in kpc, not parsecs.Module docstrings no longer list their classes and functions; the API pages list each module’s members.
The equations in 18 docstrings now render on the API pages.
[1.2.0] - 2026-07-17¶
Systematic audit of every module outside the BruteForce hot path (covered in
1.1.1): population fitting, LOS dust, offsets, stellar models, priors, dust
maps, plotting, data loading, and utilities. 113 verified findings were fixed
across pull requests #61-#69, each with a regression test (the suite grew from
725 to over 1000 tests). Highlights below; full details are in the PR
descriptions. Items marked (output-changing) alter numerical results (in
every case toward the statistically correct answer).
Fixed: statistical correctness (output-changing)¶
Cluster fitting: binary modeling was silently non-functional (an
smf=keyword typo swallowed by**kwargs); post-MS models were underweighted ~21x; the (mass, SMF) integration measure is now normalized; the uniform outlier model is a proper density (it previously favored flagging the best-measured stars as outliers).LOS dust: monotonicity constraints now apply to the effective extinction profile, and kernels renormalize for truncation at the reddening bounds.
Photometric offsets: leave-one-out reweighting used incorrect importance weights, shrinking fitted-band offsets toward 1.
Priors: metallicity/age priors were silently dropped when passed as plain arrays; the disk/halo normalization was inconsistent near the Sun; age-prior weights now condition on metallicity; the dust-map A(V) prior renormalizes over
avlim.Dust maps: Bayestar queries honor the map’s per-pixel reliability metadata by default (
apply_reliability_mask=Falserestores raw values).Plotting / post-processing: distance posteriors regenerated from fit summaries were biased low (missing scale-to-distance Jacobian);
bin_pdfs_distred(cdf=True)now cumulates along the reddening axis as documented; offset-diagnostic mask indexing fixed.BruteForce: parallax-based model pre-selection now uses the marginal (not conditional) scale uncertainty, so heavily reddened stars with good parallaxes no longer lose valid models before the posterior stage.
Utilities:
quantileuses one midpoint-CDF convention for weighted and unweighted input (unweighted values shift slightly); truncated-normal densities are tail-stable; a singular covariance in a batch no longer corrupts the parallel sampler.
Fixed: robustness and API contracts¶
Silent failure modes now raise clear errors: omitting a multi-valued grid axis (was quietly pinned to the grid edge), zero surviving filters in
load_models(which also now drops a constantafecolumn), a dust map without sky coordinates, invalidavlim, mis-shaped weights/coordinates in the plotting utilities, and half-specified offset priors.NaN fluxes and zero errors are masked per band instead of invalidating the whole object; generated grids no longer contain invalid models; caller-supplied arrays (
lnprior,hist2d_kwargs, labels) are no longer mutated in place.Additions:
dustfileacceptspathlib.Path;bin_pdfs_distred/dist_vs_redsupport importance weights and multi-object input;phot_loglikesupports shared model grids and extra chi-square terms;draw_sargainedmax_attempts(its RNG stream changed, so fixed-seed draws differ; the distribution is verified identical).
Performance (equivalence-tested)¶
Grid generation 16.7x per model; photometric-offset calibration ~12x
end-to-end; LOS dust likelihood up to 4.2x (prior transform 51x);
draw_sar 2.6-4.5x; Bayestar() init 8x; galactic prior 1.75x
(logp_extinction ~9x); Isochrone memory roughly halved; EEPTracks
caches ~264 MB smaller (now versioned _cachev2.pkl).
Infrastructure¶
The
docsCI check no longer fails instantly on every pull request: the build was decoupled from the master-only GitHub Pages deployment environment, so PRs now actually build the documentation (deploys still run only from master).
[1.1.1] - 2026-06-27¶
Performance pass on the individual-star BruteForce fitter (loglike_grid ->
logpost_grid -> _fit). Benchmarked on the real MIST grid (grid_mist_v9.h5,
613,530 models) and the real Orion field (Gaia parallaxes), across a cohort
spanning the full selected-model range (4 .. the max_models=50000 cap).
End-to-end ~2.6x faster loglike_grid, ~2.3x faster fit() on a 4-core host
(the parallel kernels scale further with physical cores). Every change is either
verified bitwise-identical in the saved (float32) outputs or proven more
accurate against a high-Nmc reference; see bench/RESULTS.md for the full
methodology and numbers.
Changed¶
Monte-Carlo prior integration in
logpost_gridnow uses antithetic sampling (affects output – more accurate): the per-model prior integral draws the proposal normals in antithetic pairs(z, -z). The estimator stays unbiased; because the integrand is dominated alongln(d)by the galactic-density falloff and the+log(d)volume Jacobian (both monotone in the sampling direction), this cuts the MC-integration variance ~20x and reduces the finite-Nmc(Jensen) bias oflog(mean(w)). Versus anNmc -> infgold standard the per-model log-posterior RMSE drops to ~0.28x and the bias 3-65x, at the sameNmcand half the Gaussian draws. The resulting shift in posterior summaries is within the procedure’s intrinsic Monte-Carlo scatter. An adversarial review confirmed the one regime where antithetic could mildly increase variance (a very precise parallax centered on the photometric proposal mode with poorly-constraining photometry) is empirically negligible for real Gaia data (worst observed std ratio ~1.06) and never introduces bias.mc_essoutput is now a weight-concentration diagnostic, not a strict effective-sample count: under antithetic correlation1 / sum(w_i^2)no longer counts independent samples (it can read up to ~2x optimistic for well-mixed models). It remains monotone and useful as a proposal/target mismatch indicator – low values still flag poor overlap.sample_multivariate_normalgained an optionalantithetickeyword (defaultFalse);logp_galactic_structuregained optionalfeh/logaarray keywords (a fast path for the per-point metallicity/age prior). Existing calls are unaffected.
Performance¶
All of the following are verified bitwise-identical to the prior results in
the saved fit() outputs (any differences are float64 round-off below float32
storage precision that flip no discrete selection):
loglike_grid’s per-model numba kernels (_get_seds,_optimize_fit_mag,_optimize_fit_flux,_get_sed_mle) and the multivariate-normal sampler are parallelized withprange(rows are independent; reductions are exact). The two convergence max-reductions in_optimize_fit_magare parallelized as well.Removed dead computation in
_get_sed_mle(models_int/reddeningwere computed – 4.9Mpowcalls per evaluation – but never read).Fused kernels
_chi2_from_residand_init_mag_residreplace large NumPy temporaries;np.zeros->np.emptyfor fully-overwritten arrays._get_sedsflux conversion usesexp(fac*mag)instead of10**(-0.4*mag)(~1.6x faster on that hot path; agreement ~1e-15).The batched 3x3 covariance regularization uses an analytic symmetric-3x3 minimum-eigenvalue kernel instead of
numpy.linalg.eigvalsh(no less accurate; the regularization decision is unchanged).logpost_gridno longer tiles a structured label array across all MC samples for the default galactic prior (a numpy structured-dtypenp.tileis ~100x slower than the float equivalent; it dominated logpost for large selected-model counts). It now hands the prior the per-pointfeh/logaas plain float arrays. Bitwise-identical; ~1.24x fasterlogpost_gridatNsel=50000(~165 ms/object), no change for small selections.
[1.1.0] - 2026-05-29¶
Maintenance and polish release: verified bug fixes (several affecting numerical output on specific paths), documentation accuracy, test-suite hygiene, and release-process improvements. All fixes ship with regression tests.
Fixed¶
FastNN.encodesilent mis-scaling for 6-sample batches (affects output): a 2-D input of shape(6 params, 6 samples)broadcast against the parameter bounds without error and normalized along the wrong axis, producing silently corrupted SEDs/bolometric corrections only when exactly 6 valid samples were evaluated (reached viaStellarPopsynthesis). Now dispatches on input dimensionality explicitly.los_dustkernels collapsing per-object cloud means (affects output):kernel_gauss/kernel_lorentz/kernel_tophatcollapsed an array-valued mean to a scalar, so line-of-sight fits using a reddening template (template_reds,additive_foreground) evaluated every object against object 0’s cloud mean. Means are now kept broadcastable. The default uniform-cloud path is unchanged.Binary companions no longer discard a valid primary SED (affects binary grid generation):
StarEvolTrack.get_sedsreturned an all-NaN combined SED (dropping the valid primary) when the secondary’s age could not be matched to the unrealistically tight default tolerance. The defaulttolis relaxed1e-6 -> 1e-2dex and a primary-only fallback is used when the companion does not converge. Regenerate binary (smf>0) grids to benefit; single-star fitting and the shipped default grids are unaffected.load_offsetsno longer crashes on a single-filter offsets file.logp_imfno longer raisesZeroDivisionErrorfor a power-law slope of exactly 1.0 (flat-in-log); uses the logarithmic mass integral._fetch(data download) removes a stale/broken symlink before re-linking and falls back to a file copy on filesystems without symlink support.BruteForce.logpost_gridfloors the Monte-Carlo sample count at 1 (a very smallmem_limcould drive it to 0 and crash).BruteForce.loglike_gridguards the dimensional-priorlog(Ndim)term and fails fast with a clear error on a fully-masked object (was an opaqueZeroDivisionErrordeep in the optimizer).cornerplotno longer mutates a caller-suppliedlabelslist in place.dist_vs_redusesinterpolation="none"so binned PDF images show raw bins; fixed a no-op smoothing guard inhist2d.Removed a dead
from scipy import polyfitimport (scipyhas no top-levelpolyfit; the code always usednumpy.polyfit).
Changed¶
Python support: dropped end-of-life Python 3.8 (
requires-python>=3.9); added 3.13 to the classifiers and tooling targets.Dependencies: raised the
numbafloor to>=0.59.0(the old 0.53 floor predatesnumpy>=1.22/2.x support);numpyremains uncapped (the code is numpy-2.0 clean, verified on 2.2).Documentation engine: standardized on a single NumPy-docstring processor (
napoleon); removed the redundantnumpydocextension. The docs now build with zero warnings.Tutorials: standardized on the committed
Orion_l209.1_b-19.9example field; replaced chi2/Nbands quality cuts with goodness-of-fit p-values.
Documentation¶
Corrected numerous stale code examples across the docs and docstrings to match the real API (
StarGrid(models, labels),get_seds3-tuple in magnitudes,magnitude(flux, err),sample_multivariate_normal(size=), the prior call signatures,samps_dred, the data cache path/env var, real filter names, and the maggies flux convention).Added
summary_plotto the API reference; documentedBruteForce.fit’s performance/accuracy parameters (max_models,precision_shrinkage,subsample_mode,R_solar,Z_solar); reframed fit-quality guidance around the p-value methodology.
Testing & packaging¶
Added regression tests covering every fix above; reconciled the
conftestcoverage guidance with what CI actually runs (NUMBA_DISABLE_JIT=1 pytest --cov); the defaultpytestrun no longer enables coverage (which crashed on some WSL/DrvFs paths).Added a tag-triggered PyPI publish workflow (Trusted Publishing); fixed the stale
MANIFEST.indata-file path.
[1.0.0] - 2025-12-08¶
First stable release of brutus following code verification, testing, and documentation improvements.
Added¶
Documentation: Scientific background pages, user guides, API documentation with examples, and ReadTheDocs hosting
Testing: 92% code coverage, 606 tests, GitHub Actions CI/CD with Codecov integration
Code verification: All functions verified for correctness; fixed IMF normalization bug, StarGrid distance reference, and other issues
Changed¶
Development status updated to Production/Stable
Added
tqdmas formal dependencyEnforced Black formatting across codebase
[0.9.0] - 2024-08-28¶
Major refactoring to improve usability and maintainability while preserving scientific functionality.
Added¶
Modern packaging: Migrated to
pyproject.tomlwith black, isort, flake8, mypyTesting: pytest framework with 100+ tests, coverage reporting, multi-platform CI
Modular architecture: Split into
brutus.core,brutus.analysis,brutus.plotting,brutus.dust,brutus.utils,brutus.data,brutus.priorsPerformance: Numba JIT compilation, vectorized operations, improved caching
Changed¶
Minimum Python version: 3.8+ (dropped Python 2.7)
Split large modules (
utils.py,plotting.py) into focused submodulesUpdated all dependencies to modern versions
Fixed¶
Windows/WSL compatibility documentation
Circular imports and module loading issues
Infinite loop bug in
hist2dfunction
Migration¶
Update imports:
# Old
from brutus.seds import Isochrone
from brutus.fitting import BruteForce
# New
from brutus import Isochrone, BruteForce
# or
from brutus.core import Isochrone
from brutus.analysis import BruteForce
All scientific algorithms, file formats, and core APIs remain unchanged.
[0.8.3] - Previous Release¶
Final release using old project structure and Python 2 compatibility.
Features: individual star fitting, cluster modeling, 3D dust mapping, MIST support, neural network SED prediction.
For migration questions or bug reports, see the issue tracker.