Note
Experiment 2 re-uses the campaign (and also existing simulations) from experiment 1; Especially, the same config.py is used. Therefore, we recommend to continue the experiment in the directory myFirstCampaign/experiment1, and not to create a new sub-campaign using ./playground.py preparecampaign ../myFirstCampaign.
We have seen during Experiment 1 that a manual search for the saturation point by selecting different offered traffic values can take some time, depending on the initial estimation of the user.
With the help of the database, the search for the saturation point can be done in a much more efficient way than by selecting “random” values for the offered traffic and simulating until the point is found. A typical offered traffic vs. throughput curve will always be a bisector in the beginning, reach the saturation point and then, depending on the type of system under simulation, flatten or decrease. Therefore, this saturation point can be found efficiently using binary search: Starting with a small offered traffic as initial value, simulations are run sequentially, doubling the offered traffic every time.
If the throughput is less than the offered traffic, the upper bound is found, and the binary search continues with the mean value of the upper- and the lower bound. This procedure continues until the upper- and lower bound have converged.
As this search for the saturation throughput is needed often, it is directly encapsulated into the parameter generation process as described in the previous experiment.
We now have to distinguish between two types of parameters:
In the following, the existing file campaignConfiguration.py is changed to implement the binary search. An example implementation can be found in the directory openWNS/tests/system/wifimac-tests/PyConfig/experiment2
The parameter class recognises the scenario parameters by a new parameter parameterRange; only one parameter (the input parameter) must be without this parameterRange, but with the default (i.e. starting value) instead. Hence, the parameter class from the first experiment would look like the following:
from openwns.wrowser.simdb.Parameters import AutoSimulationParameters, Parameters, Bool, Int, Float, String
import openwns.wrowser.Configuration as config
import openwns.wrowser.simdb.Database as db
import subprocess
class Set(AutoSimulationParameters):
# scenario parameters
simTime = Float(parameterRange = [5.0])
distance = Float(parameterRange = [25.0])
ulRatio = Float(parameterRange = [0.0])
packetSize = Int(parameterRange = [1480*8, 80*8])
# input parameter
offeredTraffic = Int(default = 100000)
The next step is to define a function that returns the output value - the throughput in our case. To be compatible, this function must have a defined signature and return value:
def getTotalThroughput(paramsString, inputName, cursor):
myQuery = " \
SELECT idResults.scenario_id, idResults." + inputName + ", VAL.mean \
FROM moments VAL, (SELECT scenario_id, " + inputName + " FROM parameter_sets \
WHERE " + paramsString + ") AS idResults \
WHERE VAL.scenario_id = idResults.scenario_id AND \
VAL.alt_name = 'ip.endToEnd.window.incoming.bitThroughput_Moments' \
ORDER BY idResults." + inputName + ";"
cursor.execute(myQuery)
resultsIn = cursor.fetchall()
myQuery = " \
SELECT idResults.scenario_id, idResults." + inputName + ", VAL.mean \
FROM moments VAL, (SELECT scenario_id, " + inputName + " FROM parameter_sets \
WHERE " + paramsString + ") AS idResults \
WHERE VAL.scenario_id = idResults.scenario_id AND \
VAL.alt_name = 'ip.endToEnd.window.aggregated.bitThroughput_Moments' \
ORDER BY idResults." + inputName + ";"
cursor.execute(myQuery)
resultsAgg = cursor.fetchall()
results = []
for i in zip(resultsAgg, resultsIn):
agg = i[0]
inc = i[1]
assert(agg[0] == inc[0])
assert(agg[1] == inc[1])
results.append([agg[0], agg[1], agg[2] + inc[2]])
return results
This function works in the following way: As input, it gets
This input is used to start two queries: The first one gets a list of 3-tuples, containing the scenario-id, the value of the input name and the mean value of the moment probe with the name ip.endToEnd.window.incoming.bitThroughput_Moments. The inner SELECT - statement is only used to find the correct scenario-id, using the paramsString.
Similarly, the second query gets the same 3-tuple, but with the mean value of the probe ip.endToEnd.window.aggregated.bitThroughput_Moments. The remaining lines go through both lists (at the same time using the zip command), compare the scenario-id and the value of the input parameter and append another 3-tuple, consisting of the scenario-id, the input value and the sum of the incoming and aggregated traffic - which should be the same as the input value in our case.
With the following lines, the cursor from the appropriate database belonging to the simulation campaign is fetched:
conf = config.Configuration()
conf.read("./.campaign.conf")
db.Database.connectConf(conf)
cursor = db.Database.getCursor()
Finally, one round of the binary search (i.e. for every combination of the scenario parameter values, given in as the parameterRange), can be started by creating an instance of the class Set and using the new member function binarySearch:
params = Set('offeredTraffic', cursor, conf.parser.getint("Campaign", "id"), getTotalThroughput)
[status, results] = params.binarySearch(maxError = 0.1,
exactness = 0.05,
createSimulations=True,
debug=True)
To create the instance params, new parameters are required, namely the name of the input variable, the cursor, the campaign id and a pointer to the function that retrieves the input and the output variable from the database for a given scenario.
The binary search requires as parameters the maximum error (how much deviation of the output from the input is allowed to still count as match) and the exactness which must be undercut to stop the search. As result, it returns the number of new/waiting/finished scenarios, together with the results if any finished scenarios exist.
The last lines automatically create new scenarios and execute them, if new scenarios have been created:
print "%d new / %d waiting / %d finished simulations" %(status['new'],
status['waiting'],
status['finished'])
if(status['new'] > 0):
subprocess.call(['./simcontrol.py --create-scenarios'],
shell = True)
subprocess.call(['./simcontrol.py --execute-locally --restrict-state=NotQueued'],
shell = True)
When called, the current campaignConfiguration.py executes one step of the binary search, for every combination of simulation parameter settings. Again, the execution is done by simcontrol.py when populating the database with new scenarios:
$ ./simcontrol.py --create-database
{'packetSize': 11840} : waiting for output
{'packetSize': 640} : initial simulation
1 new / 1 waiting / 0 finished simulations
Scenarios successfully created.
Executing scenario with id: 11
Executing scenario with id: 12
Database entries successfully created.
Without deleting the previous results of the experiment, the binary search will use the existing scenarios and continue the binary search at the most suitable point. Another call to the simcontrol.py will compute the next round of the binary search for both packet sizes:
$ ./simcontrol.py --create-database
{'packetSize': 11840} : 100000 ... 200000 --> newValue 200000
{'packetSize': 640} : 100000 ... 200000 --> newValue 200000
2 new / 0 waiting / 0 finished simulations
Skipping /home/wns/myFirstCampaign/experiemt1/11, it already exists (consider --force switch)
Skipping /home/wns/myFirstCampaign/experiemt1/12, it already exists (consider --force switch)
Scenarios successfully created.
Executing scenario with id: 13
Executing scenario with id: 14
Database entries successfully created.
The binary search has collected the information that, for both packet sizes, the saturation point was not reached at 0.1Mb/s. Thus, the next two simulations with 0.2Mb/s are created and executed.
The iterative execution of several rounds can be automated by the parameter --interval=TIME: It causes the simcontrol.py repeat the creation of new scenarios. If the simulations are executed locally, the parameter TIME can be set to 1 (second). In this way, the saturation point can be evaluated automatically up to a predefined exactness:
$ ./simcontrol.py --create-database --interval=1
Running command ...
{'packetSize': 11840} : 200000 ... 400000 --> newValue 400000
{'packetSize': 640} : 200000 ... 400000 --> newValue 400000
2 new / 0 waiting / 0 finished simulations
Skipping /home/wns/myFirstCampaign/experiemt1/11, it already exists (consider --force switch)
Skipping /home/wns/myFirstCampaign/experiemt1/12, it already exists (consider --force switch)
Skipping /home/wns/myFirstCampaign/experiemt1/13, it already exists (consider --force switch)
Skipping /home/wns/myFirstCampaign/experiemt1/14, it already exists (consider --force switch)
Scenarios successfully created.
Executing scenario with id: 15
Executing scenario with id: 16
Database entries successfully created.
Sleeping until 15:54 2009-4-1...
Running command ...
[...]
{'packetSize': 11840} : 18400000 ... 19200000 --> Exactness < 0.05 , stop!
{'packetSize': 640} : 3000000 ... 3100000 --> Exactness < 0.05 , stop!
0 new / 0 waiting / 2 finished simulations
Finally, for both packet sizes the saturation point has been found up to the configured exactness: Between 18.4 and 19.2 Mb/s for a packet size of 1480B and between 3 and 3.1 Mb/s for the smaller packet size 80B (minus 10% error margin for each). Using the wrowser, it is possible to plot this, see Throughput plot.
Throughput plot