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 |
|---|---|---|
|
hand |
the single vocabulary: types, flags, destinations, default values, the member constructors, the function-library resolution |
|
hand |
the function declaration library: one entry per shared declaration of a function that items override |
|
hand |
the |
|
hand |
the enum registrator for the other enums shared by C++ and Python ( |
|
hand |
the output-variable texts shared by four or more items |
|
hand |
one |
|
tool |
which item descriptions were checked against their implementation, by whom, when, and the fingerprint of the implementation then; written by |
|
hand |
one |
|
hand |
one |
|
hand |
|
|
hand |
the hand-written Python interface (functions and classes whose C++ binding is not generated from an item or structure), in declaration calls on |
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,descriptionand — for parameters —defaultValueare required; a forgotten one raises during generation. Where a parameter genuinely has no default, writedefaultValue=NoDefaultValue.Default values are real values (
True,0.,1e-8); C++ constants and constructor calls areCppValues (DVInvalidIndex,DVDefaultColor,DVZeroVector3D).A frame is one parameter of type
THomogeneousTransformation(e.g.referenceHT), which C++ stores, and optionally its parts: aTVectorND(3)and aTMatrixND(3, 3)parameter withpartOfHT='referenceHT', which are not stored but read and write their share of the HT. All three default toCppValue(<C++ default>, 'None', ...):Noneis “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/SetObjectParameterand the keyword of the Python item class forward it to the new one with aDeprecationWarning, 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/1of the joints) - keeps its definition and addsdeprecated=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, andexudev scriptsreports it (#2804, #2805).A function with
implementation=Noneis a declaration whose body is in the hand-written.cpp;''is an empty body.isVirtualdefaults toTrue.
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, itscParentClassand the function name. Both keys areNoneunless the declaration really depends on them —sharedFunctionsholds 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=orargs=. These default toNone, 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 bytools/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 |
|---|---|---|
|
|
the brief description of an item: the paragraph under its heading, the docstring of its Python class and the comment of its C++ classes |
|
|
the full description of an item, after the generated part of its page - the long text, with its own headings |
|
|
the paragraph of the page of a kind of item, and its page General info for all …; a heading there is written with |
|
|
the paragraph under the structure’s heading |
|
|
one cell of the parameter table |
|
the |
the text of the Python-C++ interface pages, and the docstring of the function |
|
|
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'...'orr"""...""". Python reads'\theta'as a tab followed byheta;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, andtools/checkDefinitions.pyenforces it on the literal’s own spelling. A text of several lines is oner"""...""", not a chain of one-line strings joined by+.Mathematics is
$...$inline and$$...$$on its own lines, with the macros thatconf.pydeclares -\LU{0}{\pv},\Rcal,\eqDot.tools/checkMathMacros.pyfails 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 motioninsidedetailedDescription, which sits under the item’s DESCRIPTION heading, and## TitleinsidesectionText, 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 isRECURRING_HEADINGSintools/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.pyrejects 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 theabbreviationsdict intools/generators/examplesDocsEmitter.py, which also writes the list; add one there before using it.tools/checkDefinitions.pynames 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.pyrejects a link whose target is an equation label.An example or test model is named,
`NGsolveFFRF.py`, and listed in the fieldexamples, 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.pyrejects it (#2758).A citation is
[CITE:ZwoelferGerstmayr2021], with a key ofdocs/bibliographyDoc.bib. The converter drops the marker andconf.pydoes 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.pyrejects aCITE: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 byitemDocsEmitter.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:andReturns:are read. The def is named<Item>_<parameter>because four items have aforceUserFunction; the name the page prints is the parameter’spythonName. The annotation types -Real,Index,Bool,MainSystem,np.ndarrayand the rest - are names indefinitions/definitionTypes.py, so the file stays ordinary, importable Python. Nothing is executed: the source is read withast, and an annotation reaches the page as it is written.The def is the signature. The parameter has no
type=:ItemDefinitiongives it the typePyFunction<Item><Parameter>and enters thestd::functionof the def’s annotations intodefinitionTypes.userFunctionSignatures(UserFunctionSignature, the tabledefinitionTypes.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.pyreports 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::objectstands forBodyGraphicsDataandMatrixContainer.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.pycarries aProtocolper 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 showsdocs/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 |
written: one or two sentences, what it is and what it is for |
the image below it |
|
Simpler: the Create functions that add the item |
|
Interface |
generated from the declared types: |
Parameters, Visualization parameters, Output variables |
generated from the |
Drawing |
|
Detailed description |
written, |
user functions |
generated from the |
Mini example |
written, |
examples, test models |
|
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; |
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
MiniExamplesReferenceSolutionofpython/testing/runTestSuiteRefSol.py;runTestSuite.pyruns 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 fornsteps, 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.--calibrateprints new values when a model or the machine changes.