1 import copy

2 import datetime

3 import pytest

4 from ..agent_model.agents import BaseAgent

5 from ..agent_model.util import get_default_currency_data

6

7@pytest.fixture

8 def kwargs():

9 return {

10 'model': object(),

11 'agent_id': 'test_agent',

12 'amount': 10,

13 'description': 'test_description',

14 'agent_class': 'test_agent_class',

15 'properties': {'test_property': {'value': 1}},

16 'capacity': {'test_currency': 2},

17 'thresholds': {'test_currency': {

18 'path': 'test_currency',

19 'limit': '<',

20 'value': 0.5,

21 }},

22 'flows': {

23 'in': {

24 'test_currency': {

25 'value': 1,

26 'connections': ['test_agent_2']

27 }

28 },

29 'out': {

30 'test_currency': {

31 'value': 1,

32 'connections': ['test_agent_2']

33 }

34 }

35 },

36 'cause_of_death': 'test_death',

37 'active': 5,

38 'storage': {'test_currency': 1},

39 'attributes': {'test_attribute': 1},

40 }

41

42 class TestAgentInit:

43 def test_agent_init_empty(self):

44 """Confirm that all attributes are set correctly when no kwargs are passed"""

45 model = object()

46 test_agent = BaseAgent(model, 'test_agent')

47 assert test_agent.agent_id == 'test_agent'

48 assert test_agent.amount == 1

49 assert test_agent.model == model

50 assert test_agent.registered == False

51 assert test_agent.cause_of_death == None

52 assert str(test_agent.flows) == str({'in': {}, 'out': {}})

53 empty_strings = {'description', 'agent_class'}

54 empty_dicts = {'properties', 'capacity', 'thresholds', 'storage', 'attributes', 'records'}

55 for k in empty_strings:

56 assert getattr(test_agent, k) == ''

57 for k in empty_dicts:

58 assert str(getattr(test_agent, k)) == str({})

59

60 def test_agent_init_full(self, kwargs):

61 """Confirm that all kwargs are set correctly"""

62 test_agent = BaseAgent(**kwargs)

63 for k, v in kwargs.items():

64 assert str(getattr(test_agent, k)) == str(v)

65

66 def test_agent_init_kwargs_immutable(self, kwargs):

67 """Test that the kwargs passed to Agent.__init__() are immutable.

68

69 We pass a set of kwargs to the Agent class and modify them outside of

70 the class. Then, we confirm that the Agent's internal attributes are

71 not modified by the external changes to the kwargs object. This test

72 ensures that the Agent's initialization process correctly creates

73 a copy of the kwargs object to ensure immutability."""

74 test_agent = BaseAgent(**kwargs)

75 # Confirm that class is not

76 def recursively_modify_kwargs(obj):

77 if isinstance(obj, dict):

78 for k, v in obj.items():

79 obj[k] = recursively_modify_kwargs(v)

80 elif isinstance(obj, object):

81 return object()

82 elif isinstance(obj, list):

83 return [recursively_modify_kwargs(i) for i in obj]

84 elif isinstance(obj, (int, float)):

85 return obj + 1

86 else:

87 return f'{obj}_modified'

88 recursively_modify_kwargs(kwargs)

89 for k, v in kwargs.items():

90 assert str(getattr(test_agent, k)) != str(v)

91

92@pytest.fixture

93 def mock_model():

94 class MockModel:

95 floating_point_precision = 6

96 agents = {}

97 time = datetime.datetime(2020, 1, 1)

98 currencies = {'test_currency': {'currency_type': 'currency'}}

99 return MockModel()

100

101@pytest.fixture

102 def basic_model(mock_model, kwargs):

103 test_agent = BaseAgent(**{**kwargs, 'model': mock_model})

104 test_agent_2 = BaseAgent(mock_model, 'test_agent_2', capacity={'test_currency': 2})

105 mock_model.agents = {

106 'test_agent': test_agent,

107 'test_agent_2': test_agent_2

108 }

109 return mock_model

110

111 class TestAgentRegister:

112 def test_agent_register_empty(self):

113 """Confirm that all attributes set correctly when no kwargs passed"""

114 test_agent = BaseAgent(object(), 'test_agent')

115 assert test_agent.registered == False

116 test_agent.register()

117 assert test_agent.registered == True

118 assert test_agent.attributes == {'age': 0}

119 assert test_agent.records['active'] == []

120 assert test_agent.records['cause_of_death'] == None

121 assert test_agent.records['storage'] == {}

122 assert test_agent.records['attributes'] == {'age': []}

123 assert test_agent.records['flows'] == {'in': {}, 'out': {}}

124

125 def test_agent_register_full_missing_connection(self, basic_model):

126 """Confirm that an error is raised if connection agent not in model"""

127 test_agent = basic_model.agents['test_agent']

128 basic_model.agents.pop('test_agent_2')

129 with pytest.raises(ValueError):

130 test_agent.register()

131 assert test_agent.registered == False

132

133 def test_agent_register_full_missing_currency(self, basic_model):

134 """Confirm that an error is raised if capacity missing for storage"""

135 test_agent = basic_model.agents['test_agent']

136 basic_model.agents['test_agent_2'].capacity.pop('test_currency')

137 with pytest.raises(ValueError):

138 test_agent.register()

139 assert test_agent.registered == False

140

141 def test_agent_register_full_missing_capacity(self, basic_model):

142 """Confirm that an error is raised if initial storage greater than capacity"""

143 test_agent = basic_model.agents['test_agent']

144 test_agent.capacity['test_currency'] = 0

145 with pytest.raises(ValueError):

146 test_agent.register()

147 assert test_agent.registered == False

148

149 def test_agent_register_full(self, basic_model):

150 """Confirm that all fields set correctly when kwargs passed"""

151 test_agent = basic_model.agents['test_agent']

152 test_agent.register()

153 assert test_agent.registered == True

154 assert test_agent.attributes == {'age': 0, 'test_attribute': 1}

155 assert test_agent.records['active'] == []

156 assert test_agent.records['storage'] == {'test_currency': []}

157 assert test_agent.records['attributes'] == {'age': [], 'test_attribute': []}

158 assert test_agent.records['flows'] == {

159 'in': {'test_currency': {'test_agent_2': []}},

160 'out': {'test_currency': {'test_agent_2': []}}

161 }

162

163 def test_agent_register_record_initial_state(self, basic_model):

164 """Confirm that initial state is recorded when requested"""

165 test_agent = basic_model.agents['test_agent']

166 test_agent.register(record_initial_state=True)

167 assert test_agent.records['active'] == [5]

168 assert test_agent.records['storage'] == {'test_currency': [1]}

169 assert test_agent.records['attributes'] == {'age': [0], 'test_attribute': [1]}

170 assert test_agent.records['flows'] == {

171 'in': {'test_currency': {'test_agent_2': [0]}},

172 'out': {'test_currency': {'test_agent_2': [0]}}

173 }

174

175@pytest.fixture

176 def flow():

177 return {

178 'value': 1,

179 'criteria': [{

180 'buffer': 1,

181 }],

182 'deprive': {

183 'value': 2,

184 },

185 'growth': {

186 'daily': {

187 'type': 'clipped',

188 },

189 'lifetime': {

190 'type': 'sigmoid'

191 }

192 },

193 'connections': ['test_agent_2']

194 }

195

196 class TestAgentRegisterFlow:

197 def test_agent_register_flow(self, basic_model, flow):

198 """Confirm that all flow attributes are set correctly"""

199 test_agent = basic_model.agents['test_agent']

200 test_agent.properties['lifetime'] = {'value': 100}

201 test_agent.flows['in']['test_currency'] = flow

202 test_agent.register(record_initial_state=True)

203

204 for attr in [

205 'in_test_currency_criteria_0_buffer',

206 'in_test_currency_deprive',

207 'in_test_currency_daily_growth_factor',

208 'in_test_currency_lifetime_growth_factor',

209 ]:

210 assert attr in test_agent.attributes

211 assert len(test_agent.records['attributes'][attr]) == 1

212

213@pytest.fixture

214 def mock_model_with_currencies(mock_model):

215 mock_model.currencies = {

216 'test_currency_1': {

217 'currency_type': 'currency',

218 'class': 'test_currency_class'},

219 'test_currency_2': {

220 'currency_type': 'currency',

221 'class': 'test_currency_class'},

222 'test_currency_class': {

223 'currency_type': 'class',

224 'currencies': ['test_currency_1', 'test_currency_2']},

225 }

226 return mock_model

227

228 class TestAgentView:

