Skip to content

Commit

Permalink
Release 0.0.9
Browse files Browse the repository at this point in the history
  • Loading branch information
mattdailis committed Jul 15, 2024
1 parent 1f3e264 commit ab3ea94
Show file tree
Hide file tree
Showing 87 changed files with 910 additions and 1,038 deletions.
10 changes: 5 additions & 5 deletions docs-src/1_tutorials/getting-started/1-gettingstarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,13 @@ with using(model.data_model.recording_rate, rate):
With our effect model in place, we are done coding up the `collect_data` activity and the final result should look
something like this:

[//]: # (:::{doctest})
% :::{doctest})

[//]: # (>>> print("This parrot wouldn't voom if you put 3000 volts through it!"))
% >>> print("This parrot wouldn't voom if you put 3000 volts through it!"))

[//]: # (This parrot wouldn't voom if you put 3000 volts through it!!)
% This parrot wouldn't voom if you put 3000 volts through it!!)

[//]: # (:::)
% :::)

:::{testsetup}
import pymerlin
Expand Down Expand Up @@ -318,7 +318,7 @@ res = simulate(
Schedule.build(("00:00:01", Directive("collect_data", {"rate": 20.0, "duration": "00:00:01"}))),
"01:00:00"
)
if str(res) != "({'recording_rate': [ProfileSegment(extent=+00:00:01.0000.0, dynamics=0.0), ProfileSegment(extent=+00:00:01.0000.0, dynamics=20.0), ProfileSegment(extent=+00:59:58.0000.0, dynamics=0.0)]}, [], [])":
if str(res) != "({'recording_rate': [ProfileSegment(extent=+00:00:01.0000.0, dynamics=0.0), ProfileSegment(extent=+00:00:01.0000.0, dynamics=20.0), ProfileSegment(extent=+00:59:58.0000.0, dynamics=0.0)]}, [Span(type='collect_data', start=+00:00:01.0000.0, duration=+00:00:01.0000.0)], [])":
print(res) # This causes cleanup to fail
:::

Expand Down
43 changes: 22 additions & 21 deletions docs-src/1_tutorials/getting-started/3-enum-derived-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,29 @@ mission, the magnetometer is placed in a high rate collection mode and at other
mode. For our model, we want to be able to track the collection mode over time along with the associated data collection
rate of that mode.

The first thing we'll do to accomplish this is create a Java enumeration called `MagDataCollectionMode` that gives us
the list of available collection modes along with a mapping of those modes to data collection rates
using [enum fields](https://issac88.medium.com/java-enum-fields-methods-constructors-3a19256f58b). We will also add a
getter method to get the data rate based on the mode. Let's say that we have three modes, `OFF`, `LOW_RATE`,
and `HIGH_RATE` with values `0.0`, `500.0`, and `5000.0`, respectively. After coding this up, our enum should look like
this:
The first thing we'll do to accomplish this is create a python dictionary called `MagDataCollectionMode` that gives us
the list of available collection modes along with a mapping of those modes to data collection rates. Let's say that we
have three modes, `OFF`, `LOW_RATE`, and `HIGH_RATE` with values `0.0`, `500.0`, and `5000.0`, respectively. After
coding this up, our enum should look like this:

:::{testcode}
from enum import Enum

class MagDataCollectionMode(Enum):
OFF = 0.0 # kbps
LOW_RATE = 500.0 # kbps
HIGH_RATE = 5000.0 # kbps
MagDataCollectionMode = {
"OFF": 0.0, # kbps
"LOW_RATE": 500.0, # kbps
"HIGH_RATE": 5000.0 # kbps
}
:::

% TODO: consider using aenum to allow for duplicate right-hand-sides of these
assignments https://stackoverflow.com/a/35968057/15403349

With our enumeration built, we can now add a couple of new resources to our `DataModel` class. The first resource, which
we'll call `mag_data_mode`, will track the current data collection mode for the magnetometer. Declare this resource as a
discrete `cell` of type `MagDataCollectionMode` and then add the following lines of code to the constructor
to initialize the resource to `OFF` and register it with the UI.

```python
self.mag_data_mode = registrar.cell(discrete(MagDataCollectionMode.OFF));
self.mag_data_mode = registrar.cell("OFF")
registrar.resource("mag_data_mode", mag_data_mode.get)
```

Expand All @@ -46,19 +46,20 @@ deriving this value and don't intend to emit effects directly onto this resource
discrete `Resource` of type `Double` instead of a `cell`.

When we go to define this resource in the constructor, we need to tell the resource to get its value by mapping
the `mag_data_mode` to its corresponding rate. A special static method in the `DiscreteResourceMonad` class called `map()`
the `mag_data_mode` to its corresponding rate. A special static method in the `DiscreteResourceMonad` class
called `map()`
allows us to define a function that operates on the value of a resource to get a derived resource value. In this case,
that function is simply the getter function we added to the `MagDataCollectionMode`. The resulting definition and
registration code for `mag_data_rate` then becomes

```python
registrar.resource("mag_data_rate", lambda: MagDataCollectionMode.get_data_rate(mag_data_rate.get()));
self.mag_data_rate = self.mag_data_mode.map(lambda mode: MagDataCollectionMode[mode])
registrar.resource("mag_data_rate", self.mag_data_rate.get)
```

:::info

Instead of deriving a resource value from a function using `map()`, there are a number of static methods in
the `DiscreteResources` class, which you can use to `add()`, `multiply()`, `divide()`, etc. resources. For example, you
could have a `Total` resource that simple used `add()` to sum some resources together.

:::{info}
Instead of deriving a cell value from a function using `map()`, you can directly add or subtract cells, for example:
`````python
self.total_data_rate = self.mag_data_rate + self.recording_rate
````
:::
88 changes: 82 additions & 6 deletions docs-src/1_tutorials/getting-started/4-current-value.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,91 @@ class. For our case here, we want to get the current value of the `mag_data_rate
to the requested value, so we have to be a little careful about the order of operations within our effect model. The
resulting activity type and its effect model should look something like this:

```java
@Model.ActivityType()
def change_mag_mode(model, mode: MagDataCollectionMode = MagDataCollectionMode.LOW_RATE):
:::{testsetup}
from pymerlin import MissionModel, simulate, Schedule, Duration
from pymerlin._internal._decorators import Validation, Task
from pymerlin._internal._registrar import CellRef
from pymerlin._internal._schedule import Directive
from pymerlin.model_actions import delay, spawn


@MissionModel
class Model:
def __init__(self, registrar):
self.counter: CellRef = registrar.cell(0)
self.data_model = DataModel(registrar)
registrar.resource("counter", self.counter.get)


@Model.ActivityType
def increment_counter(model):
counter = model.counter
counter.set_value(counter.get() + 1)


class DataModel:
def __init__(self, registrar):
self.recording_rate = registrar.cell(0)
registrar.resource("recording_rate", self.recording_rate.get)

self.ssr_volume_simple = registrar.cell(0.0)
registrar.resource("ssr_volume_simple", self.ssr_volume_simple.get)

self.ssr_volume_sampled = registrar.cell(0.0)
registrar.resource("ssr_volume_sampled", self.ssr_volume_sampled.get)

spawn(integrate_sampled_ssr(self))

self.mag_data_mode = registrar.cell("OFF")
registrar.resource("mag_data_mode", self.mag_data_mode.get)

self.mag_data_rate = self.mag_data_mode.map(lambda mode: MagDataCollectionMode[mode])
registrar.resource("mag_data_rate", self.mag_data_rate.get)

self.total_data_rate = self.mag_data_rate + self.recording_rate
registrar.resource("total_data_rate", self.total_data_rate.get)


INTEGRATION_SAMPLE_INTERVAL = Duration.of(60, Duration.SECONDS)


@Task
def integrate_sampled_ssr(data_model: DataModel):
while True:
delay(INTEGRATION_SAMPLE_INTERVAL)
current_recording_rate = data_model.recording_rate.get()
data_model.ssr_volume_sampled += (
current_recording_rate
* INTEGRATION_SAMPLE_INTERVAL.to_number_in(Duration.SECONDS)
/ 1000.0) # Mbit -> Gbit


@Model.ActivityType
@Validation(lambda rate: rate < 100.0, "Collection rate is beyond buffer limit of 100.0 Mbps")
def collect_data(model, rate=0.0, duration="01:00:00"):
duration = Duration.from_string(duration)
model.data_model.recording_rate += rate
delay(duration)
model.data_model.ssr_volume_simple += rate * duration.to_number_in(Duration.SECONDS) / 1000.0
model.data_model.recording_rate -= rate


MagDataCollectionMode = {
"OFF": 0.0, # kbps
"LOW_RATE": 500.0, # kbps
"HIGH_RATE": 5000.0 # kbps
}
:::

:::{testcode}
@Model.ActivityType
def change_mag_mode(model, mode="LOW_RATE"):
current_rate = model.data_model.mag_data_rate.get()
new_rate = mode.get_data_rate()
// Divide by 10^3 for kbps->Mbps conversion
new_rate = MagDataCollectionMode[mode]
# Divide by 10^3 for kbps->Mbps conversion
model.data_model.recording_rate += (new_rate-current_rate)/1.0e3
model.data_model.mag_data_mode.set(mode)
```
:::

Looking at our new activity definition, you can see how we use the `increase()` effect on `recording_rate` to "increase"
the data rate based on the net data change from the old rate. You may also notice a magic number where we do a unit
Expand Down
12 changes: 6 additions & 6 deletions docs-src/1_tutorials/getting-started/6-integrating-rate.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ implementation of this function looks like this:

```python
@Task
def integrate_sampled_ssr():
while (True):
def integrate_sampled_ssr(data_model: DataModel):
while True:
delay(INTEGRATION_SAMPLE_INTERVAL)
current_recording_rate = currentValue(recording_rate)
ssr_volume_sampled += (
current_recording_rate
* INTEGRATION_SAMPLE_INTERVAL.to_number_in(Duration.SECONDS)
current_recording_rate = data_model.recording_rate.get()
data_model.ssr_volume_sampled += (
current_recording_rate
* INTEGRATION_SAMPLE_INTERVAL.to_number_in(Duration.SECONDS)
/ 1000.0) # Mbit -> Gbit
```

Expand Down
Loading

0 comments on commit ab3ea94

Please sign in to comment.