Technical Details

As a scripting language, luafaudes invites the impatient to directly start from the luafaudes-tutorials, perhaps guided by the faudes user reference. This works out quite well for simple scripts. When it comes to the development of more involved scripts, however, some amount of technical background on Lua and the conventions used for the integration of libFAUDES are crucial to avoid potential pitfalls.

Data Types

Lua distinguishes between several data types, including numbers, booleans, strings and so called user-data. For libFAUDES integration, the elementary faudes-types Integer, Boolean and String are mapped to the corresponding native Lua-type. Any other faudes-type is mapped to user-data. In particular, Lua itself cannot distinguish among non-elementary faudes-types. Thus, in order to instantiate a non-elementary faudes-typed object you must use an explicit constructor. Most faudes-types implement a default constructor, a copy constructor and a from-file constructor.

-- elemantary types
str = "hello Lua"                   -- have a variable that holds a string
num = 77                            -- have a variable that holds a number

-- non-elementary faudes-types
ev1 = faudes.EventSet()             -- instantiate an empty faudes EventSet object
ev2 = faudes.EventSet("myfile.txt") -- construct an EventSet from file
ev3 = faudes.EventSet(ev2)          -- copy-construct an EventSet from specified set

Native Lua data types are addressed by std Lua libraries for e.g. maths and string manipulation. Non-elementary faudes-types provide (allmost) the same interface as their C++ counterparts. E.g., every faudes-typed object provides the Read and Write methods for tokenized IO, including initialsation from a string.

-- initialize the EventSet from a token string
ev4 = faudes.EventSet()             
ev4:FromString("<A> a b c </A>")    

-- write 
ev4:Write()                           -- write to console
ev4:Write("myfile.txt")               -- write to file
ev4:SWrite()                          -- report statistics (here: event count)

-- read
ev4:Read("myfile.txt")                -- read from file

Assignment

Lua variables are typed at run-time, i.e., a variable holds a value and this value determines the current type. This concept has a little twist when it comes to user-data: a variable can hold a value of Lua-type user-data, but the value is actually a reference to the faudes-type object. Thus, everything feels like "variables are values" but faudes-typed objects behave like "variables are references". This shows in particular with the assignment operator.

-- native variables
str1 = "hello"                      -- str1 holds the value "hello"
str2 = str1                         -- str2 holds the value "hello"
str2 = "world"                      -- str2 holds the value "world", str1 still holds "hello"
print(str1,str2)                    -- prints: hello world


-- faudes-typed objects
ev1 = faudes.EventSet()             -- ev1 holds a reference to the newly constructed object
ev1:FromString("<A> a b c </A>")    -- set the EventSet object to consist of three events a,b,c

ev2 = ev1                           -- ev2 refers to the same set as ev1
ev2:Clear()                         -- clears the set
print(ev1)                          -- prints the empty set

Regarding native variables, the assignment behaves like str2 is assigned another copy of str1. If, in analogy, you want ev2 to refer to another copy of the set referred to by ev1, you need to explictly invoke the Copy method.

ev1 = faudes.EventSet()             -- ev1 holds a reference to the newly constructed object
ev1:FromString("<A> a b c </A>")    -- set the EventSet object to consist of three events a,b,c

ev2 = ev1:Copy()                    -- ev2 is assigned a reference of the newly created copy of the set
ev2:Clear()                         -- clears the set ev2
print(ev1)                          -- prints a,b,c

If you intentionally keep multiple references to a faudes-typed object, you must explicitely invoke the Assign method to change the object while keeping references valid.

ev1 = faudes.EventSet()             -- ev1 holds a reference to the newly constructed object
ev1:FromString("<A> a b c </A>")    -- set the EventSet object to consist of three events a,b,c
ev2 = faudes.EventSet()             -- ev2 holds a reference to the newly constructed object
ev2:FromString("<A> x y z </A>")    -- set the EventSet object to consist of three events x,y,z

ev3 = ev1                           -- ev3 refers to the same set as ev1
ev3:Assign(ev2)                     -- assign x,y,z to the set referred to be ev1 and ev3
print(ev1)                          -- prints x,y,z

Faudes Functions

Any function that is listed in the user reference documentation can be accessed from within luafaudes. The respective wrapper code is automatically generated from the faudes run-time-interface. It consists of an entry in the global table faudes with a key that matches the faudes function name and a value of Lua type function. The latter is implemented to dispatch C++ function overloading and to call the appropriate faudes function. You can inspect the table faudes from the luafaudes console by for k,v in pairs(faudes) do print(k,v) end.

By convention, faudes functions have parameters with access attributes In, Out, InOut, to indicate whether the respective parameter is an argument, a result of both. This design is a typical C++ construct to allow functions to behave smart on derived classes, e.g., functions that care about event attributes whenever the result type supports event attributes.

However, the design does not translate directly to Lua. Here, parameters of a function are allways passed by value and thus cannot be results. The latter are to be returned by a return statement, which supports multiple return values. Since the actual value of a user typed variable is a reference to a faudes-typed object, we can still call faudes functions with the signatures specified in the run-time-interface --- except for results of native Lua type string, integer or boolean. The current implementation of the luabindings code generator supports at most one native Lua result (access attribute "Out") which will be returned as a single return value. Future versions may extend to multiple return values.

Example:

The SupConNB supports the following signature

SupConNB( +In+ System GPlant, +In+ Generator GSpec, +Out+ Generator GSupervisor)

In order to invoke SupConNB from Lua, you can use exactly the same signature since all parameters are of non-elementary faudes-type. As third parameter GSupervisor you must pass a Generator or any derived type. If you actually pass a System, the function will configure the controllability attributes of the result according to the parameter GPlant.

plant=faudes.System("myplant.gen")             -- load argument from file
spec=faudes.Generator("myspec.gen")            -- load argument from file
sup=faudes.System()                            -- instantiate result
SupConNB(plant,spec,sup)                       -- invoke function

Example:

The function IsControllable supports the following signature

IsControllable( +In+ System GPlant, +In+ Generator GSupervisor +Out+ Boolean BRes)

Here, the third parameter is of Lua native boolean type with access attribute Out. In order to invoke this function from Lua, you only pass the first two parameters and expect the result as return value.

plant=faudes.System("myplant.gen")             -- load argument from file
sup=faudes.Generator("myspec.gen")             -- load argument from file
res=IsControllable(plant,sup)                  -- invoke function, get result as return value

Operators

Lua supports a number of operators, including relational operators ==, ~=, <=, >=, <, > and arithmetic operators *, +, -. Since the above operators are overloaded in libFAUDES for elementary set algebra, luafaudes attempts to provide similar semantics.

aset=faudes.EventSet("afile.txt")
bset=faudes.EventSet("bfile.txt")

cset = aset + bset                               -- set union
cset = aset - bset                               -- set difference
cset = aset * bset                               -- set intersection

if aset <= aset + bset then                      -- test set inclusion
  print("a <= a+b     ---  OK")
end
if aset ~= cset  then                            -- test set equality
  print("a ~= c")     ---  OK")
end

The above example evaluates as expected. However, when using faudes Alphabet typed sets instead of plain faudes EventSet sets the first if-clause will evaluate to false, perhaps unexpectedly. The reason is that the union aset + bset even for Alphabet arguments is defined to evaluate to a plain EventSet (which is compliant to the corresponding C++ operators) and that Lua insists in matching data types (technically meta-tables) for relational operators. The latter condition is violated since the right-hand side is an EventSet and the left-hand-side is an Alphabet. The current version of luafaudes implements a rather crude hack to make the above code-fragment work with Alphabets as expected. However, it is recommended that you circumvent the issue by using explicit set comparison methods. See the containers tutorial for available functions.

Lua Functions

Within a luafaudes script one can of course define functions, including functions that operate on faudes-typed objects and functions that in turn invoke functions provided by libFAUDES. We discuss some potential issues by the below example that generates a buffer with a specified capacity.

function BigBuffer(qn,buffer)

-- Clear result, set alphabet
buffer:Clear()
buffer:InsEvent('beta_a')
buffer:InsEvent('alpha_b')

-- Insert states
for i=1,qn do
  buffer:InsState(string.format('s%d',i))
end

-- Have marked initial state
buffer:SetMarkedState('s1')
buffer:SetInitState('s1')

-- Insert transitions
for i=1,qn-1 do
  s1=string.format('s%d',i)
  s2=string.format('s%d',i+1)
  buffer:SetTransition(s1,'beta_a',s2)
  buffer:SetTransition(s2,'alpha_b',s1)
end

-- Done
return 

Remark. Elementary typed parameters (boolean, integer and string) are passed by value. In this sense, elementary typed function parameters are local to the function. An assignment qn=77 at the end of the above function has no effect to the calling code. Elementary typed results should be returned by an explicit return statement.

