Tubes: sets of trajectories
We now focus on dynamical items that are evolving with time. The trajectories \(x(\cdot)\) and vector of trajectories \(\mathbf{x}(\cdot)\), presented in the previous pages, have also their interval counterpart: tubes. This page provides the main uses of these sets. They will be involved afterwards for solving problems related to differential equations.
Definition
A tube is defined over a temporal tdomain \([t_0,t_f]\) as an envelope of trajectories that are defined over the same tdomain. We speak about an envelope as it may exist trajectories enclosed in the tube that are not solutions of our problem.
In this library, a tube \([x](\cdot):[t_0,t_f]\rightarrow\mathbb{IR}\) is an interval of two trajectories \([\underline{x}(\cdot),\overline{x}(\cdot)]\) such that \(\forall t\in[t_0,t_f]\), \(\underline{x}(t)\leqslant\overline{x}(t)\). We also consider empty tubes that depict an absence of solutions, denoted \(\varnothing(\cdot)\). A trajectory \(x(\cdot)\) belongs to the tube \(\left[x\right](\cdot)\) if \(\forall t\in[t_0,t_f], x\left(t\right)\in\left[x\right]\left(t\right)\).
Note
Important: we assume that all the tubes and/or the trajectories involved in a given resolution process share the same tdomain \([t_0,t_f]\).
Slices
The box \([k\delta,k\delta+\delta]\times\left[x\right]\left(t_{k}\right)\), with \(t_{k}\in[k\delta,k\delta+\delta]\), is called the \(k\)th slice of the tube \([x](\cdot)\) and is denoted by \([\![x]\!]^{(k)}\).
New in version 2.0: Custom discretization of tubes is now at hand, they can be made of slices that are not all defined with the same sampling time \(\delta\).
This implementation takes rigorously into account floating point precision when building a tube. Further computations involving \([x](\cdot)\) will be implicitly based on its slices, thus keeping a reliable outer approximation of the solution set.
Creating tubes
The vocabulary for tubes involves two notions:
the tdomain, that represents the temporal domain \([t_0,t_f]\)
the codomain, that represents the set of feasible values of the tube \([x]([t_0,t_f])\)
To create a Tube
with a constant codomain:
tdomain = Interval(0,10)
# Oneslice tubes:
x1 = Tube(tdomain) # [0,10]→[∞,∞]
x2 = Tube(tdomain, Interval(0,2)) # [0,10]→[0,2]
# 100slices tubes:
dt = 0.1
x3 = Tube(tdomain, dt, Interval(0,2)) # [0,10]→[0,2]
x4 = Tube(tdomain, dt, Interval(0,oo)) # [0,10]→[0,∞]
Interval tdomain(0,10);
// Oneslice tubes:
Tube x1(tdomain); // [0,10]→[∞,∞]
Tube x2(tdomain, Interval(0,2)); // [0,10]→[0,2]
// 100slices tubes:
float dt = 0.1;
Tube x3(tdomain, dt, Interval(0,2)); // [0,10]→[0,2]
Tube x4(tdomain, dt, Interval(0,oo)); // [0,10]→[0,∞]
The dt
variable defines the temporal width of the slices. Note that it is also possible to create slices of different width; this will be explained afterwards.
To create a copy of a tube with the same time discretization, use:
x5 = Tube(x4) # identical tube (100 slices, [0,10]→[0,∞])
x5.set(Interval(5)) # 100 slices, same timestep, but [0,10]→[5]
Tube x5(x4); // identical tube (100 slices, [0,10]→[0,∞])
x5.set(Interval(5.)); // 100 slices, same timestep, but [0,10]→[5]
As tubes are intervals of trajectories, a Tube
can be defined from Trajectory
objects:
traj = TrajectoryVector(tdomain, TFunction("(sin(t) ; cos(t) ; cos(t)+t/10)"))
x8 = Tube(traj[0], dt) # 100 slices tube enclosing sin(t)
x9 = Tube(traj[1], traj[2], dt) # 100 slices tube defined as [cos(t),cos(t)+t/10]
TrajectoryVector traj(tdomain, TFunction("(sin(t) ; cos(t) ; cos(t)+t/10)"));
Tube x8(traj[0], dt); // 100 slices tube enclosing sin(t)
Tube x9(traj[1], traj[2], dt); // 100 slices tube defined as [cos(t),cos(t)+t/10]
It is also possible to create a tube from a thick function, where the uncertainty is explicitly set in the formula:
dt = 0.01
x10 = Tube(tdomain, dt, \
TFunction("abs(cos(t)+t/5)+(t/2)*[0.1,0.1]"))
dt = 0.01;
Tube x10(tdomain, dt,
TFunction("abs(cos(t)+t/5)+(t/2)*[0.1,0.1]"));
Finally, as tube is an envelope (union) of trajectories, the following operations are allowed:
f = TFunction("(cos(t) ; cos(t)+t/10 ; sin(t)+t/10 ; sin(t))") # 4d temporal function
traj = TrajectoryVector(tdomain, f) # 4d trajectory defined over [0,10]
# 1d tube [x](·) defined as a union of the 4 trajectories
x = Tube(traj[0], dt)  traj[1]  traj[2]  traj[3]
TFunction f("(cos(t) ; cos(t)+t/10 ; sin(t)+t/10 ; sin(t))"); // 4d temporal function
TrajectoryVector traj(tdomain, f); // 4d trajectory defined over [0,10]
// 1d tube [x](·) defined as a union of the 4 trajectories
Tube x = Tube(traj[0], dt)  traj[1]  traj[2]  traj[3];
Which produces:
The vector case
TubeVector
, allowing to create tubes \([\mathbf{x}](\cdot):[t_0,t_f]\to\mathbb{IR}^n\).# TubeVector from a formula; the function's output is twodimensional
x = TubeVector(tdomain, dt, \
TFunction("(sin(sqrt(t)+((t5)^2)*[0.01,0.01]) ; \
cos(t)+sin(t/0.2)*[0.1,0.1])"))
// TubeVector from a formula; the function's output is twodimensional
TubeVector x(tdomain, dt,
TFunction("(sin(sqrt(t)+((t5)^2)*[0.01,0.01]) ; \
cos(t)+sin(t/0.2)*[0.1,0.1])"));
produces (each dimension displayed on the same figure):
New in version 3.0.10: The definition of a TubeVector
can also be done from a list. The above example could be written as:
# TubeVector as a list of scalar tubes
x = TubeVector([ \
Tube(tdomain, dt, TFunction("(sin(sqrt(t)+((t5)^2)*[0.01,0.01]))")), \
Tube(tdomain, dt, TFunction("cos(t)+sin(t/0.2)*[0.1,0.1]")) \
])
// TubeVector as a list of scalar tubes
TubeVector x({
Tube(tdomain, dt, TFunction("(sin(sqrt(t)+((t5)^2)*[0.01,0.01]))")),
Tube(tdomain, dt, TFunction("cos(t)+sin(t/0.2)*[0.1,0.1]"))
});
As for Vector
and IntervalVector
objects, a Tube
component of a TubeVector
is available by reference with []
.
Vector operations are also at hand:
Return type 
Code 
Meaning 



number of dimensions of the tube vector 
– 

adds or removes dimensions 


extracts a subvector from 
– 

puts a subvector at 
Evaluations
Once created, several evaluations of the tubes can be made, as for trajectories.
Accessing values with
()
x(6.) # evaluation of [x](·) at 6 x(Interval(5,6)) # evaluation of [x](·) over [5,6] x.codomain() # envelope of valuesx(6.) // evaluation of [x](·) at 6 x(Interval(5.,6.)) // evaluation of [x](·) over [5,6] x.codomain() // envelope of valuesInversion of tubes
The inversion of a tube \([x](\cdot)\), denoted \([x]^{1}([y])\), returns the set \([t]\) enclosing the preimages of \([y]\). The
invert()
method returns the squaredunion of these subsets, or the set of solutions within a vector ofInterval
objects. The following example returns the different subsets of the inversion \([x]^{1}([0,0.2])\) projected in red in next figure:v_t = [] x.invert(Interval(0,0.2), v_t, tdomain) # inversion # tdomain is a temporal subset over which the inversion is performed for t in v_t: tbox = IntervalVector([t,[0,0.2]]) fig.draw_box(tbox, "red") # boxes displayvector<Interval> v_t; // vector of preimages x.invert(Interval(0.,0.2), v_t, tdomain); // inversion // tdomain is a temporal subset over which the inversion is performed for(auto& t : v_t) { IntervalVector tbox = {t,{0.,0.2}}; fig.draw_box(tbox, "red"); // boxes display }
Operations on sets
Set operations are available for Tube
and TubeVector
objects. In the following table, if \([x](\cdot)\) is a tube object, \(z(\cdot)\) is a trajectory.
Return type 
Code 
Meaning, \(\forall t\in[t_0,t_f]\) 



\([x](t)=[y](t)\) 


\([x](t)\neq [y](t)\) 


\([x](t)=\varnothing\) 


\([x](t)\subseteq [y](t)\) 


\([x](t)\subseteq [y](t)\wedge [x](t)\neq [y](t)\) 


\([x](t)\supseteq [y](t)\) 


\([x](t)\supseteq [y](t)\wedge [x](t)\neq [y](t)\) 


\(z(t)\in [x](t)\) 


\([x](t)\cap [y](t)\neq\varnothing\) 
The
contains()
methodTesting if a tube \([x](\cdot)\) contains a solution \(z(\cdot)\) may lead to uncertainties. Indeed, the reliable representation of a
Trajectory
may lead to some wrapping effect, and so this contains test may not be able to conclude. Therefore, thecontains()
method returns aBoolInterval
value. Its values can be eitherYES
,NO
orMAYBE
. TheMAYBE
case is rare but may appear due to the numerical representation of a trajectory. In practice, this may happen if the thin envelope of \(z(\cdot)\) overlaps a boundary of the tube \([x](\cdot)\).
In addition of these test functions, operations on sets are available:
Code 
Meaning, \(\forall t\in[t_0,t_f]\) 


\([x](t)\cap [y](t)\) 

\([x](t)\sqcup[y](t)\) 

\([x](t)\leftarrow \varnothing\) 

\([x](t)\leftarrow [y](t)\) 

\([x](t)\leftarrow ([x](t)\cap [y](t))\) 

\([x](t)\leftarrow ([x](t)\sqcup[y](t))\) 
Finally, one can also access properties of the sets. First for Tube
:
Return type 
Code 
Meaning 



diameters of the tube as a trajectory, \(d(\cdot)=\overline{x}(\cdot)\underline{x}(\cdot)\) 


maximal diameter 


the volume (surface) of the tube 


a Tube with the same midtraj and radius increased by 
Then for TubeVector
:
Return type 
Code 
Meaning 



diameters of the tube as a trajectory, \(\mathbf{d}(\cdot)=\overline{\mathbf{x}}(\cdot)\underline{\mathbf{x}}(\cdot)\) 


maximal diameter 


approximated trajectory: list of diagonals of each slice 


the volume of the tube 


new tube: same midtraj, each dimension increased by 
The
diam()
methodsThese methods are used to evaluate the thickness of aTube
or aTubeVector
. They are mainly used for display purposes, for instance for displaying a tube with a color function depending on its thickness.However, without derivative knowledge, and because the tube is made of boxed slices, the trajectory will be discontinuous and so the returned object will not reliably represent the actual diameters.The
diag()
methodsIt holds the same of the
diag()
methods.
Classical operations on sets are applicable on tubes. We recall that the tubes and trajectories have to share the same tdomain for these operations.
x4 = (x1  x2) & x3
Tube x4 = (x1  x2) & x3;
The same for mathematical functions:
x2 = abs(x1)
x3 = cos(x1) + sqrt(x2 + pow(x1, Interval(2,3)))
Tube x2 = abs(x1);
Tube x3 = cos(x1) + sqrt(x2 + pow(x1, Interval(2,3)));
The following functions can be used:
Code 
Meaning 


