Modeler¶
Object-oriented Python APIs are provided for the virtual machine powering the LSP language. With only a few classes, you can load modules, interact with their variables and execute them. If you are not familiar with the LSP language, please have a look at the language reference for more information.
Create a module
First, you have to create an LSPModeler
environment. As with
the main LocalSolver object, we recommend using a “with” statement that will
ensure the native memory allocated for the modeler is correctly released
when the environment is discarded. LSPModeler
is the main
class of the Modeler library and allows you to create a module in one of
two ways:
- You can load a module from an existing LSP file with the method
LSPModeler.load_module()
. - Or you can create an empty module with the method
LSPModeler.create_module()
.
Both of these methods return an instance of LSPModule
which
will enable you to interact with the module variables and functions.
Launch a module
There are two launch modes for a module:
Optimization mode: This is the classic and default mode for the modeler. At a very least, you must implement a
model
method in your LSP module that will build an LSModel. You can then callLSPModule.run()
to start the execution of the module. This function takes as first argument a LocalSolver instance that you have to create beforehand using theLSPModeler.create_solver()
method. After the LocalSolver model has been built, the resolution process will be started automatically.Note that the run method also accepts as a parameter a list of arguments that will be passed as global variables to your module:
with LSPModeler() as modeler: solver = modeler.create_solver() module = modeler.load_module("my_lsp_file.lsp") module.run(solver, "lsIterationLimit=100", "lsTimeLimit=10")
Main mode: In this mode, the modeler behaves like a classical programming language. To use this mode, you have to implement a function named
main
in which you are free to do anything you want without being limited by the formalism of the functionsinput
,model
,param
,display
andoutput
. You can then callLSPModule.run_main()
to start the execution of the module.Note that unlike the optimization mode, it is your responsibility to manually create the solver instances, close your models and launch the resolutions. In return, you are free to run several successive resolutions or none at all if you just want to use LSP for its pure programming features.
For more details on the differences between the optimization and the main mode, read Main mode.
Interacting with variables
The class LSPModule
behaves like a dictionnary and you can
retrieve, set or create new variables that will be exposed in your LSP
code:
module = ...
print(module["lsIterationLimit"])
module["lsIterationLimit"] = 100000
module["myVariable"] = "spam, egg and cheese"
module["customData"] = -45.2
# Removing a variable, reset its value to "nil" in LSP
del module["lsIterationLimit"]
Under the hood, LSPModule
overloads the special methods
LSPModule.__getitem__()
, LSPModule.__setitem__()
and LSPModule.__delitem__()
.
These methods can receive or return python values that are transparently transformed to their LSP equivalent. The following python types are supported: boolean, integers, floating point numbers, strings and the special value None (which is transposed to nil in LSP).
If simple values are not sufficient for your usage, you can also create
array-like or dictionary-like structures with
LSPModeler.create_map()
. For more information on maps you can
look at the map module.
Using external functions
You can use native Python functions as
LSP functions in the modeler thanks to the
method LSPModeler.create_function()
. The Python function must take
a modeler instance as first argument, and LSP values as subsequent
arguments. The function can return a value that will be converted and
transmitted to the LSP caller:
def my_lsp_function(modeler, arg1, arg2):
return arg1 + arg2
lsp_func = modeler.create_function(my_lsp_function)
result = lsp_func(1.5, 2.3)
print("result = {}".format(result)) # prints "result = 3.8"
In the snippet above we declare an LSP function that takes two LSP doubles
as input, adds them together and returns the result. We can then call the
function with LSPFunction.__call__()
to execute the function and
retrieve the result.
You can also assign the function to a variable in a module with
LSPModule.__setitem__()
. After doing so, the function will be
callable within any function of the module.
Memory management
When you access a modeler object from the Python API, your Python program
takes a new reference to that modeler object (LSPMap
,
LSPModule
or LSPFunction
are examples of references to
modeler objects).
A modeler object can only be destroyed if no more Python reference points to it. In other words, a Python reference on a modeler object prevents the object from being freed even if it is no longer used in the modeler. If you have to create, access and destroy many objects in your LSP programs, you may keep in memory objects that have become inaccessible within your LSP programs.
We advise you not to rely on the destructors of python objects (__del__
)
to ensure a proper release of these references. Instead, use one of the 3
methods described below that are based on the with
statement. They are
much more explicit and safer:
Wait for the destruction of your LSPModeler object. All references are automatically freed when the modeler is released by using the special
with
statement on yourLSPModeler
instance. This is the simplest solution and the recommended solution if the interactions between the modeler and your LSP programs are limited to a few exchanges at the beginning/end of the resolution.Release specific references at any time by using the
with
statement on objectsLSPMap
,LSPModule
,LSPFunction
orlocalsolver.LocalSolver
. For instance:with modeler.create_map() as map: map.append(42) # Reference to 'map' is freed at the end of the ``with`` block
Release all references attached to a
LSPReferenceScope
. When you instantiate a new reference scope object by doingwith LSPReferenceScope(modeler) as scope:
, all references created from that point anywhere in the API will be automatically associated with that scope. At the end of thewith
statement, all references registred in the scope will be released and the previous scope will be restored and set as the current one.
Note that invoking any method on a released reference will throw an exception. It is therefore important not to release your references too early and to keep them alive as long as you need them.
Note also that several references can point to the same object if you query
repeatedly the same key of a map or a module. At each call, a new
reference is created. This is not a problem in itself, but try to minimize
the number of calls to functions that return objects of type
LSPMap
, LSPModule
or LSPFunction
to reduce the
number of references.