# 13. Model parser

## `sasmodels.generate`

SAS model constructor.

Small angle scattering models are defined by a set of kernel functions:

> Iq(q, p1, p2, …) returns the scattering at q for a form with particular dimensions averaged over all orientations.

> Iqac(qab, qc, p1, p2, …) returns the scattering at qab, qc for a rotationally symmetric form with particular dimensions. qab, qc are determined from shape orientation and scattering angles. This call is used if the shape has orientation parameters theta and phi.

> Iqabc(qa, qb, qc, p1, p2, …) returns the scattering at qa, qb, qc for a form with particular dimensions. qa, qb, qc are determined from shape orientation and scattering angles. This call is used if the shape has orientation parameters theta, phi and psi.

> Iqxy(qx, qy, p1, p2, …) returns the scattering at qx, qy. Use this to create an arbitrary 2D theory function, needed for q-dependent background functions and for models with non-uniform magnetism.

> form_volume(p1, p2, …) returns the volume of the form with particular dimension, or 1.0 if no volume normalization is required.

> shell_volume(p1, p2, …) returns the volume of the shell for forms which are hollow.

> radius_effective(mode, p1, p2, …) returns the effective radius of the form with particular dimensions. Mode determines the type of effective radius returned, with mode=1 for equivalent volume.

These functions are defined in a kernel module .py script and an associated set of .c files. The model constructor will use them to create models with polydispersity across volume and orientation parameters, and provide scale and background parameters for each model.

C code should be stylized C-99 functions written for OpenCL. All functions need prototype declarations even if the are defined before they are used. Although OpenCL supports #include preprocessor directives, the list of includes should be given as part of the metadata in the kernel module definition. The included files should be listed using a path relative to the kernel module, or if using “lib/file.c” if it is one of the standard includes provided with the sasmodels source. The includes need to be listed in order so that functions are defined before they are used.

Floating point values should be declared as double. For single precision calculations, double will be replaced by float. The single precision conversion will also tag floating point constants with “f” to make them single precision constants. When using integral values in floating point expressions, they should be expressed as floating point values by including a decimal point. This includes 0., 1. and 2.

OpenCL has a sincos function which can improve performance when both the sin and cos values are needed for a particular argument. Since this function does not exist in C99, all use of sincos should be replaced by the macro SINCOS(value, sn, cn) where sn and cn are previously declared double variables. When compiled for systems without OpenCL, SINCOS will be replaced by sin and cos calls. If value is an expression, it will appear twice in this case; whether or not it will be evaluated twice depends on the quality of the compiler.

The kernel module must set variables defining the kernel meta data:

> id is an implicit variable formed from the filename. It will be a valid python identifier, and will be used as the reference into the html documentation, with ‘_’ replaced by ‘-‘.

> name is the model name as displayed to the user. If it is missing, it will be constructed from the id.

> title is a short description of the model, suitable for a tool tip, or a one line model summary in a table of models.

> description is an extended description of the model to be displayed while the model parameters are being edited.

> parameters is the list of parameters. Parameters in the kernel functions must appear in the same order as they appear in the parameters list. Two additional parameters, scale and background are added to the beginning of the parameter list. They will show up in the documentation as model parameters, but they are never sent to the kernel functions. Note that effect_radius and volfraction must occur first in structure factor calculations.

> category is the default category for the model. The category is two level structure, with the form “group:section”, indicating where in the manual the model will be located. Models are alphabetical within their section.

> source is the list of C-99 source files that must be joined to create the OpenCL kernel functions. The files defining the functions need to be listed before the files which use the functions.

> form_volume, Iq, Iqac, Iqabc are strings containing the C source code for the body of the volume, Iq, and Iqac functions respectively. These can also be defined in the last source file.

> Iq, Iqac, Iqabc also be instead be python functions defining the kernel. If they are marked as Iq.vectorized = True then the kernel is passed the entire q vector at once, otherwise it is passed values one q at a time. The performance improvement of this step is significant.

> valid expression that evaluates to True if the input parameters are valid (e.g., “bell_radius >= radius” for the barbell or capped cylinder models). The expression can call C functions, including those defined in your model file.

A `modelinfo.ModelInfo` structure is constructed from the kernel meta data and returned to the caller.

Valid inputs should be identified by the valid expression. Particularly with polydispersity, there are some sets of shape parameters which lead to nonsensical forms, such as a capped cylinder where the cap radius is smaller than the cylinder radius. The polydispersity calculation will ignore these points, effectively chopping the parameter weight distributions at the boundary of the infeasible region. The resulting scattering will be set to background, even for models with no polydispersity. If the valid expression misses some parameter combinations and they reach the kernel, the kernel should probably return NaN rather than zero. Even if the volume also evaluates to zero for these parameters, the distribution weights are still accumulated and the average volume calculation will be slightly off.

The doc string at the start of the kernel module will be used to construct the model documentation web pages. Embedded figures should appear in the subdirectory “img” beside the model definition, and tagged with the kernel module name to avoid collision with other models. Some file systems are case-sensitive, so only use lower case characters for file names and extensions.