\([x](\cdot)^2\) 

\(\sqrt{[x](\cdot)}\) 

\([x](\cdot)^n\) 

\([x](\cdot)^{[y]} = e^{[y]\log([x](\cdot))}\) 

\(\sqrt[n]{[x](\cdot)}\) 

\(\exp([x](\cdot))\) 

\(\log([x](\cdot))\) 

\(\cos([x](\cdot))\) 

\(\sin([x](\cdot))\) 

\(\tan([x](\cdot))\) 

\(\textrm{acos}([x](\cdot))\) 

\(\textrm{asin}([x](\cdot))\) 

\(\textrm{atan}([x](\cdot))\) 

\(\textrm{atan2}([y](\cdot),[x](\cdot))\)
\(\textrm{atan2}([y],[x](\cdot))\)
\(\textrm{atan2}([y](\cdot),[x])\)

Integral computations
Reliable integral computations are available on tubes.
The computation is reliable because it stands on the tube’s slices. The result is an outer approximation of the integral of the tube represented by these slices:
Computation of the tube primitive \([p](\cdot)=\int_{0}^{\cdot}[x](\tau)d\tau\):
p = x.primitive()
Tube p = x.primitive();
Computation of the intervalintegral \([s]=\int_{0}^{[t]}[x](\tau)d\tau\):
t = Interval(...)
s = x.integral(t)
Interval t(...);
Interval s = x.integral(t);
Computation of \([s]=\int_{[t_1]}^{[t_2]}[x](\tau)d\tau\):
t1 = Interval(...)
t2 = Interval(...)
s = x.integral(t1, t2)
Interval t1(...), t2(...);
Interval s = x.integral(t1, t2);
Also, a decomposition of the intervalintegral of \([x](\cdot)=[x^(\cdot),x^+(\cdot)]\) with \([s^]=\int_{[t_1]}^{[t_2]}x^(\tau)d\tau\) and \([s^+]=\int_{[t_1]}^{[t_2]}x^+(\tau)d\tau\) is computable by:
t1 = Interval(...)
t2 = Interval(...)
s = x.partial_integral(t1, t2)
# s[0] is [s^]
# s[1] is [s^+]
Interval t1, t2;
pair<Interval,Interval> s;
s = x.partial_integral(t1, t2);
// s.first is [s^]
// s.second is [s^+]
Note: \([s]=[s^]\sqcup[s^+]\).
Updating values
The set()
methods allow various updates on tubes. For instance:
x.set(Interval(0,2), Interval(5,6)) # then [x]([5,6])=[0,2]
x.set(Interval(0.,2.), Interval(5.,6.)); // then [x]([5,6])=[0,2]
produces:
Warning
Be careful when updating a tube without the use of dedicated contractors. Tube discretization has to be kept in mind whenever an update is performed for some input \(t\). For reliable operations, please see the contractors section.
See also the following methods:
x.set(Interval(0,oo)) # set a constant codomain for all t
x.set(Interval(0), 4.) # set a value at some t: [x](4)=[0]
x.set_empty() # empty set for all t
x.set(Interval(0,oo)); // set a constant codomain for all t
x.set(Interval(0.), 4.); // set a value at some t: [x](4)=[0]
x.set_empty(); // empty set for all t
Sampling and tdomain
The following methods are related to tdomains and slices structure:
Return type 
Code 
Meaning 



temporal domain \([t_0,t_f]\) (tdomain) 
– 

shifts the tdomain to \([t_0+a,t_f+a]\) 


tests if 
– 

samples the tube at \(t\) (adds a slice) 
– 

samples 
– 

removes the sampling at \(t\), if it exists 
The
sample()
methodsCustom sampling of tubes is available. The
sample(t)
method allows to cut a slice defined over \(t\) into two slices on each side of \(t\). If \(t\) already separates two slices (if it corresponds to a gate), then nothing is changed.
Next pages will present reliable operators to reduce the range of the presented domains (intervals, boxes, tubes) in a reliable way and according to the constraints defining a problem.