# definitions/ — the item and structure definitions as Python **The input of the code generators.** Every item, every settings structure and every pybind binding is defined here as Python, and `tools/regenerate.py` writes the C++, the Python interface and the reference manual from it. A new item, step by step: [docs/dev/NEW_ITEM.md](../docs/dev/NEW_ITEM.md). To change an item or a structure, edit the file here and run `python tools/regenerate.py --check` (in `venvExuP313`): it validates the definitions and regenerates everything. The generators read these files through `tools/generators/definitionLoader.py`; the `pybind*.py` files are replayed by `tools/generators/pybindEmitter.py`. **Order matters** within and across the files: classes are generated in the order they appear, and structures written into one C++ header (`appendToFile`) depend on it. ## Files | file | written by | what it is | |---|---|---| | `definitionTypes.py` | hand | the single vocabulary: types, flags, destinations, default values, the member constructors, the function-library resolution | | `itemFunctions.py` | hand | the function declaration library: one entry per shared declaration of a function that items override | | `outputVariableTypes.py` | hand | the `OutputVariableType` registrator: names and bit positions, generates `src/Autogenerated/OutputVariableTypes.h` and the Python enum | | `enumTypes.py` | hand | the enum registrator for the other enums shared by C++ and Python (`ConfigurationType`, `NodeType`, ...): values, descriptions, string functions; generates `src/Autogenerated/EnumTypes.h` and the Python enums | | `outputVariableDescriptions.py` | hand | the output-variable texts shared by four or more items | | `itemDefsNodes.py`, `itemDefsObjects.py`, `itemDefsMarkers.py`, `itemDefsLoads.py`, `itemDefsSensors.py` | hand | one `ItemDefinition(...)` per item | | `descriptionChecks.json` | tool | which item descriptions were checked against their implementation, by whom, when, and the fingerprint of the implementation then; written by `tools/checkDescriptions.py --mark` (#2717) | | `itemKindDefinitions.py` | hand | one `ItemKindDefinition(...)` per kind of item: the page of the kind, with the general section every item of the kind refers to | | `structureDefs*.py` | hand | one `StructureDefinition(...)` per settings / solver / system structure | | `pybindTypes.py` | hand | `PybindInterface`, the recorder of the hand-written Python interface declarations, with its call vocabulary and the shared stub type names | | `pybindModule.py`, `pybindSystemContainer.py`, `pybindRenderer.py`, `pybindMainSystem.py`, `pybindSystemData.py`, `pybindSymbolic.py`, `pybindGeneralContact.py`, `pybindDataStructures.py`, `pybindEnums.py`, `pybindGeneralInformation.py` | hand | the hand-written Python interface (functions and classes whose C++ binding is not generated from an item or structure), in declaration calls on `pb`; order matters, as the calls are replayed in order | `definitionTypes.py` is grouped in three blocks by where a name is used: **SHARED** (types, default values, sentinels), **ITEMS**, **STRUCTURES**. ## Members ```python ItemParameter(type=TReal(minimum=0), destination=DestComp+DestParam, pythonName='mass', defaultValue=0., description=r'$m$mass [SI:kg] of mass point') ItemFunction(type=Tvoid, destination=DestComp, pythonName='ComputeContactForces', description='...', cFlags=CFConst, args='...') ItemFunctionDef('ComputeODE2LHS') ItemFunctionDef('GetNumberOfNodes', implementation='return 1;') ``` - `type`, `destination`, `pythonName`, `description` and — for parameters — `defaultValue` are **required**; a forgotten one raises during generation. Where a parameter genuinely has no default, write `defaultValue=NoDefaultValue`. - Default values are real values (`True`, `0.`, `1e-8`); C++ constants and constructor calls are `CppValue`s (`DVInvalidIndex`, `DVDefaultColor`, `DVZeroVector3D`). - **A frame** is one parameter of type `THomogeneousTransformation` (e.g. `referenceHT`), which C++ stores, and optionally its parts: a `TVectorND(3)` and a `TMatrixND(3, 3)` parameter with `partOfHT='referenceHT'`, which are not stored but read and write their share of the HT. All three default to `CppValue(, 'None', ...)`: `None` is "not given"; the dictionary holds all three, and an HT and a part given together must agree (`EPyUtils::HTFromDictionary`, #2793). - **A renamed parameter** keeps its old name as `ItemParameter(type=, destination=, pythonName='', deprecated=Deprecated('', ), defaultValue=NoDefaultValue, description=r'')`. It is not stored: the dictionary, `Get`/`SetObjectParameter` and the keyword of the Python item class forward it to the new one with a `DeprecationWarning`, tested after every current name; the page lists it below the table (#2589). - **A deprecated parameter that stays** - no new name to forward to (`rotationMarker0/1` of the joints) - keeps its definition and adds `deprecated=Deprecated('', , advice='')`: it is stored and works as before, setting it other than its default warns, the page lists it below the table with the advice, and `exudev scripts` reports it (#2804, #2805). - A function with `implementation=None` is a declaration whose body is in the hand-written `.cpp`; `''` is an empty body. `isVirtual` defaults to `True`. ## The function library (`itemFunctions.py`) An item does not restate a declaration it shares with other items: it writes `ItemFunctionDef(name)` and the declaration comes from the library. - An entry is found by the item's `classType`, its `cParentClass` and the function name. Both keys are **`None` unless the declaration really depends on them** — `sharedFunctions` holds the ones that are the same for every item type, then one list per item type; inside a list, entries with no parent first, then grouped by parent. - Where a name is still not unique, the use site says which with `destination=`, `cFlags=` or `args=`. These default to `None`, so a forgotten one **raises and lists the alternatives**. - `description=` at the use site overrides the library text. Use it only where the text is genuinely item-specific. - Only functions that **override** a parent's virtual function belong in the library. Functions new in one item are written out with `ItemFunction(...)`. - The library states what the C++ base headers (`src/System/CObject*.h`, `CNode*.h`, `CMarker*.h`, …) declare. **Change both together** - `tools/generators/definitionValidator.py`, run by `tools/regenerate.py`, fails when they disagree. ## Writing a description **Read this before writing or changing a description.** A description is not free text: it is the source of a page of the reference manual, and it is converted, checked and published. The rules are here and nowhere else - a definition file's header points at this section, and so does `CLAUDE.md`. These are the fields that are descriptions, and all of them are converted the same way, by `ConvertText` in `tools/generators/latexToMarkdown.py`: | field | where | becomes | |---|---|---| | `overallDescription` | `ItemDefinition` | the brief description of an item: the paragraph under its heading, the docstring of its Python class and the comment of its C++ classes | | `detailedDescription` | `ItemDefinition` | the full description of an item, after the generated part of its page - the long text, with its own headings | | `overallDescription`, `detailedDescription` | `ItemKindDefinition` | the paragraph of the page of a kind of item, and its page *General info for all ...*; a heading there is written with `##` | | `classDescription` | `StructureDefinition` | the paragraph under the structure's heading | | `description` | `ItemParameter`, `ItemFunction`, `StructureParameter`, `StructureFunction` | one cell of the parameter table | | `description` | the `pb....(...)` calls of the `pybind*.py` files | the text of the Python-C++ interface pages, and the docstring of the function | | `sectionText` | `StructureDefinition` | the heading and the paragraph that open the section a group of structures forms | `miniExample` is not a description: it is Python, it is run by the test suite, and it is published as a code block. ### The text The text is **Markdown** - MyST Markdown, as the hand-written chapters in `docs/manual/` are - with **LaTeX for the mathematics**. Nothing else is supported: a construct that works in LaTeX but is not in the list below reaches the page as itself and is a defect. - **Write a description that carries mathematics or a backslash as a raw string**, `r'...'` or `r"""..."""`. Python reads `'\theta'` as a tab followed by `heta`; `r'\theta'` is the six characters MathJax needs. This is the one mistake that is silent, so it is the one rule with no exception, and `tools/checkDefinitions.py` enforces it on the literal's own spelling. A text of several lines is one `r"""..."""`, not a chain of one-line strings joined by `+`. - **Mathematics** is `$...$` inline and `$$...$$` on its own lines, with the macros that `conf.py` declares - `\LU{0}{\pv}`, `\Rcal`, `\eqDot`. `tools/checkMathMacros.py` fails on a macro it does not know, so a typo in one is found. Display mathematics stands on its own lines between `$$` and `$$`; several aligned equations go inside `\begin{aligned} ... \end{aligned}`. A label goes after the closing `$$`, `$$ (eq-objectground-position)`, and is referenced as any other target. - **A heading** is the Markdown heading it becomes, and the level is fixed by the page it lands in: `#### Equations of motion` inside `detailedDescription`, which sits under the item's *DESCRIPTION* heading, and `## Title` inside `sectionText`, which sits under the page title. Where something refers to a heading, the MyST target goes on the line above it: `(sec-objectjointgeneric-quantities)=`. Use the recurring title where it fits - the list is `RECURRING_HEADINGS` in `tools/checkDefinitions.py`, which rejects both a wrong level and a recurring title spelled differently. - **Inline code** - a parameter name, a Python call, a type - is a backtick span, `mbs.Assemble()`. `tools/checkDefinitions.py` rejects a `\texttt{...}` outside mathematics. - **A list** is `\bi ... \item ... \ei` (bulleted) or `\ben ... \item ... \een` (numbered). - **An abbreviation** is `ABRV:ODE2` - no backslash, no braces - which links to the list of abbreviations. The key ends where the word ends. The keys are the `abbreviations` dict in `tools/generators/examplesDocsEmitter.py`, which also writes the list; add one there before using it. `tools/checkDefinitions.py` names the file and the line of a key that does not exist. - **A reference** is a Markdown link to the target: `[](#sec-item-objectground)` for a section. A **figure** is referenced by its number, `` {numref}`fig-objectspheresphrecontact` `` (*Fig. 12.3*): an empty link would print the caption, and a caption with math stays LaTeX source there in the PDF. Leave the text of a section link empty and the page supplies the heading, or write your own, `[the ground object](#sec-item-objectground)`. The target name is lower case with `-` between the parts, and it must sit on a **heading** or a **named figure**; a target above a paragraph resolves in no link at all. A section target goes on the line above its heading, `(sec-item-objectground)=`. - **A reference to an equation** is the role, `` {eq}`eq-objectground-position` ``, and not a link. The two render the same number in the HTML, but the LaTeX writer gives a link to an equation an anchor that does not match the label it writes on the equation itself, so all 42 of them came out of the PDF as undefined references. `tools/checkDefinitions.py` rejects a link whose target is an equation label. - **An example or test model** is named, `` `NGsolveFFRF.py` ``, and listed in the field `examples`, which puts it on the page with its github link; it is not linked by its page, `[](#examples-...)`. The PDF leaves those pages out, so the link is unresolved there; `tools/checkDefinitions.py` rejects it (#2758). - **A citation** is `[CITE:ZwoelferGerstmayr2021]`, with a key of `docs/bibliographyDoc.bib`. The converter drops the marker and `conf.py` does the rest: it appends a Markdown link definition for every key of the bibliography, so `[Key]` becomes a link into the generated references page. The marker is there to be checked - `tools/checkDefinitions.py` rejects a `CITE:` key the bibliography does not have, and a key written **without** the marker - and a `[CITE:` left in a generated page is a conversion that did not happen. Without it nothing could be told apart: a description is full of square brackets that are not citations (`[SI:kg]`, `[0,0,0]`). - **A table** is a Markdown pipe table, written where it belongs in the text: ``` | intermediate variables | symbol | description | |---|---|---| | body's reference point | $\pRefG$ | position of the body's reference point | ``` The header row says what the columns are - *intermediate variables | symbol | description* for the quantities of an item, *output variables | symbol | formula*, *input parameter | symbol | description* - and a cell may hold mathematics, a link or inline code, as any other text may. A `|` inside a cell is written `\|`; inside `$...$` it needs nothing. - **A user function** is not written in the description at all: it is an ordinary Python function, in the definition file, immediately above the `definitions.append(...)` it belongs to, and the parameter is given it by object. The block of the page - the signature line, the prose, the table of the arguments and the return value - is generated from it (`tools/generators/userFunctionModel.py`, called by `itemDocsEmitter.UserFunctionDocumentation`): ```python def ObjectGround_graphicsDataUserFunction(mbs: MainSystem, itemNumber: Index) -> BodyGraphicsData: r"""A user function, which is called by the visualization thread ... the details, in as many lines as it takes Args: mbs: provides reference to mbs, which can be used in the user function itemNumber: integer number of the object in mbs Returns: list of `GraphicsData` dictionaries, see Section [](#sec-graphicsdata) """ ... ItemParameter(..., pythonName='graphicsDataUserFunction', userFunction=ObjectGround_graphicsDataUserFunction, userFunctionExample=r''''''), ``` The **first line** of the docstring is the summary and what follows it the details; both are Markdown, by the rules of this section, and their line breaks are kept. Only `Args:` and `Returns:` are read. The def is named `_` because four items have a `forceUserFunction`; **the name the page prints is the parameter's `pythonName`**. The annotation types - `Real`, `Index`, `Bool`, `MainSystem`, `np.ndarray` and the rest - are names in `definitions/definitionTypes.py`, so the file stays ordinary, importable Python. Nothing is executed: the source is read with `ast`, and an annotation reaches the page **as it is written**. **The def is the signature.** The parameter has no `type=`: `ItemDefinition` gives it the type `PyFunction` and enters the `std::function` of the def's annotations into `definitionTypes.userFunctionSignatures` (`UserFunctionSignature`, the table `definitionTypes.annotationToCpp`); the C++ member, the Python interface and the symbolic user functions are generated from it, user functions with the same signature sharing one member of the symbolic set (#2664). What is still checked: that the docstring describes every argument - `tools/checkDefinitions.py` reports it with the file and the line, and the generator refuses to emit. A size is in the annotation where it is fixed (`Vector3D`), else a formula in the argument's line; `py::object` stands for `BodyGraphicsData` and `MatrixContainer`. The **size** of an argument belongs in its `Args:` line, as a formula - `q: generalized coordinates, $\qv \in \Rcal^{n_{ODE2}}$` - and not in its type, because a formula renders in a table cell and not inside a code block. That is what lets the arguments be a real signature. **What a user gets from the def**: `python/exudyn/itemInterface.py` carries a `Protocol` per user function - `class ObjectGroundGraphicsDataUserFunction(Protocol)` with a typed `__call__` and the docstring - and the parameter of the item class is annotated with it, so an editor completes the arguments of the function being written and marks a wrong one. A Protocol is never instantiated and nothing inherits from it: it costs nothing at runtime. The user functions still written as a hand-made block in a description are being converted item by item (#2664); a new one is written as a def. - **A figure** that belongs to an item is `\addExampleImage{RevoluteJointZ}`, which shows `docs/figures/RevoluteJointZ.png`. - **A comment** is ``, on a line of its own or after text, and the converter removes it so that it never reaches the page. A run of comment lines is one comment, which is what a commented-out table or figure wants to be. **A `%` is a comment only inside mathematics**, where MathJax and LaTeX are the ones that read it; anywhere else it is rejected, because the converter strips a `%` before the mathematics is protected and would take the rest of the line with it. ### How to check what you wrote ``` python tools/regenerate.py --check # in venvExuP313: converts and regenerates exudev generate --all-checks # the same, plus the eight checks exudev docs # the HTML, strict: a reference to nothing fails ``` Then read the page: `docs/generated/items/.md` for an item, `docs/generated/structures/` for a structure, `docs/generated/cInterface/` for a `pybind*` text. A macro that is none of the above is carried through silently, so the page is the only place a mistake shows. ### A description checked against its implementation An item description is checked once against the C++ - the coordinates, the output variables it declares and those the code computes, what the equations say - and the check is recorded, with a fingerprint of the implementation: ``` python tools/checkDescriptions.py # how many items of each kind are checked python tools/checkDescriptions.py --mark NodePoint # record the check of an item, after reading both python tools/checkDescriptions.py --check # in exudev generate --all-checks ``` The fingerprint covers the hand-written source of the item (`src/Impls/C.cpp`, and its functions in the shared sources, such as `CLoad.cpp`, `CSensor.cpp`, `evaluateUserFunctions.cpp`) and its generated header without comments, so a change of a description leaves it, and a change of the code does not: `--check` then names the item with the old and the new fingerprint, until its description has been looked at again and `--mark` records the new state (#2717). ## The page of an item What a reference page of an item contains, and who writes it. Say only what is the item's own: what all items of a kind share is the general section of the kind in `itemKindDefinitions.py`, published on the page *General info for all ...* of the kind in the [items reference manual](#sec-item-reference-manual) - link it, do not repeat it. | part | source | |---|---| | title and `overallDescription` | written: one or two sentences, what it is and what it is for | | the image below it | `image='itemImages/ObjectGround.png'`, a file in `docs/figures/`; the item images are drawn by `tools/itemImages/itemImages.py`, one hand-written scene each (#2830) | | **Simpler**: the Create functions that add the item | `createFunctions=['CreateRigidBody']`, objects and loads; the one place a page names them | | **Interface** | generated from the declared types: `itemTypes`, `requestedTypes`, `accessFunctionTypes`, and `requestedNodeTypes` of a node marker (which also generates the check of `Assemble` and the answer of `mbs.Inspect`, #2817) | | **Parameters**, **Visualization parameters**, **Output variables** | generated from the `description` of each member | | **Drawing** | `drawing=r'...'`, what the item draws beyond the drawing of its kind (`drawing` and `drawingSettings` of the kind in `itemKindDefinitions.py`, which the page links), or `'The item draws nothing.'`; `drawingSettings=['general.cylinderTiling', ...]`, the visualization settings its drawing reads beyond those of its kind - `test_itemDrawing.py` compares them with the C++ (#2840) | | **Detailed description** | written, `detailedDescription`, headings by kind (below) | | user functions | generated from the `def` (#2664) | | **Mini example** | written, `miniExample` (below) | | examples, test models | `examples=['Examples/notebooks/tutorialRigidBodyCreate.py', ...]`, relative to `python/`, chosen for the basic items and all sensors; without it, every script that names the item | ### The detailed description, by kind A heading used on several pages is spelled as in `RECURRING_HEADINGS` of `tools/checkDefinitions.py`; the headings are not enforced, a page of an unusual item may deviate. | kind | headings | |---|---| | node | *Coordinates* (table: index, symbol, kind, meaning, frame), *Configuration*, *Frame and interpretation* (which objects read the node differently), *Action on the equations of motion*; constraints and singularities where there are any. A generic node: one sentence - the object defines the meaning | | body | *Definition of quantities*, *Equations of motion*, *Marker interfaces* - the Jacobians of this body only | | finite element | *Nodes and coordinates*, *Kinematics and interpolation*, *Strains*, *Mass matrix*, *Elastic forces*, *Limitations*, *Marker interfaces*; `ObjectANCFCable2D` is the model page | | super element | *Definition of quantities*, *Equations of motion*, *Marker interfaces*, where they apply | | connector | *Definition of quantities*, *Geometric relations*, *Connector forces*; the step to the generalized forces is in the general section | | constraint, joint | *Definition of quantities*, *Geometric relations*, *Connector constraint equations* (index 3, and index 2 where there is one), *Post Newton Step* where there is one; what the multipliers are, in which frame | | marker | *Marker quantities* (table, with frames), *Jacobians* | | load | *Generalized forces*: the load, its frame, $\Qm = \Jm\tp\fv$ with the transformation | | sensor | *Measures*, and details only where there are any; no equations | Rules: - **Every vector has its frame** (`notation.md`: $\LU{0}{\pv}$, $\LU{b}{\pv}$). - **The equations are those of the C++** (`src/Impls/C.cpp`), read, not recalled. - **A defect found while writing** is said on the page with its issue number, and raised as an issue; it is not fixed as part of the text. ### The mini example - the shortest model that shows the item in its typical use - 5 to 25 lines; - runs after the lines the generator puts in front of it: `SC`, `mbs`, `oGround`, `nGround` (`tools/generators/miniExampleEmitter.py`); - the item and what it is combined with are created by their class names, not by Create functions; - it solves, and sets `exu.sys['testResult']`; the comment states the expected value - an analytical one where the model has one; - the reference value goes into `MiniExamplesReferenceSolution` of `python/testing/runTestSuiteRefSol.py`; `runTestSuite.py` runs every mini example. - `miniExamplePerformanceTest={'numberOfSteps': n}` is the length of its **performance run** (`python/testing/runMiniExamplePerformance.py`, #2745): the mini example solved once more with a 200 times smaller step size for `n` steps, about 2 s of solver time in the full run, a tenth in the regular run; `{'skip': True}` where a dynamic solve of the model makes no sense. `--calibrate` prints new values when a model or the machine changes.