﻿P := plan {
    // ******* Parameters *******

    // The product profit per unit (P rows).
    param Products := [
        { PID: "P0", Profit: 12.0, Want:  7.0 },
        { PID: "P1", Profit: 15.0, Want: 10.0 },
    ];

    // The resource quantities that we have (R rows).
    param Resources := [
        { RID: "R0", Have: 20 },
        { RID: "R1", Have: 35 },
    ];

    // The amount of each resource required for each product unit (R x P rows).
    param R_Per_P := [
        { PID: "P0", RID: "R0", Need: 1.0 },
        { PID: "P0", RID: "R1", Need: 2.0 },
        { PID: "P1", RID: "R0", Need: 3.0 },
        { PID: "P1", RID: "R1", Need: 2.0 },
    ];

    // ******* Computed Constants *******

    const NumProds := Count(Products);

    // Indexed products.
    const IndexedProducts := ForEach(p: Products, Index: Range(NumProds), p+>{ Index });

    // Resource amount information, eg, amt we have, and amt need for each kind of product.
    const ResAmtInfo := R_Per_P
        ->GroupBy([key] RID, [group] Needs: group->KeyJoin(IndexedProducts, PID, PID, { Index, RID, Need }))
        ->KeyJoin(as rn, Resources, RID, RID, rn+>{ Have });

    // ******* Free Variables *******

    // How much of each product to make (P slots in a tensor).
    var Make from Tensor.Fill(0.0, NumProds) def Tensor.From(IndexedProducts.Want);

    // ******* Computed Variables *******

    let ResAmts := ResAmtInfo->{ RID, Have, Need: Sum(Needs, Need * Make[Index]) };
    let ProdAmts := IndexedProducts+>{ Make: Make[Index] };

    // ******* Measures *******

    // Total profit.
    msr Profit := Sum(IndexedProducts, Profit * Make[Index]);

    // ******* Constraints *******

    con ResCons := ResAmts.Need <= ResAmts.Have;
};

P;
