Http Client¶
Tutorial¶
Create Project¶
On the menu bar, create a new project.
File
-> New
A pop-up window will appear where project name needs to be provided. Provide a meaningful name for the project (e.g., name of the example name you are currently testing).
Add a Digital Twin¶
On the menu bar, add a digital twin.
Project
-> Add Digital Twin
A pop-up window will appear where project name needs to be provided. Provide a meaningful name for the simulation environment (e.g., name of the example name you are currently testing).
Setup Simulation Parameters¶
For this example, we are not simulating time series, but directly the Wave function. Because of this, set
Save Sim. Endpoints to
False
.Save Sim. Time series to
False
.Time Series to
False
.Simulation Name to a meaningful name.
Add Constant/Simulated variables¶
The purpose of this example is to reproduce the examples goal, where function to simulate is,
From function, we see that z
is our variable to simulate depending on x
and y
values. Add 3 variables within the Constant/Simulated variables panel as follows,
Type |
Name |
Sim. Min. |
Sim. Max. |
Sim. Res. |
---|---|---|---|---|
Constant |
x |
-10 |
10 |
0.1 |
Constant |
y |
-10 |
10 |
0.1 |
Simulated |
z |
NaN |
NaN |
NaN |
Note
z
values are NaN
as they are not provided via the panel but within the custom code. They are calculated at simulation runtime.
Reset/Update Custom Code¶
Because new variable have been added, custom code layout/template needs to be updated.
To do so click either button,
Reset Code, to reset all custom code.
Update mandatory variable to update just mandatory regions of the code.
Because we don’t have any code written so far, we can directly click on Reset Code.
After performing this action, you will be able to see how the mandatory regions have been automatically updated by the digital twin,
ExtendedVariables region now contains the 3 created variables.
MandatoryCodeIni has been updated from
TOBESET
to1.0d
that is because we previously set Time Series toFalse
.MandatoryCodeEval has added the line
this.z.Value = TOBESET
; detecting that variablez
is a simulated one and therefore its value needs to be provided.
Before
#region ExtendedVariables
// Not currently in use
#endregion
...
public override void Initialize()
{
...
#region MandatoryCodeIni
this.XAxis.SimMin = TOBESET; // Start value
this.XAxis.SimRes = TOBESET; // Step (Resolution)
this.XAxis.SimMax = TOBESET; // End value
#endregion
}
public override void EvaluateStep()
{
...
#region MandatoryCodeEval
// Not currently in use
#endregion
}
After
#region ExtendedVariables
// Constant variables
private ExtendedVariable x; // x
private ExtendedVariable y; // y
// Simulated variables
private ExtendedVariable z; // z
#endregion
...
public override void Initialize()
{
...
#region MandatoryCodeIni
this.XAxis.SimMin = 1.0d; // Start value
this.XAxis.SimRes = 1.0d; // Step (Resolution)
this.XAxis.SimMax = 1.0d; // End value
#endregion
}
public override void EvaluateStep()
{
...
#region MandatoryCodeEval
// Simulated variables (ti) - Values.
this.z.Value = TOBESET;
#endregion
}
Python Code / API¶
For this example, we will implement our Wave function instead of directly within the digital twin custom code, in an external source.
To do so we will even use another programming language (Python).
Create Python code¶
Create a folder named ExternalCode
.
Now create a file within it called functions.py
with following content
# importing "math" for mathematical operations
import math
def wave(x, y):
return math.cos(x)*math.sin(y)
As you can see right now we have the same function we defined in the Out of the Box example within the CustomCodeEval region but now in an external python file.
Create a Python API¶
Once the python code is created, we need to create a Web API that serves that code, allowing clients to connect and perform requests (e.g., executing our wave(x, y)
function).
To do so, create a file also within the ExternalCode
folder named server.py
with following content,
#!/usr/bin/env python
# encoding: utf-8
# API modules
import json
from flask import Flask, request, jsonify
# Code modules
import functions
# App - Instance
app = Flask(__name__)
# Endpoints
@app.route('/wave', methods=['GET'])
def wave():
try:
# Get input variables from request
x = float(request.args.get('x'))
y = float(request.args.get('y'))
# Execute python script
z = wave(x, y)
response = {
"x": x,
"y": y,
"z": z
}
return jsonify(response)
except:
print("Something else went wrong")
@app.route('/')
def index():
return jsonify({'Error': 'Use a specific endpoint from this API :)'})
# App - Run (Use 'waitress' for production mode!)
if __name__ == "__main__":
from waitress import serve
serve(app, host="127.0.0.1", port=5003)
From previous server.py
code, we see that we import the previously created module functions
and we use its function wave(x,y)
.
We can now run our python server by opening a console/terminal within the ExternalCode
directory and run following command,
python server.py
If there are no issues with previous code, API will start running and available for us to call it from within the digital twin with an Http Client.
Note
The purpose of this tutorial is not how to create/build a Python API, but how to consume it from within the toolbox inbuilt Http Client.
Create an HttpClient¶
Within the region UserCustomVariables declare an Http Client as follows,
#region UserCustomVariables
private HttpClient httpClient = null;
#endregion
Error
Http Client must be declared on the the global scope (UserCustomVariables region) so that it can be used within all simulations (combinations).
Now, within the Initialize method - CustomCodeIni region we will add the following lines,
#region CustomCodeIni
// Avoid creating an HttpClient foreach simulation!
if(this.httpClient == null)
{
this.httpClient = new HttpClient();
}
#endregion
Use the Http Client¶
Once the Http Client is instantiated, we need to create the http request. To do so, we use the EvaluateStep method - CustomCodeEval region were we will,
Create a dictionary to hold variables
x
andy
and its values.Warning
Use
.ToString(CultureInfo.InvariantCulture)
. This will avoid possible conflict if Python API and Toolbox have different cultures (e.g., decimal separator can be either ‘.’ or ‘,’).Warning
Dictionary is a
key-value pair
. Make sure that thekey
name matches both in Python API and here.Define the request endpoint. That is the Uri where server is listening to and the method.
Warning
Make sure the IP Address/Uri is the same one as in the
server.py
file (e.g.,host="127.0.0.1"
,port=5003
).Use
QueryHelpers
to build a request the request.Call the python server with a
Get
request.Warning
Do not use asynchronous requests/methods. either
await
or use.Result
Ensure response status is
Successful
.Read and deserialize response content (e.g., into another dictionary)
Warning
Do not use asynchronous requests/methods. either
await
or use.Result
Get
z
result value from deserialized dictionaryWarning
Use overload with
Culture
set toCultureInfo.InvariantCulture
when parsing.
After following previous steps we implemented all required code to build a request, call the api and get results back to consume the within the digital twin. Following code block reproduces previous points from a code perspective.
#region CustomCodeEval
// 1 - Request Dictionary (Inputs)
Dictionary<string, string> requestInputs = new Dictionary<string, string>();
requestInputs.Add("x", this.x.Value.ToString(CultureInfo.InvariantCulture));
requestInputs.Add("y", this.y.Value.ToString(CultureInfo.InvariantCulture));
// 2 - Python API and Endpoints
string apiEndpoint = "http://127.0.0.1:5003";
string apiWaveEndpoint = apiEndpoint + "/wave";
// 3 - Query Builder
string apiWaveGetRequest = QueryHelpers.AddQueryString(apiWaveEndpoint, requestInputs);
// 4 - Call the Wave endpoint (awaiting; synchronously)
HttpResponseMessage apiWaveGetResponse = this.httpClient.GetAsync(apiWaveGetRequest).Result;
// 5 - Ensure response is successful
apiWaveGetResponse.EnsureSuccessStatusCode();
// 6 - Read and deserialize response content
string apiWaveGetResponseContent = apiWaveGetResponse.Content.ReadAsStringAsync().Result;
Dictionary<string, string> requestOutputs = JsonConvert.DeserializeObject<Dictionary<string, string>>(apiWaveGetResponseContent);
// 7 - Get `z` result value
double zResult = double.Parse(requestOutputs["z"], CultureInfo.InvariantCulture);
#endregion
Provide Simulated variables values¶
If we click on Validate Code, we will get an error similar to,
Error (CS0103). Line: 66, Column: 17. The name 'TOBESET' does not exist in the current context
This indicates that we still need to provide a value four our simulated variable z
to be able to validate (compile) code properly.
To pre-calculate the value of z
depending on x
and y
, we can use the CustomCodeEval region and replace the TOBESET
by calculated value as follows,
#region MandatoryCodeEval
// Simulated variables (ti) - Values.
this.z.Value = zResult;
#endregion
If we click on Validate Code again we will now get a successful code compilation,
Compile succeeded. Code updated.
Simulate¶
After having everything setup, we can proceed to simulate (clicking Sim / Opt
button) the digital twin with all possible combinations (40401
for this example).
Once simulation finishes, a report can be generated (clicking Report
button). This will add a report on the Reports tab ready to be explored.
Note
Current example is not a time series, so in the 2D view of the report, only points will appear on the plots. Switch to the 3D view mode to reproduce the waves plot.
Files¶
Project generated on the previous tutorial section can be downloaded,
-
Note
When downloading, unzip
ExternalCode.zip
file to get tutorial folder.