Skip to content

Commit

Permalink
docs; fixes #2, #10, #11
Browse files Browse the repository at this point in the history
  • Loading branch information
mhinsch committed Dec 5, 2024
1 parent 3920fbb commit 9bbc0c4
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 11 deletions.
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

[![CI](https://github.com/mhinsch/MiniObserve.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/mhinsch/MiniObserve.jl/actions/workflows/ci.yml) [![](https://img.shields.io/badge/docs-stable-blue.svg)](http://mhinsch.github.io/MiniObserve.jl/dev)

Minimalist (and minimally intrusive) macro set for extracting information from complex objects.
Minimalist (and minimally intrusive) macro set for extracting information from complex objects, e.g. simulations.

Given a complex object `MiniObserve` lets you generate functions to extract and print information from that object by means of a simple declarative interface. It can for example be used to extract information from a simulation model at each time step and write that information to a file.
Given a number of complicated data structures `MiniObserve` lets you generate functions to extract and print information from these objects by means of a simple, near declarative interface. It can for example be used to extract information from a simulation model at each time step and write that information to a file.

Using MiniObserve has several advantages over hand-written analysis code:
* a concise declarative interface that puts all information in one place
Expand All @@ -18,7 +18,7 @@ Using MiniObserve has several advantages over hand-written analysis code:
Most of the heavy lifting is done by the `@observe` macro:

```Julia
@observe(statstype, model [, user_arg1...], declarations)
@observe(statstype, arg1 [, arg2...], declarations)
```

It will generate a custom data type to hold the desired information and an overload of the `observe` function that - given a model object - will calculate the information and return a data object.
Expand All @@ -32,7 +32,7 @@ As a simple example, let us assume we have the following `@observe` declaration:
@record "time" model.time
@record "N" Int length(model.population)

@for ind in model.population begin
for ind in model.population
@stat("capital", MaxMinAcc{Float64}, MeanVarAcc{FloatT}) <| ind.capital
@stat("n_alone", CountAcc) <| has_neighbours(ind)
end
Expand Down Expand Up @@ -69,11 +69,14 @@ print_header(stdout, Data)
log_results(stdout, data)
```

## Additional parameters
## User code

Note that `@observe` can take arbitrarily many parameters. Of these only the first two (the name of the data type and a model) and the last (the declaration itself) are required. All parameters between the second and the last are passed through unchanged and can for example be used to print additional information that is not part of the model object.
Fundamentally `@observe`'s operation is very simple, which makes it very flexible, but also easy to break. Any code in the declaration block will be copied into the `observe` function verbatim. The only changes `@observe` applies are:
* At the beginning of the function a number of local variables containing the single analysis results are created.
* Every occurence of `@record` and `@stat` is replaced with the appropriate code to store the result or add it to an accumulator object (see below), respectively.
* At the end of the function the constructor of the analysis data type is called, collating all results into one data structure.

In fact there is nothing preventing a user from using `@observe` on several complex objects at the same time by for example passing two different model objects to `observe`.
`@observe` does not perform any further sanity checks on code outside of the "pseudo-macros", so it is the user's responsibility to make sure not to break anything (it is for example a very bad idea to add a return statement to the analysis code).

## Statistics

Expand All @@ -82,7 +85,7 @@ An important part of MiniObserve is the ability to analyse collections of items
In the example above this is used in the expression

```Julia
@for ind in model.population begin
for ind in model.population
@stat("capital", MaxMinAcc{Float64}, MeanVarAcc{FloatT}) <| ind.capital
@stat("n_alone", CountAcc) <| has_neighbours(ind)
end
Expand Down
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ makedocs(sitename="MiniObserve.jl",
warn_outdated = true,
collapselevel = 1
),
modules = [Observation, StatsAccumulator],
modules = [Observation, StatsAccumulatorBase, StatsAccumulator],
pages=["Home" => "index.md",
"Readme" => "README.md",
"Observation" => "obs.md",
Expand Down
10 changes: 8 additions & 2 deletions src/Observation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,11 @@ end
Generate a full analysis and logging suite for a set of data structures.
Given a declaration
Observe expects a (new) type name, a number of user arguments and a block of declarations. It will generate a function `observe` that takes the user arguments and returns an instance of the data type. The declaration block will be copied verbatim to the body of the `observe` function, but all occurences of the "pseudo-macros" `@record` and `@stat` will be replaced with corresponding analysis code.
The newly defined result data type will contain properties for all calculated results.
So, given a declaration
```Julia
@observe Data model stat1 stat2 begin
Expand Down Expand Up @@ -153,7 +157,9 @@ struct Data
end
```
The macro will also create a method for `observe(::Type{Data), model...)` that will perform the required calculations and returns a `Data` object. Use `print_header` to print a header for the generated type to an output and `log_results` to print the content of a data object.
The macro will also create a method `observe(::Type{Data), model, stat1, stat2)` that will perform the required calculations and returns a `Data` object.
Use `print_header` to print a header for the generated type to an output and `log_results` to print the content of a data object.
"""
macro observe(tname, args_and_decl...)
observe_syntax = "@observe <type name> <user arg> [<user args> ...] <declaration block>"
Expand Down

0 comments on commit 9bbc0c4

Please sign in to comment.