Coverage for test/test_agent.py: 99%

405 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-05-04 13:14 +0700

1import copy 

2import datetime 

3import pytest 

4from ..agent_model.agents import BaseAgent 

5from ..agent_model.util import get_default_currency_data 

6 

7@pytest.fixture 

8def 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 

42class 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 

93def 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 

102def 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 

111class 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 

176def 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 

196class 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 

214def 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 

228class 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 

255class 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 

271class 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 

311class 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 

336class 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 

385def 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 

394class 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 

467class 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 

573class 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 

728class 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'