229 def test_agent_view_empty(self, mock_model_with_currencies):

230 """Confirm that view returns empty dict if no storage or capacity"""

231 test_agent = BaseAgent(mock_model_with_currencies, 'test_agent')

232 test_agent.register()

233 assert test_agent.view('test_currency_1') == {'test_currency_1': 0}

234 assert test_agent.view('test_currency_2') == {'test_currency_2': 0}

235 assert test_agent.view('test_currency_class') == {}

236

237 def test_agent_view_full(self, mock_model_with_currencies):

238 """Confirm view returns correct values for currency or currency class"""

239 test_agent = BaseAgent(

240 mock_model_with_currencies,

241 'test_agent',

242 storage={'test_currency_1': 1, 'test_currency_2': 2},

243 capacity={'test_currency_1': 1, 'test_currency_2': 2},

244 )

245 assert test_agent.view('test_currency_1') == {'test_currency_1': 1}

246 assert test_agent.view('test_currency_2') == {'test_currency_2': 2}

247 assert test_agent.view('test_currency_class') == {'test_currency_1': 1, 'test_currency_2': 2}

248

249 def test_agent_view_error(self, mock_model_with_currencies):

250 """Confirm that error is raised if view currency not in model"""

251 test_agent = BaseAgent(mock_model_with_currencies, 'test_agent')

252 with pytest.raises(KeyError):

253 test_agent.view('test_currency_3')

254

255 class TestAgentSerialize:

256 def test_agent_serialize(self, basic_model, kwargs):

257 """Confirm that all fields are serialized correctly"""

258 test_agent = basic_model.agents['test_agent']

259 test_agent.register()

260 serialized = test_agent.serialize()

261 serializable = {'agent_id', 'amount', 'description', 'agent_class',

262 'properties', 'capacity', 'thresholds', 'flows',

263 'cause_of_death', 'active', 'storage', 'attributes'}

264 assert set(serialized.keys()) == serializable

265 for key in serializable:

266 if key == 'attributes':

267 assert serialized[key] == {'age': 0, 'test_attribute': 1}

268 else:

269 assert serialized[key] == kwargs[key]

270

271 class TestAgentGetRecords:

272 def test_agent_get_records_basic(self, basic_model):

273 """Confirm that all fields are recorded correctly"""

274 test_agent = basic_model.agents['test_agent']

275 test_agent.register(record_initial_state=True)

276 records = test_agent.get_records()

277

278 assert records['active'] == [5]

279 assert records['cause_of_death'] == 'test_death'

280 assert records['storage'] == {'test_currency': [1]}

281 assert records['attributes'] == {'age': [0], 'test_attribute': [1]}

282 assert records['flows'] == {

283 'in': {'test_currency': {'test_agent_2': [0]}},

284 'out': {'test_currency': {'test_agent_2': [0]}}

285 }

286

287 def test_agent_get_records_static(self, basic_model, kwargs):

288 """Confirm that static fields are recorded correctly"""

289 test_agent = basic_model.agents['test_agent']

290 test_agent.register(record_initial_state=True)

291 records = test_agent.get_records(static=True)

292 static_keys = {'agent_id', 'amount', 'agent_class', 'description',

293 'properties', 'capacity', 'thresholds', 'flows'}

294 assert set(records['static'].keys()) == static_keys

295 for key in static_keys:

296 assert records['static'][key] == kwargs[key]

297

298 def test_agent_get_records_clear_cache(self, basic_model):

299 """Confirm that get_records clears cache when requested"""

300 test_agent = basic_model.agents['test_agent']

301 test_agent.register(record_initial_state=True)

302 test_agent.get_records(clear_cache=True)

303 def recursively_check_empty(dictionary):

304 for key in dictionary:

305 if isinstance(dictionary[key], dict):

306 recursively_check_empty(dictionary[key])

307 elif isinstance(dictionary[key], list):

308 assert dictionary[key] == []

309 recursively_check_empty(test_agent.records)

310

311 class TestAgentSave:

312 def test_agent_save(self, basic_model, kwargs):

313 """Test that save returns a dictionary matching initialization"""

314 test_agent = basic_model.agents['test_agent']

315 test_agent.register(record_initial_state=True)

316 expected = copy.deepcopy(kwargs)

317 del expected['model']

318 expected['attributes'] = {'age': 0, 'test_attribute': 1}

