Skip to content

Trajectory

trajectory

trajectory

Base class of trajectory classes: - Trajectory2D - Trajectory3D

Methods

  • _prepare_plot: Prepares the plots.
  • _scatter_start_point: Scatter all the start points.
  • _scatter_trajectory: Scatter all the trajectories.
  • _plot_lines: Plots the lines of all the trajectories.
  • _create_sliders_plot: Creates the sliders plot.
  • thermalize: Adds thermalization steps and random initial position.
  • initial_position: Adds a trajectory with the given initial position.
  • plot: Prepares the plots and computes the values. Returns the axis and the figure.
  • add_slider: Adds a Slider for the dF function.
  • _calculate_values: Computes the trajectories.
Source code in phaseportrait/trajectories/trajectory.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
class trajectory:
    """
    trajectory
    ----------
    Base class of trajectory classes: 
    - Trajectory2D
    - Trajectory3D

    Methods
    -------    
    * _prepare_plot: Prepares the plots.
    * _scatter_start_point: Scatter all the start points.
    * _scatter_trajectory: Scatter all the trajectories.
    * _plot_lines: Plots the lines of all the trajectories.
    * _create_sliders_plot: Creates the sliders plot.
    * thermalize: Adds thermalization steps and random initial position.
    * initial_position: Adds a trajectory with the given initial position.
    * plot: Prepares the plots and computes the values. Returns the axis and the figure.
    * add_slider: Adds a `Slider` for the `dF` function.
    * _calculate_values: Computes the trajectories.
    """

    _name_ = 'trajectory'

    def __init__(self, dF, dimension, *, Range=None, dF_args={}, n_points=10000, runge_kutta_step=0.01, runge_kutta_freq=1, **kargs):
        """
        Creates an instance of trajectory

        Args:
            dF (callable) : A dF type function.
            dimension : The number of dimensions in which the trajectory is calculated. Must equal `dF` return lengh.
            Range (Union[float,list], default=None) : Range of every coordinate in the graphs.
            dF_args (dict) : If necesary, must contain the kargs for the `dF` function.
            n_points (int, default=10000    ) : Number of positions to be saved for every trajectory.
            runge_kutta_step (float, default=0.1) : Step of 'time' in the Runge-Kutta method.
            runge_kutta_freq (int) : Number of times `dF` is aplied between positions saved.
            lines (bool, defaullt=True) : Must be `True` if method _plot_lines is used.
            Titulo (str) : Title of the plot.
            color (str) : Matplotlib `Cmap`.
            size (float) : Size of the scattered points.
            thermalization (int) : Thermalization steps before points saved.
            mark_start_point (bool) : Marks the start position if True.
        """

        self._dimension = dimension
        self.dF_args = dF_args.copy()
        self.dF = dF 
        self.Range = Range

        try: 
            if kargs['numba']:
                from numba import jit
                self.dF = jit(self.dF, nopython=True)
                if not self.dF_args:
                    exceptions.dFArgsRequired()
        except KeyError:
            pass

        # Genral Runge-Kutta variables
        self.runge_kutta_step = runge_kutta_step
        self.runge_kutta_freq = runge_kutta_freq
        self.n_points = n_points
        self.trajectories = []

        # Plotting variables
        self.Title = kargs['Title'] if kargs.get('Title') else 'Trajectory'

        self.sliders = {}
        self.sliders_fig = False

        self.lines = kargs.get('lines', True)

        self.thermalization = kargs.get('thermalization')
        if not self.thermalization:
            self.thermalization = 0
        self.size = kargs.get('size')
        if not self.size:
            self.size = 0.5
        self.color = kargs.get('color')
        if self.color is None:
            self.color =  'viridis'
        self._mark_start_position = kargs.get('mark_start_position')

    # This functions must be overwriten on child classes:
    def _prepare_plot(self):...
    def _scatter_start_point(self, val_init):...
    def _scatter_trajectory(self, val, color, cmap):...
    def _plot_lines(self, val, val_init, *, color=None):...


    # General functions
    def _create_sliders_plot(self):
        if not isinstance(self.sliders_fig, plt.Figure):
            self.sliders_fig, self.sliders_ax = plt.subplots() 
            self.sliders_ax.set_visible(False)


    def thermalize(self, *, thermalization_steps=200):
        """
        Shortcut to:

        ```py
        self.thermalization = thermalization_steps
        self.initial_position()
        ```
        """

        if self.thermalization is None:
            self.thermalization = thermalization_steps
        self.initial_position()


    def initial_position(self, *args, color=None, **kargs):
        """
        Adds a initial position for the computation.
        More than one can be added.

        Args:
            args (Union[float, list[2], list[3]], optional) : Initial position for the computation. If None, a random position is chosen.

        Kargs:
            color (str|None): If a color is given it is used to plot that trajectory. If None the speed is used to color the line/dots. 

        Example:
        -------
        This example generates 2 circles with diferent radius. 
        ```
        def Circle(x,y,*, w=1, z=1):
            return w*y, -z*x

        circle = Trayectoria2D(Circle, n_points=1300, size=2, mark_start_position=True, Titulo='Just a circle')
        circle.initial_position(1,1)
        circle.initial_position(2,2)
        ```
        """
        if not args:
            args = np.random.rand(self._dimension)

        if len(args) < self._dimension:
            raise InvalidInitialPositions(args, self._dimension)

        flag = False
        for trajectory in self.trajectories:
            for a, b in zip(args, trajectory.initial_value):
                if a!=b:
                    flag = True

        if not flag and len(self.trajectories)>0:
            return

        class coloredRungeKutta(RungeKutta):
            def __init__(self, portrait, color, dF, dimension, max_values, *, dt=0.1, dF_args=None, initial_values=None, thermalization=0):
                super().__init__(portrait, dF, dimension, max_values, dt=dt, dF_args=dF_args, initial_values=initial_values, thermalization=thermalization)
                self.__color__ = color

        self.trajectories.append(
            coloredRungeKutta(
                self, color, self.dF, self._dimension, self.n_points, 
                dt=self.runge_kutta_step,
                dF_args=self.dF_args, 
                initial_values=args,
                thermalization=self.thermalization
                )
            )


    def initial_positions(self, *positions, **kargs):
        """
        Adds initial positions for the computation.
        Calls `trajectory.initial_position` for each position given.

        Args:
            postitions (list,list[list]): Initial positions for the computation.
        """
        positions_standar = np.array(positions).reshape(-1, self._dimension)
        for position in positions_standar:
            if len(position) < self._dimension:
                raise InvalidInitialPositions(position, self._dimension)
            self.initial_position(*position, **kargs)


    def _calculate_values(self, *args, all_initial_conditions=False, **kargs):
        if all_initial_conditions:
            for trajectory in self.trajectories:
                trajectory.position = trajectory.initial_value.copy()

        for trajectory in self.trajectories:
            trajectory.compute_all(save_freq=self.runge_kutta_freq)


    def plot(self, color=None, labels=False, grid=False, **kargs):
        """
        Prepares the plots and computes the values.

        Args:
            color (str) : Matplotlib `Cmap`.
            labels (str) : Label the initial positions

        Returns: 
            (tuple(matplotlib Figure, matplotlib Axis)): 
        """

        self._prepare_plot(grid=grid)
        self.dF_args.update({name: slider.value for name, slider in self.sliders.items() if slider.value!= None})
        for trajectory in self.trajectories:
            trajectory.dF_args = self.dF_args

        self._calculate_values(all_initial_conditions=True)

        if color is not None:
            self.color = color
        cmap = self.color

        for trajectory in self.trajectories:
            val = trajectory.positions
            vel = trajectory.velocities
            val_init = trajectory.initial_value

            if self.lines:
                self._plot_lines(val, vel, val_init, color=trajectory.__color__)

            else:
                def norma(v):
                    suma = 0
                    for i in range(self._dimension):
                        suma += np.nan_to_num(v[i]**2)
                    return np.sqrt(suma)
                if self.color == 't':
                    color = np.linspace(0,1, vel.shape[1])
                else:
                    color = norma(vel[:])
                    color /= color.max()

                self._scatter_trajectory(val, color, cmap)

            if self._mark_start_position:
                self._scatter_start_point(val_init)

        for fig in self.fig.values():
            if self.lines and labels:
                fig.legend()
            fig.canvas.draw_idle()
        try:
            self.sliders_fig.canvas.draw_idle()
        except:
            pass

        try:
            return self.fig, self.ax
        except AttributeError:
            return None


    def add_slider(self, param_name, *, valinit=None, valstep=0.1, valinterval=10):
        """
        Adds a `Slider` for the `dF` function.

        Args:
            param_name (str) : Name of the variable. Must be in the `dF` kargs of the `Map1D.dF` function.
            valinit (float, defautl=None): Initial position of the Slider
            valinterval (Union[float,list], default=10): Min and max value for the param range.
            valstep (float, default=0.1): Separation between consecutive values in the param range.
        """ 
        self._create_sliders_plot()
        self.sliders.update({param_name: sliders.Slider(self, param_name, valinit=valinit, valstep=valstep, valinterval=valinterval)})

        self.sliders[param_name].slider.on_changed(self.sliders[param_name])


    @property
    def dF(self):
        return self._dF

    @dF.setter
    def dF(self, func):
        if not callable(func):
            raise exceptions.dFNotCallable(func)
        try:
            sig = signature(func)
        except ValueError:
            pass
        else:
            if len(sig.parameters)<self._dimension + len(self.dF_args):
                raise exceptions.dFInvalid(sig, self.dF_args)
        self._dF = func


    @property
    def Range(self):
        return self._Range

    @Range.setter
    def Range(self, value):
        if value == None:
            self._Range = None
            return
        self._Range = np.array(utils.construct_interval(value, dim=3))

    @property
    def dF_args(self):
        return self._dF_args

    @dF_args.setter
    def dF_args(self, value):
        if value:
            if not isinstance(value, dict):
                raise exceptions.dF_argsInvalid(value)
        self._dF_args = value

__init__(dF, dimension, *, Range=None, dF_args={}, n_points=10000, runge_kutta_step=0.01, runge_kutta_freq=1, **kargs)

Creates an instance of trajectory

Parameters:

Name Type Description Default
dF callable)

A dF type function.

required
dimension

The number of dimensions in which the trajectory is calculated. Must equal dF return lengh.

required
Range Union[float,list], default=None)

Range of every coordinate in the graphs.

None
dF_args dict)

If necesary, must contain the kargs for the dF function.

{}
n_points int, default=10000 )

Number of positions to be saved for every trajectory.

10000
runge_kutta_step float, default=0.1)

Step of 'time' in the Runge-Kutta method.

0.01
runge_kutta_freq int)

Number of times dF is aplied between positions saved.

1
lines bool, defaullt=True)

Must be True if method _plot_lines is used.

required
Titulo str)

Title of the plot.

required
color str)

Matplotlib Cmap.

required
size float)

Size of the scattered points.

required
thermalization int)

Thermalization steps before points saved.

required
mark_start_point bool)

Marks the start position if True.

required
Source code in phaseportrait/trajectories/trajectory.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def __init__(self, dF, dimension, *, Range=None, dF_args={}, n_points=10000, runge_kutta_step=0.01, runge_kutta_freq=1, **kargs):
    """
    Creates an instance of trajectory

    Args:
        dF (callable) : A dF type function.
        dimension : The number of dimensions in which the trajectory is calculated. Must equal `dF` return lengh.
        Range (Union[float,list], default=None) : Range of every coordinate in the graphs.
        dF_args (dict) : If necesary, must contain the kargs for the `dF` function.
        n_points (int, default=10000    ) : Number of positions to be saved for every trajectory.
        runge_kutta_step (float, default=0.1) : Step of 'time' in the Runge-Kutta method.
        runge_kutta_freq (int) : Number of times `dF` is aplied between positions saved.
        lines (bool, defaullt=True) : Must be `True` if method _plot_lines is used.
        Titulo (str) : Title of the plot.
        color (str) : Matplotlib `Cmap`.
        size (float) : Size of the scattered points.
        thermalization (int) : Thermalization steps before points saved.
        mark_start_point (bool) : Marks the start position if True.
    """

    self._dimension = dimension
    self.dF_args = dF_args.copy()
    self.dF = dF 
    self.Range = Range

    try: 
        if kargs['numba']:
            from numba import jit
            self.dF = jit(self.dF, nopython=True)
            if not self.dF_args:
                exceptions.dFArgsRequired()
    except KeyError:
        pass

    # Genral Runge-Kutta variables
    self.runge_kutta_step = runge_kutta_step
    self.runge_kutta_freq = runge_kutta_freq
    self.n_points = n_points
    self.trajectories = []

    # Plotting variables
    self.Title = kargs['Title'] if kargs.get('Title') else 'Trajectory'

    self.sliders = {}
    self.sliders_fig = False

    self.lines = kargs.get('lines', True)

    self.thermalization = kargs.get('thermalization')
    if not self.thermalization:
        self.thermalization = 0
    self.size = kargs.get('size')
    if not self.size:
        self.size = 0.5
    self.color = kargs.get('color')
    if self.color is None:
        self.color =  'viridis'
    self._mark_start_position = kargs.get('mark_start_position')

