{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# `Config` objects in `vipdopt`\n", "\n", "`vipdopt` provides functionality for loading data from configuration files for\n", "easy access within a script. Data can be loaded from a YAML or JSON file and will\n", "be stored in the `Config` class." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# imports\n", "from pathlib import Path\n", "import sys \n", "import yaml\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "np.set_printoptions(threshold=100)\n", "\n", "# Get vipdopt directory path from Notebook\n", "parent_dir = str(Path().resolve().parents[2])\n", "\n", "# Add to sys.path\n", "sys.path.insert(0, parent_dir)\n", "\n", "# Imports from vipdopt\n", "from vipdopt.configuration import Config, TemplateRenderer, SonyBayerRenderer, SonyBayerConfig\n", "from vipdopt.simulation import Power\n", "from vipdopt.utils import rmtree" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `Config` class is essentially a Python `dict` with keys for all of the values\n", "in the original configuration file." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "At first, device_scale_um = 0.051\n", "Now, device_scale_um = 0.06\n" ] } ], "source": [ "# Load file\n", "cfg = Config()\n", "cfg.read_file('config_example_3d.yml')\n", "\n", "# Can also create a `Config` directly from a file\n", "cfg2 = Config.from_file('config_example_3d.yml')\n", "\n", "assert cfg == cfg2\n", "\n", "# Accessing properties using `dict`-like access\n", "print(f'At first, device_scale_um = {cfg[\"device_scale_um\"]}')\n", "cfg['device_scale_um'] = 0.06\n", "print(f'Now, device_scale_um = {cfg[\"device_scale_um\"]}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Config` objects are particularly helpful for setting up simulation objects automatically. In the example below, a `Power` monitor is created using values from a `Config` we loaded." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "cfg = Config.from_file('example_render.yml')\n", "\n", "pow = Power('focal_monitor_0')\n", "properites = {\n", " 'monitor type': 'point',\n", " 'x': cfg['adjoint_x_positions_um'][0] * 1e-6,\n", " 'y': cfg['adjoint_y_positions_um'][0] * 1e-6,\n", " 'z': cfg['adjoint_vertical_um'] * 1e-6,\n", " 'override global monitor settings': 1,\n", " 'use wavelength spacing': 1,\n", " 'use source limits': 1,\n", " 'frequency points': cfg['num_design_frequency_points'],\n", "}\n", "pow.update(**properites)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generating Configuration Files using `Jinja2`\n", "\n", "Sometimes you may wish to compute certain properties using the values of other ones in\n", "your configuration file. For example `pixel_width = 2` and `num_pixels = 10` and you\n", "want to have a third value `total_width = pixel_width * num_pixels`. This would save time if you later wanted to tweak the values in your configuration file, as you would only need to change `pixel_width` and `num_pixels` rather than all of three.\n", "Unfortunately, most standard configuration file formats do not\n", "support this kind of functionality.\n", "\n", "`Jinja2` is an extensible templating engine. Special placeholders are placed in a template file to allow writing code similar to Python syntax. Then data is passed to the template to compute the placeholder values and render a final document.\n", "\n", "For the `total_width` example, this template would look something like:\n", "\n", "```yaml\n", "pixel_width: {{ data.pixel_width }}\n", "num_pixels: {{ data.num_pixels }}\n", "total_width: {{ data.pixel_width * data.num_pixels }}\n", "```\n", "\n", "Here, `data` is a dictionary being passed into the template renderer, which allows the use of its various values. the double curly braces `{{}}` serve as the placeholders that are evaluated by the renderer.\n", "\n", "If we were to pass a dictionary such as `{'pixel_wdith': 2, 'num_pixels': 10}` to the renderer, our output would be:\n", "\n", "```yaml\n", "pixel_width: 2\n", "num_pixels: 10\n", "total_width: 20\n", "```\n", "\n", "For more information on `Jinja2`, please check the [official documentation](https://jinja.palletsprojects.com/en/3.1.x/). [This guide](https://ttl255.com/jinja2-tutorial-part-1-introduction-and-variable-substitution/) also serves as a good starting point for formatting template files.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### General Workflow using `Jinja2`\n", "\n", "The convenience of `Jinja2` creates a sort of workflow one should use when using configuration files in `vipdopt`:\n", "\n", "1. Create an initial configuration file with general values (e.g. `pixel_width`)\n", "2. Create a template file that uses the values from the configuration file to compute other values\n", "3. Render the template file into a final \"rendered\" configuration file\n", "\n", "Below are two examples of this workflow in code." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "pixel_width: 2\n", "num_pixels: 10\n", "total_width: 20\n" ] } ], "source": [ "#\n", "# Pixel Example\n", "#\n", "\n", "pixel_source_directory = Path('pixel_example/')\n", "pixel_source_directory.mkdir(exist_ok=True)\n", "\n", "# Step 1 - Create initial config file \n", "initial_data = {\n", " 'pixel_width': 2,\n", " 'num_pixels': 10,\n", "}\n", "config_file = pixel_source_directory / 'initial_config.yaml'\n", "with config_file.open('w') as f:\n", " yaml.safe_dump(initial_data, f)\n", "\n", "# Step 2 - Create template file \n", "template_str = \"\"\"pixel_width: {{ data.pixel_width }}\n", "num_pixels: {{ data.num_pixels }}\n", "total_width: {{ data.pixel_width * data.num_pixels }}\n", "\"\"\"\n", "template_file = pixel_source_directory / 'template.j2'\n", "with template_file.open('w') as f:\n", " f.write(template_str)\n", "\n", "# Step 3 - Render final config file\n", "loaded_data = Config.from_file(config_file)\n", "renderer = TemplateRenderer(pixel_source_directory)\n", "renderer.set_template(template_file)\n", "output_str = renderer.render(data=loaded_data)\n", "\n", "print(output_str)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# \n", "# SONY Bayer Filter Example\n", "#\n", "pixel_source_directory = Path('.')\n", "\n", "# Step 1 - create initial config file (already done)\n", "og_config_file = pixel_source_directory / 'config_example_3d.yml'\n", "data = SonyBayerConfig.from_file(og_config_file)\n", "\n", "# Step 2 - create template file (already done)\n", "template_filename = 'derived_simulation_properties.j2'\n", "\n", "# Step 3 - render template file\n", "renderer = SonyBayerRenderer(pixel_source_directory)\n", "renderer.set_template(template_filename)\n", "\n", "output_file = pixel_source_directory / 'test_render.yml'\n", "\n", "renderer.render_to_file(output_file, data=data, pi=np.pi)" ] } ], "metadata": { "kernelspec": { "display_name": "venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.5" } }, "nbformat": 4, "nbformat_minor": 2 }