Code follows the C99 standard with the following extensions and conditions:

    M_PI_180 = pi/180
    M_4PI_3 = 4pi/3
    square(x) = x*x
    cube(x) = x*x*x
    sas_sinx_x(x) = sin(x)/x, with sin(0)/0 -> 1
    all double precision constants must include the decimal point
    all double declarations may be converted to half, float, or long double
    FLOAT_SIZE is the number of bytes in the converted variables

`load_kernel_module()` loads the model definition file and `modelinfo.make_model_info()` parses it. `make_source()` converts C-based model definitions to C source code, including the polydispersity integral. `model_sources()` returns the list of source files the model depends on, and `ocl_timestamp()` returns the latest time stamp amongst the source files (so you can check if the model needs to be rebuilt).

The function `make_doc()` extracts the doc string and adds the parameter table to the top. make_figure in sasmodels/doc/genmodel creates the default figure for the model. [These two sets of code should mignrate into docs.py so docs can be updated in one place].

sasmodels.generate.contains_Fq(source: list[str]) → bool

  Return True if C source defines “void Fq(“.

sasmodels.generate.contains_shell_volume(source: list[str]) → bool

  Return True if C source defines “double shell_volume(“.

sasmodels.generate.convert_section_titles_to_boldface(s: str) → str

  Use explicit bold-face rather than section headings so that the table of contents is not polluted with section names from the model documentation.

  Sections are identified as the title line followed by a line of punctuation at least as long as the title line.

sasmodels.generate.convert_type(source: str, dtype: dtype) → str

  Convert code from double precision to the desired type.

  Floating point constants are tagged with ‘f’ for single precision or ‘L’ for long double precision.

sasmodels.generate.demo_time() → None

  Show how long it takes to process a model.

sasmodels.generate.dll_timestamp(model_info: ModelInfo) → int

  Return a timestamp for the model corresponding to the most recently changed file or dependency.

sasmodels.generate.find_xy_mode(source: list[str]) → bool

  Return the xy mode as qa, qac, qabc or qxy.

  Note this is not a C parser, and so can be easily confused by non-standard syntax. Also, it will incorrectly identify the following as having 2D models:

      /*
      double Iqac(qab, qc, ...) { ... fill this in later ... }
      */

  If you want to comment out the function, use // on the front of the line:

      /*
      // double Iqac(qab, qc, ...) { ... fill this in later ... }
      */

sasmodels.generate.format_units(units: str) → str

  Convert units into ReStructured Text format.

sasmodels.generate.get_data_path(external_dir, target_file)

  Search for the target file relative in the installed application.

  Search first in the location of the generate module in case we are running directly from the distribution. Search next to the python executable for windows installs. Search in the ../Resources directory next to the executable for Mac OS/X installs.

sasmodels.generate.indent(s: str, depth: int) → str

  Indent a string of text with depth additional spaces on each line.

sasmodels.generate.kernel_name(model_info: ModelInfo, variant: str) → str

  Name of the exported kernel symbol.

  variant is “Iq”, “Iqxy” or “Imagnetic”.

sasmodels.generate.load_kernel_module(model_name: str) → ModuleType

  Return the kernel module named in model_name.

  If the name ends in .py then load it as a custom model using `custom.__init__.load_custom_kernel_module()`, otherwise load it as a builtin from sasmodels.models.

sasmodels.generate.load_template(filename: str) → str

  Load template file from sasmodels resource directory.

sasmodels.generate.main() → None

  Program which prints the source produced by the model.

sasmodels.generate.make_doc(model_info: ModelInfo) → str

  Return the documentation for the model.

sasmodels.generate.make_html(model_info: ModelInfo) → str

  Convert model docs directly to html.

sasmodels.generate.make_partable(pars: list[Parameter]) → str

  Generate the parameter table to include in the sphinx documentation.

sasmodels.generate.make_source(model_info: ModelInfo) → dict[str, str]

  Generate the OpenCL/ctypes kernel from the module info.

  Uses source files found in the given search path. Returns None if this is a pure python model, with no C source components.

sasmodels.generate.model_sources(model_info: ModelInfo) → list[str]

  Return a list of the sources file paths for the module.

sasmodels.generate.ocl_timestamp(model_info: ModelInfo) → int

  Return a timestamp for the model corresponding to the most recently changed file or dependency.

  Note that this does not look at the time stamps for the OpenCL header information since that need not trigger a recompile of the DLL.

sasmodels.generate.read_text(f)

sasmodels.generate.set_integration_size(info: ModelInfo, n: int) → None

  Update the model definition, replacing the gaussian integration with a gaussian integration of a different size.

  Note: this really ought to be a method in modelinfo, but that leads to import loops.

sasmodels.generate.tag_source(source: str) → str

  Return a unique tag for the source code.

sasmodels.generate.test_tag_float()

  Check that floating point constants are identified and tagged with ‘f’

sasmodels.generate.view_html(model_name: str) → None

  Load the model definition and view its help.

sasmodels.generate.view_html_from_info(info: ModelInfo) → None

  View the help for a loaded model definition.