Skip to content

Add Schemas for Stock and Flow - #35

Open
jpfairbanks wants to merge 11 commits into
mainfrom
jpfairbanks-patch-1
Open

jpfairbanks wants to merge 11 commits into
mainfrom
jpfairbanks-patch-1

Conversation

@jpfairbanks

Copy link
Copy Markdown
Member

Not entirely sure what some of the tables are, need to update once I talk to @Xiaoyan-Li.

not entirely sure what some of the tables are, need to update
@codecov

codecov Bot commented Sep 27, 2023

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (53b161d) 87.07% compared to head (cad1534) 87.07%.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #35   +/-   ##
=======================================
  Coverage   87.07%   87.07%           
=======================================
  Files           6        6           
  Lines         325      325           
  Branches       54       54           
=======================================
  Hits          283      283           
  Misses         36       36           
  Partials        6        6           
Files Coverage Δ
src/acsets/acsets.py 87.55% <ø> (ø)

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jpfairbanks

Copy link
Copy Markdown
Member Author

Here is the example

julia>  sir
StockAndFlowpUntyped{Symbol, String} {Flow:2, Stock:3, Link:3, Name:0, FuncFlow:0}
┌──────┬───┬───┬──────────────┬──────────────────┐
│ Flow │ u │ d │        fname │               ϕf │
├──────┼───┼───┼──────────────┼──────────────────┤
│    1 │ 1 │ 2 │ NewIncidence │ p.cβ*u.S*u.I/p.N │
│    2 │ 2 │ 3 │  NewRecovery │       (u.I/p.tr) │
└──────┴───┴───┴──────────────┴──────────────────┘
┌───────┬───────┐
│ Stock │ sname │
├───────┼───────┤
│     1 │     S │
│     2 │     I │
│     3 │     R │
└───────┴───────┘
┌──────┬───┬───┐
│ Link │ s │ t │
├──────┼───┼───┤
│    1 │ 1 │ 1 │
│    2 │ 2 │ 1 │
│    3 │ 2 │ 2 │
└──────┴───┴───┘

And in JSON

{
    "Flow": [
        {
            "_id": 1,
            "u": 1,
            "d": 2,
            "fname": "NewIncidence",
            "ϕf": "p.cβ*u.S*u.I/p.N"
        },
        {
            "_id": 2,
            "u": 2,
            "d": 3,
            "fname": "NewRecovery",
            "ϕf": "(u.I/p.tr)"
        }
    ],
    "Stock": [
        {
            "_id": 1,
            "sname": "S"
        },
        {
            "_id": 2,
            "sname": "I"
        },
        {
            "_id": 3,
            "sname": "R"
        }
    ],
    "Link": [
        {
            "_id": 1,
            "s": 1,
            "t": 1
        },
        {
            "_id": 2,
            "s": 2,
            "t": 1
        },
        {
            "_id": 3,
            "s": 2,
            "t": 2
        }
    ],
    "Name": [
    ],
    "FuncFlow": [
    ]
}

@cthoyt

cthoyt commented Sep 28, 2023

Copy link
Copy Markdown
Contributor

I've done the following:

  1. Created the JSON schema objects for the two catlab schemas for stockflows
  2. Added the JSON example
  3. Added a testing framework to make sure the JSON examples are valid under the schema

Unfortunately, it's never so easy. The validator is giving the following feedback:

jsonschema.exceptions.ValidationError: 'SV' is a required property

I guess this means we need to explicitly specify all of the parts of the schema, even if they're empty lists. It might be possible to make all fields default to an empty list but I don't want to conflate two different lines of work in this PR

@cthoyt

cthoyt commented Sep 28, 2023

Copy link
Copy Markdown
Contributor

I was able to update the example to fix the names and include the missing fields - note @jpfairbanks's example had "Stock" and "Flow" as the keys but the schema was looking for "S" and "F". I changed them manually to make it work, but this might indicate there's something else funny going on

Comment thread src/acsets/acsets.py
ob.name,
**{
prop.name: (Union[self.valtype(prop), None], None)
prop.name: (Union[self.valtype(prop), None], Field(title=ob.title, description=ob.description))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it the case that all elements will always be lists? If that's the case, we can use this line to introduce default empty lists.

"name": "V"
},
{
"title": "Linkage Variable?",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some question marks here we probably want to address

@jpfairbanks

Copy link
Copy Markdown
Member Author

I think the two example I posted is an example of the primitive schema which uses Stock and Flow for the table names. In the extended schema the tables are abbreviated (because there are so many more of them).

The example was right, but it was for the StockFlowp.json schema. I didn't post the corresponding example for the StockFlow.json schema.

@cthoyt

cthoyt commented Sep 28, 2023

Copy link
Copy Markdown
Contributor

Oh I see. Then we should merge them together and denote which parts are optional

@jpfairbanks

Copy link
Copy Markdown
Member Author

"Optional Tables" doesn't really fit with Catlab's model of schemas. Tables can be empty, but that doesn't make the table optional. Catlab has a notion of schema inheritance where a schema can extend another schema by adding tables or fields.

I guess the longer term right thing to do is make StockFlow.jl use that inheritance mechanism, but that would require updating library code in StockFlow and user code. It would be a breaking change to the API of that repo, because it requires changing the field names.

I think keeping them separate for now and just validating models against the correct schema is the easier move at this point.

@cthoyt

cthoyt commented Sep 28, 2023

Copy link
Copy Markdown
Contributor

Okay so then my understanding is that you have some fields but don’t necessarily have to use all of them. So is it correct that one of these schemas is a proper superset of the other? Then we can just keep the more expressive one, right? We won’t need two AMRs

@jpfairbanks

Copy link
Copy Markdown
Member Author

Okay so then my understanding is that you have some fields but don’t necessarily have to use all of them.

Well that depends on how you interpret "use". If a database instance X conforms to a schema S and S has a table T, the instance X an have an empty T table. I would say that the database is using table T, but that it is just using it to store 0 rows. An empty list is a list and is distinct from null.

The broader Catlab view is that relationships between schemas also introduces relationships between instances over those schemas. For example, If S\T is the schema you get by removing table T from schema S, then any instance X over S can be converted to an instance X' over S\T defined as "take all the tables in X, except the T table". So if X has no rows in its table T, then this operation is lossless. This operation has an adjoint, which is to take any instance over S\T to an instance over S by adding an empty T table. This view is called Functorial Data Migration

So is it correct that one of these schemas is a proper superset of the other?

The way that it is implemented, neither schema is a proper superset of the other for two reasons.

  1. The tables that represent stocks and flows are given different names. In the primitive schema, they are called Stock and Flow and in the full-fledged schema they are called S and F. I don't know why it was implemented that way. Maybe because there are many tables in the big schema and the author wanted to use short names for consistency. We can use functorial data migration to migrate data between the two schemas. So this kind of renaming doesn't really bother Catlab users. If we renamed the tables in the upstream repo, then we could use the inheritance mechanism in catlab, but that would be a breaking change. This is SWE-solvable and the more minor problem.
  2. The StockFlow schema does not have a field for ϕf::Attr(Flow, FuncFlow). This is because the flow functions are represented as a compute graph, and not an attribute holding a primitive function (this is the real problem see below).

Then we can just keep the more expressive one, right?

There is a key conceptual difference between them. The StockFlowp schema (the p stands for primitive) treats the computation of the flow functions as a primitive symbolic function. The computation of the flow rates is given by evaluating a black-box that takes in the current state of some stocks and computes the flow per unit time. In the existing implementation, this function is stored as a julia function that gets called in the solver. I have hot-swapped some stuff to store them as strings for this exercise in this file. But conceptually, the flow functions are just functions that you can invoke to get the flow rate.

In the StockFlow schema, the computation of that function is realized as a compute graph, that is part of the model. That is why there are so many more tables with cryptic names like LSV. It is storing a compute graph of these functions. The primitive schema is simpler and thus its category of models is easier to study mathematically. The original paper describing this repo they focus on the primitive models, additional work uses the other form with less developed theory (because they are more complicated mathematically).

We won’t need two AMRs

I think that we need both formats. If you want to store your flow functions as a black box primitive function, you should use StockFlowp and if you want to include the entire compute graph in the model, then you should use StockFlow.

If you want to start with one format at first, then you should do the StockFlowp and we can pass flow functions around symbolically. Once people have made progress on that, we can introduce the idea of storing the flow functions as compute graphs in the StockFlow schema.

{
"title": "Flow Function",
"description": null,
"name": "ϕf",

@mattprintz mattprintz Sep 28, 2023

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any potential integration issues with using a less-common unicode character as a JSON key?
I'm also wondering if this might be especially an issue since there are two unicode glyphs for phi, U+03C6 and U+03D5.
While this is definitely the convention in Julia, perhaps for the interchange format it could be something that is potentially more universally accessible such as ffunc or flowfunc?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should test the JSON libraries. This would be a breaking change to StockFlow.jl so let's find an upstream bug before we patch it?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I didn't realize that this would be a breaking change in the other library.

According to the JSON spec, it should be fine, and it obviously works in Python and JS, so I'm happy to assume that everything conforms to the spec until proven otherwise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the SF.jl library already exists and I was trying to get this in to py-acsets with the minimal code change possible. So far we haven't made any other changes to SF.jl.

@jpfairbanks

Copy link
Copy Markdown
Member Author

I got some feedback from the StockFlow.jl team on the names for the full-fledged schema AlgebraicJulia/StockFlow.jl#92 (comment).

A nice hand drawn schema with longer names.

@jpfairbanks

Copy link
Copy Markdown
Member Author

Ok, digging into the full-fledged stock flow theory, I definitely think we should just start with StockFlowp at this point.

@liunelson

Copy link
Copy Markdown

Hey all, I'd like to jump into this conversation to express some comments about the StockFlow(p) schema.

A minor issue is that the proposed schema deviates from some of the conventions specified in the Model Representation schemas:

  • _id instead of id
  • _id is an integer that is reused elsewhere, instead of an unique string
  • sname, fname instead of name
{
  "Stock": [
    {
      "id": "S",
      "name": "Susceptible"
    },
    {
      "id": "I",
      "name": "Infected"
    },
    {
      "id": "R",
      "name": "Recovered"
    }
  ],
  "Flow": [
    {
      "id": "NewIncidence",
      "u": "S",
      "d": "I",
      "name": "NewIncidence",
      "ϕf": "p.β * u.S * u.I / p.N"
    },
    {
      "id": "NewRecovery",
      "u": "I",
      "d": "R",
      "name": "NewRecovery",
      "ϕf": "(u.I/p.tr)"
    }
  ],
  "Link": [
      {
        "id": "1",
        "s": "S",
        "t": "NewIncidence"
      }, ...
  ]
}

Also, is the intention to have a section (semantics?) that lists and stores the parameter values (or references to files)?

  "semantics": {
    "ode": {
      "rates": [
        {
          "target": "NewIncidence",
          "expression": "β * S * I / N",
          "expression_mathml": ...
        }, ...
      ],
      "initials": [
        {
          "target": "S",
          "expression": "S0",
          "expression_mathml": "<ci>S0</ci>"
        }, ...
      ],
      "parameters": [
        {
          "id": "β",
          "name": "β",
          "description": "Infection rate",
          "units": ...,
          "value": 2.7e-7,
          "distribution": ...
        }, ...
      ], ...

Otherwise, might the above points be addressed within the Model-Representation schema and outside of the AlgebraicJulia py-acsets schema?

@cthoyt cthoyt mentioned this pull request Oct 12, 2023
@olynch

olynch commented Nov 7, 2023

Copy link
Copy Markdown
Member

I'm happy to merge this once it's been rebased; I shouldn't have let it languish so long.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants