The PyMCDP user manual

A quick tour

Describing Monotone Co-Design Problems (MCDPs)

The simplest MCDP can be defined as:

mcdp {

}

That is an empty MCDP - it has no functionality or resources.

The interface of an MCDP is defined using the keywords provides and requires:

mcdp {
    provides capacity [J]
    requires mass [g]

    # ...
}

The code above defines an MCDP with one functionality, capacity, measured in joules, and one resource, mass, measured in grams. (See how to describe types.)

Graphically, this is how the interface is represented:

G cluster1 cluster4 node3 capacity [J] node2 node3->node2 node5 mass [g] node2->node5

Constant functionality and resources

The following is a minimal example of a complete MCDP. We have given hard bounds to both capacity and mass.

mcdp {
    provides capacity [J]
    requires mass [g]

    provided capacity <= 500 J
    required mass >= 100g
}
G cluster1 cluster2 cluster6 node5 capacity [J] node4 500 J node5->node4 node3 100 g node7 mass [g] node3->node7

Describing relations between functionality and resources

Functionality and resources can depend on each other using any monotone relations.

For example, we can describe a linear relation between mass and capacity, given by the specific energy.

mcdp {
    provides capacity [J]
    requires mass [g]

    specific_energy = 4 J / g
    required mass >= provided capacity / specific_energy
}
G cluster1 cluster2 cluster5 node4 capacity [J] node3 × 0.25000 g/J node4->node3 node6 mass [g] node3->node6

Units

PyMCDP is picky about units, but generally very helpful. As long as the units have the right dimensionality, it will insert the appropriate conversions.

For example, this is the same example with the specific energy given in kWh/kg.

mcdp {
    provides capacity [J]
    requires mass [g]

    specific_energy = 200 kWh / kg
    required mass >= provided capacity / specific_energy
}
G cluster1 cluster2 cluster7 node6 capacity [J] node3 × 0.00500 kg/kWh node6->node3 node5 node3->node5 [J*kg/kWh] node4 node8 mass [g] node4->node8 node5->node4 [J*kg/kWh]

Composing MCDPs

Suppose we define a simple model called Battery as follows:

Battery.mcdpmcdp {
    provides capacity [J]
    requires mass [g]
    specific_energy = 100 kWh / kg
    required mass >= provided capacity / specific_energy
}
G cluster1 cluster4 node3 capacity [J] node2 Battery node3->node2 node5 mass [g] node2->node5

Let’s also define the MCDP Actuation1:

Actuation1.mcdpmcdp {
    provides lift [N]
    requires power [W]

    l = lift
    p0 = 5 W
    p1 = 6 W/N
    p2 = 7 W/N^2
    required power >= p0 + p1 * l + p2 * l^2
}
G cluster1 cluster4 node3 lift [N] node2 Actuation1 node3->node2 node5 power [W] node2->node5
G cluster16 cluster1 cluster2 node17 power [W] node15 lift [N] node5 node15->node5 node3 × 6.00000 W/N node9 node3->node9 [W] node4 node14 node4->node14 [W] node12 node5->node12 [N] node13 node5->node13 [N] node6 × 7.00000 W/N² node11 node6->node11 [W] node7 + 5 W node7->node17 node8 ^ 2 node10 node8->node10 [N²] node9->node4 [W] node10->node6 [N²] node11->node4 [W] node12->node3 [N] node13->node8 [N] node14->node7 [W]

Then we can combine these two together.

We can re-use previously defined MCDPs using the keyword new. This creates two sub-design problems, for now unconnected.

mcdp {
    actuation = new Actuation1
    battery = new Battery
}
G cluster1 node2 battery node7 node2->node7 mass [g] node3 actuation node6 node3->node6 power [W] node4 node4->node3 lift [N] node5 node5->node2 capacity [J]

To create a complete MCDP, take “endurance” as a high-level functionality. Then the energy required is equal to endurance × power.

mcdp {
    actuation = new Actuation1
    battery = new Battery

    # battery must provide power for actuation
    provides endurance [s]
    energy = provided endurance * (power required by actuation)

    capacity provided by battery >= energy
}
G cluster1 cluster2 node10 endurance [s] node5 node10->node5 node3 battery node9 node3->node9 mass [g] node4 actuation node6 node4->node6 power [W] node7 node5->node7 [J] node6->node5 [W] node7->node3 capacity [J] node8 node8->node4 lift [N]

We can create a model with a loop by introducing another constraint.

Take extra_payload to represent the user payload that we must carry.

Then the lift provided by the actuator must be at least the mass of the battery plus the mass of the payload times gravity:

Composition.mcdpmcdp {
    actuation = new Actuation1
    battery = new Battery

    # battery must provide power for actuation
    provides endurance [s]
    energy = provided endurance * (power required by actuation)

    capacity provided by battery >= energy

    # actuation must carry payload + battery
    provides payload [g]
    gravity = 9.81 m/s^2
    total_mass = (mass required by battery + provided payload)

    weight = total_mass * gravity

    lift provided by actuation >= weight

    # minimize total mass
    requires mass [g]
    required mass >= total_mass
}
G cluster1 cluster2 cluster19 node17 endurance [s] node7 node17->node7 node18 payload [g] node4 node18->node4 node3 × 9.81000 m/s² node14 node3->node14 [g*m/s²] node16 node4->node16 [g] node5 battery node10 node5->node10 mass [g] node6 actuation node12 node6->node12 power [W] node11 node7->node11 [J] node8 node15 node8->node15 [N] node9 node13 node9->node13 [g] node20 mass [g] node9->node20 node10->node4 [g] node11->node5 capacity [J] node12->node7 [W] node13->node3 [g] node14->node8 [g*m/s²] node15->node6 lift [N] node16->node9 [g]

Catalogues

We can also enumerate an arbitrary relation, as follows:

catalogue {
    provides capacity [J]
    requires mass [g]

    model1 |  5 MJ | 100 g
    model2 |  6 MJ | 200 g
    model3 | 10 MJ | 400 g
}
G cluster1 cluster4 node3 capacity [J] node2 CatalogueDP node3->node2 node5 mass [g] node2->node5

Coproducts (alternatives)

The coproduct construct allows to describe the idea of “alternatives”. The name comes from the category-theoretical concept of coproduct.

As an example, let us consider how to model the choice between different battery technologies.

Let us consider the model of a battery in which we take the functionality to be the capacity and the resources to be the mass [g] and the cost [$].

G cluster1 cluster4 node3 capacity [J] node2 Battery1 node3->node2 node5 mass [g] node2->node5

Consider two different battery technologies, characterized by their specific energy (Joules per gram) and specific cost (USD per gram).

Specifically, consider Nickel-Hidrogen batteries and Lithium-Polymer batteries. On technology is cheaper but leads to heavier batteries and viceversa. Because of this fact, there might be designs in which we prefer either.

First we model the two battery technologies separately as two MCDP using the same interface (same resources and same functionality).

Battery_LiPo.mcdpmcdp {
    provides capacity [J]
    requires mass [g]
    requires cost [$]

    specific_energy = 150 Wh/kg
    specific_cost = 2.50 Wh/$

    required mass >= provided capacity / specific_energy
    required cost >= provided capacity / specific_cost
}
Battery1_NiH2.mcdpmcdp {
    provides capacity [J]
    requires mass [g]
    requires cost [$]

    specific_energy = 45 Wh/kg
    specific_cost = 10.50 Wh/$

    required mass >= provided capacity / specific_energy
    required cost >= provided capacity / specific_cost
}
G cluster1 cluster4 node3 capacity [J] node2 Battery1_LiPo node3->node2 node5 cost [USD] node2->node5 node6 mass [g] node2->node6 G cluster1 cluster4 node3 capacity [J] node2 Battery1_NiH2 node3->node2 node5 cost [USD] node2->node5 node6 mass [g] node2->node6

Then we can define the coproduct of the two using the keyword choose. Graphically, the choice is indicated through dashed lines.

Batteries.mcdpchoose(
    NiH2: `Battery1_LiPo,
    LiPo: `Battery1_NiH2
)
G cluster1 cluster2 cluster9 node8 capacity [J] node5 node8->node5 node3 node11 mass [g] node3->node11 node4 node10 cost [USD] node4->node10 node6 NiH2 node5->node6 node7 LiPo node5->node7 node6->node3 node6->node4 node7->node3 node7->node4

Describing uncertain MCDPs

The keyword Uncertain is used to define uncertain relations.

For example, suppose there is some uncertain in the value of the specific energy, varying between 100 Wh/kg and 120 Wh/kg. This is one way to describe such uncertainty:

mcdp {
  provides capacity [Wh]
  requires mass     [kg]

  required mass >= 
    Uncertain(provided capacity/120 Wh/kg ,
              provided capacity/100 Wh/kg )

}

The resulting MCDP has an uncertainty gate, marked with “?”, which joins two branches, the optimistic and the pessimistic branch.

G cluster1 cluster2 cluster12 node11 capacity [Wh] node4 node11->node4 node3 × 0.00833 kg/Wh node7 node3->node7 [kg] node9 node4->node9 [Wh] node10 node4->node10 [Wh] node5 × 0.01000 kg/Wh node8 node5->node8 [kg] node6 node13 mass [kg] node6->node13 node7->node6 [kg] node8->node6 [kg] node9->node3 [Wh] node10->node5 [Wh]

Reference

Describing Posets

All values belong to posets.

PyMCDP knows a few built-in posets, and gives you the possibility of creating your own.

Natural numbers

The natural numbers with a completion are expressed as Nat and their values using the syntax Nat:42.

Floating point numbers

Floating point with completion are indicated by R, and their values as 42 [].

Floating point with completion and units are indicated using units, such as:

g, J, m, s, m/s, …

Their values are indicated as follows:

10 g, 20 J, 10 m, 10 s, 23 m/s, …

Finite Posets

It is possible to define and use your own arbitrary finite posets.

For example, create a file named my_poset.mcdp_poset containing the following definition:

my_poset.mcdp_posetfinite_poset {
    a <= b <= c
    c <= d
    c <= e  
}

This defines a poset with 5 elements a, b, c, d, e and with the given order relations.

Now that this poset has been defined, it can be used in the definition of an MCDP, by referring to it by name using the backtick notation, as in “`my_poset”.

To refer to its elements, use the notation `my_poset: element.

For example:

mcdp {
    provides f [`my_poset]

    f <= `my_poset : c
}
G cluster1 cluster2 node4 f [`my_poset] node3 'c' node4->node3

Poset Products

Use the Unicode symbol “×” or the simple letter x to create a poset product.

The syntax is

space × space × … × space

For example:

J × A

This represents a product of Joules and Amperes.

Tuple making

To create a tuple, use angular brackets.

The syntax is:

< element, element, … >
⟨ element, element, … ⟩

An example using regular brackets:

<0 J, 1 A>

An example using fancy unicode brackets:

0 J, 1 A

Tuple accessing

To access the elements of the tuple, use the syntax

take(value, index)

For example:

mcdp {
    provides out [ J x A ]

    take(out, 0) <= 10 J
    take(out, 1) <= 2 A
}

This is equivalent to

mcdp {
    provides out [ J x A ]

    out <= <10 J, 2 A>
}

Named Poset Products

PyMCDP also supports named products, in which each entry in the tuple is associated to a name. For example, the following declares a product of the two spaces J and A with the two entries named energy and current.

product(energy:J, current:A)

Then it is possible to index those entries using one of these two syntaxes:

take(value, label)
(value).label

For example:

mcdp {
    provides out [ product(energy:J, current:A) ]

    (out).energy <= 10 J
    (out).current <= 2 A
}

Operations on MCDPs

Abstraction and flattening

It is easy to create recursive composition in MCDP.

Composition1.mcdpmcdp {
  a = instance mcdp {
    c = instance mcdp {
      provides f [Nat]
      requires r [Nat]
      provided f + Nat:1 <= required r
    }

    provides f using c
    requires r for   c
  }

  b = instance mcdp {
    d = instance mcdp {
      provides f [Nat]
      requires r [Nat]
      provided f + Nat:1 <= required r
    }

    provides f using d
    requires r for   d
  }

  r required by a <= f provided by b
  requires r for b
  provides f using a
}
G cluster1 cluster2 cluster3 a cluster4 c cluster6 b cluster7 d cluster11 node10 f [N] node5 PlusNat:N→N node10->node5 node9 node8 PlusNat:N→N node9->node8 f [N] node5->node9 r [N] node12 r [N] node8->node12

We can completely abstract an MCDP, using the abstract keyword.

abstract `Composition1
G cluster1 cluster4 node3 f [N] node2 Series0 node3->node2 node5 r [N] node2->node5

And we can also completely flatten it, by erasing the border between subproblems:

flatten `Composition1
G cluster1 cluster2 cluster7 node6 f [N] node3 PlusNat:N→N node6->node3 node5 node3->node5 [N] node4 PlusNat:N→N node8 r [N] node4->node8 node5->node4 [N]

Approximations

Multiplication dual

mcdp  {
    provides a [R]
    requires b [R]
    requires c [R]
    a <= b * c
}
$$n=1$$ $$n=3$$ $$n=5$$ $$n=10$$ $$n=25$$

Addition dual

mcdp {
    provides a [R]
    requires b [R]
    requires c [R]
    a <= b + c
}
$$n=1$$ $$n=3$$ $$n=5$$ $$n=10$$ $$n=25$$

Scenarios

Modeling energetics constraints in a UAV

Battery model

A battery is specified as a DP with the functionalities:

and resources

G cluster1 cluster5 node3 capacity [J] node2 node3->node2 node4 missions node4->node2 node8 maintenance node2->node8 node6 cost [USD] node2->node6 node7 mass [g] node2->node7

The parameters for the model are the specific energy, the specific cost, and the number of cycles:

specific_energy = 150 Wh/kg
specific_cost = 2.50 Wh/$
cycles = 600 []

The cost for one battery is given by

unit_cost = provided capacity / specific_cost

The number of replacements is:

num_replacements = ceil(provided missions / cycles)

The total budget for the solution is given by the unit cost times the number of replacements:

required cost >= unit_cost  * num_replacements

The code below is the complete model for the battery:

Battery_LiPo.mcdpmcdp {
    provides capacity [J]
    provides missions [R]

    requires mass     [g]
    requires cost     [$]

    # Number of replacements
    requires maintenance [R]

    # Battery properties
    specific_energy = 150 Wh/kg
    specific_cost = 2.50 Wh/$
    cycles = 600 []

    # How many times should it be replaced?
    num_replacements = ceil(provided missions / cycles)
    required maintenance >= num_replacements

    required mass >= provided capacity / specific_energy

    # cost for one battery
    unit_cost = provided capacity / specific_cost
    required cost >= unit_cost  * num_replacements
}

This is a graphical representation of the network of constraints:

G cluster1 cluster2 cluster22 node20 capacity [J] node8 node20->node8 node21 missions node3 × 0.00167 node21->node3 node12 node3->node12 node4 × 0.40000 USD/Wh node14 node4->node14 [J*USD/Wh] node5 × 0.00667 kg/Wh node13 node5->node13 [J*kg/Wh] node6 node15 node6->node15 [J*USD/Wh] node7 node24 mass [g] node7->node24 node18 node8->node18 [J] node19 node8->node19 [J] node9 node16 node9->node16 node25 maintenance node9->node25 node10 node23 cost [USD] node10->node23 node11 ceil node17 node11->node17 node12->node11 node13->node7 [J*kg/Wh] node14->node6 [J*USD/Wh] node15->node10 [J*USD/Wh] node16->node6 node17->node9 node18->node5 [J] node19->node4 [J]

Actuation

The actuation is defined as a DP where the functionalities are:

and the resources are:

G cluster1 cluster5 node3 velocity [m/s] node2 node3->node2 node4 lift [N] node4->node2 node8 power [W] node2->node8 node6 actuator_mass [g] node2->node6 node7 cost [USD] node2->node7

The model first describes some hard constraints for the quantities:

provided lift <= 100N
required actuator_mass >= 100 g
required cost >= 100 $
provided velocity <= 3 m/s

Then it describes a nonlinear polynomial (and monotone) relation between lift and power:

p0 = 2 W
p1 = 1.5 W/N^2

required power >= p0 + (lift^2) * p1

This is the complete MCDP:

Actuation.mcdpmcdp {
    provides lift  [N]
    provides velocity [m/s]

    requires power [W]
    requires actuator_mass [g]
    requires cost [$]

    provided lift <= 100N
    required actuator_mass >= 100 g
    required cost >= 100 $
    provided velocity <= 3 m/s

    p0 = 2 W
    p1 = 1.5 W/N^2

    required power >= p0 + (lift^2) * p1
}

This is the graphical representation:

G cluster1 cluster2 cluster17 node16 lift [N] node7 node16->node7 node15 velocity [m/s] node9 3 m/s node15->node9 node3 × 1.50000 W/N² node12 node3->node12 [W] node4 100 g node18 actuator_mass [g] node4->node18 node5 100 USD node19 cost [USD] node5->node19 node6 + 2 W node20 power [W] node6->node20 node13 node7->node13 [N] node14 node7->node14 [N] node8 100 N node10 ^ 2 node11 node10->node11 [N²] node11->node3 [N²] node12->node6 [W] node13->node8 [N] node14->node10 [N]

Actuation + energetics

Next, we will define an MCDP that contains both actuation and energetics as sub-MCDPs.

We will take as high-level functionality:

G cluster8 cluster1 node9 total_cost [USD] node10 total_mass [g] node3 velocity [m/s] node2 node3->node2 node4 endurance [s] node4->node2 node5 extra_payload [kg] node5->node2 node6 num_missions node6->node2 node7 extra_power [W] node7->node2 node2->node9 node2->node10

In the model below, first we instantiate the models of battery and actuation:

battery = new Battery_LCO
actuation = new Actuation

The power constraint can be written as follows:

total_power = power required by actuation + extra_power
capacity provided by battery >= endurance * total_power

The lift constraint is the following:

total_mass = (
     mass required by battery +
     actuator_mass required by actuation
     + extra_payload)

gravity = 9.81 m/s^2
weight = total_mass * gravity

lift provided by actuation >= weight

The cost constraint is the following:

labor_cost = (10 $) * (maintenance required by battery)

 total_cost >= (
   cost required by actuation +
   cost required by battery +
   labor_cost)
ActuationEnergetics.mcdpmcdp {
    provides endurance     [s]
    provides extra_payload [kg]
    provides extra_power   [W]
    provides num_missions [R]
    provides velocity [m/s]

    requires total_cost [$]

    battery = new Battery_LCO
    actuation = new Actuation

    total_power = power required by actuation + extra_power
    capacity provided by battery >= endurance * total_power

    total_mass = (
      mass required by battery +
      actuator_mass required by actuation
      + extra_payload)

    gravity = 9.81 m/s^2
    weight = total_mass * gravity

    lift provided by actuation >= weight
    velocity provided by actuation >= velocity

    labor_cost = (10 $) * (maintenance required by battery)

    total_cost >= (
    cost required by actuation +
    cost required by battery +
    labor_cost)

    battery.missions >= num_missions

    requires total_mass >= total_mass   
}
G cluster1 cluster2 cluster31 node26 velocity [m/s] node9 actuation node26->node9 node27 endurance [s] node12 node27->node12 node28 extra_payload [kg] node7 node28->node7 node29 num_missions node8 battery node29->node8 node30 extra_power [W] node5 node30->node5 node3 × 9.81000 m/s² node20 node3->node20 [g*m/s²] node4 × 10.00000 USD node16 node4->node16 [USD] node17 node5->node17 [W] node6 node32 total_cost [USD] node6->node32 node14 node7->node14 [g] node13 node8->node13 cost [USD] node19 node8->node19 maintenance node23 node8->node23 mass [g] node15 node9->node15 cost [USD] node18 node9->node18 actuator_mass [g] node22 node9->node22 power [W] node10 node25 node10->node25 [g] node33 total_mass [g] node10->node33 node11 node24 node11->node24 [N] node21 node12->node21 [J] node13->node6 [USD] node14->node10 [g] node15->node6 [USD] node16->node6 [USD] node17->node12 [W] node18->node7 [g] node19->node4 node20->node11 [g*m/s²] node21->node8 capacity [J] node22->node5 [W] node23->node7 [g] node24->node9 lift [N] node25->node3 [g]

Other parts

These are other parts that we need.

G cluster1 cluster4 node3 computation [flops] node2 node3->node2 node5 power [W] node2->node5 G cluster1 cluster6 node3 resolution [pixels/deg] node2 node3->node2 node4 fov [] node4->node2 node5 framerate [Hz] node5->node2 node7 power [W] node2->node7
G cluster1 cluster4 node3 velocity [m/s] node2 node3->node2 node8 camera_resolution [pixels/deg] node2->node8 node5 camera_fov [] node2->node5 node6 camera_framerate [Hz] node2->node6 node7 computation [flops] node2->node7 G cluster1 cluster4 node3 distance [km] node2 node3->node2 node5 velocity [m/s] node2->node5 node6 endurance [s] node2->node6
Computer.mcdpmcdp {
    requires power [W]
    provides computation [flops]

    p0 = 1 W
    p1 = 100 W/flops
    power >= p0 + p1 * computation
}
Sensor.mcdptemplate mcdp {
    provides framerate [Hz]
    provides resolution [pixels/deg]
    provides fov [deg]
    requires power [W]
}
Perception.mcdptemplate mcdp {
    provides velocity [m/s]
    requires computation [flops]
    requires camera_framerate [Hz]
    requires camera_resolution [pixels/deg]
    requires camera_fov [deg]
}
Strategy.mcdpmcdp {
    provides distance [km]
    requires endurance [s]
    requires velocity [m/s]

    distance <= endurance * velocity
}

Complete drone model

Based on the previous models, we can assemble the model for the entire drone, with high-level functionality: travel_distance carry payload * num_missions

G cluster1 cluster6 node3 travel_distance [km] node2 node3->node2 node4 num_missions node4->node2 node5 carry_payload [g] node5->node2 node7 total_cost_ownership [USD] node2->node7 G cluster1 cluster31 cluster2 node28 travel_distance [km] node8 strategy node28->node8 node29 num_missions node6 actuation_energetics node29->node6 node30 carry_payload [g] node9 node30->node9 node3 perception node14 node3->node14 camera_framerate [Hz] node16 node3->node16 camera_resolution [pixels/deg] node17 node3->node17 camera_fov [] node24 node3->node24 computation [flops] node4 node27 node4->node27 [W] node5 node32 total_cost_ownership [USD] node5->node32 node19 node6->node19 total_cost [USD] node25 node6->node25 total_mass [g] node7 shipping node15 node7->node15 postage [USD] node21 node8->node21 endurance [s] node22 node8->node22 velocity [m/s] node20 node9->node20 [kg] node10 node18 node10->node18 [m/s] node26 node10->node26 [m/s] node11 computer node23 node11->node23 power [W] node12 sensor node13 node12->node13 power [W] node13->node4 [W] node14->node12 framerate [Hz] node15->node5 [USD] node16->node12 resolution [pixels/deg] node17->node12 fov [] node18->node3 velocity [m/s] node19->node5 [USD] node20->node6 extra_payload [kg] node21->node6 endurance [s] node22->node10 [m/s] node23->node4 [W] node24->node11 computation [flops] node25->node7 ships [g] node26->node6 velocity [m/s] node27->node6 extra_power [W]
DroneComplete.mcdpmcdp {
    provides travel_distance [km]
    provides num_missions [R]
    provides carry_payload [g]

    requires total_cost_ownership [$]

    strategy = new Strategy

    actuation_energetics = new ActuationEnergetics

    endurance prov. by actuation_energetics>= endurance req. by strategy
    velocity prov. by actuation_energetics>= velocity req. by strategy
    num_missions prov. by actuation_energetics >= provided num_missions
    extra_payload prov. by actuation_energetics>= provided carry_payload
    distance prov. by strategy >= provided travel_distance

    computer = new Computer
    perception = new Perception
    computation prov. by computer>= computation req. by perception


    sensor = new Sensor
    resolution prov. by sensor >= camera_resolution req. by perception
    sensor.framerate >= perception.camera_framerate
    sensor.fov >= perception.camera_fov

    perception.velocity >= strategy.velocity
    actuation_energetics.extra_power >= computer.power + sensor.power

    # We can take into account the shipping cost
    shipping = new Shipping

    total_mass = actuation_energetics.total_mass

    shipping.ships >= total_mass

    total_cost_ownership >= shipping.postage + actuation_energetics.total_cost
}

Customer preferences in the loop

Finally, we can define a model of the customer:

G cluster1 cluster4 node3 budget [USD] node2 node3->node2 node5 travel_distance [km] node2->node5 node6 num_missions node2->node6 node7 carry_payload [g] node2->node7

and put it in the loop:

CustomerPlusEngineering.mcdpmcdp {
    customer = new Customer
    robot = new DroneComplete

    budget provided by customer >= total_cost_ownership required by robot

    travel_distance provided by robot >= travel_distance required by customer
    num_missions    provided by robot >= num_missions    required by customer
    carry_payload   provided by robot >= carry_payload   required by customer
}
G cluster1 node2 customer node4 node2->node4 num_missions node5 node2->node5 carry_payload [g] node7 node2->node7 travel_distance [km] node3 robot node6 node3->node6 total_cost_ownership [USD] node4->node3 num_missions node5->node3 carry_payload [g] node6->node2 budget [USD] node7->node3 travel_distance [km]

The socket/plugs/converters domain

We are going to model the domain of AC sockets and plugs.

Sockets

There are many AC power plugs and sockets.

Type A
Type B
Type C
Type D
Type E
Type F
Type G
Type H
Type I
Type J
Type K
Type L
Type M
Type N
Type O

Some of them are compatible. For example we can fit a plug ot Type A into a socket of Type B. This creates a natural partial order structure.

We can use a finite_poset to describe the poset as follows:

socket_type.mcdp_posetfinite_poset {
  TypeA TypeB TypeC TypeD TypeE TypeF  TypeG  TypeH  
  TypeI2 TypeI3 TypeJ TypeK TypeL TypeM TypeN  TypeO

  # A <= B: if it fits in A, it also fits in B
  TypeA  <= TypeB
  TypeC  <= TypeD   
  TypeE  <= TypeD   
  TypeF  <= TypeD   
  TypeC  <= TypeE
  TypeF  <= TypeE
  TypeC  <= TypeH
  TypeI2 <= TypeI3
  TypeC  <= TypeJ
  TypeK  <= TypeC
  TypeC  <= TypeL
  TypeC  <= TypeN
  TypeC  <= TypeO
}

Voltages

Around the world, the two main voltages are 110V and 220V. In this case, we cannot use the usual Volt poset indicated by V, because that would mean that 220V is always preferable to 110V.

Thus we create a discrete poset as follows:

AC_voltages.mcdp_posetfinite_poset {
  v110 v220
}

Frequencies

Similarly, we model different frequencies with the poset

AC_frequencies.mcdp_posetfinite_poset {
    f50 f60
}

Power consumption

The function of a socket in the wall is to provide power. This function is parameterized (at least) by:

Therefore, we can create the poset `AC_power as follows:

AC_power.mcdp_posetproduct(
         task: S(AC_power),
       socket: `socket_type,
      voltage: `AC_voltages,
    frequency: `AC_frequencies,
        watts: W
)

Modeling a plug adapter

Based on these definitions, we can define the function of a socket adapter.

Consider one of these OREI adapters, which you can buy for $7.31:

This is an adapter from Type L to either Type C or Type A:

Type L
Type A
Type C

A plug adapter can be modeled as follows:

orei.mcdp# This device converts from TypeC to either TypeL or TypeA
mcdp {
    provides out [`AC_power]
    requires in  [`AC_power] 
    requires budget [USD]

    budget >= 7.31 USD

    (required in).socket >= `socket_type : TypeL
    (required in).voltage   >=  (provided out).voltage
    (required in).frequency >=  (provided out).frequency
    (required in).watts     >=  (provided out).watts

    (provided out).socket <= any-of({`socket_type : TypeA, `socket_type : TypeC})
}
G cluster1 cluster4 node3 out [`AC_power] node2 node3->node2 node5 budget [USD] node2->node5 node6 in [`AC_power] node2->node6 G cluster1 cluster2 cluster18 node17 out [`AC_power] node6 node17->node6 node3 7.31 USD node19 budget [USD] node3->node19 node4 'AC_power' node12 node4->node12 [FinitePoset(1 els)] node5 'TypeL' node13 node5->node13 [`socket_type] node10 node6->node10 [FinitePoset(1 els)] node11 node6->node11 [`socket_type] node14 node6->node14 [`AC_frequencies] node15 node6->node15 [`AC_voltages] node16 node6->node16 [W] node7 node20 in [`AC_power] node7->node20 node8 'AC_power' node9 ↓{'TypeA', 'TypeC'} node10->node8 [FinitePoset(1 els)] node11->node9 [`socket_type] node12->node7 [FinitePoset(1 els)] node13->node7 [`socket_type] node14->node7 [`AC_frequencies] node15->node7 [`AC_voltages] node16->node7 [W]

A 2-in-1 adapter

This is another handy 2-in-1 adapter that sells for 6.31:

This one provides 2 outputs:

orei_2in1.mcdpmcdp {
    provides out1 [`AC_power]
    provides out2 [`AC_power]
    requires in  [`AC_power] 
    requires budget [USD]

    budget >= 6.31 USD
 
    (required in).socket >= `socket_type : TypeM

    (provided out1).socket <= any-of({`socket_type : TypeA, `socket_type : TypeC})
    (provided out2).socket <= any-of({`socket_type : TypeA, `socket_type : TypeC})

    # this forces the two voltages to be the same
    (required in).voltage   >=  (provided out1).voltage
    (required in).voltage   >=  (provided out2).voltage
    (required in).frequency >=  (provided out1).frequency
    (required in).frequency >=  (provided out2).frequency
    # this says that the power sums
    total_power = (provided out1).watts + (provided out2).watts
    (required in).watts >=  total_power
}

We can forget all this complexity and consider the block:

G cluster1 cluster5 node3 out1 [`AC_power] node2 node3->node2 node4 out2 [`AC_power] node4->node2 node6 budget [USD] node2->node6 node7 in [`AC_power] node2->node7 G cluster1 cluster2 cluster33 node32 out2 [`AC_power] node11 node32->node11 node31 out1 [`AC_power] node12 node31->node12 node3 node29 node3->node29 [W] node4 6.31 USD node34 budget [USD] node4->node34 node5 'AC_power' node20 node5->node20 [FinitePoset(1 els)] node6 'TypeM' node25 node6->node25 [`socket_type] node7 node21 node7->node21 [`AC_voltages] node8 node27 node8->node27 [`AC_frequencies] node9 ↓{'TypeA', 'TypeC'} node10 ↓{'TypeA', 'TypeC'} node16 node11->node16 [`socket_type] node18 node11->node18 [`AC_frequencies] node23 node11->node23 [`AC_voltages] node24 node11->node24 [W] node28 node11->node28 [FinitePoset(1 els)] node17 node12->node17 [FinitePoset(1 els)] node19 node12->node19 [`AC_frequencies] node22 node12->node22 [`socket_type] node26 node12->node26 [`AC_voltages] node30 node12->node30 [W] node13 node35 in [`AC_power] node13->node35 node14 'AC_power' node15 'AC_power' node16->node9 [`socket_type] node17->node14 [FinitePoset(1 els)] node18->node8 [`AC_frequencies] node19->node8 [`AC_frequencies] node20->node13 [FinitePoset(1 els)] node21->node13 [`AC_voltages] node22->node10 [`socket_type] node23->node7 [`AC_voltages] node24->node3 [W] node25->node13 [`socket_type] node26->node7 [`AC_voltages] node27->node13 [`AC_frequencies] node28->node15 [FinitePoset(1 els)] node29->node13 [W] node30->node3 [W]

DC connectors

We can repeat the same story with DC connectors.

barrel_connectors.mcdp_posetfinite_poset {
    tip_sleeve_2_5mm
    tip_sleeve_3_5mm
    barrel_5mm
    # EIAJ-01 (2.35mm barrel, 0.7mm inner diameter)
    barrel_2_35mm
    barrel_3_35mm_1_35mm
    # EIAJ-02 (4.0mm barrel, 1.7mm inner diameter)
    barrel_4mm_1_7mm
    barrel_4mm_1_5mm
    barrel_4mm_2_5mm
}

USB connectors

USB_connectors.mcdp_posetfinite_poset {
    USB_Micro_A
    USB_Micro_B
    USB_Mini_A
    USB_Mini_B
    USB_Std_A
    USB_Std_B
    USB_Type_C
}

DC connectors

We can define DC connectors to be the union (co-product) of the two sets:

DC_connectors.mcdp_posetcoproduct(`barrel_connectors, `USB_connectors)

DC power

DC_voltages.mcdp_posetfinite_poset {
  v1_5 v5 v6_6 
}
DC_power.mcdp_posetproduct(
      task: S(DC_power), 
 connector: `DC_connectors,
   voltage: `DC_voltages,
      amps: A
)

AC-DC converters

This wall charger can be used to convert from AC power to DC power.

Ravpower.mcdpmcdp {
    provides out1 [`DC_power]
    provides out2 [`DC_power]
    requires in  [`AC_power] 
    requires budget [USD]

    budget >= 10.99 USD    

    (required in).socket >= `socket_type : TypeA

    (provided out1).voltage   <= `DC_Voltages: v5
    (provided out2).voltage   <= `DC_Voltages: v5
    (provided out1).connector <= `USB_connectors:USB_Std_A 
    (provided out2).connector <= `USB_connectors:USB_Std_A

    # this forces the two voltages to be the same
    # this says that the power sums
    amps = (provided out1).amps + (provided out2).amps
    amps <= 2.4 A
    power = 5 V * (amps)

    (required in).watts >= power

}
G cluster1 cluster5 node3 out1 [`DC_power] node2 node3->node2 node4 out2 [`DC_power] node4->node2 node6 budget [USD] node2->node6 node7 in [`AC_power] node2->node7 G cluster1 cluster2 cluster43 node41 out1 [`DC_power] node12 node41->node12 node42 out2 [`DC_power] node11 node42->node11 node3 × 5.00000 V node24 node3->node24 [W] node4 node30 node4->node30 [A] node5 10.99 USD node44 budget [USD] node5->node44 node6 'AC_power' node26 node6->node26 [FinitePoset(1 els)] node7 'TypeA' node27 node7->node27 [`socket_type] node8 ↑{'f50', 'f60'} node34 node8->node34 [`AC_frequencies] node9 ↑{'v220', 'v110'} node25 node9->node25 [`AC_voltages] node10 node23 node10->node23 [A] node36 node10->node36 [A] node28 node11->node28 [`DC_connectors] node35 node11->node35 [A] node38 node11->node38 [`DC_voltages] node39 node11->node39 [FinitePoset(1 els)] node29 node12->node29 [`DC_voltages] node32 node12->node32 [FinitePoset(1 els)] node33 node12->node33 [`DC_connectors] node40 node12->node40 [A] node13 node45 in [`AC_power] node13->node45 node14 node37 node14->node37 [`USB_connectors] node15 node31 node15->node31 [`USB_connectors] node16 'USB_Std_A' node17 2.4 A node18 'DC_power' node19 'DC_power' node20 'v5' node21 'v5' node22 'USB_Std_A' node23->node3 [A] node24->node13 [W] node25->node13 [`AC_voltages] node26->node13 [FinitePoset(1 els)] node27->node13 [`socket_type] node28->node15 [`DC_connectors] node29->node20 [`DC_voltages] node30->node10 [A] node31->node16 [`USB_connectors] node32->node18 [FinitePoset(1 els)] node33->node14 [`DC_connectors] node34->node13 [`AC_frequencies] node35->node4 [A] node36->node17 [A] node37->node22 [`USB_connectors] node38->node21 [`DC_voltages] node39->node19 [FinitePoset(1 els)] node40->node4 [A]

We can query the model as follows. Suppose we need 2 outputs, each of 0.5A.

solve(
     S(DC_power):*, `USB_connectors:USB_Std_A, `DC_voltages: v5, 0.5 A,
      S(DC_power):*, `USB_connectors:USB_Std_A, `DC_voltages: v5, 0.5 A, 
    `Ravpower)

This is the output:

↑{⟨in:⟨'AC_power', 'TypeA', 'v110', 'f50', 5 W⟩, budget:10.99 USD⟩, ⟨in:⟨'AC_power', 'TypeA', 'v110', 'f60', 5 W⟩, budget:10.99 USD⟩, ⟨in:⟨'AC_power', 'TypeA', 'v220', 'f50', 5 W⟩, budget:10.99 USD⟩, ⟨in:⟨'AC_power', 'TypeA', 'v220', 'f60', 5 W⟩, budget:10.99 USD⟩}

The model says we have two options: we need to find an outlet of TypeM at either 110 V or 220 V which will provide 5 W of power. Moreover, we need at least 10.99 USD to buy the component.

Composition

This is an example of composition of the Ravpower charger and the Orei_2in1 adapter.

orei_plus_ravpower.mcdpmcdp {
    provides out [`DC_power]   
    requires in [`AC_power]   
    requires budget [USD]  
    charger = instance `Ravpower
    adapter = instance `Orei_2in1
    in required by charger <= out1 provided by adapter
    required in >= in required by adapter
    provided out <= out1 provided by charger 
    # sum the budget together
    budget >= budget required by charger + budget required by adapter
    # ignore the functions we don't need
    ignore out2 provided by charger 
    ignore out2 provided by adapter
}

Note the use of the keyword “ignore” to ignore the functionality that we do not need.

G cluster1 cluster2 cluster14 node13 out [`DC_power] node6 charger node13->node6 node3 node15 budget [USD] node3->node15 node4 ↑{⟨task:'AC_power', s... node11 node4->node11 [`AC_power] node5 ↑{⟨task:'DC_power', c... node12 node5->node12 [`DC_power] node9 node6->node9 budget [USD] node10 node6->node10 in [`AC_power] node7 adapter node8 node7->node8 budget [USD] node16 in [`AC_power] node7->node16 node8->node3 [USD] node9->node3 [USD] node10->node7 out1 [`AC_power] node11->node7 out2 [`AC_power] node12->node6 out2 [`DC_power]

We can ask now for what resources we would need for a 0.5 A load:

solve(
    S(DC_power):*, `USB_connectors:USB_Std_A, `DC_voltages: v5, 0.5 A,
    `orei_plus_ravpower)

and obtain

↑{⟨in:⟨'AC_power', 'TypeM', 'v110', 'f50', 2.5 W⟩, budget:17.3 USD⟩, ⟨in:⟨'AC_power', 'TypeM', 'v110', 'f60', 2.5 W⟩, budget:17.3 USD⟩, ⟨in:⟨'AC_power', 'TypeM', 'v220', 'f50', 2.5 W⟩, budget:17.3 USD⟩, ⟨in:⟨'AC_power', 'TypeM', 'v220', 'f60', 2.5 W⟩, budget:17.3 USD⟩}

A step-up/step-down voltage converter

Next, we are going to model a Goldsource STU-200 Step Up/Down Voltage Transformer Converter.

This is a device with 1 input and 3 outputs:

goldsource_STU_200.mcdpmcdp {
   # This provides 3 outputs: 2 AC and 1 DC
   provides out1 [`AC_power]
   provides out2 [`AC_power]
   provides out3 [`DC_power]
   requires in [`AC_power]
 
   # the AC output 1 is v110 and takes types A, B (A<=B)
   (provided out1).socket <= `socket_type: TypeB 
   (provided out1).voltage <= `AC_voltages:v110
   (provided out1).frequency <= (required in).frequency # same frequency
   power1 = (provided out1).watts

   # the AC output 2 is v220 and takes types C, D, E, F, G, H (D,G minimals)
   (provided out2).socket <= any-of({ `socket_type: TypeD, `socket_type: TypeG })
   (provided out2).voltage <= `AC_voltages:v220
   (provided out2).frequency <= (required in).frequency # same frequency
   power2 = (provided out2).watts

   # the DC output is 5v, USB Type A
   (provided out3).connector <= `USB_connectors: USB_Std_A
   (provided out3).voltage   <= `DC_voltages: v5
   amp3 = (provided out3).amps
   power3 = 5 V * (amp3)
   
   power = power1 + power2 + power3
   (required in).watts >= power

   # input is either typeC or typeD (C<=D)
   (required in).socket >= `socket_type: TypeC
}
G cluster1 cluster6 node3 out1 [`AC_power] node2 node3->node2 node4 out2 [`AC_power] node4->node2 node5 out3 [`DC_power] node5->node2 node7 in [`AC_power] node2->node7 G cluster1 cluster2 cluster47 node44 out1 [`AC_power] node13 node44->node13 node45 out2 [`AC_power] node11 node45->node11 node46 out3 [`DC_power] node10 node46->node10 node3 × 5.00000 V node34 node3->node34 [W] node4 node26 node4->node26 [W] node5 'TypeC' node31 node5->node31 [`socket_type] node6 ↑{'v220', 'v110'} node33 node6->node33 [`AC_voltages] node7 'AC_power' node37 node7->node37 [FinitePoset(1 els)] node8 node32 node8->node32 [`AC_frequencies] node9 ↓{'TypeD', 'TypeG'} node30 node10->node30 [FinitePoset(1 els)] node36 node10->node36 [`DC_voltages] node39 node10->node39 [`DC_connectors] node43 node10->node43 [A] node27 node11->node27 [`AC_voltages] node28 node11->node28 [`socket_type] node35 node11->node35 [`AC_frequencies] node40 node11->node40 [W] node42 node11->node42 [FinitePoset(1 els)] node12 node48 in [`AC_power] node12->node48 node23 node13->node23 [`AC_voltages] node24 node13->node24 [W] node29 node13->node29 [FinitePoset(1 els)] node38 node13->node38 [`AC_frequencies] node41 node13->node41 [`socket_type] node14 node25 node14->node25 [`USB_connectors] node15 'DC_power' node16 'USB_Std_A' node17 'v5' node18 'AC_power' node19 'AC_power' node20 'TypeB' node21 'v110' node22 'v220' node23->node21 [`AC_voltages] node24->node4 [W] node25->node16 [`USB_connectors] node26->node12 [W] node27->node22 [`AC_voltages] node28->node9 [`socket_type] node29->node18 [FinitePoset(1 els)] node30->node15 [FinitePoset(1 els)] node31->node12 [`socket_type] node32->node12 [`AC_frequencies] node33->node12 [`AC_voltages] node34->node4 [W] node35->node8 [`AC_frequencies] node36->node17 [`DC_voltages] node37->node12 [FinitePoset(1 els)] node38->node8 [`AC_frequencies] node39->node14 [`DC_connectors] node40->node4 [W] node41->node20 [`socket_type] node42->node19 [FinitePoset(1 els)] node43->node3 [A]

Space Rovers Energetics

One option: thermocouple

A thermocouple is a device that converts heat into electrical power.

Thermocouple.mcdptemplate mcdp {
    provides power [W]
    requires heat [W]
    requires mass [g]
    requires cost [USD]
}
G cluster1 cluster4 node3 power [W] node2 Thermocouple node3->node2 node5 heat [W] node2->node5 node6 cost [USD] node2->node6 node7 mass [g] node2->node7
One way to get the heat is to procure a bit of Plutonium.
PlutoniumPellet.mcdpmcdp {
    provides heat [W]
    requires plutonium [g]
    requires mass [g]

    # Plutonium 238
    decay_heat = 560 W/kg

    m = heat / decay_heat

    required mass >= m
    required plutonium >= m
}
G cluster1 cluster4 node3 heat [W] node2 PlutoniumPellet node3->node2 node5 mass [g] node2->node5 node6 plutonium [g] node2->node6
We can connect the two, by specifying that the heat required by the thermocouple is provided by the pellet:

G node1 thermocouple node3 node1->node3 heat [W] node6 node1->node6 cost [USD] node8 node1->node8 mass [g] node2 plutonium_pellet node5 node2->node5 mass [g] node7 node2->node7 plutonium [g] node3->node2 heat [W] node4 node4->node1 power [W]

The masses are summed together:
mcdp { 
    plutonium_pellet = new PlutoniumPellet
    thermocouple = new Thermocouple

    heat provided by plutonium_pellet >= heat required by thermocouple

    requires plutonium for plutonium_pellet
    provides power_profile <= thermocouple.power
    requires cost >= thermocouple.cost
    requires mass >= thermocouple.mass + plutonium_pellet.mass
}
G cluster1 cluster2 cluster10 node9 power_profile [W] node4 thermocouple node9->node4 node3 node12 mass [g] node3->node12 node6 node4->node6 heat [W] node8 node4->node8 mass [g] node11 cost [USD] node4->node11 node5 plutonium_pellet node7 node5->node7 mass [g] node13 plutonium [g] node5->node13 node6->node5 heat [W] node7->node3 [g] node8->node3 [g]

Putting everything together

G cluster1 cluster2 cluster36 battery_plus_solar cluster37 battery_plus_solar cluster9 rtig cluster12 rtig cluster14 plutonium_pellet cluster26 solar cluster29 battery cluster49 node48 power_profile [W] node8 node48->node8 node3 node52 mass [g] node3->node52 node38 node4 node38->node4 node39 node39->node3 node40 battery node42 node40->node42 charging_profile [W] node43 node40->node43 cost [USD] node45 node40->node45 mass [g] node41 solar_panels node44 node41->node44 cost [USD] node46 node41->node46 mass [g] node5 node41->node5 node6 node41->node6 node42->node41 power_profile [W] node43->node38 [USD] node44->node38 [USD] node45->node39 [g] node46->node39 [g] node47 0 g node7 node47->node7 node54 area [m²] node5->node54 node50 solar_radiation [lx] node6->node50 node53 plutonium [g] node7->node53 node8->node40 node22 thermocouple node8->node22 node28 solar_panels node8->node28 node30 battery node8->node30 node10 0 m² node10->node5 node11 0 lx node11->node6 node13 node13->node3 node15 × 0.00179 kg/W node21 node15->node21 [kg] node16 node19 node16->node19 [kg] node20 node16->node20 [kg] node17 node25 node17->node25 mass [g] node18 node18->node7 node19->node17 [kg] node20->node18 [kg] node21->node16 [kg] node23 node22->node23 heat [W] node24 node22->node24 mass [g] node22->node4 node23->node15 heat [W] node24->node13 [g] node25->node13 [g] node51 cost [USD] node4->node51 node27 0 g node27->node7 node28->node3 node28->node5 node28->node6 node28->node4 node32 0 lx node32->node6 node33 0 m² node33->node5 node34 0 W node35 node35->node34 [W] node30->node3 node30->node4 node30->node35 charging_profile [W] node31 0 g node31->node7

Draft documentation

Load

load <name>

`<name>

Space expressions

set-of

(V)
set-of(V)

UpperSets

The syntax is

UpperSets(poset)

For example:

UpperSets(V)

Interval

The syntax is

Interval(lower bound,upper bound)

For example:

Interval(1g, 10g)

Singletons

S(tag)

S(tag):*

Constant expressions

Top, Bottom

The syntax is:

⊤ <space>
Top <space>
⊥ <space>
Bottom <space>

For example:

Top V
 V
Bottom V
 V

set making

The syntax is:

{<element>, <element>, ..., <element> }

For example:

{0 g, 1 g}

upperclosure

The syntax is:

upperclosure <set>

For example:

upperclosure {0 g, 1 g}

Operations

ignore

Suppose f has type F. Then:

ignore f provided by x

is equivalent to

f provided by x >= any-of(Minimals F)

Equivalently,

ignore r required by x

is equivalent to

r required by x <= any-of(Maximals R)

available math operators

ceil
sqrt
square
pow
max
min

Operations on NDPs

provides f using c
requires r for   c

implemented-by

approx

approx()

abstract

abstract <ndp>

compact

compact <ndp>

template

template <ndp>

flatten

flatten <ndp>

canonical

canonical <ndp>

approx_lower, approx_upper

approx_lower(<n>, <ndp>)
approx_upper(<n>, <ndp>)

solve

solve(value, <mcdp>)

Uncertain