safenax 0.4.3__tar.gz → 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {safenax-0.4.3 → safenax-0.4.4}/PKG-INFO +1 -1
  2. {safenax-0.4.3 → safenax-0.4.4}/pyproject.toml +1 -1
  3. {safenax-0.4.3 → safenax-0.4.4}/safenax/eco_ant/eco_ant_v1.py +4 -4
  4. {safenax-0.4.3 → safenax-0.4.4}/safenax/eco_ant/eco_ant_v2.py +12 -4
  5. {safenax-0.4.3 → safenax-0.4.4}/tests/test_eco_ant_v1.py +7 -12
  6. {safenax-0.4.3 → safenax-0.4.4}/tests/test_eco_ant_v2.py +4 -10
  7. {safenax-0.4.3 → safenax-0.4.4}/uv.lock +1 -1
  8. {safenax-0.4.3 → safenax-0.4.4}/.gitignore +0 -0
  9. {safenax-0.4.3 → safenax-0.4.4}/.pre-commit-config.yaml +0 -0
  10. {safenax-0.4.3 → safenax-0.4.4}/.python-version +0 -0
  11. {safenax-0.4.3 → safenax-0.4.4}/LICENSE +0 -0
  12. {safenax-0.4.3 → safenax-0.4.4}/PUBLISHING.md +0 -0
  13. {safenax-0.4.3 → safenax-0.4.4}/README.md +0 -0
  14. {safenax-0.4.3 → safenax-0.4.4}/safenax/__init__.py +0 -0
  15. {safenax-0.4.3 → safenax-0.4.4}/safenax/eco_ant/__init__.py +0 -0
  16. {safenax-0.4.3 → safenax-0.4.4}/safenax/fragile_ant.py +0 -0
  17. {safenax-0.4.3 → safenax-0.4.4}/safenax/frozen_lake.py +0 -0
  18. {safenax-0.4.3 → safenax-0.4.4}/safenax/portfolio_optimization/__init__.py +0 -0
  19. {safenax-0.4.3 → safenax-0.4.4}/safenax/portfolio_optimization/po_crypto.py +0 -0
  20. {safenax-0.4.3 → safenax-0.4.4}/safenax/portfolio_optimization/po_garch.py +0 -0
  21. {safenax-0.4.3 → safenax-0.4.4}/safenax/wrappers/__init__.py +0 -0
  22. {safenax-0.4.3 → safenax-0.4.4}/safenax/wrappers/brax.py +0 -0
  23. {safenax-0.4.3 → safenax-0.4.4}/safenax/wrappers/log.py +0 -0
  24. {safenax-0.4.3 → safenax-0.4.4}/scripts/setup_dev.sh +0 -0
  25. {safenax-0.4.3 → safenax-0.4.4}/tests/__init__.py +0 -0
  26. {safenax-0.4.3 → safenax-0.4.4}/tests/test_frozen_lake.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: safenax
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: Constrained environments with a gymnax interface
5
5
  Project-URL: Homepage, https://github.com/0xprofessooor/safenax
6
6
  Project-URL: Repository, https://github.com/0xprofessooor/safenax
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "safenax"
7
- version = "0.4.3"
7
+ version = "0.4.4"
8
8
  description = "Constrained environments with a gymnax interface"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -27,7 +27,8 @@ class EcoAntV1(Ant):
27
27
 
28
28
  def step(self, state: State, action: jax.Array) -> State:
29
29
  # 1. RETRIEVE BATTERY FROM CURRENT OBSERVATION
30
- current_battery = state.info["battery"]
30
+ current_battery_pct = state.obs[-1]
31
+ current_battery = current_battery_pct * self.battery_limit
31
32
 
32
33
  # 2. HANDLE STOCHASTICITY
33
34
  _, noise_key = jax.random.split(state.info["rng"])
@@ -47,8 +48,7 @@ class EcoAntV1(Ant):
47
48
  # 4. PHYSICS STEP
48
49
  next_state = super().step(state, noisy_action)
49
50
 
50
- # 5. AUGMENT STATE
51
- # Termination: OR with existing done condition
51
+ # 5. Termination: OR with existing done condition
52
52
  new_done = jnp.max(jnp.array([next_state.done, is_empty]))
53
53
 
54
54
  # Cost Signal: 1.0 if battery died this step