319

320 saved = test_agent.save()

321 assert saved == expected

322 assert 'records' not in saved

323

324 def test_agent_save_with_records(self, basic_model, kwargs):

325 """Test that records are included in save if requested"""

326 test_agent = basic_model.agents['test_agent']

327 test_agent.register(record_initial_state=True)

328 expected = copy.deepcopy(kwargs)

329 del expected['model']

330 expected['attributes'] = {'age': 0, 'test_attribute': 1}

331 expected['records'] = test_agent.get_records()

332

333 saved = test_agent.save(records=True)

334 assert saved == expected

335

336 class TestAgentIncrement:

337 def test_agent_increment_positive(self, mock_model_with_currencies):

338 """Test that increment correctly increments currencies"""

339 test_agent = BaseAgent(

340 mock_model_with_currencies,

341 'test_agent',

342 storage={'test_currency_1': 1},

343 capacity={'test_currency_1': 2},

344 )

345 # Test incrementing a single currency

346 receipt = test_agent.increment('test_currency_1', 1)

347 assert receipt == {'test_currency_1': 1}

348 assert test_agent.storage['test_currency_1'] == 2

349 # Test incrementing a single currency beyond capacity

350 receipt = test_agent.increment('test_currency_1', 1)

351 assert receipt == {'test_currency_1': 0}

352 assert test_agent.storage['test_currency_1'] == 2

353 # Test incrementing a currency without capacity

354 with pytest.raises(ValueError):

355 test_agent.increment('test_currency_2', 1)

356 # Test incrementing a currency class

357 with pytest.raises(ValueError):

358 test_agent.increment('test_currency_class', 1)

359

360 def test_agent_increment_negative(self, mock_model_with_currencies):

361 """Test that increment correctly decrements currencies"""

362 test_agent = BaseAgent(

363 mock_model_with_currencies,

364 'test_agent',

365 storage={'test_currency_1': 2, 'test_currency_2': 1},

366 capacity={'test_currency_1': 2, 'test_currency_2': 2},

367 )

368 # Test decrementing a single currency

369 receipt = test_agent.increment('test_currency_1', -1)

370 assert receipt == {'test_currency_1': -1}

371 assert test_agent.storage['test_currency_1'] == 1

372 # Test decrementing a currency class

373 receipt = test_agent.increment('test_currency_class', -1)

374 assert receipt == {'test_currency_1': -0.5, 'test_currency_2': -0.5}

375 assert test_agent.storage['test_currency_1'] == 0.5

376 assert test_agent.storage['test_currency_2'] == 0.5

377 # Test decrementing a currency class beyond stored

378 receipt = test_agent.increment('test_currency_class', -2)

379 assert receipt == {'test_currency_1': -0.5, 'test_currency_2': -0.5}

380 assert test_agent.storage['test_currency_1'] == 0

381 assert test_agent.storage['test_currency_2'] == 0

382 # TODO: Test with amount>1, confirm capacity scales with amount

383

384@pytest.fixture

385 def get_flow_value_kwargs(kwargs):

386 return {

387 'dT': 1,

388 'direction': 'in',

389 'currency': 'test_currency',

390 'flow': kwargs['flows']['in']['test_currency'],

391 'influx': {},

392 }

393

394 class TestAgentGetFlowValue:

395 def test_agent_get_flow_value_basic(self, basic_model, get_flow_value_kwargs):

396 """Test that get_flow_value returns the correct value"""

397 test_agent = basic_model.agents['test_agent']

398 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

399 assert flow_value == 1

400 get_flow_value_kwargs['dT'] = 0.33

401 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

402 assert flow_value == 0.33

403

404 def test_agent_get_flow_value_requires(self, basic_model, get_flow_value_kwargs):

405 """Test that get_flow_value handles requires correctly"""

406 # Single Currency

407 test_agent = basic_model.agents['test_agent']

408 get_flow_value_kwargs['flow']['requires'] = ['test_currency_2']

409 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

410 assert flow_value == 0

411 get_flow_value_kwargs['influx'] = {'test_currency_2': 0.5}

412 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

413 assert flow_value == 0.5

414 get_flow_value_kwargs['influx'] = {'test_currency_2': 1}

415 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

416 assert flow_value == 1

417

418 # Multiple Currencies

419 get_flow_value_kwargs['flow']['requires'] = ['test_currency_2', 'test_currency_3']

