In many cases the expressions specified in the configuration file are evaluated right after the parsing has finished. A value is calculated from the right hand side and assigned to the variable on the left hand side of the assignment. In some cases it is desirable to delay the evaluation of an expression until later. This is the case when the values on the right hand side are not known straight away or if the expression should be evaluated for a number of different values of a variable appearing on the right hand side. This is often the case when specifying a quantity for different locations in space or at different times. Imagine, as an example, you want to define the value of a boundary condition of the simulation as a function of time. The boundary condition must be evaluated at each time step of the simulation. This means that the expression will contain an independent variable that specifies the point in time at which the boundary condition is calculated. Let's call this independent variable ``t``. We can add an independent variable to the The Schnek parser can be instructed to treat a variable as read only. This means that the user will not be able to assign a value to that variable in the configuration file. Instead, the variable can be used on the right hand side of expressions, without first being initialised. A read only variable can be added to the ``BlockParameters`` simply by specifying the ``readonly`` attribute. The following example shows how this is done. :: class SimulationBlock : public Block { private: double t; pParameter paramT; double value; pParameter paramValue; protected: void initParameters(BlockParameters ¶meters) { paramT = parameters.addParameter("t", &t, BlockParameters::readonly); paramValue = parameters.addParameter("value", &value); } }; In this piece of code ``t`` is added as read only variable to ``parameters`` in the ``initParameters`` method. This means that ``t`` can be used in expressions in the configuration file straight away. When using independent variables in the input deck you should keep the object returned by the ``addParameter`` method. This object is a smart pointer to an object of type ``Parameter``. This object stores information about the mathematical expression and about any dependencies of a parameter. The ``Parameter`` objects are needed to define independent and dependent variables in a deferred evaluation. Reading the configuration file takes the usual form. :: BlockClasses blocks; blocks.registerBlock("sim").setClass(); std::ifstream in("example_setup_evaluate.setup"); Parser P("my_simulation", "sim", blocks); registerCMath(P.getFunctionRegistry()); pBlock application = P.parse(in); SimulationBlock &mysim = dynamic_cast(*application); mysim.evaluateParameters(); We can now write a method that iterates through values of ``t`` and evaluates the expression supplied in the configuration file for each value of ``t``. The following function inside the ``SimulationBlock`` class does exactly this. :: class SimulationBlock : public Block { ... public: void printValues() { pBlockVariables blockVars = getVariables(); pDependencyMap depMap(new DependencyMap(blockVars)); DependencyUpdater updater(depMap); updater.addIndependent(paramT); updater.addDependent(paramValue); for (int i=0; i<=20; ++i) { t = 0.5*i; updater.update(); std::cout << t << " " << value << std::endl; } } }; Let's look at this code line by line. :: pBlockVariables blockVars = getVariables(); The ``BlockVariables`` class holds all the information about the variables, any expressions that they depend on and that haven't yet been evaluated. The ``Block::getVariables`` method returns a shared pointer to the global ``BlockVariables`` object. :: pDependencyMap depMap(new DependencyMap(blockVars)); A ``DependencyMap`` analyses the expressions stored in the ``BlockVariables`` object. Internally it will create a data structure that stores the dependencies of each variable. We create a new shared pointer to a ``DependencyMap`` by passing the ``blockVars`` pointer. :: DependencyUpdater updater(depMap); Finally the ``DependencyUpdater`` class can be used to create an ordered sequence of evaluations of expressions. It will use the dependency map to determine which expressions have to be evaluated first and which expressions don't have to be evaluated at all in order to evaluate a given set of dependent variables. :: updater.addIndependent(paramT); updater.addDependent(paramValue); For the ``DependencyUpdater`` to do its job it needs to know which variables are independent and which are dependent. This information is supplied by calling the ``addIndependent`` and the ``addDependent`` methods. Here we pass the ``Parameter`` objects which we obtained when adding the parameters to the ``BlockParameters`` object. :: for (int i=0; i<=20; ++i) { t = 0.5*i; updater.update(); std::cout << t << " " << value << std::endl; } Once the information has been set up we can change the value of ``t`` and then call ``update`` on the ``DependencyUpdater``. This will evaluete all the expressions needed to calculate the dependent variables and store the result in the memory locations of these dependants. This means that after a call to ``update`` the value of the ``value`` variable will have been updated. For example, we can write the following expression in the ``example_setup_evaluate.setup`` file. :: value = exp(-t/5); This will create the following output. :: 0 1 0.5 0.904837 1 0.818731 1.5 0.740818 2 0.67032 2.5 0.606531 3 0.548812 3.5 0.496585 4 0.449329 4.5 0.40657 5 0.367879 5.5 0.332871 6 0.301194 6.5 0.272532 7 0.246597 7.5 0.22313 8 0.201897 8.5 0.182684 9 0.165299 9.5 0.149569 10 0.135335 In this example only one expression is being evaluated. But the updater does not care how many steps need to be taken to arrive at the result. Consider the following input file. :: float decay = exp(-t/5); float phase = 2*t; float oscillation = sin(phase); value = oscillation*decay; The ``DependencyUpdater`` makes sure that ``decay`` and ``phase`` are evaluated first. ``oscillation`` depends on ``phase`` and so it will be evaluated only after ``phase`` has been updated. Finally ``value`` is calculated from the updated values of ``oscillation`` and ``decay``. The result is as follows. :: 0 0 0.5 0.761394 1 0.74447 1.5 0.104544 2 -0.5073 2.5 -0.581617 3 -0.153346 3.5 0.32625 4 0.444547 4.5 0.167555 5 -0.200134 5.5 -0.332868 6 -0.161613 6.5 0.114509 7 0.244281 7.5 0.145099 8 -0.0581267 8.5 -0.175631 9 -0.124137 9.5 0.0224169 10 0.123554 The code for this example can be downloaded `here `__. The setup file can be found underĀ \ `example\_setup\_evaluate.setup `__.