@@ -61,7 +61,7 @@ class EcoAntV1(Ant):
61
61
  **next_state.info,
62
62
  "rng": noise_key,
63
63
  "cost": cost,
64
- "battery": jnp.where(new_done, jnp.array(self.battery_limit), new_battery),
64
+ "battery": new_battery,
65
65
  }
66
66
 
67
67
  return next_state.replace(obs=new_obs, done=new_done, info=new_info)
@@ -27,7 +27,8 @@ class EcoAntV2(Ant):
27
27
 
28
28
  def step(self, state: State, action: jax.Array) -> State:
29
29
  # 1. RETRIEVE BATTERY FROM CURRENT OBSERVATION
30
- current_battery = state.info["battery"]
30
+ current_battery_pct = state.obs[-1]
31
+ current_battery = current_battery_pct * self.battery_limit
31
32
 
32
33
  # 2. HANDLE STOCHASTICITY
33
34
  _, noise_key = jax.random.split(state.info["rng"])
@@ -42,6 +43,7 @@ class EcoAntV2(Ant):
42
43
  # Check constraints
43
44
  is_empty = new_battery <= 0.0
44
45
  new_battery = jnp.maximum(new_battery, 0.0)
46
+ new_battery_pct = new_battery / self.battery_limit
45
47
 
46
48
  # 4. PHYSICS STEP
47
49
  next_state = super().step(state, noisy_action)
@@ -49,18 +51,24 @@ class EcoAntV2(Ant):
49
51
  # 5. Termination: OR with existing done condition
50
52
  new_done = jnp.max(jnp.array([next_state.done, is_empty]))
51
53
 
54
+ # Observation: Append the new battery level to the observation vector
55
+ new_obs = jnp.concatenate([next_state.obs, jnp.array([new_battery_pct])])
56
+
52
57
  new_info = {
53
58
  **next_state.info,
54
59
  "rng": noise_key,
55
60
  "cost": energy_used,
56
- "battery": jnp.where(new_done, jnp.array(self.battery_limit), new_battery),
61
+ "battery": new_battery,
57
62
  }
58
63
 
59
- return next_state.replace(done=new_done, info=new_info)
64
+ return next_state.replace(obs=new_obs, done=new_done, info=new_info)
60
65
 
61
66
  def reset(self, rng: jax.Array) -> State:
62
67
  state = super().reset(rng)
63
68
 
69
+ # Append initial battery to observation
70
+ new_obs = jnp.concatenate([state.obs, jnp.array([1.0])])
71
+
64
72
  # Initialize info
65
73
  new_info = {
66
74
  **state.info,
@@ -69,7 +77,7 @@ class EcoAntV2(Ant):
69
77
  "battery": jnp.array(self.battery_limit),
70
78
  }
71
79
 
72
- return state.replace(info=new_info)
80
+ return state.replace(obs=new_obs, info=new_info)
73
81
 
74
82
 
75
83
  envs.register_environment(EcoAntV2.name, EcoAntV2)
@@ -87,6 +87,7 @@ def test_termination_and_cost(env: EcoAntV1, key: jax.Array):
87
87
  # 1. Manually set battery to near-death in INFO (not Obs)
88
88
  # The environment logic reads current_battery = state.info["battery"]
89
89
  # So to test termination, we must hack the info dict.
90
+ env.battery_limit = 0.1 # Temporarily set limit low for test
90
91
  new_info = state.info.copy()
91
92
  new_info["battery"] = jnp.array(0.1)
92
93
 
@@ -102,7 +103,7 @@ def test_termination_and_cost(env: EcoAntV1, key: jax.Array):
102
103
  assert next_state.obs[-1] <= 0.0
103
104
 
104
105
  # Check Raw Battery Floor
105
- assert next_state.info["battery"] == 10.0
106
+ assert next_state.info["battery"] == 0.0
106
107
 
107
108
  # Check Cost Signal
108
109
  assert next_state.info["cost"] == 1.0
@@ -143,7 +144,7 @@ def test_wrapper_step_api(wrapped_env: BraxToGymnaxWrapper, key: jax.Array):
143
144
  assert info["battery"] < 10.0
144
145
 
145
146
 
