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.

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

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 CppValues (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(<C++ default>, '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=<as the new one>, destination=<as the new one>, pythonName='<old>', deprecated=Deprecated('<version>', <year>), defaultValue=NoDefaultValue, description=r'<new>'). 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('<version>', <year>, advice='<what to do instead>'): 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):

    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 Python script shown under the block>'''),
    

    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 <Item>_<parameter> 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<Item><Parameter> 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 <!-- like this -->, 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/<Item>.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/Impl<Kind>s/C<Item>.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 - 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/Impl<Kind>s/C<Item>.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.