Medium visualization ==================== Interactive three-dimensional volumes ------------------------------------- Start with the interactive volumes below. Drag to rotate, scroll to zoom, and hover to inspect the refractive index. Plotly is included with BornSim and is the default for 3D views. Angular and polar result plots use Matplotlib. Interactive random-medium volumes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For random media, a volume view reveals connected high refractive index regions throughout the sample. Set ``field="refractive_index"`` and ``opacity_scale="increasing"`` to make low refractive index contours transparent and higher refractive index contours increasingly opaque. ``opacity`` sets the maximum contour opacity. This emphasizes high absolute refractive index, rather than large positive and negative fluctuations equally. Opacity is a display setting; it does not represent material absorption. The opacity ramp uses each sample's displayed refractive index range, so equal opacity in two different samples need not represent the same refractive index. The following views use the same Gaussian and exponential samples as :doc:`auto_examples/random_media/random_medium`: seed 42, 16 voxels per axis, 25 nm spacing, refractive index standard deviation 0.01, and correlation length 75 nm. Drag to rotate and scroll to zoom. Each view includes Plotly and works offline. Interactive views are generated during the documentation build alongside interactive gallery examples. Gaussian covariance ^^^^^^^^^^^^^^^^^^^ .. raw:: html Exponential covariance ^^^^^^^^^^^^^^^^^^^^^^ .. raw:: html .. code-block:: python from bornsim.units import ureg from bornsim import Grid from bornsim.medium.random_medium import GaussianMedium grid = Grid( shape=(16, 16, 16), spacing=2.5e-08 * ureg.meter, ) medium = GaussianMedium( refractive_index_std=0.01, correlation_length=7.5e-08 * ureg.meter, background_refractive_index=1.33, ) volume = medium.to_volume( grid=grid, seed=42, ) figure = volume.plot_3d( mode='volume', field='refractive_index', surface_count=16, opacity=0.2, opacity_scale='increasing', ) figure.write_html(file='medium.html', include_plotlyjs=True) Plotly also supports ``mode="isosurface"``; its default mode is ``volume``. Uniform fields render as volume data; isosurface mode requires a non-uniform field. The HTML export includes Plotly itself and works offline. Call ``figure.show()`` to display a Plotly figure. See its official `3D isosurface documentation `_ and `rendering documentation `_. Three-dimensional media ----------------------- ``Volume.plot_3d()`` returns a Plotly volume by default for generated random fields, structured samples, and custom voxel arrays. Call ``figure.show()`` to display it or ``figure.write_html()`` to export an interactive view. Select ``backend="matplotlib"`` for static figures, then use ``plt.show()`` or ``figure.savefig()``. The gallery embeds interactive 3D views alongside their plotting cells. Constructing a figure opens no window. Secondary views: voxel cross-sections -------------------------------------- ``Volume.plot_slice()`` builds a Matplotlib figure with physical axis labels, an equal spatial aspect ratio, and a colorbar. It handles axis orientation and voxel-edge extents automatically. The default plane is the middle z voxel; on an even grid its centre is at positive half-spacing, not at zero. .. code-block:: python from bornsim.units import ureg from bornsim import Grid, Sphere, StructuredMedium structure = StructuredMedium() structure.add_background(refractive_index=1.33) sphere = Sphere( radius=1.5e-07 * ureg.meter, refractive_index=1.34, ) structure.add_structures(sphere) grid_configuration_1 = Grid( shape=(24, 24, 24), spacing=2e-08 * ureg.meter, ) volume = structure.to_volume(grid=grid_configuration_1) figure = volume.plot_slice( normal='z', field='refractive_index', length_unit='nanometer', ) figure.savefig('sphere-slice.png') Use ``normal="x"`` or ``normal="y"`` for other orientations and ``index`` to select a voxel plane. Field choices match ``plot_3d()``. Color limits use the entire volume, so different slices share the same scale. Finite-sample cross sections ---------------------------- ``Result.plot()`` plots differential scattering coefficients. For a finite sample, ``Result.plot_cross_section()`` multiplies curves and ensemble standard errors by the stored physical sample volume and converts the display area units. Both return Matplotlib figures and accept a custom ``title`` and ``log_y=True``. .. code-block:: python from bornsim.units import ureg from bornsim import Solver, Source source_configuration_1 = Source(wavelength=6.33e-07 * ureg.meter) solver = Solver( source=source_configuration_1, order=3, ) result = solver.solve(target=volume) figure = result.plot_cross_section(area_unit='nanometer**2') figure.savefig('sphere-scattering.png') The original Volume is not required for this plot. Normalization uses the entire voxel box: :math:`d\sigma/d\Omega=V\,\mathrm{differential}`. Cumulative curves preserve amplitude interference; ``terms=True`` shows isolated terms instead. Analytical infinite-medium results have no finite-sample cross section. Sphere offsets and phase functions ---------------------------------- ``Sphere.centre`` specifies a three-component offset from the voxel-box centre, as a quantity with explicit length units. For example, ``centre=np.array([40, 20, 0]) * ureg.nanometer`` shifts a sphere by 40 nm along x and 20 nm along y. Keep its radius plus the absolute offset within each box half-width to avoid clipping. Offsets aligned with the voxel spacing translate the existing sampled mask; other offsets resample the interface. The :doc:`auto_examples/structured_media/dielectric_sphere` example also plots the sphere's directional 3D phase function. It calls ``Solver.solve`` with the fixed generated volume to integrate a single deterministic sample over polar Gauss nodes and azimuth. This supplies the solid-angle normalization that a single meridian cut cannot determine. Refine both angular quadratures to check the numerical normalization. The phase surface represents probability density per steradian, with incidence along +z, rather than spatial medium geometry. It retains both polar angle and azimuth, so interference between multiple structures remains visible. No azimuth averaging is applied to the 3D view. Increase ``azimuth_samples`` to resolve finer azimuthal features. One deterministic sample provides no estimate of random sampling uncertainty. Matplotlib voxel rendering -------------------------- With ``backend="matplotlib"``, ``mode="voxels"`` renders the sampled material using its voxel cells and physical extents. Use ``Volume.plot_slice()`` for a two-dimensional cross-section of a selected voxel plane. .. code-block:: python from bornsim.units import ureg import matplotlib.pyplot as plt from bornsim import Grid from bornsim.medium.random_medium import WhittleMaternMedium medium = WhittleMaternMedium( smoothness=1.5, correlation_length=6e-08 * ureg.meter, background_refractive_index=1.33, refractive_index_std=0.01, ) grid_configuration_1 = Grid( shape=(16, 16, 16), spacing=2.5e-08 * ureg.meter, ) volume = medium.to_volume( grid=grid_configuration_1, seed=42, ) figure = volume.plot_3d( backend='matplotlib', mode='voxels', field='delta_refractive_index', length_unit='nanometer', ) plt.show() Structured samples and voxels ----------------------------- ``mode="voxels"`` shows the actual cells with nonzero refractive index contrast. A sphere's staircase boundary is therefore visible rather than smoothed by interpolation. Only exposed faces are drawn. Use ``Volume.plot_slice()`` to inspect exact voxel values in a two-dimensional cross-section. For an entirely zero field, voxels display the full uniform box. .. code-block:: python from bornsim.units import ureg import matplotlib.pyplot as plt from bornsim import Grid, Sphere, StructuredMedium sample = StructuredMedium() sample.add_background(refractive_index=1.33) sphere = Sphere( radius=1.5e-07 * ureg.meter, refractive_index=1.34, ) sample.add_structures(sphere) grid_configuration_1 = Grid( shape=(24, 24, 24), spacing=2e-08 * ureg.meter, ) volume = sample.to_volume(grid=grid_configuration_1) figure = volume.plot_3d( backend='matplotlib', mode='voxels', field='refractive_index', ) figure.savefig('sphere-3d.png') plt.show() Random backgrounds under structures ----------------------------------- The composition methods update an existing ``StructuredMedium``. A random background fills uncovered voxels; structures overwrite that background, including its fluctuations, inside their masks. The exterior reference refractive index is the random statistics' uniform background refractive index. .. code-block:: python from bornsim.units import ureg from bornsim import Grid, Sphere, StructuredMedium from bornsim.medium.random_medium import WhittleMaternMedium statistics = WhittleMaternMedium( refractive_index_std=0.002, correlation_length=5e-08 * ureg.meter, background_refractive_index=1.33, smoothness=1.5, ) sample = StructuredMedium() sample.add_background(medium=statistics) sphere = Sphere( radius=7e-08 * ureg.meter, refractive_index=1.345, ) sample.add_structures(sphere) grid_configuration_1 = Grid( shape=(16, 16, 16), spacing=2.5e-08 * ureg.meter, ) volume = sample.to_volume( grid=grid_configuration_1, seed=42, ) figure = volume.plot_3d( field='refractive_index', opacity_scale='increasing', ) figure.show() Interpretation and performance ------------------------------ ``field="delta_refractive_index"`` plots fluctuations about the uniform background; ``field="refractive_index"`` plots :math:`n_0+\delta n`. ``field="permittivity"`` displays the solver's linearized relative permittivity :math:`n_0^2+2n_0\delta n`, which omits the quadratic fluctuation term. These views represent the input material, not a calculated electromagnetic field. Axis labels state the display unit; stored data remains in SI units. The spatial aspect ratio follows physical dimensions. Matplotlib voxel geometry and ``Volume.plot_slice()`` show the sampled material. Plotly level surfaces interpolate between voxel centres and can make curved interfaces appear smoother than the solver's staircase mask. Neither visualization replaces checks of voxel refinement for quantitative calculations. The current grid limit is 32 voxels per axis (32,768 samples). Interactive volume views reveal high refractive index regions throughout random fields; ``Volume.plot_slice()`` is useful for inspecting exact voxel values. Filled voxel views reveal only the outer faces. Dense or transparent Matplotlib scenes can have depth-ordering limitations; see the `mplot3d FAQ `_. Use ``Volume.plot_slice()`` or opaque voxels when checking the sampled geometry. Plotly rendering depends on browser graphics support and surface count; changing surface count changes visualization, not numerical resolution.