146
- def test_wrapper_autoreset_logic(wrapped_env: BraxToGymnaxWrapper, key: jax.Array):
147
+ def test_wrapper_autoreset_logic(key: jax.Array):
147
148
  """
148
149
  CRITICAL TEST: Verifies AutoResetWrapper behavior on battery death.
149
150
 
@@ -154,16 +155,10 @@ def test_wrapper_autoreset_logic(wrapped_env: BraxToGymnaxWrapper, key: jax.Arra
154
155
  - Returns `done=True` (signaling the end of the dying episode)
155
156
  - Returns `obs` from the NEW reset state (Battery=1.0, not 0.0!)
156
157
  """
158
+ brax_env = EcoAntV1(battery_limit=0.1, noise_scale=0.1)
159
+ wrapped_env = BraxToGymnaxWrapper(env=brax_env, episode_length=10)
157
160
  obs, state = wrapped_env.reset(key)
158
161
 
159
- # 1. Force near-death
160
- new_info = state.info.copy()
161
- new_info["battery"] = jnp.array(0.1)
162
- # We must also update 'obs' because the Wrapper might use 'obs' for something,
163
- # though usually physics uses 'state'. Let's keep obs as is (1.0) to prove
164
- # the physics engine checks 'info', not 'obs'.
165
- state = state.replace(info=new_info)
166
-
167
162
  # 2. Kill the agent
168
163
  action = jnp.ones(wrapped_env.action_size)
169
164
  next_obs, next_state, reward, done, info = wrapped_env.step(key, state, action)
@@ -176,7 +171,7 @@ def test_wrapper_autoreset_logic(wrapped_env: BraxToGymnaxWrapper, key: jax.Arra
176
171
  # B. The 'info' contains the terminal cost of the DEAD agent
177
172
  # But includes the battery reset
178
173
  assert info["cost"] == 1.0
179
- assert info["battery"] == 10.0
174
+ assert info["battery"] == 0.0
180
175
 
181
176
  # C. The 'next_obs' is from the NEW alive agent (AutoReset happened)
182
177
  # The observation returned is for the *next* step.
@@ -184,4 +179,4 @@ def test_wrapper_autoreset_logic(wrapped_env: BraxToGymnaxWrapper, key: jax.Arra
184
179
 
185
180
  # D. The 'next_state' is the NEW alive state
186
181
  assert next_state.info["cost"] == 1.0
187
- assert next_state.info["battery"] == 10.0
182
+ assert next_state.info["battery"] == 0.0
@@ -78,18 +78,12 @@ def test_battery_depletion_in_info(env: EcoAntV2, key: jax.Array):
78
78
  assert jnp.allclose(start_battery - energy_used, current_battery, atol=1e-5)
79
79
 
80
80
 
81
- def test_termination_logic(env: EcoAntV2, key: jax.Array):
81
+ def test_termination_logic(key: jax.Array):
82
82
  """
83
83
  Verifies that the episode terminates when info['battery'] hits zero.
84
84
  """
85
- state = env.reset(key)
86
-
87
- # 1. Manually set battery to be very low in the info dict
88
- # This simulates a "near death" state
89
- low_battery_info = state.info.copy()
90
- low_battery_info["battery"] = jnp.array(0.1)
91
-
92
- near_death_state = state.replace(info=low_battery_info)
85
+ env = EcoAntV2(battery_limit=0.1, noise_scale=0.1)
86
+ near_death_state = env.reset(key)
93
87
 
94
88
  # 2. Take a large step to consume > 0.1 energy
95
89
  # Action of 1.0s usually consumes ~4.0 energy
@@ -100,7 +94,7 @@ def test_termination_logic(env: EcoAntV2, key: jax.Array):
100
94
  assert next_state.done == 1.0
101
95
 
102
96
  # 4. Verify Battery Floor
103
- assert next_state.info["battery"] == 10.0
97
+ assert next_state.info["battery"] == 0.0
104
98
 
105
99
 
106
100
  def test_stochasticity_impact(env: EcoAntV2, key: jax.Array):
@@ -1501,7 +1501,7 @@ wheels = [
1501
1501
 
1502
1502
  [[package]]
1503
1503
  name = "safenax"
1504
- version = "0.4.2"
1504
+ version = "0.4.3"
1505
1505
  source = { editable = "." }
1506
1506
  dependencies = [
1507
1507
  { name = "brax" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes