Subscriptions
Version |
Date |
Author |
Purpose |
|---|---|---|---|
1 |
July 2015 |
Gary Turner |
Initial version |
2 |
March 2022 |
Gary Turner |
Added verification and improved user's guide |
3 |
December 2022 |
Gary Turner |
Toggle for silently failing when unsubscribing a disabled model |
4 |
September 2025 |
Hirad Mirhashemi |
Updated verification and user's guide |
5 |
August 2026 |
Nino Tarantino |
Converted to reStructuredText and updated coverage |
Introduction
The Subscriptions Model is used as a base for many models; its primary purpose is to facilitate the activation and deactivation of models as they are needed at different phases of a complex simulation.
Consider a simulation that includes two models, A and B, with model A requiring input from model B. For some part of the simulation, model A is required, so its execution must be scheduled in the master simulation sequence. Whenever model A runs, model B must also run, so its execution must be scheduled in the master simulation sequence. But running both models all of the time is highly wasteful, and will slow the simulation unnecessarily. So, for the times that the models are not needed, their scheduled execution should simple return without additional process. Thus, we have the concept of models being active and inactive, and of subscriptions that toggle between the two states.
At some point in the simulation, the simulation subscribes to model A, flagging it as needing to run. In turn, model A subscribes to model B, flagging it as needing to run. As long as a model is subscribed, its scheduled execution will run. If it is unsubscribed, its scheduled execution will return without process. Then, when model A is no longer needed, the simulation can unsubscribe from it, and model A can unsubscribe from model B, potentially deactivating both models and preventing their further execution.
Here, I say potentially deactivating both models because it may not be appropriate to stop them. Suppose a third model, C, also requires input from model B and has been subscribed in the interim time. Stopping model B with the deactivation of model A will break model C. So we need a count of the number of targets depending on a model being executed. If that count is zero, the model does not need to run.
This model handles that subscription count for all models that inherit it.
Requirements
The model shall provide an active flag to inheriting models to identify whether those model should be run.
This model shall control the active flag via a count of the number of demands placed on the inheriting models.
This model shall provide the option to permanently deactivate a model, preventing further demands from re-activating it.
Model Specifications
Architectural Considerations
Existing External Capabilities
Support
No dependencies.
Model Structure
-
class SubscriptionBase
- #include "models/utilities/subscriptions/include/subscriptions.hh"
Provides a subscription baseline for all models
Models can subscribe to anything that inherits from this class. When the first subscription is processed, the model is activated. When subsequent subscriptions are processed, nothing happens. When the last subscription is canceled, the model deactivates.
Subclassed by AbstractTableLookup, AccumulatedAbsoluteDeltas, AeroExecutiveBase, AeroInterfaceBase, ApsidesPredictor, AtmosRelativeState, AtmosphereExecInterface, ConstraintSet, ContactStateOverride, DynamicMassGroup, EGM2008_WGS84, EclipseCalculator, ExtendedPlanetaryDerivedState, GravityFidelityManager, ImpactPoint, LookupAtmosWinds, MassDerivativeDynamics, PistonThruster, PistonThrusterGroup, PlanetPlanetState, PointingRefFrame, RangeComputation, RcsGeneric, RocketMotor_Basic, SeparationState, SimSpeedMonitor, SimpleLookupWind, SimplePlanetRelState, SubsonicWake, TwistSway, Vector3IntegrableObject, VentSet, WakeEffectsBase, WatchValuesBaseCore
Public Functions
-
SubscriptionBase() = default
Default constructor
-
SubscriptionBase(const SubscriptionBase&) = delete
Copy constructor deleted
-
SubscriptionBase &operator=(const SubscriptionBase&) = delete
Copy assignment operator deleted
-
virtual ~SubscriptionBase() = default
Virtual destructor
-
virtual void subscribe()
Instructs this model to turn itself on if everything is configured correctly
This may be called before initialization, in which case it is unlikely that everything is configured correctly. In that case, the sub_pending flag gets set so that when initialize gets called, the subscription can be completed.
-
virtual void unsubscribe()
Tells this model that whoever had previously subscribed to it no longer needs it
If this model had only one subscription, the last customer just left the building and this model can turn itself off.
Note
Takes no action if the model is already disabled.
-
virtual void initialize()
Configure pending subscriptions
If this class is inherited from, calling this method should be the last step in a child class' initialize() method.
Warning
Calling this function will internally mark the object as initialized. If your child class has input parameters which need to be checked for correctness before marking itself as initialized, make sure to do so before calling this function.
-
virtual void disable()
Completely disables the model
Sets active to
false, preventing the execution of the model, regardless of the current number of active or pending subscriptions.To re-enable the model requires setting the enabled flag and re-subscribing.
-
inline bool is_initialized() const
- Returns:
trueif the model has successfully been initialized
Public Members
-
std::string subscribe_name = {"unnamed-instance"}
Optional setting, useful for messages and debugging only. Not used for other purposes.
- Units:
--
-
bool initialize_on_failed_activation = {false}
Configuration flag controls the response if the model fails to activate during an initialization operation with subscriptions already pending. If false, retain subscriptions but fail initialization.
- Units:
--
-
bool quiet_unsubscribe_warning = {false}
Optional flag to control whether a warning is printed when attempting to unsubscribe from a model with no active or pending subscriptions.
- Units:
--
-
bool quiet_disabled_warning = {false}
Optional flag to quiet the error meesage that would be posted when subscribing to a disabled model.
- Units:
--
Protected Functions
-
void subscribe_internal()
Internal subscription logic
-
inline virtual void activate()
Enable execution of the model
Note
Derived classes will typically override this to include a call to the specific update() implementation and potentially subscribe to lower- level components within the class.
-
inline virtual void deactivate()
Disable execution of the model
Note
Derived classes should remember to unsubscribe from any subscriptions made during activate().
Protected Attributes
-
bool enabled = {true}
Master flag. Use as a gate on initialize() if desired. If false, will prevent subscription requests.
- Units:
--
-
bool initialized = {false}
The model is ready to be activated
- Units:
--
-
bool active = {false}
The model can be executed. The first line of code in the model inheriting from this should be
if (!active) { return; }
- Units:
--
-
int sub_pending = {0}
Internal subscription count. Incremented if subscriptions are received prior to initialization.
- Units:
--
-
int num_subscriptions = {0}
Number of subscriptions, post-initialization.
- Units:
--
-
SubscriptionBase() = default
Mathematical Formulation
No mathematical formulation.
User's Guide
This model is not intended to be used as a stand-alone model, but rather as the base for other models that need to be activated or deactivated during a complex simulation.
Notes on Disabling a Model
The design decision as to how disabling the model affects the
subscription-count and pending-subscription-count is complicated. There
are two possible results for each count - being that they are left
untouched, or set to zero. Identifying the best default behavior
requires consideration of how to process initialize(), YourModel::update(),
subscribe() and unsubscribe() calls while the model is disabled.
It is generally desirable that a disabled model cannot be initialized.
Calls to initialize() while disabled should be blocked. This is an
important features that supports blocking of expensive and
time-consuming initialization routines in models inheriting this
capability when said models are not necessary for a particular scenario.
Similarly, it is a required feature that calls to YourModel::update()
should not be processed while a model is inactive; this is the main
purpose of the model. The activity flag can be set by a call to
subscribe() if the model is initialized. Therefore, to support cases
where a model is disabled post-initialization, calls to subscribe()
must not set the active flag when the model is disabled, thereby
preventing a disabled model from executing. Any calls to subscribe()
while the model is disabled should be interpreted as user-error and
therefore flagged with a message; a model should not be simultaneously
required (as suggested by the subscribe() call) and not available (as
suggested by being disabled).
Because of the inherent design associated with blocking YourModel::update() calls
for inactive models (previous paragraph), calls to subscribe() while
a model is disabled are immediately flagged as errors and no further
action is taken. This decision has implications on how to handle the
subscription-count and pending-subscription-count, discussed below.
By symmetry, calls to unsubscribe() while disabled should also
result in no further action being taken. This is less significant than
calls to subscribe() because the model is not executing anyway while
it is disabled; failing to turn it off while it is already off has no
executable consequence. However, when an unsubscribe() call is made,
the conceptual design calls for the subscriptions-count (or
pending-subscriptions-count) to be decremented. This count should never
go below zero because unsubscribe() calls should always follow
subscribe() calls. Internal sanity checking requires that an error
message be posted if the an unsubscribe() is received while the
respective count is equal to zero because that implies a
misconfiguration. This is where the logical paths start to conflict. If
commanding unsubscribe() while disabled results in no operation, then
subscriptions posted before the model was disabled could still be
included in the count. Conversely, if commanding unsubscribe() while
disabled results in decrementing the counts, then subscriptions posted
after the model was disabled (or circumvented in sim-configuration in
anticipation of the model being disabled) would result in a confusing
error message when attempting to decrement the respective counts from
zero. The latter option is the more objectionable outcome, so the design
decision follows the path of symmetry, that calls to unsubscribe() are
not processed (exiting silently) while the model is disabled.
However, this raises a problem of its own in the unlikely scenario that
a model is only temporarily disabled - a possibility that is not
supported in this core capability, but feasibly implementable in a
derived class. If a model is re-enabled, its count of subscriptions is
unreliable given that any subscribe() commands and unsubscribe()
commands have exited without affecting the count while the model was
disabled. This makes the value of the counts at this point largely
arbitrary. The two most obvious values would be zero, or the value the
model had at the time it was previously disabled. Potential use-cases
have been evaluated with neither resolution being universally desirable.
So we have a wholly arbitrary decision to make for a situation that is
not even supported by this model. The decision was made to leave the
counts untouched at the time the model is disabled to better support
debugging. This decision could be revisited at some time in the
future.
Control Flags
The model provides three optional control flags:
initialize_on_failed_activationcontrols the consequences when the model is initialized with pending subscriptions and the consequential activation fails, while the initialization would otherwise have been successful without pending subscriptions. Note:Initializing with pending subscriptions will automatically lead to an attempt to activate the model. This is a deliberate design decision to make the activation of the model independent of the whether the subscription comes before or after the initialization of the model; both processes are necessary and sufficient for activation to be attempted.
Note that while failing activation is not a plausible scenario in this base model (where the
activate()method simply sets theactiveflag to true), it is a very real possibility in a derived model where theactivate()method might check availability or compatibility of some data set, or check for null pointers, or other pre-executive verification activities.Initialization and activation are independent processes with independent objectives. The situation of interest here is the one in which the model successfully initializes, but the activation step fails.
A model that is both initialized and subscribed should be activated. If the activation fails, then one of the two pillars must also be failed. This flag identifies which to fail:
initialize_on_failed_activation = falseresults in setting the model's initialized flag to false (indicating that initialization failed), but it retains its pending subscriptions.initialize_on_failed_activation = trueresults in the model being marked as successfully initialized but the pending subscriptions are removed.
Specific error messages are produced to alert the user to the outcome of the failed activation.
quiet_unsubscribe_warningprovides a means of suppressing warnings that would typically be generated when attempting to unsubscribe from a model that has no subscriptions.The underlying design assumption has
unsubscribe()only being called from the same unit that previously generated thesubscribe()call, so these calls should always occur in pairs and the situation in whichunsubscribe()is called without a previoussubscribe()should not arise. If an unsubscribed model receives instruction to remove a subscription, this is usually indicative of a problem in the model's architecture and a warning message is generated.This pairing of calls is especially important that when a model can be subscribed from multiple locations. Having one model remove another's subscription fundamentally breaks the purpose of the model.
However, when a model has only one dedicated subscriber, this architecture can be onerous on the object making the
subscribe()/unsubscribe()calls. There are going to be cases in which a subscription is conditional upon some configuration setting, and for which that setting is no longer testable at the point of decision over whether to callunsubscribe(). For these situations, it would be necessary to do one of:Add an internal flag, effectively confirming that the sub-model has been subscribed and that it can therefore be unsubscribed. This is necessary when the sub-model may be subscribed from multiple locations, but unnecessarily onerous for maintaining the activity of a dedicated sub-model.
Check the active status of the sub-model to identify whether it has been subscribed. This is fundamentally risky and should never be used when the sub-model may be subscribed from multiple locations (for reasons that should be apparent); for maintaining the activity of a dedicated sub-model it is an acceptable option and less onerous than maintaining an internal flag, but still requires an additional logic step that may be difficult to inject in some situations.
Issue
unsubscribe()unconditionally and accept (and ignore) the warning message generated when the sub-model was not previously subscribed. Particularly for large projects, having an architecture in which some warning and error messages are “standard operating procedure” can get very difficult to manage, and easily leads to obfuscation of messages that should be addressed. This is bad practice and not a viable solution.Issue
unsubscribe()unconditionally and bypass the generation of the warning message. This is fundamentally risky and should never be used when the sub-model may be subscribed from multiple locations (for reasons that should be apparent); for maintaining the activity of a dedicated sub-model it is an acceptable and simple option.
The
quiet_unsubscribe_warningflag supports the last of these options by allowing the higher-level object to unconditionally unsubscribe from the sub-model. If the subscription had previously been applied,unsubscribe()removes it. If it had not been applied (and this flag is set to true),unsubscribe()has no effect.Note that the use of this flag is inherently dangerous and should only be used when there is no possibility that some other model may have subscribed to the sub-model in question.
quiet_disabled_warningis similar in intent to thequiet_unsubscribe_warningflag; in this case we consider the error message that is posted when a subscription is made to a model that has been disabled. This sequence is a more serious problem then that of unsubscribing from an inactive model:With
quiet_unsubscribe_warningthe situation is that some entity is communicating that one of the models on which it depends is no longer required, but that dependency was not active anyway. The effect is typically negligible, the model was inactive before, and it probably should be inactive now. A warning is issued to alert the user to a possible misconfiguration that may have affected data upstream, but the downstream effect of not having a model available that isn't needed anyway is not significant. Blocking this warning message is quite reasonable.The
quiet_disabled_warningflag is more significant. With this flag, we are removing an error message that may otherwise be used to alert the user to significant downstream data effects. In this situation, some entity may be communicating that it requires support from some other model, but that model is not available to provide that support. The downstream data is likely to be affected by this configuration, and an error message is the appropriate response - all data beyond this point is suspect - and should be issued in this case.
This flag is primarily included to support a situation in which the supporting model is not truly required, but simply useful or desirable in some circumstances. In this situation, there may be use-cases where the supporting model is desired, and use-cases where it is not. As an example, consider some simulation-event that triggers the activation of some model:
that activated model isn't required by the event, but it is desirable that in at least some cases, the model be activated in response to the simulation-event;
to support use-cases where the model is not needed, that model may be disabled but that would result in a subscription to a disabled model and consequential error message even though this was the intended pattern.
Because blocking this message can have serious consequences, this flag has been implemented as a single-use flag. It can be set to true to block the error message from a specific
subscribe()call, but it resets to false and any subsequentsubscribe()calls will trigger the error message again (unless the flag is set back to true before each subsequent call).
Extension
The following methods may need redefining in the derived class:
Initialize
This new method should perform all initialization steps required of the
new model. The final step should be a call to
SubscriptionBase::initialize() where the initialized flag gets
set.
Disable
The base implementation deactivates and disables the model, leaving the subscription counts untouched. This should be sufficient for most applications.
Activate
It may be desirable to include an automatic call to the model's regular
execution (e.g. YourModel::update()) as a part of the activation process. This is
not included in the base implementation.
It may also be necessary to subscribe to additional model dependencies.
The base model rejects subscriptions when activation fails in response
to a subscribe() call on a pre-initialized instance. In this case, the
model will remain inactive and the number of subscriptions will not be
incremented. Any unsubscribe() calls that come after the failed
activation will generate a warning (unless quiet_unsubscribe_warning
is set to true).
Deactivate
If the activate() call resulted in subscription to other models,
these should be unsubscribed at deactivate().
Verification
Code Coverage
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File Lines Exec Cover Missing
------------------------------------------------------------------------------
models/utilities/subscriptions/include/subscriptions.hh
7 4 57% 86,158,166
models/utilities/subscriptions/src/subscriptions.cc
56 56 100%
------------------------------------------------------------------------------
TOTAL 63 60 95%
------------------------------------------------------------------------------
See detailed coverage information here.
Exceptions
The destructor is marked uncovered because the destruction of a dynamically-allocated
isntance of SubscriptionBase is not covered in testing. The compiler emits separate
branches for non-dynamically allocated and dynamically allocated objects, and only the
non-dynamically allocated case is tested.
86 virtual ~SubscriptionBase() = default;
The SubscriptionBase::activate() and SubscriptionBase::deactivate() functions
are not tested.
158 virtual void activate(){ active = true;}
166 virtual void deactivate(){active = false;}
Simulation Configurations
SIM_unit_subs
This verification simulation tests the full model by running different sequences of method calls. It ensures that the model correct tracks pending and active subscriptions before and after initialization, supports activation and deactivation based on those subscriptions, and allows the model to be fully disabled. The simulation also verifies that errors are raised appropriately according to the optional control flags.
Unit-Test Cases
RUN_01_multiple_subscriptions
This run executes a sequence of subscribe and unsubscribe calls:
t=0:
subscribe()(number of subscriptions = 1, model activates)t=1:
subscribe()(number of subscriptions = 2)t=2:
unsubscribe()(number of subscriptions = 1)t=3:
subscribe()(number of subscriptions = 2)t=4:
unsubscribe()(number of subscriptions = 1)t=5:
unsubscribe()(number of subscriptions = 0, model deactivates)t=6:
unsubscribe()(results in a message about unsubscribing from an unsubscribed model)Message: Pre-init unsubscribe error. Instruction received to unsubscribe the model (unnamed-instance) but there are no pending subscriptions. Check your configuration. Cannot process unsubscriptions in anticipation of incoming subscriptions. Command failed. Model remains unsubscribed.
t=7:
unsubscribe()withquiet_unsubscribe_warningflag set (no effect)
time |
enabled |
active |
active subscription count |
pending subscription count |
|---|---|---|---|---|
0 |
1 |
0 |
0 |
1 |
1 |
1 |
0 |
0 |
2 |
2 |
1 |
0 |
0 |
1 |
3 |
1 |
0 |
0 |
2 |
4 |
1 |
0 |
0 |
1 |
5 |
1 |
0 |
0 |
0 |
6 |
1 |
0 |
0 |
0 |
7 |
1 |
0 |
0 |
0 |
RUN_02_not_initialized
This run repeats the same sequence as RUN_01_multiple_subscriptions, but without the model
being initialized. This run verifies that subscribe() / unsubscribe() calls affect only the
pending subscriptions count, and not the actual active status of the model. Throughout this run, the
model remains inactive.
t=0:
subscribe()(number of pending subscriptions = 1)t=1:
subscribe()(number of pending subscriptions = 2)t=2:
unsubscribe()(number of pending subscriptions = 1)t=3:
subscribe()(number of pending subscriptions = 2)t=4:
unsubscribe()(number of pending subscriptions = 1)t=5:
unsubscribe()(number of pending subscriptions = 0)t=6:
unsubscribe()(results in a message about unsubscribing from a model with no pending subscriptions)Message: Pre-init unsubscribe error. Instruction received to unsubscribe the model (unnamed-instance) but there are no pending subscriptions. Check your configuration. Cannot process unsubscriptions in anticipation of incoming subscriptions. Command failed. Model remains unsubscribed.
t=7:
unsubscribe()withquiet_unsubscribe_warningflag set (no effect)
RUN_03_initialize_with_pending
This run tests the ability of the model to transfer pending subscriptions to active subscriptions at model initialization.
t=1:
subscribe()(being prior to initialization, this increments the pending subscription count to 1)t=2:
subscribe()(being prior to initialization, this increments the pending subscription count to 2)t=3:
subscribe()(being prior to initialization, this increments the pending subscription count to 3)t=4:
initialize()(model activates; 3 pending subscriptions are moved to active subscriptions)
time |
enabled |
active |
active subscription count |
pending subscription count |
|---|---|---|---|---|
0 |
1 |
0 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
2 |
1 |
0 |
0 |
2 |
3 |
1 |
0 |
0 |
3 |
4 |
1 |
1 |
3 |
0 |
RUN_04_subscribe_disable_init
This run tests the sequence of subscription - disable - initialize. With this sequence:
t=0:
subscribe()(being prior to initialization, this increments the pending subscription count to 1)subscribe()(being prior to initialization, this increments the pending subscription count to 2)
t=1:
disable()(sets the enabled flag to false)t=2:
initialize()(has no effect on a disabled model)
Note
Disabling the model does not affect the pending subscription count. This is a somewhat arbitrary design decision discussed in the User's Guide.
time |
enabled |
active |
active subscription count |
pending subscription count |
|---|---|---|---|---|
0 |
1 |
0 |
0 |
2 |
1 |
0 |
0 |
0 |
2 |
2 |
0 |
0 |
0 |
2 |
RUN_05_subscribe_init_disable
This run tests the sequence of subscription - initialize - disable. With this sequence:
t=0:
subscribe()(being prior to initialization, this increments the pending subscription count to 1)subscribe()(being prior to initialization, this increments the pending subscription count to 2)
t=1:
initialize()(the pending subscriptions are applied, model activates, subscription count = 2)t=2:
disable()(sets the enabled flag and active flag to false, leaving the subscriptions count at 2)
Note
Disabling the model while it is active does not result in changing the subscriptions count, but this value is hereafter unreliable. See discussion in the User's Guide.
time |
enabled |
active |
active subscription count |
pending subscription count |
|---|---|---|---|---|
0 |
1 |
0 |
0 |
2 |
1 |
1 |
1 |
2 |
0 |
2 |
0 |
0 |
2 |
0 |
RUN_06_disabled
This run tests the effect of issuing subscriptions while disabled:
t=0:
subscribe()(results in an error message, the model cannot be subscribed while disabled)
Message: Subscription Error Model (unnamed-instance) has been disabled for this scenario. Cannot subscribe to a disabled model.
t=1:
unsubscribe()(no effect)t=2:
initialize()(no effect)subscribe()(repeat of previous error message)
Message: Subscription Error Model (unnamed-instance) has been disabled for this scenario. Cannot subscribe to a disabled model.
subscribe()(no error message posted,quiet_disabled_warningresets)
Checking on quiet_disabled_warning reset: Before subscribe: 1 No error message during subscribe() After subscribe: 0
t=3:
unsubscribe()(no effect)
time |
enabled |
active |
active subscription count |
pending subscription count |
|---|---|---|---|---|
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
2 |
0 |
0 |
0 |
0 |
3 |
0 |
0 |
0 |
0 |
RUN_07_activation_fails
This run tests the consequences of failing to activate the model when initializing with pending subscriptions:
t=0:
subscribe()(being prior to initialization, this increments the pending subscription count to 1)subscribe()(being prior to initialization, this increments the pending subscription count to 2)subscribe()(being prior to initialization, this increments the pending subscription count to 3)
t=1:
initialize()called with initialize_on_failed_activation at default false (error message, pending subscriptions retained but initialization failed)Message: Failure During Initialization. The SubscriptionBase initialization for 'activation-failure test case' failed when the model attempted to activate during initialization: - activation sequence executed due to having pending subscriptions. Model has been neither initialized nor activated but pending subscriptions have been retained per setting of configuration flag initialize_on_failed_activation. Rerun <model>.initialize() to apply pending subscriptions and activate the model.
t=2:
initialize()called withinitialize_on_failed_activationset to true (error message, model flagged as initialized but pending subscriptions are stripped)Message: Failure During Initialization. The SubscriptionBase initialization for 'activation-failure test case' failed when the model attempted to activate during initialization: - activation sequence executed due to having pending subscriptions. Model is marked as having been initialized but not activated; pending-subscriptions have been removed per setting of configuration flag initialize_on_failed_activation. Re-subscribe to the model to activate it.
t=3:
subscribe()called (forwards toactivate(), which fails again; model remains inactive and rejects the subscription)
time |
initialize_on_failed_activation |
initialized |
active |
pending subscription count |
active subscription count |
|---|---|---|---|---|---|
0 |
0 |
0 |
0 |
3 |
0 |
1 |
0 |
0 |
0 |
3 |
0 |
2 |
1 |
1 |
0 |
0 |
0 |
3 |
1 |
1 |
0 |
0 |
0 |
RUN_08_getters
This run tests the model's getter methods:
is_enabled(),is_initialized(), andis_active()are called and their results printed to the consoleThe model is
initialize()d andsubscribe()d tois_enabled(),is_initialized(), andis_active()are called and their results printed to the console
**********************************************************************
enabled: 1 (1)
initialized: 0 (0)
active: 0 (0)
**********************************************************************
Initialize and Subscribe
**********************************************************************
enabled: 1 (1)
initialized: 1 (1)
active: 1 (1)
***********************************************************************