420 get_flow_value_kwargs['influx'] = {'test_currency_2': 0.5, 'test_currency_3': 0.5}

421 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

422 assert flow_value == 0.25

423

424 def test_agent_get_flow_value_criteria_basic(self, basic_model, get_flow_value_kwargs):

425 # TODO: Move some of this to the test for evaluate_criteria.

426 test_agent = basic_model.agents['test_agent']

427 # Equality | Attribute

428 test_agent.flows['in']['test_currency']['criteria'] = [{

429 'path': 'test_attribute',

430 'limit': '=',

431 'value': 1,

432 }]

433 get_flow_value_kwargs['flow'] = test_agent.flows['in']['test_currency']

434 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

435 assert flow_value == 1

436 test_agent.flows['in']['test_currency']['criteria'][0]['value'] = 2

437 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

438 assert flow_value == 0

439

440 def test_agent_get_flow_value_growth(self, basic_model, get_flow_value_kwargs):

441 test_agent = basic_model.agents['test_agent']

442 get_flow_value_kwargs['flow']['growth'] = {'lifetime': {'type': 'sigmoid'}}

443 test_agent.properties['lifetime'] = {'value': 10}

444 test_agent.attributes['age'] = 0

445 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

446 assert 0 < flow_value < 0.0001

447 test_agent.attributes['age'] = 5

448 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

449 assert flow_value == 0.5

450 test_agent.attributes['age'] = 10

451 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

452 assert 0.9999 < flow_value < 1

453

454 def test_agent_get_flow_value_weighted(self, basic_model, get_flow_value_kwargs):

455 test_agent = basic_model.agents['test_agent']

456 test_agent.properties['test_property']['value'] = 0.5

457 test_agent.attributes['test_attribute'] = 0.5

458 test_agent.storage['test_currency'] = 10 # divided by active=5

459 get_flow_value_kwargs['flow']['weighted'] = [

460 'test_property', 'test_attribute', 'test_currency']

461 flow_value = test_agent.get_flow_value(**get_flow_value_kwargs)

462 assert flow_value == 1 * 0.5 * 0.5 * 10 / 5

463 get_flow_value_kwargs['flow']['weighted'] = ['missing_weight']

464 with pytest.raises(ValueError):

465 test_agent.get_flow_value(**get_flow_value_kwargs)

466

467 class TestAgentProcessFlow:

468 def test_process_flow_influx(self, mock_model_with_currencies):

469 test_agent = BaseAgent(

470 mock_model_with_currencies,

471 'test_agent',

472 flows={

473 'in': {

474 'test_currency_1': {

475 'value': 1,

476 'connections': ['test_agent_2'],

477 },

478 },

479 'out': {

480 'test_currency_2': {

481 'value': 1,

482 'connections': ['test_agent_2'],

483 },

484 }

485 }

486 )

487 test_agent_2 = BaseAgent(

488 mock_model_with_currencies,

489 'test_agent_2',

490 capacity={'test_currency_1': 2, 'test_currency_2': 2},

491 )

492 mock_model_with_currencies.agents = {

493 'test_agent': test_agent,

494 'test_agent_2': test_agent_2,

495 }

496 test_agent.register()

497 test_agent_2.register()

498 influx = {}

499 for direction in ('in', 'out'):

500 currency = next(iter(test_agent.flows[direction]))

501 kwargs = dict(dT=1, direction=direction, currency=currency,

502 flow=test_agent.flows[direction][currency], influx=influx,

503 target=1, actual=1)

504 test_agent.process_flow(**kwargs)

505 assert influx == {'test_currency_1': 1}

506

507 def test_process_flow_deprive(self, mock_model_with_currencies):

508 test_agent = BaseAgent(

509 mock_model_with_currencies,

510 'test_agent',

511 amount=5,

512 flows={

513 'in': {

514 'test_currency_1': {

515 'value': 1,

516 'connections': ['test_agent_2'],

517 'deprive': {'value': 2}

518 },

519 },

520 }

521 )

522 test_agent_2 = BaseAgent(

523 mock_model_with_currencies,

524 'test_agent_2',

525 capacity={'test_currency_1': 2},

526 )

527 mock_model_with_currencies.agents = {

528 'test_agent': test_agent,

529 'test_agent_2': test_agent_2,

530 }

531 test_agent.register()

532 test_agent_2.register()

533 # Start with deprive buffer full

534 assert test_agent.attributes['in_test_currency_1_deprive'] == 2

535 # Actual is equal to half of target; half of the active are deprived

536 process_kwargs = dict(dT=1, direction='in', currency='test_currency_1',

537 flow=test_agent.flows['in']['test_currency_1'], influx={},

538 target=10, actual=5)

539 test_agent.process_flow(**process_kwargs)

540 assert test_agent.attributes['in_test_currency_1_deprive'] == 1.5

541 test_agent.process_flow(**process_kwargs)

542 assert test_agent.attributes['in_test_currency_1_deprive'] == 1

543 test_agent.process_flow(**process_kwargs)

544 assert test_agent.attributes['in_test_currency_1_deprive'] == 0.5

545 test_agent.process_flow(**process_kwargs)

546 assert test_agent.attributes['in_test_currency_1_deprive'] == 0

547 # Deprive buffer is empty, so half of the active die

548 test_agent.process_flow(**process_kwargs)

549 assert test_agent.active == 2

550 # Actual is equal to target again, buffer resets

551 process_kwargs['actual'] = 10

552 test_agent.process_flow(**process_kwargs)

553 assert test_agent.attributes['in_test_currency_1_deprive'] == 2

554 # Actual equal to zero; all of active are deprived

555 process_kwargs['actual'] = 0

556 test_agent.process_flow(**process_kwargs)

557 assert test_agent.active == 2

558 assert test_agent.attributes['in_test_currency_1_deprive'] == 1

559 # Buffer responds to DT

560 process_kwargs['dT'] = 0.5

561 test_agent.process_flow(**process_kwargs)

562 assert test_agent.active == 2

563 assert test_agent.attributes['in_test_currency_1_deprive'] == 0.5

564 test_agent.process_flow(**process_kwargs)

565 assert test_agent.active == 2

566 assert test_agent.attributes['in_test_currency_1_deprive'] == 0

567 # When deprive buffer is empty, all active die (per dT)

568 process_kwargs['dT'] = 1

569 test_agent.process_flow(**process_kwargs)

570 assert test_agent.active == 0

571 assert test_agent.cause_of_death == 'test_agent deprived of test_currency_1'

572

573 class TestAgentStep:

574 def test_agent_step_empty(self, basic_model):

575 test_agent = BaseAgent(basic_model, 'test_agent')

576 assert not test_agent.registered

577 test_agent.step()

578 assert test_agent.registered

579 assert test_agent.records == {

580 'active': [1],

581 'cause_of_death': None,

582 'storage': {},

583 'attributes': {'age': [1]},

584 'flows': {'in': {}, 'out': {}}

585 }

586 test_agent.step()

587 assert test_agent.attributes['age'] == 2

588 assert test_agent.records['attributes']['age'] == [1, 2]

589 assert test_agent.records['active'] == [1, 1]

590

591 def test_agent_step_threshold(self, mock_model_with_currencies):

592 test_agent = BaseAgent(

593 mock_model_with_currencies,

594 'test_agent',

595 thresholds={'test_currency_1': {

596 'path': 'in_test_currency_1_ratio',

597 'limit': '<',

598 'value': 0.5,

599 }},

600 flows={'in': {'test_currency_1': {'connections': ['test_structure']}}},

601 )

602 test_structure = BaseAgent(

603 mock_model_with_currencies,

604 'test_structure',

605 capacity={'test_currency_1': 10, 'test_currency_2': 10},

606 storage={'test_currency_1': 5, 'test_currency_2': 5},

607 )

608 mock_model_with_currencies.agents = {

609 'test_agent': test_agent,

610 'test_structure': test_structure,

611 }

612 test_agent.register()

613 test_structure.register()

614 # Threshold not met

615 test_agent.step()

616 assert test_agent.active

617 assert test_agent.cause_of_death == None

618 assert test_agent.records['flows']['in']['test_currency_1']['test_structure'] == [0]

619 assert test_agent.attributes['age'] == 1

620 # Threshold met

621 test_structure.storage['test_currency_1'] = 4.9

622 test_agent.step()

623 assert test_agent.active == 0

624 assert test_agent.cause_of_death == 'test_agent passed test_currency_1 threshold'

625 assert test_agent.records['flows']['in']['test_currency_1']['test_structure'] == [0, 0]

