Elements
Elements of systems: entities that carry states, and groups of identical elements.
BaseElement(label, visual=None)
Bases: BasePart[BaseSpace, SimpleNamespace]
Base of every element, a part of a system that holds states.
A subclass takes its states and parameters as arguments of its own __init__, annotated with Annotated and the
spec StateSpec or ParameterSpec, and stores each under its own name, as in scikit-learn. A subclass
drawn in its own way gives visual a default in its signature. It returns its laws from _get_laws, which every
subclass implements, returning an empty list when the element has none.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the element, unique among the parts of its system. |
required |
visual
|
VisualSpec | None
|
How the element is drawn, or |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
default_visual_ |
The visual of the element when it sets none. |
|
laws_ |
Sequence[BasePart]
|
The laws the element contributes for the namespace of its last run. |
Examples:
>>> from typing import Annotated
>>> from skcomplex.core.components.elements._base import BaseElement
>>> from skcomplex.core.specs import ParameterSpec, StateSpec
>>> from skcomplex.core.specs import VisualSpec
>>> class PopulationElement(BaseElement):
... def __init__(
... self,
... label,
... n: Annotated[float, StateSpec()] = 1.0,
... r: Annotated[float, ParameterSpec()] = 0.5,
... visual=VisualSpec('line'),
... ):
... super().__init__(label, visual=visual)
... self.n = n
... self.r = r
...
... def _get_laws(self, namespace):
... return []
>>> PopulationElement('rabbits', n=40).get_params(deep=False)
{'label': 'rabbits', 'n': 40, 'r': 0.5, 'visual': VisualSpec(kind='line', options={})}
Source code in src/skcomplex/core/components/elements/_base.py
70 71 72 | |
get_states()
Return the states the element carries with their specs, those of its class and those it declares from data.
Returns:
| Name | Type | Description |
|---|---|---|
states |
dict[str, StateSpec]
|
A mapping from the name of every state to its spec. |
Examples:
>>> from typing import Annotated
>>> from skcomplex.core.components.elements import Element
>>> from skcomplex.core.specs import StateSpec
>>> class TankElement(Element):
... def __init__(self, label, level: Annotated[float, StateSpec()] = 1.0):
... super().__init__(label)
... self.level = level
>>> list(TankElement('tank').get_states())
['level']
Source code in src/skcomplex/core/components/elements/_base.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
CompartmentElement(label, visual=None, *, n=0.0)
Bases: Element
Compartment, the amount of units in one named state, as a count or a fraction.
The label of the element names the compartment, such as 'A', 'B', or 'source'. A compartment has no laws
of its own, and reactions and flows between compartments move its units.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the compartment, unique among the parts of its system. |
required |
visual
|
VisualSpec | None
|
How the element is drawn, or |
None
|
n
|
Annotated[float, StateSpec(domain=closed_open(0, oo))]
|
The initial amount, a nonnegative count or fraction. |
0.0
|
Attributes:
| Name | Type | Description |
|---|---|---|
default_visual_ |
The visual of the element when it sets none. |
Examples:
>>> from skcomplex.core.components.elements import CompartmentElement
>>> from skcomplex.core.components.system import System
>>> from skcomplex.core.engine.runtime import Simulation
>>> system = System(elements=[CompartmentElement('A', n=990), CompartmentElement('B', n=10)])
>>> Simulation(system, dt=1.0).prepare().run(until=2).trajectory_.values('A.n').tolist()
[990.0, 990.0, 990.0]
Source code in src/skcomplex/core/components/elements/_compartment.py
44 45 46 47 48 49 50 51 52 | |
DeclaredElement(label, visual=None, *, variables=None)
Bases: Element
Element whose states and parameters are data, for a system that knows them only when it builds the element.
The compiler treats a declared variable as it treats an annotated argument of the constructor: the spec gives the
declaration and the value gives the default. A law reads a declared variable from its namespace, and get_value
reads its value from the element. A subclass that derives its variables from other arguments overrides
_declare_variables, and variables then holds the variables that replace the derived ones. The mappings the
constructor takes are read as immutable, so set_params changes a variable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the element, unique among the parts of its system. |
required |
visual
|
VisualSpec | None
|
How the element is drawn, or |
None
|
variables
|
Mapping[str, DeclaredVariableSpec] | None
|
The declared states and parameters by name, or |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
default_visual_ |
The visual of the element when it sets none. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
A name is not an identifier or is an argument of the constructor, raised by |
Examples:
>>> from skcomplex.core.components.elements import DeclaredElement
>>> from skcomplex.core.specs import DeclaredVariableSpec, ParameterSpec, StateSpec
>>> variables = {'x': DeclaredVariableSpec(1.0, StateSpec()), 'r': DeclaredVariableSpec(0.5, ParameterSpec())}
>>> element = DeclaredElement('cell', variables=variables)
>>> element.get_value('x'), element.set_params(r=0.25).get_value('r')
(1.0, 0.25)
Source code in src/skcomplex/core/components/elements/_declared.py
52 53 54 55 56 57 58 59 60 | |
get_value(name)
Return the value of a declared state or parameter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the variable. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
value |
object
|
The value. |
Examples:
>>> from skcomplex.core.components.elements import DeclaredElement
>>> from skcomplex.core.specs import DeclaredVariableSpec, StateSpec
>>> DeclaredElement('cell', variables={'x': DeclaredVariableSpec(1.0, StateSpec())}).get_value('x')
1.0
Source code in src/skcomplex/core/components/elements/_declared.py
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | |
set_params(**params)
Set arguments of the constructor and values of declared variables, which keep their specs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**params
|
object
|
The new values by name. |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
element |
Self
|
The element. |
Examples:
>>> from skcomplex.core.components.elements import DeclaredElement
>>> from skcomplex.core.specs import DeclaredVariableSpec, StateSpec
>>> element = DeclaredElement('cell', variables={'x': DeclaredVariableSpec(1.0, StateSpec())})
>>> element.set_params(label='other', x=2.0).get_value('x')
2.0
Source code in src/skcomplex/core/components/elements/_declared.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
DensityElement(label, visual=None, *, f=0.0)
Bases: Element
Value of a density at one class, a member of a distribution over the classes.
A distribution is a group of densities on the sites of a lattice of classes, one member per class, and encounter, proliferation, and destruction laws change it. A density has no laws of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the density, unique among the parts of its system. |
required |
visual
|
VisualSpec | None
|
How the element is drawn, or |
None
|
f
|
Annotated[float, StateSpec(domain=closed_open(0, oo))]
|
The initial density, a nonnegative number. |
0.0
|
Attributes:
| Name | Type | Description |
|---|---|---|
default_visual_ |
The visual of the element when it sets none. |
Examples:
>>> from skcomplex.core.components.elements import DensityElement, GroupElement
>>> from skcomplex.core.components.spaces import LatticeSpace
>>> classes = LatticeSpace(5, boundary='fixed')
>>> group = GroupElement('distribution', element=DensityElement, space=classes, on='sites', values={'f': 0.2})
>>> group.prepare().size_
5
Source code in src/skcomplex/core/components/elements/_density.py
44 45 46 47 48 49 50 51 52 | |
Element(label, visual=None)
Bases: BaseElement
Base class of entities that carry state, such as particles, populations, agents, or firms.
A subclass takes its states, its parameters, and visual as arguments of its own __init__, annotating each state
with a StateSpec and each parameter with a ParameterSpec inside Annotated, and returns its intrinsic dynamics
from _get_laws, which returns no laws by default. A subclass drawn in its own way gives visual a default in its
signature.
An element whose laws have the agent trait, such as rules, alone or in a group, is an agent: under discrete time it
runs on the agent stepper, whose rules receive every member as an agent with its states as attributes. Its states
then hold any value, not only numbers. A member is removed only when its states tagged with a quantity are settled
to zero, and its states and parameters cannot take the names of the agent view, such as location or alive, or
of the attributes Mesa reads, such as position or system.
A subclass that connects to other elements declares its ports in the class variable ports, a mapping from the name
of a port to its ConnectorSpec, and takes one algebraic state per variable of a port, named <port>_<variable>.
A flow is positive into the element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the element, unique among the parts of its system. |
required |
visual
|
VisualSpec | None
|
How the element is drawn, or |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
default_visual_ |
The visual of the element when it sets none. |
Examples:
>>> from typing import Annotated
>>> from skcomplex.core.components.elements import Element
>>> from skcomplex.core.specs import StateSpec
>>> from skcomplex.core.specs import VisualSpec
>>> from sage.all__sagemath_symbolics import oo
>>> from sage.sets.real_set import RealSet
>>> class PopulationElement(Element):
... def __init__(
... self,
... label,
... n: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
... visual=VisualSpec('line'),
... ):
... super().__init__(label, visual=visual)
... self.n = n
>>> PopulationElement('rabbits', n=40)
PopulationElement(label='rabbits', n=40)
Source code in src/skcomplex/core/components/elements/_base.py
70 71 72 | |
FieldElement(label, *, element, space, boundary=None, values=None, visual=None, discretization='finite-difference', symmetry=None)
Bases: LatticeElement
Group of members of one element type at the sites of a lattice, the nodes of a network, or a mesh.
The states of the members are fields, with one value per site, and the laws of the element type are written for
one site, with laplacian, gradient, divergence, and advection reading the neighbouring sites. Every state
takes a boundary condition at the edges of a lattice: 'periodic', 'zero-flux', or ('fixed', value), where
the value can be a function of the node coordinates on a mesh.
The discretization turns the operators into numbers. 'finite-difference' uses stencils on lattices and the graph
Laplacian on networks. 'spectral' multiplies Fourier modes on periodic lattices, with the two-thirds rule
removing aliasing. 'finite-volume' updates cell averages by the differences of the FluxLaws of the states.
'finite-element' assembles stiffness and mass matrices on a MeshSpace.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the field. |
required |
element
|
type[BaseElement]
|
The element type of the members. |
required |
space
|
LatticeSpace | NetworkSpace | MeshSpace
|
The lattice, the network, or the mesh. |
required |
boundary
|
Mapping[str, object] | None
|
A mapping from state name to its boundary condition, which overrides the edges of the lattice. The value of
|
None
|
values
|
Mapping[str, object] | None
|
Arrays of the lattice shape, or with one value per network node in node order, or shared values. |
None
|
visual
|
VisualSpec | None
|
How the field is drawn, or |
None
|
discretization
|
str
|
|
'finite-difference'
|
symmetry
|
Mapping[str, Sequence[object]] | None
|
A mapping from state name to its parity along every axis of a periodic lattice, |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
default_visual_ |
An image on a lattice, a colored mesh on a mesh, and on a network the visual of a member, or the sites of the network when the member sets none. |
|
size_ |
The number of sites of the space. |
|
values_ |
The values with one row per site, or one value shared by every site. |
Examples:
>>> from typing import Annotated
>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.components.elements import Element, FieldElement
>>> from skcomplex.core.components.laws import RateLaw
>>> from skcomplex.core.components.laws.utils import laplacian
>>> from skcomplex.core.components.spaces import LatticeSpace
>>> from skcomplex.core.specs import ParameterSpec, StateSpec
>>> class HeatElement(Element):
... def __init__(
... self, label, u: Annotated[float, StateSpec()] = 0.0, D: Annotated[float, ParameterSpec()] = 1.0
... ):
... super().__init__(label)
... self.u = u
... self.D = D
...
... def _get_laws(self, namespace):
... return [RateLaw(namespace.u, namespace.D * laplacian(namespace.u))]
>>> space = LatticeSpace((10, 20))
>>> plate = FieldElement('plate', element=HeatElement, space=space, boundary={'u': 'zero-flux'})
>>> plate.prepare().size_, plate.default_visual_.kind
(200, 'image')
Source code in src/skcomplex/core/components/elements/_field.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
GroupElement(label, *, element, size=None, values=None, visual=None, space=None, variable=False, on=None, location=None, layer=None, capacity=None, rank=None)
Bases: BaseElement
Group of identical elements of one type, whose variables are arrays with one row per member.
A value is either an array whose first dimension is the size of the group, or one value shared by every member. Variables without a value take the default of the constructor of the element type, and the laws of the group are the laws of its element type, written for one member.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the group, unique among the children of its system. |
required |
element
|
type[BaseElement]
|
The element type of the members. |
required |
size
|
int | None
|
The number of members. |
None
|
values
|
Mapping[str, object] | None
|
A mapping from state or parameter name to its per-member or shared value. |
None
|
visual
|
VisualSpec | None
|
How the group is drawn, or |
None
|
space
|
BaseSpace | None
|
The space whose sites the members occupy, such as a lattice with one member per site, or |
None
|
variable
|
bool
|
Whether laws add and remove members during a run, such as the births and deaths of agents. |
False
|
on
|
str | None
|
The part of the space with one member each, |
None
|
location
|
object
|
The initial places of agents in the space: cell coordinates, a network node, a position, or a polygon that
holds the member at a uniform point inside it, one per member or one shared by every member, or |
None
|
layer
|
str | None
|
The layer of the network whose edges a group on edges lives on, or |
None
|
capacity
|
int | None
|
The largest number of members of a variable group on the sites or edges of a network, which compiled
backends reserve rows for, or |
None
|
rank
|
int | None
|
The largest number of members of a hyperedge of a variable group on the hyperedges of a network, the rank
of its hypergraph, which compiled backends reserve a column per member for, so the group holds hyperedges
of every size up to it, or |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
default_visual_ |
The visual of a member, or the plain visual when the member sets none, which the group takes when it sets none. |
|
size_ |
The number of members: the size, or for a group on parts without one, the number of parts of its space. |
|
values_ |
The values with one row per member, or one value shared by every member. |
Examples:
>>> from typing import Annotated
>>> import networkx as nx
>>> from skcomplex.core.specs import StateSpec
>>> from skcomplex.core.components.elements import Element, GroupElement
>>> from skcomplex.core.components.spaces import NetworkSpace
>>> from skcomplex.core.specs import LayerSpec
>>> from skcomplex.core.specs import VisualSpec
>>> class PopulationElement(Element):
... def __init__(self, label, n: Annotated[float, StateSpec()] = 1.0, visual=VisualSpec('line')):
... super().__init__(label, visual=visual)
... self.n = n
>>> GroupElement('herds', element=PopulationElement, size=3).prepare().size_
3
>>> ring = NetworkSpace(nx.cycle_graph(30))
>>> GroupElement('weights', element=PopulationElement, space=ring, on='edges').prepare().size_
30
>>> space = NetworkSpace(nx.cycle_graph(4), layers=[LayerSpec('work', nx.complete_graph(4))])
>>> wages = GroupElement('wages', element=PopulationElement, space=space, on='edges', layer='work')
>>> wages.prepare().size_
6
>>> herds = GroupElement('herds', element=PopulationElement, size=3).prepare()
>>> herds.default_visual_
VisualSpec(kind='line', options={})
Source code in src/skcomplex/core/components/elements/_group.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
get_declaring_part()
Return what declares the variables of a member: its class, or a member when it declares them from data.
Returns:
| Name | Type | Description |
|---|---|---|
part |
type[BaseElement] | BaseElement
|
The element class, or a member with the values of the group. |
Examples:
>>> from skcomplex.core.components.elements import DeclaredElement, GroupElement
>>> from skcomplex.core.specs import DeclaredVariableSpec, StateSpec
>>> values = {'variables': {'x': DeclaredVariableSpec(1.0, StateSpec())}}
>>> group = GroupElement('cells', element=DeclaredElement, size=2, values=values)
>>> group.get_declaring_part().get_value('x')
1.0
Source code in src/skcomplex/core/components/elements/_group.py
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | |
get_states()
Return the states of a member of the group with their specs.
Returns:
| Name | Type | Description |
|---|---|---|
states |
dict[str, StateSpec]
|
A mapping from the name of every state to its spec. |
Examples:
>>> from skcomplex.core.components.elements import DeclaredElement, GroupElement
>>> from skcomplex.core.specs import DeclaredVariableSpec, StateSpec
>>> values = {'variables': {'x': DeclaredVariableSpec(1.0, StateSpec())}}
>>> list(GroupElement('cells', element=DeclaredElement, size=3, values=values).get_states())
['x']
Source code in src/skcomplex/core/components/elements/_group.py
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | |
LatticeElement(label, *, element, space, values=None, visual=None)
Bases: GroupElement
Group of elements with one member per site of a lattice or node of a network.
The laws of the element type are written for one cell, and local rules read the cells around it. Preparing turns values in the shape of the lattice, or mappings from node to value on a network, into one row per site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the group, unique among the parts of its system. |
required |
element
|
type[BaseElement]
|
The element type of the cells. |
required |
space
|
BaseSpace
|
The lattice or other space of sites, or the network whose nodes are the sites, in the order of
|
required |
values
|
Mapping[str, object] | None
|
A mapping from state or parameter name to an array of the lattice shape, an array in node order, a mapping from node to value on a network, or a value shared by every cell. |
None
|
visual
|
VisualSpec | None
|
How the group is drawn, or |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
default_visual_ |
The visual of a member, or the default visual of the space when the member sets none, which the group takes when it sets none. |
|
size_ |
The number of sites of the space. |
|
values_ |
The values with one row per site, or one value shared by every site. |
Examples:
>>> from typing import Annotated
>>> import numpy as np
>>> from sage.sets.finite_enumerated_set import FiniteEnumeratedSet
>>> from skcomplex.core.components.elements import Element, LatticeElement
>>> from skcomplex.core.components.spaces import LatticeSpace
>>> from skcomplex.core.specs import StateSpec
>>> class CellElement(Element):
... def __init__(self, label, alive: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 0):
... super().__init__(label)
... self.alive = alive
>>> space = LatticeSpace((3, 4))
>>> cells = LatticeElement('cells', element=CellElement, space=space, values={'alive': np.eye(3, 4)})
>>> cells.prepare().size_, cells.values_['alive'].shape
(12, (12,))
Source code in src/skcomplex/core/components/elements/_lattice.py
68 69 70 71 72 73 74 75 76 77 | |
PatchElement(label, tiles=None, *, prototiles=(), filled=False, max_size=10000000, bounds=None, visual=None)
Bases: Element
Element that holds tiles, each a prototile under an affine placement, replaced in parallel by substitutions.
A subclass returns the SubstitutionLaws of the patch from _get_laws. Its states are the count of every
prototile and the number size of tiles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the element. |
required |
tiles
|
Annotated[Sequence[tuple[str, AffineSpec]] | None, StateSpec(domain=None)]
|
Pairs of a prototile name and its placement at generation 0, or |
None
|
prototiles
|
Sequence[PrototileSpec]
|
The prototiles. |
()
|
filled
|
bool
|
Whether the geometry visual draws polygons as a filled image instead of outlines. |
False
|
max_size
|
int
|
The largest number of tiles a generation may produce. |
10000000
|
bounds
|
Sequence[float] | None
|
The box |
None
|
visual
|
VisualSpec | None
|
How the patch is drawn, or |
None
|
Examples:
>>> from skcomplex.core.components.elements import PatchElement
>>> from skcomplex.core.specs import PrototileSpec
>>> PatchElement('cantor', prototiles=[PrototileSpec('interval', [(0, 0), (1, 0)])]).filled
False
Source code in src/skcomplex/core/components/elements/_patch.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
PointElement(label, x=0.0, x_d1=0.0, visual=None)
Bases: Element
Point of a continuous space, a member with a vector state x of order 2 that locates it.
The laws that act on the point give the second derivative of x, and the companion state x_d1 holds its first
derivative. The boundary of the space acts on x.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the point. |
required |
x
|
Annotated[ArrayLike, StateSpec(vector=True, order=2, role=position)]
|
The initial value of the vector state. A number applies to every component. |
0.0
|
x_d1
|
ArrayLike
|
The initial value of the first derivative of the vector state. A number applies to every component. |
0.0
|
visual
|
VisualSpec | None
|
How the point is drawn, or |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
default_visual_ |
Points at the values of |
Examples:
>>> from skcomplex.core.components.elements import PointElement
>>> from skcomplex.core.components.spaces import ContinuousSpace
>>> from skcomplex.core.components.system import System
>>> from skcomplex.core.engine.runtime import Simulation
>>> system = System(elements=[PointElement('ball', x_d1=[1.0, 0.0])], space=ContinuousSpace(n_dim=2))
>>> Simulation(system, dt=1.0).prepare().run(until=2.0).trajectory_.values('ball.x')[-1].tolist()
[2.0, 0.0]
Source code in src/skcomplex/core/components/elements/_point.py
48 49 50 51 52 53 54 55 56 57 | |
WordElement(label, axiom='', *, alphabet=None, ignore='', geometry=None, max_size=10000000, bounds=None, visual=None)
Bases: Element
Element that holds a word of modules rewritten in parallel by string productions.
A subclass returns the ProductionLaws of the word from _get_laws. Its states are the count of every symbol
whose name is a word of letters, digits, and underscores, and the length size of the word.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
The label of the element. |
required |
axiom
|
Annotated[Letters, StateSpec(domain=None)]
|
The word at generation 0. |
''
|
alphabet
|
str | Mapping[str, int] | None
|
The symbols, as a string of one-character symbols or a mapping from name to number of parameters, or
|
None
|
ignore
|
str | Sequence[str]
|
The symbols that contexts skip. |
''
|
geometry
|
GeometrySpec | None
|
The interpretation of the word as line segments, or |
None
|
max_size
|
int
|
The largest word a generation may produce. |
10000000
|
bounds
|
Sequence[float] | None
|
The box |
None
|
visual
|
VisualSpec | None
|
How the word is drawn, or |
None
|
Examples:
>>> from skcomplex.core.components.elements import WordElement
>>> from skcomplex.core.components.laws import ProductionLaw
>>> class AlgaeElement(WordElement):
... def _get_laws(self, namespace):
... return [ProductionLaw('A', 'AB'), ProductionLaw('B', 'A')]
>>> AlgaeElement('algae', axiom='A').axiom
'A'
Source code in src/skcomplex/core/components/elements/_word.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | |