Skip to content
#edge computing Open access

guv-calcs v0.8.0: An open-source Python library for modeling germicidal UV in indoor environments

Oct 2026 · Zenodo (CERN European Organization for Nuclear Research)
Infection Control and Ventilation

Abstract

guv-calcs is an open-source Python scientific computing library for physically based modeling of germicidal ultraviolet (GUV) light in indoor environments, with a focus on far-UVC (200–235 nm) applications. The library provides core numerical tools for representing lamps, spatial geometry, calculation planes and volumes, and exposure metrics such as irradiance, fluence rate, and cumulative dose. The package implements vectorized radiative transfer calculations using measured photometric data (IES/ULD files), supports flexible three-dimensional room geometries, and is designed for integration with higher-level tools such as visualization dashboards, airflow models, and risk frameworks for airborne pathogen control. The codebase emphasizes reproducibility, transparency, and modularity so that researchers, engineers, and manufacturers can inspect, extend, and validate the underlying calculations. guv-calcs forms the computational backbone of the broader Illuminate ecosystem, but is usable as a standalone library for custom analyses, batch simulations, and method development. The project aims to improve rigor and accessibility in far-UVC modeling by providing a free, auditable, and community-maintained alternative to proprietary software. v0.8.0 Release Notes Added Lamp(photometric_axis=...) / Lamp.set_photometric_axis(): declare where the beam points in the IES file's frame ("down", "up", "horizontal_0/90/180/270"). The aim always means the beam; file dimensions are permuted into the aim frame; serialized in to_dict Fixture.photometric_depth: how far the photometric center sits behind the emitting face, so a housing can be centered on the point LengthUnits.abbreviation ("m", "ft", "in", "cm", "mm", "yd"), LengthUnits.is_metric and LengthUnits.decimals (default display precision per unit), plus round_length(units, *values); "yd" is accepted as an alias for yards LampPlacer accepts units= (for_dims takes them from the RoomDimensions) and scales its physical placement distances (5 cm wall inset, 10 cm ceiling drop, 2 cm fixture margin, occupancy tolerances) into the room's units, so corner/edge/downlight placement behaves the same in centimeters, inches or millimeters as in meters Room(x, y, z, origin=(x0, y0)) places a rectangular room with its minimum corner at (x0, y0) instead of the origin (x/y ranges given as tuples are shifted too) generate_report() prints an Origin row (x_min, y_min) when the floor plan does not start at the origin; the Dimensions row is the bounding-box extents generate_report() lists the room's objects (id, name, shape, size, base centre, rotation, reflectance, transmittance, enabled) and the footprint vertices of extruded objects Object.set_num_points(num_x, num_y, face=None) sets per-axis grid counts per face or for every face, matching Room.set_reflectance_num_points; a single positional count still gives a square grid. Per-face counts survive set_dimensions/convert_units and round-trip through to_dict (face_grids) Changed Housing bounding box shows a 3D luminous opening (surface height) in both directions along the aim axis instead of only in front create_standard_zones() rounds converted plane heights to the unit's display precision (1.8 m -> 70.9 in, 180 cm, 1800 mm), matching the hand-rounded feet values Project(units=...) validates the unit through LengthUnits and stores the canonical token Lamp.set_units() no longer rewrites the IES header's unit code; the IES file keeps its native feet/meters and the lamp surface converts at the boundary (set_width/set_length write the IES value in the IES's own units) generate_report() no longer repeats the unit on every floor plan vertex row; the unit is given once, in the Dimensions row Fixed generate_report() labeled floor area and volume as m 2 / m 3 for every unit other than feet; the labels now use the room unit's abbreviation (cm 2, in 3, ...) Room.get_efficacy_data() reported CADR in cfm for millimeter rooms; metric units (m, cm, mm) now all get lps A lamp restored from to_dict()/a .guv file into a non-meter room had its emissive surface width/length re-read from the IES file in meters but labeled in the room's units (e.g. 0.06 "feet"); LampSurface.set_ies now converts IES dimensions into the surface's units Grid axes could lose a point after a unit conversion when span / spacing landed a hair under an integer (7.9999999 -> 7); the point count now tolerates float noise RoomPlotter sized photometric webs with the conversion the wrong way round (room units -> meters instead of meters -> room units), so webs in feet rooms drew too small SurfaceGrid.x1/x2/y1/y2 (mins/maxs) for a polygon grid ignored the grid's origin, so a plane built from a floor outline that does not touch the axes (e.g. a traced room starting at (2, 12)) reported extents shifted to (0, 0); standard zones in such rooms were drawn in the wrong place by clients that position them from these extents. VolumeGrid was already correct Object side faces had their normals pointing into the object (SurfaceGrid.from_wall gives an inward normal for a CCW edge, which is the room convention), so object reflectance contributed nothing to zones below R=1 and diverged at R=1 as the inward-facing faces bounced light inside a closed cavity. Walls are now built with outward normals, and every object face uses use_normal so it cannot be lit from behind Changing the occluder set (an object added, moved, disabled or removed) did not invalidate a zone's cached per-lamp values, so a soft calculate() after disabling an object kept the stale shadowed result. LightingCalculator.compute now recomputes every lamp when the occluder geometry differs from the cached scene state Direct incidence on room surfaces and object faces is now shadowed by the other surfaces when occlusion is active (ReflectanceManager.calculate_incidence gained enable_occlusion, which Room.calculate passes), so a wall behind an opaque object no longer reflects light it never received

View source

Similar papers

#computer vision Review Sep 2017

Agile Software Development Methods: Review and Analysis

This publication proposes a definition and a classification of agile software development approaches and analyses ten software development methods that can be characterized as being "agile" against the defined criterion.

P. Abrahamsson, O. Salo, Jussi Ronkainen et al. · 727 citations · ⚡54
#computer vision Jun 2008

The impact of agile practices on communication in software development

The study shows that agile practices improve both informal and formal communication, but indicates that, in larger development situations involving multiple external stakeholders, a mismatch of adequate communication mechanisms can sometimes even hinder the communication.

M. Pikkarainen, Jukka Haikara, O. Salo et al. · 401 citations · ⚡48
#machine learning Review Open access Oct 2014

Software development in startup companies: A systematic mapping study

The results indicate that software engineering work practices are chosen opportunistically, adapted and configured to provide value under the constrains imposed by the startup context.

Nicolò Paternoster, Carmine Giardino, M. Unterkalmsteiner et al. · 394 citations · ⚡54

Related blog posts

Microsoft Research Blog Oct 6, 2026

What AI gets wrong and what failure teaches us

Jennifer Neville did not want to go into computer science—but that’s exactly where she landed. Neville discusses the starts and stops that led to her professional sweet spot and her work identifying “surprising failures” making it hard for AI to handle complexity.  The post What AI gets wrong and what failure teaches us appeared first on Microsoft Research.

We use cookies to run the site and, with your consent, for analytics and to show ads. See our Cookie Policy.