Changelog
Release history for the deism package. This page is the canonical
changelog; update it when cutting a release.
2.3.1.1
Playground fixes on top of 2.3.1; the solver is unchanged.
Run validation lights the right pipeline stage again. The 2.3.1 refactor that shared the parameter validation with the script export collapsed every validation failure onto the generic parameters chip. Room-geometry errors (unusable vertex set, vertex inside the hull of the others, convex-hull build failure) mark update_room again, a source or receiver outside the room marks update_source_receiver, and T60 input on a convex room marks update_wall_materials, as in 2.3.0. Form-field errors keep the parameters chip.
Saved-result path matches the results folder. The launcher archives each run under a resolved path, so it shares the prefix of the
resultsDirit hands the page and the run card shows the shortenedresults/<run>/result.jsonform even when the playground root is reached through a symlink (for example/tmpor/varon macOS) or a relative path.Git LFS pointer files never win over a usable dataset. Both the working-directory lookup of a sampled-directivity MAT file and the fallback search (
DEISM_DATA_DIR, packaged examples tree, playground download cache) now skip unfetched LFS pointer files and continue to the next candidate. A clone withoutgit lfs pulltherefore loads the copy thatdeism-playgrounddownloaded instead of failing with “Missing LFS data”. An explicitdata_dirstill reports its own file.Run-card help text reworded;
demo.html/native.htmlrebuilt from the sources.Tests: browser workflow tests assert the marked stage for each validation case;
tests/test_playground_datasets.pycovers the LFS pointer skip in the environment directory and in the working directory;tests/test_playground_result_storage.pycovers the resolved archive path for a relative results directory.Packaging is unchanged:
DATASET_RELEASEkeeps pointing at the v2.3.0 release assets and the JavaScript engine (2.3.0-js) is untouched.
2.3.1
Playground: export Python scripts. The run card gains Set up and Last run buttons. Set up writes an editable script for the validated current controls without running; Last run writes the settings captured when the last successful run was dispatched, unaffected by later edits, preset changes or failed runs. The script rebuilds the simulation through the public
DEISMAPI: room geometry (convex wall centers keep their material rows; vertices are world coordinates, the rotation only turns the acoustic frames), per-wall materials, sampled directivities resolved to the original MAT files (DATA_DIR/DEISM_DATA_DIR, never downloaded), frequency settings, optional fluctuations and RTF/RIR output. Running it computes the result in memory and plots it; it writes no files.The launcher (
deism-playground) saves exports through a newPOST /export-scriptendpoint intoplayground/scripts/of its writable playground root (git-ignored) asdeism_setup.py/deism_lastrun.pywith numbered suffixes for repeats, never overwriting; the page and the console print the saved path. The standalonedemo.htmlcannot write to a project folder and says so.Unfixed fluctuation seeds are drawn on the page before a run is dispatched, so the seed used is recorded in the result archive and in the Last run export; exporting a Set up with unfixed fluctuations draws a seed of its own. Offline JavaScript runs use a different solver and random generator, so their exports reproduce the settings, not the numbers.
Tests:
tests/test_playground_python_export.pyexecutes browser-generated scripts against the native adapter (grids exact, complex RTF/RIR atrtol=1e-6), including a 32-case MIX matrix over room type, output mode, fluctuations and source/receiver directivity, plus seed reproducibility in both workflows.tests/test_playground_result_storage.pycovers the save endpoint.Packaging is unchanged: the sampled-directivity datasets remain attached to the v2.3.0 GitHub release and
DATASET_RELEASEkeeps pointing there; the JavaScript engine (2.3.0-js) is untouched.
2.3.0
Packaging (datasets): the sampled-directivity MAT datasets (186 MB) are no longer part of the wheels or the sdist. They stay in the repository under Git LFS and are attached to the GitHub release;
deism-playgrounddownloads the supported datasets on first use into~/.cache/deism/sampled_directivity(SHA-256 verified againstplayground/catalog.json) and reports the directory it uses. Lookup order:--data-dir/DEISM_DATA_DIR, a checkout in the working directory (or an editable install), then the cache;--no-downloadand--clear-cachecontrol the copy. Missing datasets are listed as “not downloaded” in the selector instead of aborting the launcher, and the Python loaders fall back toDEISM_DATA_DIRand the cache after the local and packaged example trees (deism/playground_datasets.py).Behaviour change: RIR synthesis (wrap-around and pre-ringing fixed). Every impulse response changes: the bandpass window is applied with minimum phase by default and the grid resolves min(T60, RIRLength).
get_resultspreviously synthesised the impulse response on a frequency grid whose inverse FFT is periodic over exactly T60 and shaped it with a zero-phase bandpass window. The window’s pre-ringing (about 20 ms for the 45 Hz low-frequency transition) lay at negative times and folded onto the end of the period, and energy before the direct sound appeared at about -36 dB below the peak in the shoebox RIR example. Now:params["rirWindowPhase"](YAMLSignal.RIRWindowPhase) selects how the bandpass window is applied."minimum"(new default) applies it with minimum phase, computed by the real-cepstrum method: the response is causal, so nothing precedes an arrival and nothing folds across the period, at no extra cost."zero"keeps the symmetric pulses of the previous behaviour on a grid extended by a guard interval (rir_guard_interval: the lag after which the window’s impulse response stays 100 dB below its peak, 46 ms at 44.1/48 kHz), which is discarded after the inverse FFT."none"(also the legacybandpass_window=False) skips the window and now zeroes the Nyquist bin, which used to leave an alternating floor at about -50 dB.The RIR grid and the image set resolve
rirPeriod= min(T60,RIRLength): a shorter RIRLength costs fewer bins and images (image_time_limit; convex rooms now drop the images whose path exceedsc * rirPeriodafter the libroom search, as the shoebox search already did, since a later arrival would fold into the period); a longer one is zero-padded beyond T60 and reported (console, playground warning).params["rirPeriod"]andparams["rirGuard"]record the synthesis span and guard.get_resultsleavesparams["RTF"]untouched (it used to window it in place, so a second call windowed twice); the unusedparams["nSamples"]is gone; the window constants are inDEFAULT_RIR_WINDOW(params["rirWindow"]overrides them) and the window takes the true Nyquist frequency.The playground mirrors all of this (engine
2.3.0-js): an RIR window selector next to the RIR length, the synthesis span and guard in the run details, and golden fixtures for the three phases (python tools/playground_fixtures.py --rir-only).
Playground and dataset reliability: sampled-directivity loaders detect Git LFS pointers with an actionable error. Lightweight test checkouts skip original-data tests, while the LFS-enabled playground job requires all supported datasets and runs both original-data and preset/example solve checks.
Generated previews and saved results fall back to a user cache when the installed playground assets are read-only. HTTP tests use isolated preview fixtures and do not depend on generated files or original MAT datasets.
Preset regression tests compare geometry/materials at full settings and RTF/RIR solves at reduced reflection order against the example workflows. The shoebox RIR presets already carry the examples’ explicit T60 of 1 s; their numerical settings are unchanged. Unavailable solve data is reported as a skip; unexpected loader errors fail the tests.
Performance (convex rooms / DEISM-ARG): the whole convex pipeline is faster while producing bit-identical images, coefficients and RTFs (verified on 16 reference cases: orders 5-15, SH 3/5/7, ORG/LC/MIX, monopole, rotated room, three geometries):
Image finding: the libroom image-source DFS prunes subtrees whose generating-wall beam is empty (beam tracing with a conservative margin; every surviving candidate still passes the unchanged visibility test), and no longer copies a vector per visibility recursion level or inserts reflection matrices at the front of a vector.
engine.beam_pruning(defaultTrue),engine.beam_marginand the countersdfs_nodes_visited/dfs_subtrees_prunedare exposed on the C++ room engine. Rebuild the extension (python setup.py build_ext --inplace).Source directivity refit (
cal_C_nm_s_arg, fast path): batched spherical-harmonic evaluation and LAPACK solves, Hankel division inside the batch, direct complex64 output (newout_dtypeargument, default unchanged), and one fit per distinct reflection matrix (bit-exact grouping;params["directivityRefitReuseIdentical"],params["directivityRefitBatchImages"],params["directivityRefitUniqueBudgetMiB"]). No full-size complex128 intermediate remains.Wigner 3j tables: exact-rational Racah evaluation with a small process cache instead of one
sympycall per coefficient (params["wignerMethod"] = "sympy"keeps the reference path); tables are bit-identical after the complex64 cast.Vectorization of the LC/MIX coefficient arrays is a single gather.
LC solver kernel: image-major coefficient batches (
params["numbaArgLcBatchImages"], default 512) instead of strided reads across all images; the summation order is unchanged.
Measured on the IWAENC Fig. 5 room at reflection order 15 (MIX, SH 5/5, 491 frequencies, 4 cores): image finding 29.4 s → 0.23 s, directivity update 39.1 s → 1.4 s, solve 16.2 s → 3.8 s, whole run 84.8 s → 5.5 s, peak RSS 4.9 GB → 2.1 GB.
benchmarks/bench_convex_directivity_images.pytimes each stage and compares two checkouts.Fix (convex rooms with directional transducers):
init_source_directivities_ARGandinit_receiver_directivities_ARGpassedorientSource/orientReceiverin degrees straight into the Z-X-Z rotation matrix, whereas the shoebox path converts to radians first. Convex-room results with a non-zero orientation therefore used a wrong facing direction (for example the default receiver orientation of 180 degrees was applied as 180 radians). Both functions now convert to radians, matching the documented degree convention and the shoebox path.New browser playground (
playground/): a single offlinedemo.htmlrunning a JavaScript port of the solver with a live reduced-setting preview and an exact accurate tier in a Web Worker. The port is validated against the Python package by golden tests (tools/playground_fixtures.py,playground/engine/test).Playground update: the result panel shows one room transfer function and, in RIR mode, one impulse response, animated from the live preview into the accurate result (and back when the result goes stale) instead of overlaying both tiers and a difference plot. Convex rooms gain add/remove vertex controls with a convexity check. The parameter panel scrolls on its own next to the simulation panels. An Examples card loads the parameters of the example scripts (shoebox and convex base cases, JASA 2024 Fig. 8, IWAENC 2024 Fig. 5/6, the fluctuation examples); the small spherical loudspeaker datasets ship with the page for them. The JavaScript engine gains
updateFluctuationsand evaluates wall attenuation per image on demand so 24 kHz RIR grids fit in browser memory.benchmarks/playground_presets_bench.{mjs,py}run the presets through both the JavaScript engine and the Python package and record the comparison (agreement to 5e-5 with identical image sets, timings per preset). Engine fixes found by that comparison: the shoebox image set is bounded by reflection order and c·T60 only, like the defaultv2-numbabackend (the port truncated at n1..n3 like the legacy backend); a given T60 is kept exactly so the 1/T60 RIR grid matches; the convex visibility test carries the libroom tolerance; the ORG kernel evaluates each spherical harmonic once per image (2.6× faster).
2.2.1.16
New optional atmospheric path-length fluctuations for both room types and all DEISM methods.
DEISM.update_fluctuations(), called afterupdate_source_receiver()and beforerun_DEISM(), perturbs the lengthrof every image path byc * N(t * drift, sqrt(t) * volatility)witht = r / c; angles and wall attenuation are unchanged. The parameters areEnvironment.drift(dimensionless fractional delay bias),Environment.volatility(delay random-walk standard deviation in s^(1/2)) andEnvironment.fluctuationSeed(non-negative integer for reproducible draws,nullfor a fresh draw), with matching-drift,-volatilityand-fluctuationSeedcommand-line flags. Configs without the keys keep loading with the feature off, and the defaults leave results unchanged. Each call re-samples on the current images (the previous draw is removed first), so a sweep over volatilities can reuse one image set withrun_DEISM(if_clean_up=False); regenerating the images or the defaultrun_DEISM()cleanup discards the stored draw. Non-finite parameters,drift <= -1or a draw that would make a path length non-positive raiseValueError. Seeexamples/deism_volatility_example.py(shoebox) andexamples/deism_arg_volatility_example.py(convex).
2.2.1.15
Compact image storage is now the default for both room types (
convexCompactImages=1,shoeboxCompactImages=1): image generation emits frequency-independent path geometry and the frequency-dependent attenuation is rebuilt from it by a parallel numba kernel, so image generation no longer scales with the number of frequencies. The rebuild happens at a different point in each room type: convex rebuilds once inget_ref_paths_ARGand still providesimages["atten_all"], while shoebox rebuilds per solver batch and never holds the full(n_images, n_freqs)array. Set either flag to0for the previous materialized behavior.Behavior change for convex rooms with complex impedance: compact mode applies complex impedance exactly, whereas the legacy C++ attenuation truncated it to its real part. Results with complex impedance therefore shift by up to order 1e-2 relative to earlier releases — this is a correction, and the legacy value is reproducible with
convexCompactImages=0. With real impedance the two agree within the 1e-5 end-to-end bound asserted by the test suite, and measure 1e-7 to 1e-8 in practice (float32 epsilon) on rooms whose incidence cosines are all non-negative.For convex rooms the compact geometry is produced by the libroom C++ engine by default (
convexCompactEngine="cpp";"python"selects the reference producer). Measured on the bundled comparison example at reflection order 10: image preparation 2.7x faster, whole workflow 3.5x.For shoebox rooms,
images["atten_all_early"]andimages["atten_all_late"]are absent in compact mode; code reading them directly must setshoeboxCompactImages=0. The legacy Ray shoebox backend cannot consume compact storage and now raises with a pointer to the numba backend, which is the supported path.examples/deism_arg_singleparam_example.pyis a plain convex-room demo again; the compact-vs-legacy comparison moved to the newexamples/deism_arg_compact_compare.py.Python 3.10 is now the minimum supported version. Release wheels are built and tested for Python 3.10, 3.11, and 3.12 on Linux, Windows, and Apple Silicon macOS.
The shoebox reflection counter is now a regular pybind11 extension built by the platform-native toolchain and verified alongside
libroom_deism.CI now runs the critical suite on Python 3.12 across all three operating systems, on both pull requests and pushes to
main.Release artifacts are built and tested with cibuildwheel, aggregated into a single validated publish job, and rehearsed without upload for prereleases.
The four default YAML configurations under
examples/are bundled as package resources, so an installed wheel or sdist works outside a repository checkout while repository-local configurations retain precedence.The conda development environment (
deism_env.yml) moved to Python 3.12; the stale Python 3.9 lock filedeism_env_exact.ymlwas removed.