Source code for tg_model.model.elements
"""Authoring element types: :class:`Element`, :class:`Part`, :class:`System`, :class:`Requirement`.
Subclasses implement :meth:`Element.define` to record declarations on
:class:`~tg_model.model.definition_context.ModelDefinitionContext` and call
:meth:`Element.compile` (usually implicitly) to build cached definition artifacts.
See Also
--------
tg_model.model.definition_context.ModelDefinitionContext
tg_model.execution.configured_model.instantiate
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from tg_model.model.compile_types import compile_type
if TYPE_CHECKING:
from tg_model.execution.configured_model import ConfiguredModel
[docs]
class Element:
"""Abstract base for all model element types.
Subclasses implement :meth:`define` to record structure; compilation produces a
cached dict artifact consumed by instantiation and graph compilation.
Notes
-----
:meth:`compile` is idempotent per class. Use :meth:`_reset_compilation` only in tests.
"""
_compiled_definition: dict[str, Any] | None = None
[docs]
@classmethod
def define(cls, model: Any) -> None:
"""Declare this type's structure (override in subclasses).
Parameters
----------
model : ModelDefinitionContext
Definition-time recorder passed by the compiler.
"""
[docs]
@classmethod
def compile(cls) -> dict[str, Any]:
"""Compile this type's definition (cached on the class).
Returns
-------
dict
Compiled artifact (nodes, edges, registries) used by
:func:`~tg_model.execution.configured_model.instantiate`.
"""
if cls._compiled_definition is None:
cls._compiled_definition = compile_type(cls)
return cls._compiled_definition
@classmethod
def _reset_compilation(cls) -> None:
"""Clear cached compilation and definition hooks (**tests only**)."""
cls._compiled_definition = None
if getattr(cls, "_tg_definition_context", None) is not None:
cls._tg_definition_context = None
for attr in (
"_tg_behavior_spec",
"_tg_action_effects",
"_tg_initial_state_name",
"_tg_decision_specs",
"_tg_fork_join_specs",
"_tg_merge_specs",
"_tg_sequence_specs",
"_tg_guard_predicates",
):
if hasattr(cls, attr):
delattr(cls, attr)
[docs]
class Part(Element):
"""Structural part in a hierarchy (may own child parts, ports, values, behavior)."""
[docs]
class Requirement(Element):
"""Composable requirements package — **use ``parameter`` / ``attribute`` / ``constraint``**.
**DEFAULT PATTERN (always start here):** Inside ``define(cls, model)``, declare
``model.parameter``, ``model.attribute``, and ``model.constraint`` at **package scope** —
the same value/check authoring surface as :class:`Part` (unlike :class:`System`,
which is restricted to structural composition and top-level parameters). Use
``model.requirement(id, text)`` for leaf traceability statements, ``model.citation`` for
provenance, ``model.references`` for edges, and ``model.allocate`` for structural allocation.
**This is the standard, recommended API for all new requirement packages.**
Register on a :class:`Part` or :class:`System` via
:meth:`~tg_model.model.definition_context.ModelDefinitionContext.requirement_package`;
navigate with :class:`~tg_model.model.refs.RequirementRef` dot access.
**Advanced (rare — leaf reqcheck only):** ``requirement_input``, ``requirement_attribute``,
and ``requirement_accept_expr`` are low-level helpers for INCOSE-style executable acceptance
on a **single leaf** ``model.requirement(...)``, wired through ``allocate(..., inputs=...)``.
Use them **only** when you need ``summarize_requirement_satisfaction`` per-requirement rows.
**Do not use them as the default pattern.** If your check can be a package-level
``constraint``, use that instead.
Notes
-----
Package-level ``parameter``, ``attribute``, and ``constraint`` nodes become
:class:`~tg_model.execution.value_slots.ValueSlot` / graph nodes under the configured root
(dot access e.g. ``configured_root.pkg.param_name``); expressions are validated during
:meth:`compile`. Package-level slots do not yet support ``computed_by=`` or
:class:`~tg_model.model.declarations.values.RollupDecl` in graph compilation; every package
``constraint`` must supply ``expr=`` (including constant expressions with no symbols).
"""
[docs]
class System(Element):
"""Top-level system element for composition and top-level input parameters.
``System.define()`` is intentionally structural: compose child parts, declare
ports/requirements/citations as needed, and keep mission or scenario inputs as
top-level ``parameter(...)`` declarations. Derived values and executable checks
belong on owned :class:`Part` instances or requirement packages, not on the
root ``System`` itself.
"""
[docs]
@classmethod
def instantiate(cls) -> ConfiguredModel:
"""Build a :class:`~tg_model.execution.configured_model.ConfiguredModel` for this root type.
Delegates to :func:`~tg_model.execution.configured_model.instantiate` — same behavior and
no extra compilation path.
Returns
-------
ConfiguredModel
Frozen topology for graph compile and evaluation. A **new** instance on every call;
there is no shared singleton configured model.
Notes
-----
Call this on a **concrete** ``System`` subclass that implements :meth:`Element.define`
for your program root. That is the same requirement as passing that class to
:func:`~tg_model.execution.configured_model.instantiate`.
The base :class:`System` type itself is not a valid configured root; ``System.instantiate()``
fails the same way as ``instantiate(System)``.
Configured roots that are :class:`Part` subclasses still use
:func:`~tg_model.execution.configured_model.instantiate` only (no class method on
:class:`Part`).
See Also
--------
tg_model.execution.configured_model.instantiate
"""
from tg_model.execution.configured_model import instantiate as instantiate_configured
return instantiate_configured(cls)