2. Introduction to Physical Architecture API

Note: In this notebook we will use pandas dataframes library to construct and visualize tables, if you don’t have pandas installed in the current environment you may want to do so by running the cell below.

[1]:
%pip install -q pandas
Note: you may need to restart the kernel to use updated packages.

The cell below loads our test model so we could play with it and silences warnings.

[2]:
import logging

import pandas as pd

import capellambse

logging.getLogger().setLevel(logging.CRITICAL)
path_to_model = "../../../tests/data/melodymodel/5_0/Melody Model Test.aird"
model = capellambse.MelodyModel(path_to_model)

but before we jump into code, lets have a look first at Capella metamodel concerning the Physical Architecture layer (PA).

Things in PA are very similar to what we see in SysML when it comes to ibds (Internal Block Diagrams) - the boxes we see on those are Parts that are instanciated from Block objects. Same happens in Capella - the boxes we see on PAB diagrams are Parts that were instanciated from PhysicalComponents. Here also comes the very special difference of Capella - unless you explicitly enable part re-use, PhysicalComponent will always have only one Part. This is the default behavior of Capella.

Our API should support both cases but at the moment we don’t use models with part re-use enabled in production yet and so don’t test the library against this case. Yet we do implement Parts and support many parts - one component relationship model.

One more issue to mention - rendering PA diagrams outside of Capella was never a high priority so the resulting representations of PABs rendered without Capella are not very accurate at the moment. We hope to improve it soon though. If you still do want to see how it looks like when we render it right now - uncomment the # diagram in the cell below

[3]:
diagram = model.pa.diagrams.by_name("[PAB] A sample vehicle arch")
diagram
[3]:
../_images/examples_02_Intro_to_Physical_Architecture_API_5_0.svg

2.1. Example 1: List components that are visible on a diagram

To start, let’s get all parts on that diagram and turn them into PhysicalComponents.

[4]:
components_on_diagram = diagram.nodes.by_type("Part").map("type")
components_on_diagram
[4]:
  1. PhysicalComponent "Vehicle" (b327d900-abd2-4138-a111-9ff0684739d8)
  2. PhysicalComponent "Equipment compartment" (3d68852d-fcc0-452c-af12-a2fbe22f81fa)
  3. PhysicalComponent "Server" (9137f463-7497-40c2-b20a-897158fdba9a)
  4. PhysicalComponent "Compute Card 1" (63be604e-883e-41ea-9023-fc74f29906fe)
  5. PhysicalComponent "Card 1 OS" (7b188ad0-0d82-4b2c-9913-45292e537871)
  6. PhysicalComponent "Camera Driver SWC" (74067f56-33bf-47f5-bb8b-f3604097f653)
  7. PhysicalComponent "App 1 SWC" (b80a6fcc-8d35-4675-a2e6-60efcbd61e27)
  8. PhysicalComponent "Cooling Fan" (65e82f3f-c5b7-44c1-bfea-8e20bb0230be)
  9. PhysicalComponent "Compute Card 2" (3a982128-3281-4d37-8838-a6058b7a25d9)
  10. PhysicalComponent "Card 2 OS" (09e19313-c824-467f-9fb5-95ed8b4e2d51)
  11. PhysicalComponent "App 2 SWC" (ca5af12c-5259-4844-aaac-9ca9f84aa90b)
  12. PhysicalComponent "Network Switch" (b51ccc6f-5f96-4e28-b90e-72463a3b50cf)
  13. PhysicalComponent "Switch Firmware" (c78b5d7c-be0c-4ed4-9d12-d447cb39304e)
  14. PhysicalComponent "Switch Configuration" (23c47b69-7352-481d-be88-498fb351adbe)
  15. PhysicalComponent "Sensor compartment" (3f416925-9d8a-4e9c-99f3-e912efb23d2f)
  16. PhysicalComponent "Camera Assembly" (5bfc516b-c20d-4007-9a38-5ba0e889d0a4)
  17. PhysicalComponent "Camera Firmware" (db2d86d7-48ee-478b-a6fc-d6387ab0032e)

We could also get all components across the entire PA layer by doing model.pa.all_components, but we will not go there in this example.

We can review any single component from that list above:

[5]:
components_on_diagram[7]
[5]:

Cooling Fan (org.polarsys.capella.core.data.pa:PhysicalComponent)

allocated_functions

(Empty list)

applied_property_value_groups

(Empty list)

applied_property_values

(Empty list)

components

(Empty list)

constraints

(Empty list)

context_diagramContext of Cooling Fan (uuid: 65e82f3f-c5b7-44c1-bfea-8e20bb0230be_context)
deployed_components

(Empty list)

deploying_componentsBackreference to PhysicalComponent - omitted: can be slow to compute. Display this property directly to show.
description
diagrams

(Empty list)

exchanges

(Empty list)

filtering_criteria

(Empty list)

is_abstractFalse
is_actorFalse
is_humanFalse
kind<PhysicalComponentKind.HARDWARE: 'HARDWARE'>
layerPhysicalArchitecture "Physical Architecture" (26e187b6-72e7-4872-8d8d-70b96243c96c)
nameCooling Fan
nature<PhysicalComponentNature.NODE: 'NODE'>
owned_components

(Empty list)

owned_features

(Empty list)

ownerPhysicalComponent "Compute Card 1" (63be604e-883e-41ea-9023-fc74f29906fe)
parentPhysicalComponent "Compute Card 1" (63be604e-883e-41ea-9023-fc74f29906fe)
partsBackreference to Part - omitted: can be slow to compute. Display this property directly to show.
physical_links

(Empty list)

physical_paths

(Empty list)

physical_ports

(Empty list)

ports

(Empty list)

progress_statusNOT_SET
property_value_groups

(Empty list)

property_value_packages

(Empty list)

property_values

(Empty list)

pvmtProperty Value Management for PhysicalComponent "Cooling Fan" (65e82f3f-c5b7-44c1-bfea-8e20bb0230be)
realization_viewRealization view of Cooling Fan (uuid: 65e82f3f-c5b7-44c1-bfea-8e20bb0230be_realization_view)
realized_components

(Empty list)

realized_logical_components

(Empty list)

realizing_componentsBackreference to - omitted: can be slow to compute. Display this property directly to show.
related_exchangesBackreference to ComponentExchange - omitted: can be slow to compute. Display this property directly to show.
requirements

(Empty list)

sid
state_machines

(Empty list)

summary
traces

(Empty list)

uuid65e82f3f-c5b7-44c1-bfea-8e20bb0230be
validation<capellambse.extensions.validation._validate.ElementValidation object at 0x71a1c5b62d20>
visible_on_diagrams
  1. [PAB] A sample vehicle arch (uuid: _erzycVIqEeyiRNlyKPJwqw)
xtypeorg.polarsys.capella.core.data.pa:PhysicalComponent

However we may need just a few of those attributes in a view.

Now that we have a list of components lets collect some of the attributes of interest in a table. To keep it simple we’ll introduce an attribute extractor function that will turn fields of interest into a nice dictionary.

[6]:
def extract_attrs_of_interest(component):
    return {
        "name": component.name,
        "nature": component.nature,
        "kind": component.kind,
        "components": "; ".join(cmp.name for cmp in component.components),
    }

We can then apply that extractor function to our list of components and use pandas to display it for us in a tabular form:

[7]:
pd.DataFrame(list(map(extract_attrs_of_interest, components_on_diagram)))
[7]:
name nature kind components
0 Vehicle NODE SOFTWARE_DEPLOYMENT_UNIT Equipment compartment; Sensor compartment
1 Equipment compartment NODE FACILITIES Server; Network Switch
2 Server NODE HARDWARE Compute Card 1; Compute Card 2
3 Compute Card 1 NODE HARDWARE Card 1 OS; Cooling Fan
4 Card 1 OS BEHAVIOR SERVICES Camera Driver SWC; App 1 SWC
5 Camera Driver SWC BEHAVIOR SOFTWARE
6 App 1 SWC BEHAVIOR SOFTWARE
7 Cooling Fan NODE HARDWARE
8 Compute Card 2 NODE HARDWARE Card 2 OS
9 Card 2 OS BEHAVIOR SOFTWARE App 2 SWC
10 App 2 SWC BEHAVIOR SOFTWARE
11 Network Switch NODE HARDWARE Switch Firmware; Switch Configuration
12 Switch Firmware BEHAVIOR HARDWARE_COMPUTER
13 Switch Configuration BEHAVIOR DATA
14 Sensor compartment NODE FACILITIES Camera Assembly
15 Camera Assembly NODE HARDWARE Camera Firmware
16 Camera Firmware BEHAVIOR SOFTWARE_EXECUTION_UNIT

2.2. Example 2: Create HW-SW allocation table

Now let’s assume that components of “Node” nature are hardware things and “Behavior” corresponds to software components. Assuming that, let’s identify leaf hardware components (lowest replaceable units), and for each of those indicate which software components they have.

To get there, let’s first filter out the list of components of “Node” nature that have at least one subcomponent of “Behavior” nature. We’ll use the .filter method of our object list. To use that method we’ll need to provide a function or lambda that determines whether an object should be selected. We can also make use of the implicit “truthiness” of non-empty list attributes:

[8]:
cmps = model.pa.all_components[1:].by_nature("NODE")
# Note: the above [1:] assumes that 0th component is the root component and is not of interest (+ see issue #41)
cmps_with_sw = cmps.filter(lambda i: i.components.by_nature("BEHAVIOR"))
cmps_with_sw
[8]:
  1. PhysicalComponent "Sub PC" (793e6da2-d019-4716-a5c5-af8ad550ca5e)
  2. PhysicalComponent "Deploy Sub PC" (8a6c6ec9-095d-4d8b-9728-69bc79af5f27)
  3. PhysicalComponent "Compute Card 1" (63be604e-883e-41ea-9023-fc74f29906fe)
  4. PhysicalComponent "Compute Card 2" (3a982128-3281-4d37-8838-a6058b7a25d9)
  5. PhysicalComponent "Network Switch" (b51ccc6f-5f96-4e28-b90e-72463a3b50cf)
  6. PhysicalComponent "Camera Assembly" (5bfc516b-c20d-4007-9a38-5ba0e889d0a4)
  7. PhysicalComponent "Computer" (b14ff190-9198-4d05-95db-b121c11e9f17)
  8. PhysicalComponent "ISP Network" (221cef9f-0582-419c-b67e-96ddb678dd4c)

For sure we could come to the above list filtering by kind attribute, however in practice not all projects strictly use the kind attribute / it needs manual setting and maintenance and therefore is a bit less reliable.

Our next stop is to list the SW components of those HW components. As SW components may be nested (i.e. apps on OS or partitions, etc.) we would simply “flatten” that hierarchy. We can create function get_sw_components that would recursively crawl down the SW components tree and give us back a flat list and test it on one component.

[9]:
def get_sw_components(sw_component):
    subcmp = sw_component.components.by_nature("BEHAVIOR")
    for cmp in subcmp:
        subcmp += get_sw_components(cmp)
    return subcmp


get_sw_components(cmps_with_sw.by_name("Compute Card 1"))
[9]:
  1. PhysicalComponent "Card 1 OS" (7b188ad0-0d82-4b2c-9913-45292e537871)
  2. PhysicalComponent "Camera Driver SWC" (74067f56-33bf-47f5-bb8b-f3604097f653)
  3. PhysicalComponent "App 1 SWC" (b80a6fcc-8d35-4675-a2e6-60efcbd61e27)

Let’s apply the get_sw_components function to the complete list of components with SW, cmps_with_sw, and turn the results into a table. In this table we’ll serialize SW components into a semicolon-separated string:

[10]:
def describe_hw_component_sw_allocations(hw_component):
    return {
        "hardware_component": hw_component.name,
        "deployed_sw_components": "; ".join(
            i.name for i in get_sw_components(hw_component)
        ),
    }


df = pd.DataFrame(
    list(map(describe_hw_component_sw_allocations, cmps_with_sw))
)
df
[10]:
hardware_component deployed_sw_components
0 Sub PC PC 16
1 Deploy Sub PC PC 17
2 Compute Card 1 Card 1 OS; Camera Driver SWC; App 1 SWC
3 Compute Card 2 Card 2 OS; App 2 SWC
4 Network Switch Switch Firmware; Switch Configuration
5 Camera Assembly Camera Firmware
6 Computer Mail client
7 ISP Network Mail server

To complete the picture, we could also list all HW components that don’t have software (so that a sanity check could be performed):

[11]:
cmps_without_sw = cmps.filter(lambda i: not i.components.by_nature("BEHAVIOR"))
cmps_without_sw
[11]:
  1. PhysicalComponent "PC 1" (8a6d68c8-ac3d-4654-a07e-ada7adeed09f)
  2. PhysicalComponent "PC 3" (f5d7980d-e1e9-4515-8bb0-be7e80ac5839)
  3. PhysicalComponent "Vehicle" (a2c7f619-b38a-4b92-94a5-cbaa631badfc)
  4. PhysicalComponent "Vehicle" (b327d900-abd2-4138-a111-9ff0684739d8)
  5. PhysicalComponent "Equipment compartment" (3d68852d-fcc0-452c-af12-a2fbe22f81fa)
  6. PhysicalComponent "Server" (9137f463-7497-40c2-b20a-897158fdba9a)
  7. PhysicalComponent "Cooling Fan" (65e82f3f-c5b7-44c1-bfea-8e20bb0230be)
  8. PhysicalComponent "Sensor compartment" (3f416925-9d8a-4e9c-99f3-e912efb23d2f)
  9. PhysicalComponent "Router" (69bfe48d-78f0-4b1f-89c0-917dc2339ff9)
  10. PhysicalComponent "PA 1" (a0847e9c-8b82-407d-8143-e908e2db97a1)