def test03_depth_packet_stairs(variant_packet_rgb): from mitsuba.core import Ray3f as Ray3fX, Properties from mitsuba.render import Scene if mitsuba.core.MTS_ENABLE_EMBREE: pytest.skip("EMBREE enabled") props = Properties("scene") props["_unnamed_0"] = create_stairs_packet(11) scene = Scene(props) mitsuba.set_variant("scalar_rgb") from mitsuba.core import Ray3f, Vector3f n = 4 inv_n = 1.0 / (n - 1) rays = Ray3fX.zero(n * n) d = [0, 0, -1] wavelengths = [] for x in range(n): for y in range(n): o = Vector3f(x * inv_n, y * inv_n, 2) o = o * 0.999 + 0.0005 rays[x * n + y] = Ray3f(o, d, 0, 100, 0.5, wavelengths) res_naive = scene.ray_intersect_naive(rays) res = scene.ray_intersect(rays) res_shadow = scene.ray_test(rays) # TODO: spot-check (here, we only check consistency) assert ek.all(res_shadow == res.is_valid()) compare_results(res_naive, res, atol=1e-6)
def check_vectorization(func_str, wrapper = (lambda f: lambda x: f(x)) , resolution = 2): """ Helper routine which compares evaluations of the vectorized and non-vectorized version of a warping routine """ import importlib mitsuba.set_variant('scalar_rgb') func = wrapper(getattr(importlib.import_module('mitsuba.core').warp, func_str)) pdf_func = wrapper(getattr(importlib.import_module('mitsuba.core').warp, func_str + "_pdf")) try: mitsuba.set_variant('packet_rgb') except: pytest.skip("packet_rgb mode not enabled") func_vec = wrapper(getattr(importlib.import_module('mitsuba.core').warp, func_str)) pdf_func_vec = wrapper(getattr(importlib.import_module('mitsuba.core').warp, func_str + "_pdf")) from mitsuba.core import Vector2f # Generate resolution^2 test points on a 2D grid t = ek.linspace(Float, 1e-3, 1, resolution) x, y = ek.meshgrid(t, t) samples = Vector2f(x, y) # Run the sampling routine result = func_vec(samples) # Evaluate the PDF pdf = pdf_func_vec(result) # Check against the scalar version for i in range(resolution): assert ek.allclose(result.numpy()[i, :], func(samples.numpy()[i, :]), atol=1e-4) assert ek.allclose(pdf.numpy()[i], pdf_func(result.numpy()[i, :]), atol=1e-6)
def test03_position_sample_construction_dynamic_slicing(variant_packet_rgb): from mitsuba.render import PositionSample3f n_records = 5 records = PositionSample3f.zero(n_records) records.p = np.array([[1.0, 1.0, 1.0], [0.9, 0.9, 0.9], [0.7, 0.7, 0.7], [1.2, 1.5, 1.1], [1.5, 1.5, 1.5]]) records.time = [0.0, 0.5, 0.7, 1.0, 1.5] # Switch back to scalar variant mitsuba.set_variant('scalar_rgb') from mitsuba.render import PositionSample3f # Each entry of a dynamic record is a standard record (single) assert type(records[3]) is PositionSample3f # Slicing allows us to get **a copy** of one of the entries one_record = records[2] assert records[3].time == 1 \ and ek.allclose(records[3].p, [1.2, 1.5, 1.1]) # Warning! # Since slices create a copy, changes are **not** carried out in the original entry records[3].time = 42.5 assert records[3].time == 1 # However, assigning an entire record works as expected single = PositionSample3f() single.time = 13.3 records[3] = single assert (ek.allclose(records[3].time, 13.3) and ek.allclose(records[3].time, single.time))
def main(args): parser = argparse.ArgumentParser(prog='RenderDocImages') parser.add_argument('--force', action='store_true', help='Force rerendering of all documentation images') parser.add_argument('--spp', default=1, type=int, help='Samples per pixel') args = parser.parse_args() spp = args.spp force = args.force images_folder = os.path.join(os.path.dirname(__file__), '../images/render') os.makedirs(images_folder, exist_ok=True) scenes = glob.glob(os.path.join(os.path.dirname(__file__), '*.xml')) for scene_path in scenes: scene_name = os.path.split(scene_path)[-1][:-4] img_path = os.path.join(images_folder, scene_name + ".jpg") if not os.path.isfile(img_path) or force: print(scene_path) if scene_name in mode_override.keys(): mitsuba.set_variant(mode_override[scene_name]) else: mitsuba.set_variant("scalar_spectral") scene_path = os.path.abspath(scene_path) scene = load_scene(scene_path, spp=spp) render(scene, img_path)
def variants_all_rgb(request): try: import mitsuba mitsuba.set_variant(request.param) except Exception: pytest.skip('Mitsuba variant "%s" is not enabled!' % request.param) return request.param
def fixture(): try: import mitsuba mitsuba.set_variant(variant) except Exception: pytest.skip(f"Mitsuba variant '{variant}' is not enabled!")
def test03_ray_intersect(variant_scalar_rgb): from mitsuba.core import xml, Ray3f, Transform4f # Scalar scene = xml.load_dict({ "type": "scene", "foo": { "type": "rectangle", "to_world": Transform4f.scale((2.0, 0.5, 1.0)) } }) n = 15 coords = ek.linspace(Float, -1, 1, n) rays = [ Ray3f(o=[a, a, 5], d=[0, 0, -1], time=0.0, wavelengths=[]) for a in coords ] si_scalar = [] valid_count = 0 for i in range(n): its_found = scene.ray_test(rays[i]) si = scene.ray_intersect(rays[i]) assert its_found == (abs(coords[i]) <= 0.5) assert si.is_valid() == its_found si_scalar.append(si) valid_count += its_found assert valid_count == 7 try: mitsuba.set_variant('packet_rgb') from mitsuba.core import xml, Ray3f as Ray3fX except ImportError: pytest.skip("packet_rgb mode not enabled") # Packet scene_p = xml.load_dict({ "type": "scene", "foo": { "type": "rectangle", "to_world": Transform4f.scale((2.0, 0.5, 1.0)) } }) packet = Ray3fX.zero(n) for i in range(n): packet[i] = rays[i] si_p = scene_p.ray_intersect(packet) its_found_p = scene_p.ray_test(packet) assert ek.all(si_p.is_valid() == its_found_p) for i in range(n): assert ek.allclose(si_p.t[i], si_scalar[i].t)
def fresolver_append_path(func): """Function decorator that adds the mitsuba project root to the FileResolver's search path. This is useful in particular for tests that e.g. load scenes, and need to specify paths to resources. The file resolver is restored to its previous state once the test's execution has finished. """ if mitsuba.variant() == None: mitsuba.set_variant('scalar_rgb') from mitsuba.core import Thread, FileResolver par = os.path.dirname # Get the path to the source file from which this function is # being called. # Source: https://stackoverflow.com/a/24439444/3792942 caller = getframeinfo(stack()[1][0]) caller_path = par(caller.filename) # Heuristic to find the project's root directory def is_root(path): if not path: return False children = os.listdir(path) return ('ext' in children) and ('include' in children) \ and ('src' in children) and ('resources' in children) root_path = caller_path while not is_root(root_path) and (par(root_path) != root_path): root_path = par(root_path) # The @wraps decorator properly sets __name__ and other properties, so that # pytest-xdist can keep track of the original test function. @wraps(func) def f(*args, **kwargs): # New file resolver thread = Thread.thread() fres_old = thread.file_resolver() fres = FileResolver(fres_old) # Append current test directory and project root to the # search path. fres.append(caller_path) fres.append(root_path) thread.set_file_resolver(fres) # Run actual function res = func(*args, **kwargs) # Restore previous file resolver thread.set_file_resolver(fres_old) return res return f
def test_kernel_dict_check(mode_mono): # Check method raises upon missing scene type kernel_dict = KernelDict({}) with pytest.raises(ValueError): kernel_dict.check() # Check method raises if dict and set variants are incompatible mitsuba.set_variant("scalar_mono_double") with pytest.raises(KernelVariantError): kernel_dict.check()
def test_kernel_dict_construct(): # Object creation is possible only if a variant is set importlib.reload( mitsuba ) # Required to ensure that any variant set by another test is unset with pytest.raises(KernelVariantError): KernelDict() mitsuba.set_variant("scalar_mono") # variant attribute is set properly kernel_dict = KernelDict({}) assert kernel_dict.variant == "scalar_mono"
def test03_solve_quadratic(variant_scalar_rgb): from mitsuba.core import math assert ek.allclose(math.solve_quadratic(1, 4, -5), (1, -5, 1)) assert ek.allclose(math.solve_quadratic(0, 5, -10), (1, 2, 2)) try: mitsuba.set_variant("packet_rgb") from mitsuba.core import math except ImportError: pytest.skip("packet_rgb mode not enabled") assert ek.allclose(math.solve_quadratic(1, 4, -5), ([1], [-5], [1])) assert ek.allclose(math.solve_quadratic(0, 5, -10), ([1], [2], [2]))
def main(): """ Generate reference images for all the scenes contained within the TEST_SCENE_DIR directory, and for all the color mode having their `scalar_*` mode enabled. """ parser = argparse.ArgumentParser(prog='RenderReferenceImages') parser.add_argument( '--overwrite', action='store_true', help= 'Force rerendering of all reference images. Otherwise, only missing references will be rendered.' ) parser.add_argument('--spp', default=256, type=int, help='Samples per pixel') args = parser.parse_args() ref_spp = args.spp overwrite = args.overwrite for scene_fname in scenes: scene_dir = dirname(scene_fname) for variant in mitsuba.variants(): if not variant.split('_')[0] == 'scalar' or variant.endswith( 'double'): continue mitsuba.set_variant(variant) from mitsuba.core import Bitmap, Struct, Thread ref_fname = get_ref_fname(scene_fname) if os.path.exists(ref_fname) and not overwrite: continue Thread.thread().file_resolver().append(scene_dir) scene = mitsuba.core.xml.load_file(scene_fname, parameters=[('spp', str(ref_spp))]) scene.integrator().render(scene, scene.sensors()[0]) film = scene.sensors()[0].film() cur_bitmap = film.bitmap(raw=True).convert(Bitmap.PixelFormat.RGB, Struct.Type.Float32, False) # Write rendered image to a file cur_bitmap.write(ref_fname) print('Saved rendered image to: ' + ref_fname)
def test05_direction_sample_construction_dynamic_and_slicing( variant_packet_rgb): from mitsuba.render import DirectionSample3f, SurfaceInteraction3f import numpy as np np.random.seed(12345) refs = np.array([[0.0, 0.5, 0.7], [1.0, 1.5, 0.2], [-1.3, 0.0, 99.1]]) its = refs + np.random.uniform(size=refs.shape) directions = its - refs directions /= np.expand_dims(np.linalg.norm(directions, axis=1), 0).T pdfs = [0.99, 1.0, 0.05] records_batch = DirectionSample3f.zero(len(pdfs)) records_batch.p = its records_batch.d = directions records_batch.pdf = pdfs records_individual = DirectionSample3f.zero(len(pdfs)) # Switch back to scalar variant mitsuba.set_variant('scalar_rgb') from mitsuba.render import SurfaceInteraction3f, DirectionSample3f, Interaction3f for i in range(len(pdfs)): it = SurfaceInteraction3f() it.p = its[i, :] # Needs to be a "valid" (surface) interaction, otherwise interaction # will be assumed to have happened on an environment emitter. it.t = 0.1 ref = Interaction3f.zero() ref.p = refs[i, :] r = DirectionSample3f(it, ref) r.pdf = pdfs[i] records_individual[i] = r assert ek.allclose(records_batch.p, its) assert ek.allclose(records_batch.p, records_individual.p) assert ek.allclose(records_batch.d, directions) assert ek.allclose(records_batch.d, records_individual.d, atol=1e-6) assert ek.allclose(records_batch.pdf, pdfs) assert ek.allclose(records_batch.pdf, records_individual.pdf) # Slicing: get a copy of one of the entries single = records_individual[2] assert (ek.allclose(single.d, directions[2]) and ek.allclose(single.pdf, pdfs[2]))
def test_mitsuba_run_fail(): kernel_dict = { "type": "scene", "shape": {"type": "rectangle"}, "illumination": {"type": "constant"}, "sensor_0": {"type": "distant"}, "sensor_1": {"type": "distant", "id": "my_sensor"}, "integrator": {"type": "path"}, } # Runner raises if unsupported variant is active import mitsuba mitsuba.set_variant("scalar_rgb") with pytest.raises(KernelVariantError): mitsuba_run(kernel_dict)
def test04_transform_point(variant_scalar_rgb): from mitsuba.core import Transform4f A = np.eye(4) A[3, 3] = 2 assert ek.allclose(Transform4f(A).transform_point([2, 4, 6]), [1, 2, 3]) try: mitsuba.set_variant("packet_rgb") from mitsuba.core import Transform4f as Transform4fX except: return assert ek.allclose( Transform4fX(A).transform_point([[2, 4], [4, 6], [6, 8]]), [[1, 2], [2, 3], [3, 4]])
def test06_transform_normal(variant_scalar_rgb): from mitsuba.core import Transform4f A = np.eye(4) A[3, 3] = 2 A[1, 2] = .5 A[1, 1] = .5 assert ek.allclose(Transform4f(A).transform_normal([2, 4, 6]), [2, 8, 2]) try: mitsuba.set_variant("packet_rgb") from mitsuba.core import Transform4f as Transform4fX except: return assert ek.allclose( Transform4fX(A).transform_normal([[2, 4], [4, 6], [6, 8]]), [[2, 4], [8, 12], [2, 2]])
def execute(self, context): # set path to mitsuba self.set_path(bpy.path.abspath(self.prefs.mitsuba_path)) # Make sure we can load mitsuba from blender try: import mitsuba mitsuba.set_variant('scalar_rgb') except ModuleNotFoundError: self.report({'ERROR'}, "Importing Mitsuba failed. Please verify the path to the library in the addon preferences.") return {'CANCELLED'} print(context.scene.cursor.matrix, context.scene.cursor.location) self.bl_obj_keys = set(context.scene.objects.keys()) self.scene_origin = context.scene.cursor.matrix # send global location self.parse_xml(context, self.filepath) return {'FINISHED'}
def test04_put_packets_basic(variant_scalar_rgb): from mitsuba.core import srgb_to_xyz try: mitsuba.set_variant("packet_rgb") from mitsuba.core.xml import load_string from mitsuba.render import ImageBlock except ImportError: pytest.skip("packet_rgb mode not enabled") # Recall that we must pass a reconstruction filter to use the `put` methods. rfilter = load_string("""<rfilter version="2.0.0" type="box"> <float name="radius" value="0.4"/> </rfilter>""") im = ImageBlock([10, 8], 5, filter=rfilter) im.clear() n = 29 positions = np.random.uniform(size=(n, 2)) positions[:, 0] = np.floor(positions[:, 0] * (im.width() - 1)).astype(np.int) positions[:, 1] = np.floor(positions[:, 1] * (im.height() - 1)).astype(np.int) # Repeat some of the coordinates to make sure that we test the case where # the same pixel receives several values positions[-3:, :] = positions[:3, :] spectra = np.arange(n * 3).reshape((n, 3)) alphas = np.ones(shape=(n, )) border = im.border_size() ref = np.zeros(shape=(im.height() + 2 * border, im.width() + 2 * border, 3 + 1 + 1)) for i in range(n): (x, y) = positions[i, :] + border ref[int(y), int(x), :3] += srgb_to_xyz(spectra[i, :]) ref[int(y), int(x), 3] += 1 # Alpha ref[int(y), int(x), 4] += 1 # Weight # Vectorized `put` im.put(positions + 0.5, [], spectra, alphas) check_value(im, ref, atol=1e-6)
def test04_seed_vectorized(variant_scalar_rgb, sampler): """For a given seed, the first lane of a sampled packet should be equal to the sample of a scalar independent sampler.""" try: mitsuba.set_variant('packet_rgb') except: pytest.skip("packet_rgb mode not enabled") from mitsuba.core.xml import load_string sampler_p = load_string("""<sampler version="2.0.0" type="independent"> <integer name="sample_count" value="%d"/> </sampler>""" % 8) for seed in range(10): sampler.seed(seed) sampler_p.seed(seed) assert sampler.next_1d() == sampler_p.next_1d()[0] assert ek.allclose(sampler_p.next_2d(), ek.dynamic.Vector2f(sampler.next_2d()))
def make_reference_renders(): mitsuba.set_variant('scalar_rgb') from mitsuba.core import Bitmap, Struct """Produces reference images for the test scenes, printing the per-channel average pixel values to update `scenes.SCENE_AVERAGES` when needed.""" integrators = { 'depth': make_integrator('depth'), 'direct': make_integrator('direct'), 'full': make_integrator('path'), } spp = 32 averages = {n: {} for n in SCENES} for int_name, integrator in integrators.items(): for scene_name, props in SCENES.items(): scene = props['factory'](spp=spp) sensor = scene.sensors()[0] film = sensor.film() status = integrator.render(scene, sensor) # Extract per-channel averages converted = film.bitmap(raw=True).convert(Bitmap.PixelFormat.RGBA, Struct.Type.Float32, False) values = np.array(converted, copy=False) averages[scene_name][int_name] = np.mean(values, axis=(0, 1)) # Save files fname = "scene_{}_{}_reference.exr".format(scene_name, int_name) film.set_destination_file(os.path.join("/tmp", fname)) film.develop() print('========== Test scenes: per channel averages ==========') for k, avg in averages.items(): print("'{}': {{".format(k)) for int_name, v in avg.items(): print(" {:<12}{},".format("'{}':".format(int_name), list(v))) print('},')
def set_mode(mode_id: str): """ Set Eradiate's operational mode. This function sets and configures Eradiate's operational mode. Eradiate's modes map to Mitsuba's variants and are used to make contextual decisions when relevant during the translation of a scene to its kernel format. .. admonition:: Valid mode IDs :class: info * ``mono`` (monochromatic mode, single precision) * ``mono_double`` (monochromatic mode, double-precision) * ``ckd`` (CKD mode, single precision) * ``ckd_double`` (CKD mode, double-precision) * ``none`` (no mode selected) Parameters ---------- mode_id : str Mode to be selected (see list below). Raises ------ ValueError ``mode_id`` does not match any of the known mode identifiers. """ global _current_mode if mode_id in _mode_registry: mode = Mode.new(mode_id) mitsuba.set_variant(mode.kernel_variant) elif mode_id.lower() == "none": mode = None else: raise ValueError(f"unknown mode '{mode_id}'") _current_mode = mode
import os import numpy as np import mitsuba import time from math import * import sys mitsuba.set_variant('gpu_rgb') from mitsuba.core import Bitmap, Struct, Thread from mitsuba.core.xml import load_file import xml_util def print_time(elapsed_time): f, i = modf(elapsed_time) h = int(i / 3600) m = int((i % 3600) / 60) s = int(i - h * 3600 - m * 60) ms = f print("Rendering time: {}h {}m {}s {:.2f}ms".format(h, m, s, ms)) def out_config(dir, config): filepath = os.path.join(dir, 'config.txt') with open(filepath, mode="w") as f: for k, v in config.items(): line = k + ' : ' + str(v) + '\n' f.write(line) f.close()
import os import enoki as ek import mitsuba # Set the desired mitsuba variant mitsuba.set_variant('packet_rgb') from mitsuba.core import Float, UInt64, Vector2f, Vector3f from mitsuba.core import Bitmap, Struct, Thread from mitsuba.core.xml import load_file from mitsuba.render import ImageBlock # Absolute or relative path to the XML file filename = 'path/to/my/scene.xml' # Add the scene directory to the FileResolver's search path Thread.thread().file_resolver().append(os.path.dirname(filename)) # Load the scene scene = load_file(filename) # Instead of calling the scene's integrator, we build our own small integrator # This integrator simply computes the depth values per pixel sensor = scene.sensors()[0] film = sensor.film() sampler = sensor.sampler() film_size = film.crop_size() spp = 32 # Enumerate discrete sample & pixel indices, and uniformly sample # positions within each pixel.
sys.path.append("./myscripts/vae") sys.path.append("./myscripts/gen_train") from time import time import torch import mitsuba from mitsuba.heightmap import HeightMap import render_config as config import numpy as np from multiprocessing import Pool from data_handler import clip_scaled_map import utils mitsuba.set_variant(config.variant) from mitsuba.core import ScalarTransform4f, ScalarVector3f class BSSRDF_Data: """ Class for handling BSSRDF data Use this class by meshes class in dict/mesh_dict.py """ def __init__(self): self.bssrdf = {} self.mesh = {} self.bssrdf_obj = {} self.mesh_map = []
# Simple inverse rendering example: render a cornell box reference image, # then replace one of the scene parameters and try to recover it using # differentiable rendering and gradient-based optimization. (PyTorch) import enoki as ek import mitsuba mitsuba.set_variant('gpu_autodiff_rgb') from mitsuba.core import Thread, Vector3f from mitsuba.core.xml import load_file from mitsuba.python.util import traverse from mitsuba.python.autodiff import render_torch, write_bitmap import torch import time Thread.thread().file_resolver().append('cbox') scene = load_file('cbox/cbox.xml') # Find differentiable scene parameters params = traverse(scene) # Discard all parameters except for one we want to differentiate params.keep(['red.reflectance.value']) # Print the current value and keep a backup copy param_ref = params['red.reflectance.value'].torch() print(param_ref) # Render a reference image (no derivatives used yet) image_ref = render_torch(scene, spp=8) crop_size = scene.sensors()[0].film().crop_size()
extensions = [] extensions.append('sphinx.ext.autodoc') # extensions.append('sphinx.ext.autodoc.typehints') # autodoc_typehints = 'description' autodoc_default_options = {'members': True} autoclass_content = 'both' autodoc_member_order = 'bysource' # Set Mitsuba variant for autodoc import mitsuba mitsuba.set_variant('scalar_rgb') import mitsuba.python # -- Event callback for processing the docstring ---------------------------------------------- import re # Default string for members with no description param_no_descr_str = "*no description available*" # Default string for 'active' function parameter active_descr_str = "Mask to specify active lanes." # List of function parameters to not add to the docstring (will still be in the signature) parameters_to_skip = ['self'] # Used to differentiate first and second callback for classes last_class_name = ""
""" utility functions for renderign""" import sys import os sys.path.append("myscripts/gen_train") import datetime import pandas as pd import numpy as np import mitsuba import enoki as ek import torch mitsuba.set_variant("gpu_rgb") from mitsuba.core import Float, warp, Color3f, srgb_to_xyz from mitsuba.core import Bitmap, Struct from mitsuba.render import ImageBlock def index_spectrum(spec, idx): m = spec[0] m[ek.eq(idx, 1)] = spec[1] m[ek.eq(idx, 2)] = spec[2] return m def resample_wo(sampler, active): """ Sample outgoing direction and pdf with cosine weighted sampling"""
import os import numpy as np import mitsuba mitsuba.set_variant("scalar_rgb") from mitsuba.core import xml, Thread Thread.thread().file_resolver().append( os.path.dirname(__file__) + '../../../common') m = xml.load_string(""" <shape type="ply" version="0.5.0"> <string name="filename" value="meshes/bunny.ply"/> </shape> """) m.add_attribute("face_color", 3, np.random.rand(3 * m.face_count())) m.add_attribute("vertex_color", 3, np.random.rand(3 * m.vertex_count())) m.write_ply("bunny_attribute_color.ply")
def variants_all(request): try: mitsuba.set_variant(request.param) except: pytest.skip("%s mode not enabled" % request.param) return request.param
def test03_ray_intersect(variant_scalar_rgb): if mitsuba.core.MTS_ENABLE_EMBREE: pytest.skip("EMBREE enabled") from mitsuba.core.xml import load_string from mitsuba.core import Ray3f # Scalar scene = load_string("""<scene version="2.0.0"> <shape type="rectangle"> <transform name="to_world"> <scale x="2" y="0.5" z="1"/> </transform> </shape> </scene>""") n = 15 coords = ek.linspace(Float, -1, 1, n) rays = [ Ray3f(o=[a, a, 5], d=[0, 0, -1], time=0.0, wavelengths=[]) for a in coords ] si_scalar = [] valid_count = 0 for i in range(n): # print(rays[i]) its_found = scene.ray_test(rays[i]) si = scene.ray_intersect(rays[i]) assert its_found == (abs(coords[i]) <= 0.5) assert si.is_valid() == its_found si_scalar.append(si) valid_count += its_found assert valid_count == 7 try: mitsuba.set_variant('packet_rgb') from mitsuba.core.xml import load_string as load_string_packet from mitsuba.core import Ray3f as Ray3fX except ImportError: pytest.skip("packet_rgb mode not enabled") # Packet scene_p = load_string_packet("""<scene version="2.0.0"> <shape type="rectangle"> <transform name="to_world"> <scale x="2" y="0.5" z="1"/> </transform> </shape> </scene>""") packet = Ray3fX.zero(n) for i in range(n): packet[i] = rays[i] si_p = scene_p.ray_intersect(packet) its_found_p = scene_p.ray_test(packet) assert ek.all(si_p.is_valid() == its_found_p) for i in range(n): assert ek.allclose(si_p.t[i], si_scalar[i].t)