Having Your Cake and Eating It Too: 

User interfaces with core model backends

Andrew Yule, Assured Flow Solutions

Agenda

Introduction

Background / motivation

MVVM introduction

Examples implementing our MVVM

Conclusions

Introduction

About me

Chemical engineer in the oil and gas industry

Using Mathematica and the Wolfram Language for over 9 years

Assured Flow Solutions

Specialty engineering/consulting that focuses on solving production related issues for oil and gas companies

Use a combination of simulation modeling and production chemistry testing to identify issues and develop strategies to avoid them

ImageCrop[Import["\\\\Mac\\iCloud\\AFS\\Conferences\\Wolfram Technical Conferences\\2017\\Presentation Images\\Company_Overview_Small.png"],{700,435},Top]

Flow assurance??

Within the industry, our specialty is called Flow Assurance as we attempt to assure flow of hydrocarbons from reservoir to topsides

Flow assurance is necessary for many reasons the biggest of which are safety and profitability

Majority of work falls within either engineering category or production chemistry category

({{(RowDefault)}, {(RowDefault)}})

Background / Motivation

Assured Flow Solutions’ Alex

An attempt to capture our knowledge in a consistent and maintainable way

Nearly 50 unique utilities (inlcluding the beginnings of our LIMS)

Developed in Mathematica and distributed via Enterprise Wolfram Player

Utilities are combinations of calculations and workflows

Leverage power of enterprise deployement by allowing for arbitrary InputFields and importing of any file type

SlideView[Grid[{#}]&/@ArrayReshape[Show[Import[#],ImageSize->500]&/@SortBy[FileNames[__~~".png","\\\\Mac\\iCloud\\AFS\\Conferences\\Wolfram Technical Conferences\\2017\\Alex Screenshots"],FileBaseName[#]&],{6,2}]]

Something was still missing

Myself and some of my colleagues who actually develope Alex found ourselves being forced to use the User Interface instead of having the free ability to use Wolfram Language as it was meant to be used

New employees we’ve hired coming from school more more are coming in with skills in other languages like Python and using Alex in Wolfram Language also kept them somewhat isolated

Needed some better way of “opening up” the power of Alex to various ways of use

We were writing a lot of code that got mixed in with our core algorithms and things got very messy

MVVM Background

MVVM basics

Model - View - View Model

Used in .NET programming (and others) to help efficently isolate business logic from user interface logic

Import["\\\\Mac\\iCloud\\AFS\\Conferences\\Wolfram Technical Conferences\\2017\\Presentation Images\\MVVM.png"]

Our own MVVM

Model 		= Raw fundamental algorithms / calculations / etc. assessible in typical “Wolfram fashion”

View		= “Dumb” and minimal code for dispalying items in user interface

ViewModel	= Adhoc logic and code required for passing data back and forth, setting up calculations

MVVM “Rules”

1. Model code shall have 0 references to anything in the View

2. View code shall have 0 references to anything in the Model

3. ViewModel shall have “Properties” that are obtained from View’s “Members” but no direct calls to the Views “Members” shall be used

4. View shall contain most basic of types (string, number, array etc.) and UI items

5. View is allowed to call Functions from within the ViewModel

Grid[{{Style["Model",40],Style["<->",40],Style["ViewModel",40],Style["<->",40],Style["View",40]}}]

Details of Implementing MVVM

Required “helper” functions

Remove[get,set,name];

Define “get” / “set” functionality

SetAttributes[get,HoldFirst]
get[arg_]:=arg

SetAttributes[set,HoldFirst]
set[arg_,value_]:=arg=value

Build in default behavior for getting / setting your symbol

name/:get[name]:=name
name/:set[name,value_]:=(name=value;)

set[name,"John"]

get[name]

Build in actual protection for your symbol

name/:get[name]:=If[StringQ[name],name,"Error"];
name/:set[name,value_]:=name=value

set[name,1]

get[name]

name/:get[name]:=name;
name/:set[name,value_]:=If[StringQ[value],name=value,name="Error"];

set[name,1]

get[name]

Details of Implementing MVVM

Basic example

Model

addOne[number_]/;number>0:=number+1

View

uiX=1;
uiX/:set[uiX,aValue_]:=If[NumericQ[aValue],uiX=aValue,uiX="NA"]

ViewModel

x:=get[uiX]
calculateNewX[]:=set[uiX,addOne[x]]

View simulating some button being pushed

Column[{
InputField[Dynamic[uiX]],
Button["Add 1",calculateNewX[]]
}]

Details of Implementing MVVM

More complicated example

Model

hydrateCurve[aGasGravity_?NumericQ,aPressures:{_?QuantityQ..}]:=Module[{mB1,mB2,mB3},
mB1=-391.33+700.38*aGasGravity-358.67*aGasGravity^2;
mB2=211.43-321.03*aGasGravity+167.22*aGasGravity^2;
mB3=-23.917+37.667*aGasGravity-19.86*aGasGravity^2;
Table[{Quantity[mB1+mB2*Log10[QuantityMagnitude@UnitConvert[p,"PoundsForce"/"Inches"^2]]+mB3*(Log10[QuantityMagnitude@UnitConvert[p,"PoundsForce"/"Inches"^2]])^2,"DegreesFahrenheit"],p},{p,aPressures}]
];

hydrateCurve[0.8,Quantity[Range[100,1000,20],"PoundsForce"/"Inches"^2]]//ListLinePlot

View

uiGasGravity=0.8;

uiP1=15;
uiP2=1000;
uiPUnits="PoundsForce"/"Inches"^2;

uiHydrateCurve={};
uiHydrateCurve/:set[uiHydrateCurve,aValue_]:=If[
MatchQ[aValue,{{_?QuantityQ,_?QuantityQ}..}],uiHydrateCurve=QuantityMagnitude@aValue,
uiHydrateCurve={}
]

uiMain=DynamicModule[{},
Grid[{
{"Gas gravity",InputField[Dynamic[uiGasGravity],],"-"},
{"Starting pressure",InputField[Dynamic[uiP1],],uiPUnits},
{"Ending pressure",InputField[Dynamic[uiP2],],uiPUnits},
{Button["Calculate",calculate[]],SpanFromLeft},
{Dynamic@ListLinePlot[uiHydrateCurve,],SpanFromLeft}
},Alignment->Left]
]

ViewModel

gasGravity:=get[uiGasGravity];
p1:=get[uiP1];
p2:=get[uiP2];
pUnits:=get[uiPUnits]
pressureRange:=Quantity[Range[p1,p2,20],pUnits]
calculate[]:=Quiet[set[uiHydrateCurve,hydrateCurve[gasGravity,pressureRange]]];

pressureRange

Advantages

Ability for “power users” to use the core functions in a scripting way

New ExternalEvaluate framework allowing users outside of Wolfram Language to use those same functions (Python now, but others are coming)

addOne[2]

from wolframclient.evaluation import WolframLanguageSession
from wolframclient.language import wl, wlexpr
session = WolframLanguageSession()
addOnePython = session.function(wlexpr('addOne'))
addOnePython(3)

Ability to build User Interfaces that are efficient, more easily maintained, and more importantly perform correclty (not always an easy task with Dynamic’s “black magic”)

Debugging becomes easy as issues are easily recognized as a Model, View, or ViewModel problem

Improved code organization

Conclusion

Our MVVM approach is really more about code organization at it’s heart - WL is so flexible, which is one of it’s greatest strengths, but with great power should come self imposed great responsibility

Pro tip - Use an IDE like VSCode or PyCharm that let you have 3 windows open side by size (Model, ViewModel, View)