626 assert test_agent.attributes['age'] == 2

627 # Records continue to accumulate even after agent is dead, but age stays same

628 test_agent.step()

629 assert test_agent.records['flows']['in']['test_currency_1']['test_structure'] == [0, 0, 0]

630 assert test_agent.attributes['age'] == 2

631

632 def test_agent_step_flows(self, mock_model_with_currencies):

633 test_agent = BaseAgent(

634 mock_model_with_currencies,

635 'test_agent',

636 flows={

637 'in': {

638 'test_currency_1': {

639 'value': 1,

640 'connections': ['test_structure'],

641 }

642 },

643 'out': {

644 'test_currency_2': {

645 'value': 1,

646 'connections': ['test_structure'],

647 }

648 }

649 }

650 )

651 test_structure = BaseAgent(

652 mock_model_with_currencies,

653 'test_structure',

654 capacity={'test_currency_1': 10, 'test_currency_2': 10},

655 storage={'test_currency_1': 5, 'test_currency_2': 5},

656 )

657 mock_model_with_currencies.agents = {

658 'test_agent': test_agent,

659 'test_structure': test_structure,

660 }

661 test_agent.register()

662 test_structure.register()

663 test_agent.step()

664 assert test_agent.records['flows']['in']['test_currency_1']['test_structure'] == [1]

665 assert test_agent.records['flows']['out']['test_currency_2']['test_structure'] == [1]

666 assert test_structure.storage['test_currency_1'] == 4

667 assert test_structure.storage['test_currency_2'] == 6

668 test_agent.step(dT=0.5)

669 assert test_agent.records['flows']['in']['test_currency_1']['test_structure'] == [1, 0.5]

670 assert test_agent.records['flows']['out']['test_currency_2']['test_structure'] == [1, 0.5]

671 assert test_structure.storage['test_currency_1'] == 3.5

672 assert test_structure.storage['test_currency_2'] == 6.5

673

674 def test_agent_step_multi_connections(self, mock_model_with_currencies):

675 test_agent = BaseAgent(

676 mock_model_with_currencies,

677 'test_agent',

678 flows={

679 'in': {

680 'test_currency_1': {

681 'value': 2,

682 'connections': ['test_structure_1', 'test_structure_2'],

683 }

684 },

685 }

686 )

687 test_structure_1 = BaseAgent(

688 mock_model_with_currencies,

689 'test_structure_1',

690 capacity={'test_currency_1': 10},

691 storage={'test_currency_1': 5},

692 )

693 test_structure_2 = BaseAgent(

694 mock_model_with_currencies,

695 'test_structure_2',

696 capacity={'test_currency_1': 10},

697 storage={'test_currency_1': 5},

698 )

699 mock_model_with_currencies.agents = {

700 'test_agent': test_agent,

701 'test_structure_1': test_structure_1,

702 'test_structure_2': test_structure_2,

703 }

704 test_agent.register()

705 test_structure_1.register()

706 test_structure_2.register()

707

708 # If available, use first connection

709 test_agent.step()

710 assert test_agent.records['flows']['in']['test_currency_1'] == {

711 'test_structure_1': [2],

712 'test_structure_2': [0],

713 }

714 assert test_structure_1.storage['test_currency_1'] == 3

715 assert test_structure_2.storage['test_currency_1'] == 5

716

717 # If partially available, split between first and second connections

718 test_structure_1.storage['test_currency_1'] = 1

719 test_agent.step()

720 test_agent.step()

721 assert test_agent.records['flows']['in']['test_currency_1'] == {

722 'test_structure_1': [2, 1, 0],

723 'test_structure_2': [0, 1, 2],

724 }

725 assert test_structure_1.storage['test_currency_1'] == 0

726 assert test_structure_2.storage['test_currency_1'] == 2

727

728 class TestAgentKill:

729 def test_agent_kill(self, basic_model):

730 test_agent = BaseAgent(basic_model, 'test_agent', amount=2)

731 basic_model.agents = {'test_agent': test_agent}

732 test_agent.register()

733 test_agent.kill('test_reason', 1)

734 assert test_agent.active == 1

735 assert test_agent.cause_of_death == None

736 test_agent.kill('test_reason', 1)

737 assert test_agent.active == 0

738 assert test_agent.cause_of_death == 'test_reason'