@@ -70,255 +70,3 @@ tissue1 = construct(Tissue, deepcopy([population, population2, population3])) #
70
70
tissue2 = construct (Tissue, deepcopy ([population2, population, population3]))
71
71
embryo = construct (Embryo, deepcopy ([tissue1, tissue2])) # Make an embryo from Tissues
72
72
```
73
-
74
- ## Human readable printing of the embryo structure
75
- ``` julia
76
- print_human_readable (embryo)
77
- # +|Tissue; |Tissue
78
- # +|Popula; |Popula; |Popula; +|Popula; |Popula; |Popula
79
- # +Cell; Cell; Cell; +Cell; Cell; Cell; +Cell; Cell; Cell; +Cell; Cell; Cell; +Cell; Cell; Cell; +Cell; Cell; Cell
80
-
81
- print_human_readable (embryo;NcharPerName= 2 )
82
- # +|Ti; |Ti
83
- # +|Po; |Po; |Po; +|Po; |Po; |Po
84
- # +Ce; Ce; Ce; +Ce; Ce; Ce; +Ce; Ce; Ce; +Ce; Ce; Ce; +Ce; Ce; Ce; +Ce; Ce; Ce
85
- ```
86
- Here, if the 'AbstractMultiScaleArrayLeaf's contain several fields, you can specify them with fields = [ field1,field2,...]
87
- ``` julia
88
- print_human_readable (embryo;NcharPerName= 2 ,fields= [:values ])
89
- # +|Ti; |Ti
90
- # +|Po; |Po; |Po; +|Po; |Po; |Po
91
- # +va: [1.0, 2.0, 3.0]; va: [3.0, 2.0, 5.0]; va: [4.0, 6.0]; +va: [1.0, 2.0, 3.0]; va: [3.0, 2.0, 5.0]; va: [4.0, 6.0]; +va: [1.0, 2.0, 3.0]; va: [3.0, 2.0, 5.0]; va: [4.0, 6.0]; +va: [1.0, 2.0, 3.0]; va: [3.0, 2.0, 5.0]; va: [4.0, 6.0]; +va: [1.0, 2.0, 3.0]; va: [3.0, 2.0, 5.0]; va: [4.0, 6.0]; +va: [1.0, 2.0, 3.0]; va: [3.0, 2.0, 5.0]; va: [4.0, 6.0]
92
- ```
93
- if your screen is small, then print a sub-part of the AbstractMultiScaleArray:
94
- ``` julia
95
- print_human_readable (embryo. nodes[1 ]. nodes[1 ];fields= [:values ])
96
- # +values: [1.0, 2.0, 3.0]; values: [3.0, 2.0, 5.0]; values: [4.0, 6.0]
97
- ```
98
-
99
- ## Indexing and Iteration
100
-
101
- The head node then acts as the king. It is designed to have functionality which
102
- mimics a vector in order for usage in DifferentialEquations or Optim. So for example
103
-
104
- ``` julia
105
- embryo[12 ]
106
- ```
107
-
108
- returns the "12th protein", counting by Embryo > Tissue > Population > Cell in order
109
- of the vectors. The linear indexing exists for every ` AbstractMultiScaleArray ` .
110
- These types act as full linear vectors, so standard operations do the sensical
111
- operations:
112
-
113
- ``` julia
114
- embryo[10 ] = 4.0 # changes protein concentration 10
115
- embryo[2 ,3 ,1 ] # Gives the 1st cell in the 3rd population of the second tissue
116
- embryo[:] # generates a vector of all of the protein concentrations
117
- eachindex (embryo) # generates an iterator for the indices
118
- ```
119
-
120
- Continuous models can thus be written at the protein level and will work seamlessly
121
- with DifferentialEquations or Optim which will treat it like a vector of protein concentrations.
122
- Using the iterators, note that we can get each cell population by looping through
123
- 2 levels below the top, so
124
-
125
- ``` julia
126
- for cell in level_iter (embryo,3 )
127
- # Do something with the cells!
128
- end
129
- ```
130
-
131
- or the multiple level iter, which is the one generally used in
132
- DifferentialEquations.jl functions:
133
-
134
- ``` julia
135
- for (cell, dcell) in LevelIter (3 ,embryo, dembryo)
136
- # If these are similar structures, `cell` and `dcell` are the similar parts
137
- cell_ode (dcell,cell,p,t)
138
- end
139
- ```
140
-
141
- ` LevelIterIdx ` can give the indices along with iteration:
142
-
143
- ``` julia
144
- for (cell, y, z) in LevelIterIdx (embryo, 3 )
145
- # cell = embryo[y:z]
146
- end
147
- ```
148
-
149
- However, the interesting behavior comes from event handling. Since ` embryo ` will be the
150
- "vector" for the differential equation or optimization problem, it will be the value
151
- passed to the event handling. MultiScaleArrays includes behavior for changing the
152
- structure. For example:
153
-
154
- ``` julia
155
- tissue3 = construct (Tissue, deepcopy ([population, population2]))
156
- add_node! (embryo, tissue3) # Adds a new tissue to the embryo
157
- remove_node! (embryo, 2 , 1 ) # Removes population 1 from tissue 2 of the embryo
158
- ```
159
-
160
- Combined with event handling, this allows for dynamic structures to be derived from
161
- low level behaviors.
162
-
163
- ## Heterogeneous Nodes via Tuples
164
-
165
- Note that tuples can be used as well. This allows for type-stable broadcasting with
166
- heterogeneous nodes. This could be useful for mixing types
167
- inside of the nodes. For example:
168
-
169
- ``` julia
170
- struct PlantSettings{T} x:: T end
171
- struct OrganParams{T} y:: T end
172
-
173
- struct Organ{B<: Number ,P} <: AbstractMultiScaleArrayLeaf{B}
174
- values:: Vector{B}
175
- name:: Symbol
176
- params:: P
177
- end
178
-
179
- struct Plant{B,S,N<: Tuple{Vararg{<:Organ{<:Number}}} } <: AbstractMultiScaleArray{B}
180
- nodes:: N
181
- values:: Vector{B}
182
- end_idxs:: Vector{Int}
183
- settings:: S
184
- end
185
-
186
- struct Community{B,N<: Tuple{Vararg{<:Plant{<:Number}}} } <: AbstractMultiScaleArray{B}
187
- nodes:: N
188
- values:: Vector{B}
189
- end_idxs:: Vector{Int}
190
- end
191
-
192
- mutable struct Scenario{B,N<: Tuple{Vararg{<:Community{<:Number}}} } <: AbstractMultiScaleArrayHead{B}
193
- nodes:: N
194
- values:: Vector{B}
195
- end_idxs:: Vector{Int}
196
- end
197
-
198
- organ1 = Organ ([1.1 ,2.1 ,3.1 ], :Shoot , OrganParams (:grows_up ))
199
- organ2 = Organ ([4.1 ,5.1 ,6.1 ], :Root , OrganParams (" grows down" ))
200
- organ3 = Organ ([1.2 ,2.2 ,3.2 ], :Shoot , OrganParams (true ))
201
- organ4 = Organ ([4.2 ,5.2 ,6.2 ], :Root , OrganParams (1 // 3 ))
202
- plant1 = construct (Plant, (deepcopy (organ1), deepcopy (organ2)), Float64[], PlantSettings (1 ))
203
- plant2 = construct (Plant, (deepcopy (organ3), deepcopy (organ4)), Float64[], PlantSettings (1.0 ))
204
- community = construct (Community, (deepcopy (plant1), deepcopy (plant2), ))
205
- scenario = construct (Scenario, (deepcopy (community),))
206
- ```
207
-
208
- (of course at the cost of mutability).
209
-
210
- ## Idea
211
-
212
- The idea behind MultiScaleArrays is simple. The ` *DiffEq ` solvers (OrdinaryDiffEq.jl,
213
- StochasticDiffEq.jl, DelayDiffEq.jl, etc.) and native optimization packages like
214
- Optim.jl in their efficient in-place form all work with any Julia-defined
215
- ` AbstractArray ` which has a linear index. Thus, to define our multiscale model,
216
- we develop a type which has an efficient linear index. One can think of representing
217
- cells with proteins as each being an array with values for each protein. The linear
218
- index of the multiscale model would be indexing through each protein of each cell.
219
- With proper index overloads, one can define a type such that ` a[i] ` does just that,
220
- and thus it will work in the differential equation solvers. MultiScaleArrays.jl
221
- takes that further by allowing one to recursively define an arbitrary ` n ` -level
222
- hierarchical model which has efficient indexing structures. The result is a type
223
- which models complex behavior, but the standard differential equation solvers will
224
- work directly and efficiently on this type, making it easy to develop novel models
225
- without having to re-develop advanced adaptive/stiff/stochastic/etc. solving
226
- techniques for each new model.
227
-
228
- ## Defining A MultiScaleModel: The Interface
229
-
230
- The required interface is as follows. Leaf types must extend AbstractMultiScaleArrayLeaf, the
231
- highest level of the model or the head extends MultiScaleModelHead, and all
232
- intermediate types extend AbstractMultiScaleArray. The leaf has an array ` values::Vector{B} ` .
233
- Each type above then contains three fields:
234
-
235
- - ` nodes::Vector{T} `
236
- - ` values::Vector{B} `
237
- - ` end_idxs::Vector{Int} `
238
-
239
- Note that the ordering of the fields matters.
240
- ` B ` is the ` BottomType ` , which has to be the same as the eltype for the array
241
- in the leaf types. ` T ` is another ` AbstractMultiScaleArray ` . Thus at each level,
242
- an` AbstractMultiScaleArray ` contains some information of its own (` values ` ), the
243
- next level down in the heirarchy (` nodes ` ), and caching for indices (` end_idxs ` ).
244
- You can add and use extra fields as you please, and even make the types immutable.
245
-
246
- ## The MultiScaleModel API
247
-
248
- The resulting type acts as an array. A leaf type ` l ` acts exactly as an array
249
- with ` l[i] == l.values[i] ` . Higher nodes also act as a linear array. If ` ln ` is level
250
- ` n ` in the heirarchy, then ` ln.nodes ` is the vector of level ` n-1 ` objects, and ` ln.values `
251
- are its "intrinsic values". There is an indexing scheme on ` ln ` , where:
252
-
253
- - ` ln[i,j,k] ` gets the ` k ` th ` n-3 ` object in the ` j ` th ` n-2 ` object in the ` i ` th level ` n-1 `
254
- object. Of course, this recurses for the whole hierarchy.
255
- - ` ln[i] ` provides a linear index through all ` .nodes ` and ` .values ` values in every lower
256
- level and ` ln.values ` itself.
257
-
258
- Thus ` typeof(ln) <: AbstractVector{B} ` where ` B ` is the eltype of its leaves and
259
- all ` .values ` 's.
260
-
261
- In addition, iterators are provided to make it easy to iterate through levels.
262
- For ` h ` being the head node, ` level_iter(h,n) ` iterates through all level objects
263
- ` n ` levels down from the top, while ` level_iter_idx(h,n) ` is an enumeration
264
- ` (node,y,z) ` where ` node ` are the ` n ` th from the head objects, with ` h[y:z] ` being
265
- the values it holds in the linear indexing.
266
-
267
- ### Extensions
268
-
269
- Note that this only showed the most basic MultiScaleArray. These types can be
270
- extended as one pleases. For example, we can change the definition of the cell
271
- to have:
272
-
273
- ``` julia
274
- struct Cell{B} <: AbstractMultiScaleArrayLeaf{B}
275
- values:: Vector{B}
276
- celltype:: Symbol
277
- end
278
- ```
279
-
280
- Note that the ordering of the fields matters here: the extra fields must come
281
- after the standard fields (so for a leaf it comes after ` values ` , for a standard
282
- multiscale array it would come after ` nodes,values,end_idxs ` ).
283
- Then we'd construct cells with
284
- ` cell3 = Cell([3.0; 2.0; 5.0], :BCell) ` , and can give it a cell type.
285
- This information is part of the call, so
286
-
287
- ``` julia
288
- for (cell, y, z) in level_iter_idx (embryo, 2 )
289
- f (t, cell, @view embryo[y: z])
290
- end
291
- ```
292
-
293
- can allow one to check the ` cell.celltype ` in ` f ` an apply a different ODE depending
294
- on the cell type. You can add fields however you want, so you can use them
295
- to name cells and track lineages.
296
-
297
- Showing the use of ` values ` , you just pass it to the constructor. Let's pass it an array
298
- of 3 values:
299
-
300
- ``` julia
301
- tissue = construct (Tissue, deepcopy ([population; population2]), [0.0 ; 0.0 ; 0.0 ])
302
- ```
303
-
304
- We can selectively apply some function on these ` values ` via:
305
-
306
- ``` julia
307
- for (tissue, y, z) in level_iter_idx (embryo, 1 )
308
- f (t, tissue, @view embryo[y: z])
309
- end
310
- ```
311
-
312
- and mutate ` tis.values ` in ` f ` . For example, we could have
313
-
314
- ``` julia
315
- function f (du, tissue:: Tissue , p, t)
316
- du .+ = randn (3 )
317
- end
318
- ```
319
-
320
- applies normal random numbers to the three values. We could use this to add to the
321
- model the fact that ` tissue.values[1:3] ` are the tissue's position, and ` f ` would then be
322
- adding Brownian motion.
323
-
324
- Of course, you can keep going and kind of do whatever you want. The power is yours!
0 commit comments