add_slider(param_name, *, valinit=None, valstep=0.1, valinterval=10)

Adds a Slider for the dF function.

Parameters:

Name Type Description Default
param_name str)

Name of the variable. Must be in the dF kargs of the Map1D.dF function.

required
valinit float, defautl=None

Initial position of the Slider

None
valinterval Union[float,list], default=10

Min and max value for the param range.

10
valstep float, default=0.1

Separation between consecutive values in the param range.

0.1
Source code in phaseportrait/trajectories/trajectory.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
def add_slider(self, param_name, *, valinit=None, valstep=0.1, valinterval=10):
    """
    Adds a `Slider` for the `dF` function.

    Args:
        param_name (str) : Name of the variable. Must be in the `dF` kargs of the `Map1D.dF` function.
        valinit (float, defautl=None): Initial position of the Slider
        valinterval (Union[float,list], default=10): Min and max value for the param range.
        valstep (float, default=0.1): Separation between consecutive values in the param range.
    """ 
    self._create_sliders_plot()
    self.sliders.update({param_name: sliders.Slider(self, param_name, valinit=valinit, valstep=valstep, valinterval=valinterval)})

    self.sliders[param_name].slider.on_changed(self.sliders[param_name])

initial_position(*args, color=None, **kargs)

Adds a initial position for the computation. More than one can be added.

Parameters:

Name Type Description Default
args Union[float, list[2], list[3]], optional)

Initial position for the computation. If None, a random position is chosen.

()
Kargs

color (str|None): If a color is given it is used to plot that trajectory. If None the speed is used to color the line/dots.

Example:

This example generates 2 circles with diferent radius.

def Circle(x,y,*, w=1, z=1):
    return w*y, -z*x

circle = Trayectoria2D(Circle, n_points=1300, size=2, mark_start_position=True, Titulo='Just a circle')
circle.initial_position(1,1)
circle.initial_position(2,2)
Source code in phaseportrait/trajectories/trajectory.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def initial_position(self, *args, color=None, **kargs):
    """
    Adds a initial position for the computation.
    More than one can be added.

    Args:
        args (Union[float, list[2], list[3]], optional) : Initial position for the computation. If None, a random position is chosen.

    Kargs:
        color (str|None): If a color is given it is used to plot that trajectory. If None the speed is used to color the line/dots. 

    Example:
    -------
    This example generates 2 circles with diferent radius. 
    ```
    def Circle(x,y,*, w=1, z=1):
        return w*y, -z*x

    circle = Trayectoria2D(Circle, n_points=1300, size=2, mark_start_position=True, Titulo='Just a circle')
    circle.initial_position(1,1)
    circle.initial_position(2,2)
    ```
    """
    if not args:
        args = np.random.rand(self._dimension)

    if len(args) < self._dimension:
        raise InvalidInitialPositions(args, self._dimension)

    flag = False
    for trajectory in self.trajectories:
        for a, b in zip(args, trajectory.initial_value):
            if a!=b:
                flag = True

    if not flag and len(self.trajectories)>0:
        return

    class coloredRungeKutta(RungeKutta):
        def __init__(self, portrait, color, dF, dimension, max_values, *, dt=0.1, dF_args=None, initial_values=None, thermalization=0):
            super().__init__(portrait, dF, dimension, max_values, dt=dt, dF_args=dF_args, initial_values=initial_values, thermalization=thermalization)
            self.__color__ = color

    self.trajectories.append(
        coloredRungeKutta(
            self, color, self.dF, self._dimension, self.n_points, 
            dt=self.runge_kutta_step,
            dF_args=self.dF_args, 
            initial_values=args,
            thermalization=self.thermalization
            )
        )