Remark. For non-elementary types, parameters are passed by reference. Thus, you can use the explicit assignment method Assign to assign a new value to the respective object, e.g. buffer:Assign(some_generator), and thereby pass a result to the calling code. The plain assignment operator =, however, has a completely different and perhaps unexpected effect: since Lua interprets the reference itself as a value, an assignment like buffer=some_generator will have no effect on the object originally referenced by the parameter buffer.

Remark. From a Lua perspective one might have prefered to implement "buffer" as a return value. However, we intentionally used a faudes style signature for easy integration with the faudes run-time-interface; see also Lua extensions. Furthermore, one may invoke BigBuffer with any result type derived from Generator, e.g. System.

Test Cases

A simple mechanism is provided to record so called test cases. The rational is that during development of a script to implement e.g. a novel synthesis approach, one will spend quite some effort in an example and carefully validate the results. This establishes a test case. When using the script later on, perhaps on different input data, presumably with an updated version of libFAUDES or on a different platform, one would like to be sure that at least the original example still produces the correct result. This can be achieved by maintaining a protocol of the validated test case.

Test protocols are generated by the functions FAUDES_TEST_DUMP(m,d), where m is an arbitraty text message and d is the relevant data to be recorded. The protocol is written to the file tmp_NameOfScript_lua.prot and contains one entry per test dump with some statistical information on the data d (e.g. state count for generators). Once the test case is considered valid, the proposed procedure is to rename protocol file by dropping the prefix tmp_. This indicates the protocol to be the reference for automatic validation. At the very end of your script, use the function FAUDES_TEST_DIFF() to compare the current protocol with the previously recorded reference.

-- my new algorithm
function tricky(p,s,c)
 [...]
end

-- demonstrate usage by simple example
p1=faudes.Generator("plant1.gen")
s1=faudes.Generator("spec1.gen")
c1=faudes.Generator()
tricky(p1,s1,c1)

-- graphical output for inspection
c1:GraphWrite("contr1.png")

-- record test case
FAUDES_TEST_DUMP("test1",c1)

-- complex example to stress algorithm
p2=faudes.Generator("plant2.gen")
s2=faudes.Generator("spec2.gen")
c2=faudes.Generator()
tricky(p2,s2,c2)

-- record test case
FAUDES_TEST_DUMP("test2",c2)

-- compare with reference
FAUDES_TEST_DIFF()

When you run the scipt with the option -d, the last line will produce a warning if the reference protocol does not match the current protocol. The comparison is text based, so further inspection of any errors can be done via diff.

Miscellaneous

Symbolic Names

Lua automatically converts numbers to strings and vice versa, which can be quite handy. However, if you wanted to insert an event with name 2 by passing a string constant "2" to the Insert method, this may get confused with the event index 2. Thus, it is best *not* to use names that start with a digit.

Read-Only References (C++ "const")

luafaudes does not implement a concept of read-only variables, aka const. As a consequence, any access to a container member within a generator returns a writable reference. You should *not* use this reference to alter the container's content.

gen = faudes.Generator("some_file.gen")

alph = gen:Alphabet()                   -- get a reference to generator's alphabet
alph:Clear()                            -- dont do this, messes up the generator

alph = gen:Alphabet():Copy()            -- get a copy of generator's alphabet
alph:Clear()                            -- ok, wont mess up the generator

After experiencing the particular case above, we manually changed the bindings for Generator::Alphabet() to include an invokation of Copy(). Since these kind of issues are hard to track, it is recommended to use explicit invocation of Copy() whenever accessing internal data structures in a const-fashion. This imposes only a small cost on performance, since libFAUDES sets implement a copy-on-write scheme.

Lua-Modul "faudes" (C++-Namespace "faudes")

All libFAUDES related functions are located in the Lua module "faudes", corresponding to the C++ namespace "faudes". One purpose of namespaces is to avoid conflicts between various libraries. If you use luafaudes without any additional modules, chances are good that there are no conflicts to avoid. You may then use faudes.MakeGlobal() at the beginning of your Lua-script to access faudes-functions without the prefix faudes.*.

libFAUDES 2.32b --- 2024.03.01 --- with "synthesis-observer-observability-diagnosis-hiosys-iosystem-multitasking-coordinationcontrol-timed-simulator-iodevice-luabindings-hybrid-example-pybindings"