Comparing Promula to Julia
gmADS is built on the Promula platform (Processor for Multi-dimensional Analysis). This platform has been under development and sustained for use, mainly for large electric utility planning models [1], since 1982. However, in order for Promula to survive beyond its creators, it must be repackaged as open source and promoted to learning institutions. We hope these efforts will help Promula gain a following so it may benefit science and discovery for the long term.
Promula has a unique approach to handling the declaration, storage, and manipulation of multi-dimensional numerical data. It is an excellent fit for implementing models and calculations involving array variables that share dimensions. Its streamlined notation allows analysts to keep their mind on the model rather than on complex programming syntax. The system is also very fast because it stores and accesses information in memory as arrays directly rather than as tables, trees, or other structures.
Probably the best way to introduce Promula is by comparison to another popular modeling language, Julia.Â
Overview
The document compares Promula and Julia for use in multi-dimensional numerical modeling applications. These samples were randomly selected from a recent Proof of Concept project to evaluate the feasibility of rewriting a very large Promula model with Julia. Note, the Promula code uses data stored in a Promula array datastores, the Julia code uses data stored in an HDF5 hierarchical datastores.
Data Structures in Promula
For simplicity, the definition of the data structures used in this comparison is presented in Promula notation:
Define Set      EC(43)    'Economic Categories'      Enduse(6) 'Enduses'      Tech(10)  'Technology Types'      Area(25)  'Demand Area'   End Define Set   Define Variable      DOCF(Enduse,Tech,EC,Area) 'Device Operating Cost Fraction ($/Yr/$)'      DCC(Enduse,Tech,EC,Area)  'Device Capital Cost ($/mmBtu/Yr)'      MCFU(Enduse,Tech,EC,Area) 'Marginal Cost of Fuel Use ($/mmBtu)'      DCCR(Enduse,Tech,EC,Area) 'Device Capital Charge Rate ($/Yr/$)'      ECFP(Enduse,Tech,EC,Area) 'Fuel Price ($/mmBtu)'      DEE(Enduse,Tech,EC,Area)  'Device Efficiency (Btu/Btu)'   End Define Variable   Define Variable Scratch      DOMC(Enduse,Tech,EC,Area)    'Device Operating Cost ($/mmBtu)'   End Define Variable Scratch
The Promula data definition language defines the dimensions of arrays in terms of Sets, not just their sizes. This has powerful implications for how the arrays behave and interact with each other.
Calculations Comparison: MarginalCostOfFuelUsage
Promula
Define Procedure MarginalCostOfFuelUsage    DOMC=DOCF*DCC    MCFU=DCCR*DCC+DOMC+ECFP/DEE    Write Disk(MCFU) End Procedure MarginalCostOfFuelUsage (145 characters)
The Promula code above users implicit subscripting wherein arrays dimensioned by the same sets are automatically indexed in sync during calculations.
Julia
function MarginalCostOfFuelUsage(data::Data) (; db, year) = data (; Area, EC, Tech, Enduse) = data (; DOMC, DOCF, DCC, MCFU, DCCR, ECFP, DEE) = data for area in Select(Area), ec in Select(EC), tech in Select(Tech), enduse in Select(Enduse) DOMC[enduse, tech, ec, area] = DOCF[enduse, tech, ec, area] * DCC[enduse, tech, ec, area] end for area in Select(Area), ec in Select(EC), tech in Select(Tech), enduse in Select(Enduse) MCFU[enduse, tech, ec, area] = DCCR[enduse, tech, ec, area] * DCC[enduse, tech, ec, area] + DOMC[enduse, tech, ec, area] + ECFP[enduse, tech, ec, area] * finite_inverse(DEE[enduse, tech, ec, area]) end WriteDisk(db, "$Outpt/MCFU", year, MCFU) end (699 characters)
The Julia code depends on a Select and WriteDisk macros to facilitate accessing arrays that are logically indexed by sets and stored in HDF5.
Calculations Comparison: MShare
Promula
Define Procedure MShare MAW=exp(MMSM0+LN(MSMM)+MVF*LN((MCFU/Inflation/PEE)/(MCFU0/Infla0/PEE0))) TMAW(EU,EC,Area)=sum(TE)(MAW(EU,TE,EC,Area)) MMSF=MAW/TMAW Write Disk(MMSF) End Procedure MShare (200 characters)
Julia
function MShare(data::Data) (; db, year) = data (; Area, EC, Tech, Enduse) = data (; MAW, MMSM0, MSMM, MVF, MCFU, Inflation, Inflation0, PEE, MCFU0, PEE0, TMAW, MMSF) = data for area in Select(Area), ec in Select(EC), tech in Select(Tech), enduse in Select(Enduse) MAW[enduse, tech, ec, area] = finite_exp( (MMSM0[enduse, tech, ec, area] + finite_log(MSMM[enduse, tech, ec, area])) + MVF[enduse, tech, ec, area] * finite_log( (MCFU[enduse, tech, ec, area] * finite_inverse(Inflation[area]) * finite_inverse(PEE[enduse, tech, ec, area])) * finite_inverse(( MCFU0[enduse, tech, ec, area] * finite_inverse(Inflation0[area]) * finite_inverse(PEE0[enduse, tech, ec, area]) )), ), ) end for area in Select(Area), ec in Select(EC), enduse in Select(Enduse) TMAW[enduse, ec, area] = sum(MAW[enduse, tech, ec, area] for tech in Select(Tech)) end for area in Select(Area), ec in Select(EC), tech in Select(Tech), enduse in Select(Enduse) MMSF[enduse, tech, ec, area] = MAW[enduse, tech, ec, area] * finite_inverse(TMAW[enduse, ec, area]) end # Write Disk(MMSF) WriteDisk(db, "$Outpt/MMSF", year, MMSF) end (1,174 characters)
[1]: Promula has been used to develop the Energy 2020 planning model for over 30 years. Visit https://www.energy2020.com/ to learn more.