initial_positions(*positions, **kargs)

Adds initial positions for the computation. Calls trajectory.initial_position for each position given.

Parameters:

Name Type Description Default
postitions list, list[list]

Initial positions for the computation.

required
Source code in phaseportrait/trajectories/trajectory.py
181
182
183
184
185
186
187
188
189
190
191
192
193
def initial_positions(self, *positions, **kargs):
    """
    Adds initial positions for the computation.
    Calls `trajectory.initial_position` for each position given.

    Args:
        postitions (list,list[list]): Initial positions for the computation.
    """
    positions_standar = np.array(positions).reshape(-1, self._dimension)
    for position in positions_standar:
        if len(position) < self._dimension:
            raise InvalidInitialPositions(position, self._dimension)
        self.initial_position(*position, **kargs)

plot(color=None, labels=False, grid=False, **kargs)

Prepares the plots and computes the values.

Parameters:

Name Type Description Default
color str)

Matplotlib Cmap.

None
labels str)

Label the initial positions

False

Returns:

Type Description
tuple(matplotlib Figure, matplotlib Axis)
Source code in phaseportrait/trajectories/trajectory.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def plot(self, color=None, labels=False, grid=False, **kargs):
    """
    Prepares the plots and computes the values.

    Args:
        color (str) : Matplotlib `Cmap`.
        labels (str) : Label the initial positions

    Returns: 
        (tuple(matplotlib Figure, matplotlib Axis)): 
    """

    self._prepare_plot(grid=grid)
    self.dF_args.update({name: slider.value for name, slider in self.sliders.items() if slider.value!= None})
    for trajectory in self.trajectories:
        trajectory.dF_args = self.dF_args

    self._calculate_values(all_initial_conditions=True)

    if color is not None:
        self.color = color
    cmap = self.color

    for trajectory in self.trajectories:
        val = trajectory.positions
        vel = trajectory.velocities
        val_init = trajectory.initial_value

        if self.lines:
            self._plot_lines(val, vel, val_init, color=trajectory.__color__)

        else:
            def norma(v):
                suma = 0
                for i in range(self._dimension):
                    suma += np.nan_to_num(v[i]**2)
                return np.sqrt(suma)
            if self.color == 't':
                color = np.linspace(0,1, vel.shape[1])
            else:
                color = norma(vel[:])
                color /= color.max()

            self._scatter_trajectory(val, color, cmap)

        if self._mark_start_position:
            self._scatter_start_point(val_init)

    for fig in self.fig.values():
        if self.lines and labels:
            fig.legend()
        fig.canvas.draw_idle()
    try:
        self.sliders_fig.canvas.draw_idle()
    except:
        pass

    try:
        return self.fig, self.ax
    except AttributeError:
        return None

thermalize(*, thermalization_steps=200)

Shortcut to:

self.thermalization = thermalization_steps
self.initial_position()
Source code in phaseportrait/trajectories/trajectory.py
112
113
114
115
116
117
118
119
120
121
122
123
124
def thermalize(self, *, thermalization_steps=200):
    """
    Shortcut to:

    ```py
    self.thermalization = thermalization_steps
    self.initial_position()
    ```
    """

    if self.thermalization is None:
        self.thermalization = thermalization_steps
    self.initial_position()