nost-tools 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nost-tools might be problematic. Click here for more details.

@@ -0,0 +1,531 @@
1
+ """
2
+ Provides classes to execute a simulation.
3
+ """
4
+
5
+ import logging
6
+ import time
7
+ from datetime import datetime, timedelta, timezone
8
+ from enum import Enum
9
+ from typing import List, Type
10
+
11
+ from .entity import Entity
12
+ from .observer import Observable
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class Mode(str, Enum):
18
+ """
19
+ Enumeration of simulation modes.
20
+
21
+ The six simulation modes include
22
+ * `UNDEFINED`: Simulation is in an undefined state that is not one of the other modes.
23
+ For example, the simulation reverts to UNDEFINED after adding a new entity
24
+ and must be re-initialized before execution.
25
+ * `INITIALIZING`: Simulation is in the process of initialization.
26
+ * `INITIALIZED`: Simulation has finished initialization and is ready to execute.
27
+ * `EXECUTING`: Simulation is in the process of execution.
28
+ * `TERMINATING`: Simulation is in the process of termination.
29
+ * `TERMINATED`: Simulation has finished termination and is ready for initialization.
30
+ """
31
+
32
+ UNDEFINED = "UNDEFINED"
33
+ INITIALIZING = "INITIALIZING"
34
+ INITIALIZED = "INITIALIZED"
35
+ EXECUTING = "EXECUTING"
36
+ TERMINATING = "TERMINATING"
37
+ TERMINATED = "TERMINATED"
38
+
39
+
40
+ class Simulator(Observable):
41
+ """
42
+ Object that manages simulation of entities in a scenario.
43
+
44
+ Notifies observers of changes to observable properties
45
+ * `time`: current scenario time
46
+ * `mode`: current execution mode
47
+ * `duration`: scenario execution duration
48
+ * `time_step`: scenario time step duration
49
+ """
50
+
51
+ PROPERTY_MODE = "mode"
52
+ PROPERTY_TIME = "time"
53
+
54
+ def __init__(self, wallclock_offset: timedelta = timedelta()):
55
+ """
56
+ Initializes a new simulator.
57
+
58
+ Args:
59
+ wallclock_offset (:obj:`timedelta`): difference between the system
60
+ clock and trusted wallclock source (default: zero)
61
+ """
62
+ # call super class constructor
63
+ super().__init__()
64
+ # offset from the system clock to "true" time
65
+ self._wallclock_offset = wallclock_offset
66
+ # list of entities that participate in this simulation
67
+ self._entities = []
68
+ # current mode of the simulator
69
+ self._mode = Mode.UNDEFINED
70
+ # current simulation time; next simulation time, initial simulation time
71
+ self._time = self._next_time = self._init_time = 0
72
+ # current simulation time step; next simulation time step
73
+ self._time_step = self._next_time_step = None
74
+ # current simulation duration; next simulation duration
75
+ self._duration = self._next_duration = None
76
+ # wallclock time when the simulation starts or changes time scaling
77
+ self._wallclock_epoch = None
78
+ # simulation time when the simulation starts or changes time scaling
79
+ self._simulation_epoch = None
80
+ # simulation time at which to perform a time scale change
81
+ self._time_scale_change_time = None
82
+ # relationship between the wallclock time and simulation time
83
+ self._time_scale_factor = self._next_time_scale_factor = 1
84
+
85
+ def add_entity(self, entity: Entity) -> None:
86
+ """
87
+ Adds an entity the the simulation.
88
+
89
+ Args:
90
+ entity (:obj:`Entity`): entity to be added
91
+ """
92
+ if self._mode == Mode.INITIALIZING:
93
+ raise RuntimeError("Cannot add entity: simulator is initializing")
94
+ elif self._mode == Mode.EXECUTING:
95
+ raise RuntimeError("Cannot add entity: simulator is executing")
96
+ elif self._mode == Mode.TERMINATING:
97
+ raise RuntimeError("Cannot add entity: simulator is terminating")
98
+ self._set_mode(Mode.UNDEFINED)
99
+ self._entities.append(entity)
100
+
101
+ def get_entities(self) -> List[Entity]:
102
+ """
103
+ Retrieves a list of all entities in the simulation.
104
+
105
+ Returns:
106
+ List(Entity): list of entities in the simulation
107
+ """
108
+ # perform shallow copy to prevent external modification
109
+ return self._entities.copy()
110
+
111
+ def get_entities_by_name(self, name: str) -> List[Entity]:
112
+ """
113
+ Retrieves a list of entities by name.
114
+
115
+ Args:
116
+ name (str): name of the entity
117
+
118
+ Returns:
119
+ List(Entity): list of entities with a matching name
120
+ """
121
+ return [entity for entity in self._entities if entity.name == name]
122
+
123
+ def get_entities_by_type(self, type: Type) -> List[Entity]:
124
+ """
125
+ Retrieves a list of entities by type (class).
126
+
127
+ Args:
128
+ type (Type): type (class) of entity
129
+
130
+ Returns:
131
+ List(Entity): list of entities with a matching type
132
+ """
133
+ return [entity for entity in self._entities if isinstance(entity, type)]
134
+
135
+ def remove_entity(self, entity: Entity) -> Entity:
136
+ """
137
+ Removes an entity from the simulation.
138
+
139
+ Args:
140
+ entity (:obj:`Entity`): entity to be removed
141
+ Returns:
142
+ :obj:`Entity`: removed entity
143
+ """
144
+ if self._mode == Mode.INITIALIZING:
145
+ raise RuntimeError("Cannot add entity: simulator is initializing")
146
+ elif self._mode == Mode.EXECUTING:
147
+ raise RuntimeError("Cannot add entity: simulator is executing")
148
+ elif self._mode == Mode.TERMINATING:
149
+ raise RuntimeError("Cannot add entity: simulator is terminating")
150
+ if entity in self._entities:
151
+ self._set_mode(Mode.UNDEFINED)
152
+ return self._entities.remove(entity)
153
+ else:
154
+ return None
155
+
156
+ def initialize(
157
+ self,
158
+ init_time: datetime,
159
+ wallclock_epoch: datetime = None,
160
+ time_scale_factor: float = 1,
161
+ ) -> None:
162
+ """
163
+ Initializes the simulation to an initial scenario time. Requires that the
164
+ simulator is in UNDEFINED, INITIALIZED, or TERMINATED mode.
165
+
166
+ Transitions to the INITIALIZING mode, initializes all entities to the
167
+ initial scenario time, sets the wallclock epoch (wallclock time corresponding
168
+ with the initial scenario time), and finally transitions to the INITIALIZED mode.
169
+
170
+ Args:
171
+ init_time (:obj:`datetime`): initial scenario time
172
+ wallclock_epoch (:obj:`datetime`): wallclock time corresponding to the
173
+ initial scenario time, None uses the current wallclock time (default: None)
174
+ time_scale_factor (float): number of scenario seconds per wallclock second (default: 1)
175
+ """
176
+ if self._mode == Mode.INITIALIZING:
177
+ raise RuntimeError("Cannot initialize: simulator is initializing.")
178
+ elif self._mode == Mode.EXECUTING:
179
+ raise RuntimeError("Cannot initialize: simulator is executing.")
180
+ elif self._mode == Mode.TERMINATING:
181
+ raise RuntimeError("Cannot initialize: simulator is terminating.")
182
+ self._set_mode(Mode.INITIALIZING)
183
+ logger.info(
184
+ f"Initializing simulator to time {init_time} (wallclock time {wallclock_epoch})"
185
+ )
186
+ for entity in self._entities:
187
+ entity.initialize(init_time)
188
+ self._time = self._next_time = self._init_time = init_time
189
+ self._simulation_epoch = init_time
190
+ if wallclock_epoch is None:
191
+ self._wallclock_epoch = self.get_wallclock_time()
192
+ else:
193
+ self._wallclock_epoch = wallclock_epoch
194
+ self._time_scale_factor = self._next_time_scale_factor = time_scale_factor
195
+ self._set_mode(Mode.INITIALIZED)
196
+
197
+ def execute(
198
+ self,
199
+ init_time: datetime,
200
+ duration: timedelta,
201
+ time_step: timedelta,
202
+ wallclock_epoch: datetime = None,
203
+ time_scale_factor: float = 1,
204
+ ) -> None:
205
+ """
206
+ Executes a simulation for a specified duration with uniform time steps. Requires that the
207
+ simulator is in UNDEFINED, INITIALIZED, or TERMINATED mode.
208
+
209
+ Initializes the simulation (if not already in the INITIALIZED mode), waits for the
210
+ specified wallclock epoch, and transitions to the EXECUTING mode. During execution,
211
+ incrementally performs state transitions for each entity. At the end of the simulation,
212
+ transitions to the TERMINATING and, finally, TERMINATED mode.
213
+
214
+ Args:
215
+ init_time (:obj:`datetime`): initial scenario time
216
+ duration (:obj:`timedelta`): scenario execution duration
217
+ time_step (:obj:`timedelta`): scenario time step duration
218
+ wallclock_epoch (:obj:`datetime`): wallclock time corresponding to the
219
+ initial scenario time, None uses the current wallclock time (default: None)
220
+ time_scale_factor (float): number of scenario seconds per wallclock second (default value: 1)
221
+ """
222
+ if self._mode != Mode.INITIALIZED:
223
+ self.initialize(init_time, wallclock_epoch, time_scale_factor)
224
+
225
+ self._duration = self._next_duration = duration
226
+ self._time_step = self._next_time_step = time_step
227
+
228
+ logger.info(
229
+ f"Executing simulator for {duration} ({time_step} steps), starting at {self._wallclock_epoch}."
230
+ )
231
+ self._wait_for_wallclock_epoch()
232
+ self._set_mode(Mode.EXECUTING)
233
+
234
+ logger.info("Starting main simulation loop.")
235
+ while (
236
+ self._mode == Mode.EXECUTING
237
+ and self.get_time() < self.get_init_time() + self.get_duration()
238
+ ):
239
+ # compute time step (last step may be shorter)
240
+ time_step = min(
241
+ self._time_step, self._init_time + self._duration - self._time
242
+ )
243
+ # tick each entity
244
+ for entity in self._entities:
245
+ entity.tick(
246
+ min(time_step, self._init_time + self._duration - self._time)
247
+ )
248
+ # store the next time
249
+ self._next_time = self._time + time_step
250
+ if (
251
+ self._time_scale_change_time is not None
252
+ and self._time_scale_change_time < self._next_time
253
+ ):
254
+ # update the wallclock epoch of this change
255
+ self._wallclock_epoch = self.get_wallclock_time_at_simulation_time(
256
+ self._time
257
+ )
258
+ # update the simulation epoch of this change
259
+ self._simulation_epoch = self._time
260
+ # reset the flag to change the time scale factor
261
+ self._time_scale_change_time = None
262
+ # commit the change to the time scale factor and notify observers
263
+ prev_time_scale_factor = self._time_scale_factor
264
+ self._time_scale_factor = self._next_time_scale_factor
265
+ self.notify_observers(
266
+ "time_scale_factor", prev_time_scale_factor, self._time_scale_factor
267
+ )
268
+ # wait for the correct time
269
+ self._wait_for_tock()
270
+ # break out of loop if terminating execution
271
+ if self._mode == Mode.TERMINATING:
272
+ logger.debug("Terminating: exiting execution loop.")
273
+ break
274
+ # tock each entity
275
+ for entity in self._entities:
276
+ entity.tock()
277
+ # update the execution duration, if needed
278
+ if self._duration != self._next_duration:
279
+ prev_duration = self._duration
280
+ self._duration = self._next_duration
281
+ logger.info(f"Updated duration to {self._duration}.")
282
+ self.notify_observers("duration", prev_duration, self._duration)
283
+ # update the execution time step, if needed
284
+ if self._time_step != self._next_time_step:
285
+ prev_time_step = self._time_step
286
+ self._time_step = self._next_time_step
287
+ logger.info(f"Updated time step to {self._time_step}.")
288
+ self.notify_observers("time_step", prev_time_step, self._time_step)
289
+ # update the execution time
290
+ if self._time != self._next_time:
291
+ prev_time = self._time
292
+ self._time = self._next_time
293
+ logger.debug(f"Updated time {self._time}.")
294
+ self.notify_observers(self.PROPERTY_TIME, prev_time, self._time)
295
+ logger.debug(f"Simulation advanced to time {self.get_time()}.")
296
+
297
+ logger.info("Simulation complete; terminating.")
298
+ self._set_mode(Mode.TERMINATING)
299
+ self._set_mode(Mode.TERMINATED)
300
+
301
+ def _wait_for_tock(self) -> None:
302
+ """
303
+ Waits until the wallclock time matches the next time step interval.
304
+ """
305
+ while (
306
+ self._mode == Mode.EXECUTING
307
+ and self.get_wallclock_time_at_simulation_time(self._next_time)
308
+ > self.get_wallclock_time()
309
+ ):
310
+ time_diff = (
311
+ self.get_wallclock_time_at_simulation_time(self._next_time)
312
+ - self.get_wallclock_time()
313
+ )
314
+ if time_diff > timedelta(seconds=0):
315
+ logger.debug(f"Waiting for {time_diff} to advance time.")
316
+ # sleep for up to a second
317
+ time.sleep(min(1, time_diff / timedelta(seconds=1)))
318
+
319
+ def _wait_for_wallclock_epoch(self) -> None:
320
+ """
321
+ Waits until the wallclock time matches the designated wallclock epoch.
322
+ """
323
+ epoch_diff = self._wallclock_epoch - self.get_wallclock_time()
324
+ if epoch_diff > timedelta(seconds=0):
325
+ logger.info(f"Waiting for {epoch_diff} to synchronize execution start.")
326
+ time.sleep(epoch_diff / timedelta(seconds=1))
327
+
328
+ def get_mode(self) -> Mode:
329
+ """
330
+ Gets the current simulation mode.
331
+
332
+ Returns:
333
+ :obj:`Mode`: current simulation mode
334
+ """
335
+ return self._mode
336
+
337
+ def _set_mode(self, mode: Mode) -> None:
338
+ """
339
+ Sets the simulation mode and notifies observers.
340
+
341
+ Args:
342
+ mode (:obj:`Mode`): new simulation mode
343
+ """
344
+ prev_mode = self._mode
345
+ self._mode = mode
346
+ self.notify_observers(self.PROPERTY_MODE, prev_mode, self._mode)
347
+
348
+ def get_time_scale_factor(self) -> float:
349
+ """
350
+ Gets the time scale factor in scenario seconds per wall clock second (>1 is faster-than-real-time).
351
+
352
+ Returns:
353
+ float: current time scale factor
354
+ """
355
+ return self._time_scale_factor
356
+
357
+ def get_wallclock_epoch(self) -> datetime:
358
+ """
359
+ Gets the wallclock epoch.
360
+
361
+ Returns:
362
+ :obj:`datetime`: current wallclock epoch
363
+ """
364
+ return self._wallclock_epoch
365
+
366
+ def get_simulation_epoch(self) -> datetime:
367
+ """
368
+ Gets the scenario epoch.
369
+
370
+ Returns:
371
+ :obj:`datetime`: current scenario epoch
372
+ """
373
+ return self._simulation_epoch
374
+
375
+ def get_duration(self) -> timedelta:
376
+ """
377
+ Gets the scenario duration.
378
+
379
+ Returns:
380
+ :obj:`timedelta`: current scenario duration
381
+ """
382
+ return self._duration
383
+
384
+ def get_end_time(self) -> datetime:
385
+ """
386
+ Gets the scenario end time.
387
+
388
+ Returns:
389
+ :obj:`datetime`: final scenario time
390
+ """
391
+ return self._init_time + self._duration
392
+
393
+ def get_init_time(self) -> datetime:
394
+ """
395
+ Gets the initial scenario time.
396
+
397
+ Returns:
398
+ :obj:`datetime`: initial scenario time
399
+ """
400
+ return self._init_time
401
+
402
+ def get_time(self) -> datetime:
403
+ """
404
+ Gets the current scenario time.
405
+
406
+ Returns:
407
+ :obj:`datetime`: current scenario time
408
+ """
409
+ return self._time
410
+
411
+ def get_time_step(self) -> timedelta:
412
+ """
413
+ Gets the scenario time step duration.
414
+
415
+ Returns:
416
+ :obj:`timedelta`: time step duration
417
+ """
418
+ return self._time_step
419
+
420
+ def get_wallclock_time_step(self) -> timedelta:
421
+ """
422
+ Gets the wallclock time step duration.
423
+
424
+ Returns:
425
+ :obj:`timedelta`: time step duration
426
+ """
427
+ if self._time_scale_factor is None or self._time_scale_factor <= 0:
428
+ return self._time_step
429
+ else:
430
+ return self._time_step * self._time_scale_factor
431
+
432
+ def get_wallclock_time(self) -> datetime:
433
+ """
434
+ Gets the current wallclock time.
435
+
436
+ Returns:
437
+ :obj:`datetime`: current wallclock time
438
+ """
439
+ return datetime.now(tz=timezone.utc) + self._wallclock_offset
440
+
441
+ def get_wallclock_time_at_simulation_time(self, time: datetime) -> datetime:
442
+ """
443
+ Gets the wallclock time corresponding to the designated scenario time.
444
+
445
+ Args:
446
+ time (:obj:`datetime`): scenario time
447
+
448
+ Returns:
449
+ :obj:`datetime`: wallclock time
450
+ """
451
+ if self._time_scale_factor is None or self._time_scale_factor <= 0:
452
+ return self.get_wallclock_time()
453
+ else:
454
+ return (
455
+ self._wallclock_epoch
456
+ + (time - self.get_simulation_epoch()) / self._time_scale_factor
457
+ )
458
+
459
+ def set_time_scale_factor(
460
+ self, time_scale_factor: float, simulation_epoch: datetime = None
461
+ ) -> None:
462
+ """
463
+ Sets the time scale factor in scenario seconds per wallclock second
464
+ (>1 is faster-than-real-time). Requires that the simulator is in EXECUTING mode.
465
+
466
+ Args:
467
+ time_scale_factor (float): number of scenario seconds per wallclock second
468
+ simulation_epoch (:obj:`datetime`): scenario time at which the time scale factor changes
469
+ """
470
+ if self._mode != Mode.EXECUTING:
471
+ raise RuntimeError("Can only change time scale factor while executing.")
472
+ self._next_time_scale_factor = time_scale_factor
473
+ if simulation_epoch is None:
474
+ self._time_scale_change_time = self._time
475
+ else:
476
+ self._time_scale_change_time = simulation_epoch
477
+
478
+ def set_end_time(self, end_time: datetime) -> None:
479
+ """
480
+ Sets the scenario end time. Requires that the simulator is in EXECUTING mode.
481
+
482
+ Args:
483
+ end_time (:obj:`datetime`): scenario end time
484
+ """
485
+ if self._mode != Mode.EXECUTING:
486
+ raise RuntimeError("Can only change scenario end time while executing.")
487
+ self.set_duration(end_time - self._init_time)
488
+
489
+ def set_duration(self, duration: timedelta) -> None:
490
+ """
491
+ Sets the scenario duration. Requires that the simulator is in EXECUTING mode.
492
+
493
+ Args:
494
+ duration (:obj:`timedelta`): scenario duration
495
+ """
496
+ if self._mode != Mode.EXECUTING:
497
+ raise RuntimeError("Can only change scenario duration while executing.")
498
+ self._next_duration = duration
499
+
500
+ def set_time_step(self, time_step: timedelta) -> None:
501
+ """
502
+ Set the scenario time step duration. Requires that the simulator is in EXECUTING mode.
503
+
504
+ Args:
505
+ time_step (:obj:`timedelta`): scenario time step duration
506
+ """
507
+ if self._mode != Mode.EXECUTING:
508
+ raise RuntimeError("Can only change scenario time step while executing.")
509
+ self._next_time_step = time_step
510
+
511
+ def set_wallclock_offset(self, wallclock_offset: timedelta) -> None:
512
+ """
513
+ Set the wallclock offset (difference between system clock and trusted wallclock source).
514
+ Requires that the simulator is in UNDEFINED, INITIALIZING, INITIALIZED, or TERMINATED mode.
515
+
516
+ Args:
517
+ wallclock_offset(:obj:`timedelta`): difference between system clock and trusted wallclock source
518
+ """
519
+ if self._mode == Mode.EXECUTING:
520
+ raise RuntimeError("Cannot set wallclock offset: simulator is executing")
521
+ elif self._mode == Mode.TERMINATING:
522
+ raise RuntimeError("Cannot set wallclock offset: simulator is terminating")
523
+ self._wallclock_offset = wallclock_offset
524
+
525
+ def terminate(self) -> None:
526
+ """
527
+ Terminates the scenario execution. Requires that the simulator is in EXECUTING mode.
528
+ """
529
+ if self._mode != Mode.EXECUTING:
530
+ raise RuntimeError("Cannot terminate: simulator is not executing.")
531
+ self._set_mode(Mode.TERMINATING)
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.4
2
+ Name: nost_tools
3
+ Version: 2.0.0
4
+ Summary: Tools for Novel Observing Strategies Testbed (NOS-T) Applications
5
+ Author-email: "Paul T. Grogan" <paul.grogan@asu.edu>, "Emmanuel M. Gonzalez" <emmanuelgonzalez@asu.edu>
6
+ License: BSD 3-Clause License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Development Status :: 4 - Beta
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: ntplib
14
+ Requires-Dist: numpy>=2
15
+ Requires-Dist: pandas>=2
16
+ Requires-Dist: pydantic<3,>=2
17
+ Requires-Dist: python-dateutil
18
+ Requires-Dist: pika>=1
19
+ Requires-Dist: python-dotenv
20
+ Requires-Dist: pyyaml
21
+ Requires-Dist: python-keycloak>=5
22
+ Provides-Extra: dev
23
+ Requires-Dist: black[jupyter]>=24.2; extra == "dev"
24
+ Requires-Dist: coverage; extra == "dev"
25
+ Requires-Dist: pylint; extra == "dev"
26
+ Requires-Dist: pylint-pydantic; extra == "dev"
27
+ Requires-Dist: pytest; extra == "dev"
28
+ Provides-Extra: examples
29
+ Requires-Dist: dash; extra == "examples"
30
+ Requires-Dist: dash-daq; extra == "examples"
31
+ Requires-Dist: flask; extra == "examples"
32
+ Requires-Dist: python-dotenv; extra == "examples"
33
+ Requires-Dist: scipy; extra == "examples"
34
+ Requires-Dist: seaborn; extra == "examples"
35
+ Requires-Dist: skyfield; extra == "examples"
36
+ Requires-Dist: matplotlib>=3; extra == "examples"
37
+ Requires-Dist: xarray>=2023; extra == "examples"
38
+ Requires-Dist: rioxarray>=0.13; extra == "examples"
39
+ Requires-Dist: geopandas>=0.12; extra == "examples"
40
+ Requires-Dist: netCDF4>=1.6; extra == "examples"
41
+ Requires-Dist: aiobotocore>=2.17; extra == "examples"
42
+ Requires-Dist: botocore>=1.35; extra == "examples"
43
+ Requires-Dist: boto3>=1.35; extra == "examples"
44
+ Requires-Dist: tatc<=3.4.3; extra == "examples"
45
+ Requires-Dist: s3fs>=2024.10.0; extra == "examples"
46
+ Requires-Dist: pulp>=2.9.0; extra == "examples"
47
+ Requires-Dist: h5netcdf>=1.0; extra == "examples"
48
+ Provides-Extra: docs
49
+ Requires-Dist: autodoc_pydantic>=2; extra == "docs"
50
+ Requires-Dist: docutils; extra == "docs"
51
+ Requires-Dist: readthedocs-sphinx-search; extra == "docs"
52
+ Requires-Dist: sphinx>=7; extra == "docs"
53
+ Requires-Dist: sphinx_rtd_theme; extra == "docs"
54
+ Requires-Dist: sphinx-copybutton; extra == "docs"
55
+ Requires-Dist: sphinx_design; extra == "docs"
56
+ Requires-Dist: myst-parser; extra == "docs"
57
+ Requires-Dist: sphinxcontrib-mermaid; extra == "docs"
58
+ Dynamic: license-file
59
+
60
+ # Novel Observing Strategies Testbed (NOS-T)
61
+
62
+ The Novel Observing Strategies Testbed (NOS-T) is a computational testbed for
63
+ maturing technologies related to the NASA Novel Observing Strategies thrust.
64
+
65
+ Documentation: https://nost-tools.readthedocs.io
66
+
67
+ ## NOS-T Tools Installation
68
+
69
+ NOS-T tools is a collection of tools for the Novel Observing Strategies Testbed
70
+ (NOS-T). Installing `nost-tools` as an editable Python library requires `pip`
71
+ version 23 or greater (install via `python -m pip install --upgrade pip`).
72
+
73
+ To install `nost-tools`, run the following command from the project root
74
+ directory:
75
+
76
+ ```
77
+ pip install -e .
78
+ ```
79
+
80
+ Alternatively, to install supplemental libraries required for examples, run
81
+
82
+ ```
83
+ pip install -e .[examples]
84
+ ```
85
+
86
+ ## Contact
87
+
88
+ Principal Investigator: Paul T. Grogan <paul.grogan@asu.edu>
89
+
90
+ ## Acknowledgements
91
+
92
+ This material is based on work supported, in whole or in part, by the U.S.
93
+ Department of Defense and National Aeronautics and Space Administration Earth
94
+ Science Technology Office (NASA ESTO) through the Systems Engineering Research
95
+ Center (SERC) under Contract No. W15QKN-18-D-0040. SERC is a federally funded
96
+ University Affiliated Research Center managed by Stevens Institute of
97
+ Technology. Any opinions, findings, and conclusions or recommendations
98
+ expressed in this material are those of the authors and do not necessarily
99
+ reflect the views of the United States Department of Defense.
100
+
101
+ This research has made use of NASA Goddard Science Managed Cloud Environment
102
+ (SMCE), which is a service of the Computational & Information Sciences and
103
+ Technology Office at the NASA Goddard Space Flight Center.
104
+
105
+ Current project team:
106
+
107
+ - PI: Paul T. Grogan <paul.grogan@asu.edu>
108
+ - Research Scientist: Emmanuel M. Gonzalez <emmanuelgonzalez@asu.edu>
109
+
110
+ Project alumni:
111
+
112
+ - Hayden Daly
113
+ - Matthew Brand
114
+ - Jerry Sellers
115
+ - Brian Chell
116
+ - Matthew LeVine
117
+ - Leigha Capra
118
+ - Theodore Sherman
119
+ - Alex Yucra Castaneda
@@ -0,0 +1,18 @@
1
+ nost_tools/__init__.py,sha256=Kv7x-YIcNd8voKjdvQYWSvFFYHAXUfBEg8ZAFnVepA8,902
2
+ nost_tools/application.py,sha256=uyZT8MIPKOHOEJ7dtUZPriiJFp067kP8EWIPb3oOzQs,33317
3
+ nost_tools/application_utils.py,sha256=3JprjyQuONZdMw6ENRRC6M0fhRu3E3_3aUyTqoSE6AU,9648
4
+ nost_tools/configuration.py,sha256=imbYqCi6br_lRY5jjbmQkOy08gyS3EJV4cVr8qh5MY4,11943
5
+ nost_tools/entity.py,sha256=oMDRML_N-HuPxQo9N2ipyTq-oPd9mKTDHq36VZnjkGA,2137
6
+ nost_tools/errors.py,sha256=KqJ8K_qGhFsucwbYBtJ2jjEyCTl7O2wq2Ti9A0EFPeM,358
7
+ nost_tools/logger_application.py,sha256=mZ45-Mu6lwwTGiijVqEDLAcK0Afy97v-q06MvyNOx-A,7409
8
+ nost_tools/managed_application.py,sha256=JO3EMtr0Mku-YqIA38LUPvFsLlENdC7hHvQyiVJ0uHc,11670
9
+ nost_tools/manager.py,sha256=FTPHaDWEIsF69-AKqnBz586WXeRzaFGwPU_E7coDiRY,20386
10
+ nost_tools/observer.py,sha256=COc9cc7AbEO8wHwiWIUeBN0ItNtoKaxJoRPF_ugGvIE,5700
11
+ nost_tools/publisher.py,sha256=9kFNAizw7SOD6_FZvKCZJRSpPFRacIvScKu0LbsD4Gc,5432
12
+ nost_tools/schemas.py,sha256=pwFY5i8CwHv88MCKWvVoh1R5FWaPKSXWSmzOZ8ZxJRk,14085
13
+ nost_tools/simulator.py,sha256=bs2a7eYXgkhnnwk-LyYEc_Qvst_ax1Tu29OhfYHmX6w,20722
14
+ nost_tools-2.0.0.dist-info/licenses/LICENSE,sha256=ffqdXgck_Ti3X7ObTTsG2D5HqjxhAKwFoa_jbYqbrqU,1568
15
+ nost_tools-2.0.0.dist-info/METADATA,sha256=NKlTfo-sagn4ZeLcm6IBRemAXr6XNmSh9aN23K9Yqjw,4422
16
+ nost_tools-2.0.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
17
+ nost_tools-2.0.0.dist-info/top_level.txt,sha256=LNChUgrv2-wiym12O0r61kY83COjTpTiJ2Ly1Ca58A8,11
18
+ nost_tools-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+