Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
9b5bc75
add LEDRO_D_FC test circuit
phuocphn Aug 23, 2025
cdd2f9a
geneate target specs for LEDRO_D_FC
phuocphn Aug 23, 2025
1657084
refactor: PEP8 compliant
phuocphn Aug 23, 2025
905aec7
feat: add jinja2 template
phuocphn Aug 23, 2025
d06a33e
feat: add LEDRO_D_FC gym env
phuocphn Aug 23, 2025
69f9400
bug: delete design_folder when simulation is done
phuocphn Aug 25, 2025
897319e
feat: add ledro_d_fc into eval.py
phuocphn Aug 25, 2025
902b4b5
add ledro_d_fc 45nm model
phuocphn Aug 25, 2025
528cac4
fix: correct spec range and normalization vector
phuocphn Aug 27, 2025
b58079d
update: maximum freq for ac simulation
phuocphn Aug 27, 2025
31ecec2
feat: add gen spec for ledro_d_dc45
phuocphn Aug 27, 2025
65a1bc4
feat: add loguru
phuocphn Aug 27, 2025
de9e5cd
feat: add ledro_d_fc45 related classes
phuocphn Aug 27, 2025
4e70a37
feat: add training scripts
phuocphn Aug 27, 2025
b069551
feat: update .gitignore
phuocphn Aug 27, 2025
37a346d
fix: vbias max values
phuocphn Aug 27, 2025
fdd48e6
feat: generate .log files for debugging
phuocphn Aug 27, 2025
f312107
feat: add generated time info
phuocphn Aug 27, 2025
fbd06b6
Merge branch 'experiment/optimize-ledro-fully-differential-cascode-op…
phuocphn Aug 27, 2025
abfe4fd
test: run1 hyperameter setting
phuocphn Aug 27, 2025
30310b6
update params
phuocphn Aug 29, 2025
cb41fb6
update horizon length and num_workers
phuocphn Aug 29, 2025
b30cb30
update experiment results
phuocphn Aug 29, 2025
d3e7714
Merge pull request #1 from analog-ml/runs/ledro_d_fc_7nm_run3_sever3
phuocphn Aug 29, 2025
d6e8dec
use reward calculation from LEDRO implementation
phuocphn Aug 29, 2025
b4456a4
increase param range step: 400
phuocphn Aug 29, 2025
12ac8cd
update num_workers
phuocphn Aug 30, 2025
c0f0ed0
add experiment results
phuocphn Aug 30, 2025
556ee4d
update device param ranges
phuocphn Aug 31, 2025
2973585
update starting index
phuocphn Aug 31, 2025
7762c2b
update horizon length
phuocphn Aug 31, 2025
8f08909
add experiment results
phuocphn Aug 31, 2025
24e2f1b
Merge pull request #2 from analog-ml/runs/params_10_990_400_horizon20…
phuocphn Aug 31, 2025
a583013
add Zhenxin_S_FC example
phuocphn Sep 1, 2025
7a7cee5
restructure example folder
phuocphn Sep 1, 2025
bc477ae
add 65nm PTM spice model
phuocphn Sep 1, 2025
2baf41d
add example LEDRO_D_FC_45
phuocphn Sep 1, 2025
767009a
parameterize LEDRO_D_FC45 with action_normalizer
phuocphn Sep 1, 2025
cd5ff8b
fix: path too long
phuocphn Sep 1, 2025
02d3055
update: experiment name
phuocphn Sep 1, 2025
44f09bd
feat: add tensorboard log
phuocphn Sep 1, 2025
dcdf673
update action_normalizer
phuocphn Sep 1, 2025
d3baea0
add Zhenxin_S_FC circuit .cir and .yaml
phuocphn Sep 1, 2025
f4fcd47
add Zhenxin_S_FC gym env
phuocphn Sep 1, 2025
60a7f5c
add Zhenxin_S_FC 65nm PTM traning script
phuocphn Sep 1, 2025
37eeddb
add zhenxin_s_fc to gen_specs list
phuocphn Sep 1, 2025
6e05981
update horizon length
phuocphn Sep 1, 2025
bd28006
update reward threshold and specs_target
phuocphn Sep 1, 2025
4990aee
update: PEP8 compliant
phuocphn Sep 1, 2025
bdd4adc
change W/L unit
phuocphn Sep 2, 2025
bc96532
update Zhenxin_S_FC circuit template
phuocphn Sep 2, 2025
bce6b44
update target_specs and nomalize vector
phuocphn Sep 2, 2025
e32d329
update W/L bound W0.13_200 L0.12_2 M1_100
phuocphn Sep 2, 2025
1be048b
set lr
phuocphn Sep 2, 2025
b7a94f5
correct voltage bias names
phuocphn Sep 3, 2025
9a3d967
disable generalization
phuocphn Sep 4, 2025
b1b4134
set minimal voltage biases
phuocphn Sep 4, 2025
718eed6
add reward calculation example
phuocphn Sep 4, 2025
5ed931b
fix L and M parameters
phuocphn Sep 4, 2025
f936a93
update W bounds (unit: nano)
phuocphn Sep 4, 2025
9669e54
update unit: nano
phuocphn Sep 4, 2025
6f88b91
correct the initial values
phuocphn Sep 5, 2025
049da6c
reduce gain target
phuocphn Sep 5, 2025
7935a58
update gen_specs
phuocphn Sep 5, 2025
9ac3b67
add experiment results
phuocphn Sep 5, 2025
2114647
update gain target range (800,1000)
phuocphn Sep 5, 2025
bc212a7
add experiment result
phuocphn Sep 7, 2025
a08ba2c
Merge pull request #3 from analog-ml/run/optimize-Zhenxin_S_FC_65nmPT…
phuocphn Sep 7, 2025
b9963d6
📝 Add docstrings to `run/optimize-Zhenxin_S_FC_65nmPTM_run15`
coderabbitai[bot] Sep 7, 2025
5e6fe0a
Merge pull request #5 from analog-ml/coderabbitai/docstrings/a08ba2c
phuocphn Sep 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,5 @@ marimo/_lsp/
__marimo__/

# Streamlit
.streamlit/secrets.toml
.streamlit/secrets.toml
*.out
365 changes: 365 additions & 0 deletions autockt/envs/ngspice_ledro_d_fc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
"""
A new ckt environment based on a new structure of MDP
"""

import gym
from gym import spaces

import numpy as np
import random
import psutil

from multiprocessing.dummy import Pool as ThreadPool
from collections import OrderedDict
import yaml
import yaml.constructor
import statistics
import os
import IPython
import itertools
from eval_engines.util.core import *
import pickle
import os

from eval_engines.ngspice.TwoStageClass import *
from eval_engines.ngspice.LEDRO_D_FC import *
Comment on lines +20 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove star imports and duplicate import; import only what you use.

Avoid F403/F405 collisions and the class name shadowing with local LEDRO_D_FC.

-from eval_engines.util.core import *
+# from eval_engines.util.core import debug  # if needed
@@
-from eval_engines.ngspice.TwoStageClass import *
-from eval_engines.ngspice.LEDRO_D_FC import *
+from eval_engines.ngspice.LEDRO_D_FC import LEDRO_D_FC_Class

Also drop the duplicate import os on Line 22.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.12.2)

20-20: from eval_engines.util.core import * used; unable to detect undefined names

(F403)


22-22: Redefinition of unused os from line 17

Remove definition: os

(F811)


24-24: from eval_engines.ngspice.TwoStageClass import * used; unable to detect undefined names

(F403)


25-25: from eval_engines.ngspice.LEDRO_D_FC import * used; unable to detect undefined names

(F403)

🤖 Prompt for AI Agents
In autockt/envs/ngspice_ledro_d_fc.py around lines 20 to 25, replace the
wildcard/star imports and the duplicate os import with explicit imports of only
the symbols used in this module, remove the repeated "import os" on line 22, and
avoid shadowing the local class name LEDRO_D_FC by either importing the class
under a clear alias (e.g., from eval_engines.ngspice.LEDRO_D_FC import
LEDRO_D_FC as LEDRO_D_FC_Class) or import the module and reference the class via
the module (e.g., import eval_engines.ngspice.LEDRO_D_FC as ledro_module) so
there are no F403/F405 collisions and the namespace is explicit.



from loguru import logger
import sys

# Custom format string
log_format = (
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<level>{level: <8}</level> | "
"<cyan>{module}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
"<level>{message}</level>"
)

# Clear default logger
logger.remove()

# Log to stdout
logger.add(sys.stdout, format=log_format, level="DEBUG")

# Log to file with rotation and retention
logger.add(
"logs/ngspice_ledro_d_fc45.log",
format=log_format,
level="DEBUG",
rotation="1 day",
retention="7 days",
)


# way of ordering the way a yaml file is read
class OrderedDictYAMLLoader(yaml.Loader):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use SafeLoader for YAML; avoid arbitrary object construction.

yaml.load with yaml.Loader is unsafe. Switch to SafeLoader subclass and named Loader kwarg.

-class OrderedDictYAMLLoader(yaml.Loader):
+class OrderedDictYAMLLoader(yaml.SafeLoader):
@@
-        yaml.Loader.__init__(self, *args, **kwargs)
+        yaml.SafeLoader.__init__(self, *args, **kwargs)
@@
-            yaml_data = yaml.load(f, OrderedDictYAMLLoader)
+            yaml_data = yaml.load(f, Loader=OrderedDictYAMLLoader)

Also applies to: 62-62, 111-111

🤖 Prompt for AI Agents
In autockt/envs/ngspice_ledro_d_fc.py around line 56 (and also apply the same
change at lines 62 and 111), the YAML loader subclasses yaml.Loader which allows
arbitrary object construction; change these to subclass yaml.SafeLoader and pass
the SafeLoader (named Loader=...) when calling yaml.load so that parsing uses
safe loading. Update any custom constructor registrations to register with
SafeLoader instead of yaml.Loader and replace yaml.load(..., Loader=...)
accordingly to use yaml.SafeLoader to avoid unsafe deserialization.

"""
A YAML loader that loads mappings into ordered dictionaries.
"""

def __init__(self, *args, **kwargs):
yaml.Loader.__init__(self, *args, **kwargs)

self.add_constructor("tag:yaml.org,2002:map", type(self).construct_yaml_map)
self.add_constructor("tag:yaml.org,2002:omap", type(self).construct_yaml_map)

def construct_yaml_map(self, node):
data = OrderedDict()
yield data
value = self.construct_mapping(node)
data.update(value)

def construct_mapping(self, node, deep=False):
if isinstance(node, yaml.MappingNode):
self.flatten_mapping(node)
else:
raise yaml.constructor.ConstructorError(
None,
None,
"expected a mapping node, but found %s" % node.id,
node.start_mark,
)

mapping = OrderedDict()
for key_node, value_node in node.value:
key = self.construct_object(key_node, deep=deep)
value = self.construct_object(value_node, deep=deep)
mapping[key] = value
return mapping


class LEDRO_D_FC(gym.Env):
metadata = {"render.modes": ["human"]}

PERF_LOW = -1
PERF_HIGH = 0

# obtains yaml file
path = os.getcwd()
CIR_YAML = path + "/eval_engines/ngspice/ngspice_inputs/yaml_files/ledro_d_fc.yaml"

Comment on lines +98 to +101
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make CIR_YAML path robust (don’t rely on CWD).

Use path relative to this file to work across runners and notebooks.

-    path = os.getcwd()
-    CIR_YAML = path + "/eval_engines/ngspice/ngspice_inputs/yaml_files/ledro_d_fc.yaml"
+    path = os.path.dirname(os.path.abspath(__file__))
+    CIR_YAML = os.path.abspath(
+        os.path.join(path, "../../eval_engines/ngspice/ngspice_inputs/yaml_files/ledro_d_fc.yaml")
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# obtains yaml file
path = os.getcwd()
CIR_YAML = path + "/eval_engines/ngspice/ngspice_inputs/yaml_files/ledro_d_fc.yaml"
# obtains yaml file
path = os.path.dirname(os.path.abspath(__file__))
CIR_YAML = os.path.abspath(
os.path.join(path, "../../eval_engines/ngspice/ngspice_inputs/yaml_files/ledro_d_fc.yaml")
)
🤖 Prompt for AI Agents
In autockt/envs/ngspice_ledro_d_fc.py around lines 98 to 101, the CIR_YAML path
is built from os.getcwd(), which is fragile; instead compute the YAML path
relative to this module file so it works across runners/notebooks. Replace use
of os.getcwd() with the directory of this file (e.g.
os.path.dirname(os.path.abspath(__file__)) or
pathlib.Path(__file__).resolve().parent) and construct CIR_YAML via os.path.join
(or Path / operator) to point to
"eval_engines/ngspice/ngspice_inputs/yaml_files/ledro_d_fc.yaml".

def __init__(self, env_config):
self.multi_goal = env_config.get("multi_goal", False)
self.generalize = env_config.get("generalize", False)
num_valid = env_config.get("num_valid", 50)
self.specs_save = env_config.get("save_specs", False)
self.valid = env_config.get("run_valid", False)

self.env_steps = 0
with open(LEDRO_D_FC.CIR_YAML, "r") as f:
yaml_data = yaml.load(f, OrderedDictYAMLLoader)

# design specs
if self.generalize == False:
specs = yaml_data["target_specs"]
else:
load_specs_path = (
LEDRO_D_FC.path + "/autockt/gen_specs/ngspice_specs_gen_ledro_d_fc"
)
with open(load_specs_path, "rb") as f:
specs = pickle.load(f)

Comment on lines +117 to +122
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Gating and validating pickle load.

Untrusted pickle is unsafe (S301) and path may be missing. Gate with an explicit flag and validate existence.

-            with open(load_specs_path, "rb") as f:
-                specs = pickle.load(f)
+            assert env_config.get("trust_pickle", False), \
+                "Refuse to load specs from pickle unless env_config['trust_pickle']=True"
+            if not os.path.isfile(load_specs_path):
+                raise FileNotFoundError(f"Missing specs pickle at {load_specs_path}")
+            with open(load_specs_path, "rb") as f:
+                specs = pickle.load(f)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
load_specs_path = (
LEDRO_D_FC.path + "/autockt/gen_specs/ngspice_specs_gen_ledro_d_fc"
)
with open(load_specs_path, "rb") as f:
specs = pickle.load(f)
load_specs_path = (
LEDRO_D_FC.path + "/autockt/gen_specs/ngspice_specs_gen_ledro_d_fc"
)
assert env_config.get("trust_pickle", False), \
"Refuse to load specs from pickle unless env_config['trust_pickle']=True"
if not os.path.isfile(load_specs_path):
raise FileNotFoundError(f"Missing specs pickle at {load_specs_path}")
with open(load_specs_path, "rb") as f:
specs = pickle.load(f)
🧰 Tools
🪛 Ruff (0.12.2)

121-121: pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue

(S301)

self.specs = OrderedDict(sorted(specs.items(), key=lambda k: k[0]))
if self.specs_save:
with open(
"specs_" + str(num_valid) + str(random.randint(1, 100000)), "wb"
) as f:
pickle.dump(self.specs, f)

self.specs_ideal = []
self.specs_id = list(self.specs.keys())
self.fixed_goal_idx = -1
self.num_os = len(list(self.specs.values())[0])

# param array
params = yaml_data["params"]
self.params = []
self.params_id = list(params.keys())

for value in params.values():
param_vec = np.linspace(value[0], value[1], value[2])
self.params.append(param_vec)

# initialize sim environment
self.sim_env = LEDRO_D_FC_Class(
yaml_path=LEDRO_D_FC.CIR_YAML, num_process=1, path=LEDRO_D_FC.path
)
self.action_meaning = [-1, 0, 2]
self.action_space = spaces.Tuple(
[spaces.Discrete(len(self.action_meaning))] * len(self.params_id)
)
# self.action_space = spaces.Discrete(len(self.action_meaning)**len(self.params_id))
self.observation_space = spaces.Box(
low=np.array(
[LEDRO_D_FC.PERF_LOW] * 2 * len(self.specs_id)
+ len(self.params_id) * [1]
),
high=np.array(
[LEDRO_D_FC.PERF_HIGH] * 2 * len(self.specs_id)
+ len(self.params_id) * [1]
),
)
Comment on lines +153 to +162
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix observation_space bounds and normalize parameter indices (current obs violates Box).

Spec deltas can be in [-1, 1], but high is set to 0. Param indices are raw (0..N-1) while Box limits them to [0,1]. This will break Gym checks and learning stability.

Apply these diffs:

@@
-        self.observation_space = spaces.Box(
-            low=np.array(
-                [LEDRO_D_FC.PERF_LOW] * 2 * len(self.specs_id)
-                + len(self.params_id) * [1]
-            ),
-            high=np.array(
-                [LEDRO_D_FC.PERF_HIGH] * 2 * len(self.specs_id)
-                + len(self.params_id) * [1]
-            ),
-        )
+        self.observation_space = spaces.Box(
+            low=np.concatenate(
+                [np.full(2 * len(self.specs_id), LEDRO_D_FC.PERF_LOW, dtype=np.float32),
+                 np.zeros(len(self.params_id), dtype=np.float32)]
+            ),
+            high=np.concatenate(
+                [np.full(2 * len(self.specs_id), 1.0, dtype=np.float32),
+                 np.ones(len(self.params_id), dtype=np.float32)]
+            ),
+            dtype=np.float32,
+        )
@@
-        self.ob = np.concatenate(
-            [cur_spec_norm, self.specs_ideal_norm, self.cur_params_idx]
-        )
+        self.ob = np.concatenate(
+            [cur_spec_norm, self.specs_ideal_norm, self.cur_params_idx / self.param_max_idx]
+        )
@@
-        self.ob = np.concatenate(
-            [cur_spec_norm, self.specs_ideal_norm, self.cur_params_idx]
-        )
+        self.ob = np.concatenate(
+            [cur_spec_norm, self.specs_ideal_norm, self.cur_params_idx / self.param_max_idx]
+        )

Add this once after params are built (outside selected range):

# after building self.params
self.param_max_idx = np.array([len(p) - 1 for p in self.params], dtype=np.float32)

Also applies to: 220-222, 260-262

🤖 Prompt for AI Agents
In autockt/envs/ngspice_ledro_d_fc.py around lines 153-162 (and also update the
analogous blocks at 220-222 and 260-262), the observation_space bounds are
incorrect: spec deltas must span [-1, 1] and parameter indices must be
represented in [0, 1] (not raw 0..N-1). After building self.params add:
self.param_max_idx = np.array([len(p) - 1 for p in self.params],
dtype=np.float32). Then construct observation_space lows and highs so the
spec-delta entries use [-1.0] and [1.0] (repeat for 2 * len(self.specs_id)), and
the param index entries use 0.0 and 1.0 (repeat len(self.params_id)); replace
the current arrays at each location accordingly so Gym Box bounds match
normalized parameter indices and spec delta ranges.


# initialize current param/spec observations
self.cur_specs = np.zeros(len(self.specs_id), dtype=np.float32)
self.cur_params_idx = np.zeros(len(self.params_id), dtype=np.int32)

# Get the g* (overall design spec) you want to reach
self.global_g = []
for spec in list(self.specs.values()):
self.global_g.append(float(spec[self.fixed_goal_idx]))
self.g_star = np.array(self.global_g)
self.global_g = np.array(yaml_data["normalize"])

# objective number (used for validation)
self.obj_idx = 0

def reset(self):
# if multi-goal is selected, every time reset occurs, it will select a different design spec as objective
if self.generalize == True:
if self.valid == True:
if self.obj_idx > self.num_os - 1:
self.obj_idx = 0
idx = self.obj_idx
self.obj_idx += 1
else:
idx = random.randint(0, self.num_os - 1)
self.specs_ideal = []
for spec in list(self.specs.values()):
self.specs_ideal.append(spec[idx])
self.specs_ideal = np.array(self.specs_ideal)
else:
if self.multi_goal == False:
self.specs_ideal = self.g_star
else:
idx = random.randint(0, self.num_os - 1)
self.specs_ideal = []
for spec in list(self.specs.values()):
self.specs_ideal.append(spec[idx])
self.specs_ideal = np.array(self.specs_ideal)
# print("num total:"+str(self.num_os))

# applicable only when you have multiple goals, normalizes everything to some global_g
self.specs_ideal_norm = self.lookup(self.specs_ideal, self.global_g)

# initialize current parameters
self.cur_params_idx = np.array([2] * 17)
self.cur_params_idx = np.array(
# [2, 2, 2, 2, 2, 2] + [200, 200, 200, 200, 200, 200] + [10, 10, 10, 10, 10]
[2, 2, 2, 2, 2, 2]
+ [200, 200, 200, 200, 200, 200]
+ [10, 10, 10, 10, 10]
)
Comment on lines +207 to +213
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove hard-coded 17-dim indices. Compute from YAML to avoid shape mismatches.

Hard-coding breaks as soon as param counts change.

-        self.cur_params_idx = np.array([2] * 17)
-        self.cur_params_idx = np.array(
-            # [2, 2, 2, 2, 2, 2] + [200, 200, 200, 200, 200, 200] + [10, 10, 10, 10, 10]
-            [2, 2, 2, 2, 2, 2]
-            + [200, 200, 200, 200, 200, 200]
-            + [10, 10, 10, 10, 10]
-        )
+        # start from midpoints of each discrete grid
+        self.cur_params_idx = np.array([len(p) // 2 for p in self.params], dtype=np.int32)
@@
-    env.step([2] * 17)
+    env.step([1] * len(env.params_id))

Also applies to: 359-359

🧰 Tools
🪛 Ruff (0.12.2)

210-212: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)

🤖 Prompt for AI Agents
In autockt/envs/ngspice_ledro_d_fc.py around lines 207 to 213 (and also at line
359), the code hard-codes a 17-length index array which will break when
parameter counts change; replace the literal array construction with a computed
array based on the parameter counts loaded from the YAML (e.g., read lengths of
each param group or sum of group sizes) and build the concatenated numpy array
programmatically (using list multiplication or numpy.repeat with those computed
counts) so the resulting shape is derived from the YAML config instead of fixed
constants.


self.cur_specs = self.update(self.cur_params_idx)
cur_spec_norm = self.lookup(self.cur_specs, self.global_g)
reward = self.reward(self.cur_specs, self.specs_ideal)

# observation is a combination of current specs distance from ideal, ideal spec, and current param vals
self.ob = np.concatenate(
[cur_spec_norm, self.specs_ideal_norm, self.cur_params_idx]
)
return self.ob

def step(self, action):
"""
:param action: is vector with elements between 0 and 1 mapped to the index of the corresponding parameter
:return:
"""

# Take action that RL agent returns to change current params
action = list(np.reshape(np.array(action), (np.array(action).shape[0],)))
self.cur_params_idx = self.cur_params_idx + np.array(
[self.action_meaning[a] for a in action]
)

# self.cur_params_idx = self.cur_params_idx + np.array(self.action_arr[int(action)])
self.cur_params_idx = np.clip(
self.cur_params_idx,
[0] * len(self.params_id),
[(len(param_vec) - 1) for param_vec in self.params],
)

# Get current specs and normalize
self.cur_specs = self.update(self.cur_params_idx)
cur_spec_norm = self.lookup(self.cur_specs, self.global_g)
reward = self.reward(self.cur_specs, self.specs_ideal)
done = False

# incentivize reaching goal state
if reward >= 10:
done = True
print("-" * 10)
print("params = ", self.cur_params_idx)
print("specs:", self.cur_specs)
print("ideal specs:", self.specs_ideal)
print("re:", reward)
print("-" * 10)

Comment on lines +250 to +259
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Episode never terminates with current reward sign.

reward() returns a negative penalty, but done triggers on reward >= 10, which is unreachable.

-        if reward >= 10:
+        # done when total penalty magnitude is within tolerance
+        if -reward <= self.success_tolerance:
             done = True
-            print("-" * 10)
-            print("params = ", self.cur_params_idx)
-            print("specs:", self.cur_specs)
-            print("ideal specs:", self.specs_ideal)
-            print("re:", reward)
-            print("-" * 10)
+            logger.info("-" * 10)
+            logger.info(f"params = {self.cur_params_idx}")
+            logger.info(f"specs: {self.cur_specs}")
+            logger.info(f"ideal specs: {self.specs_ideal}")
+            logger.info(f"reward: {reward}")
+            logger.info("-" * 10)

Also define a tolerance in init (outside selected range):

self.success_tolerance = float(env_config.get("success_tolerance", 0.02))
🤖 Prompt for AI Agents
In autockt/envs/ngspice_ledro_d_fc.py around lines 250-259, the episode never
terminates because reward() returns a negative penalty but the code tests reward
>= 10 (unreachable); change the termination check to match the negative-sign
convention (for example use reward <= -10 or equivalently -reward >= 10) and
keep the same logging, and add a success tolerance attribute in __init__
(outside the selected range) by setting self.success_tolerance =
float(env_config.get("success_tolerance", 0.02)) so the tolerance is
configurable.

self.ob = np.concatenate(
[cur_spec_norm, self.specs_ideal_norm, self.cur_params_idx]
)
self.env_steps = self.env_steps + 1
print("***cur params idx:", self.cur_params_idx, "specs: ", self.cur_specs, " reward: ", reward)

# print('cur ob:' + str(self.cur_specs))
# print('ideal spec:' + str(self.specs_ideal))
# print(reward)
return self.ob, reward, done, {}

def lookup(self, spec, goal_spec):
goal_spec = [float(e) for e in goal_spec]
norm_spec = (spec - goal_spec) / (goal_spec + spec)
return norm_spec

def reward(self, spec, goal_spec):
"""
Reward: doesn't penalize for overshooting spec, is negative
"""
# rel_specs = self.lookup(spec, goal_spec)
# pos_val = []
# reward = 0.0
# for i, rel_spec in enumerate(rel_specs):
# if self.specs_id[i] == "ibias_max":
# rel_spec = rel_spec * -1.0 # /10.0
# if rel_spec < 0:
# reward += rel_spec
# pos_val.append(0)
# else:
# pos_val.append(1)

# return reward if reward < -0.02 else 10
norm_specs = self.lookup(spec, goal_spec)

# pay attention to reward calculation, this is not quite the reward function in RL
# but rather a penalty value for the optimization process
reward = 0
for i, rel_spec in enumerate(norm_specs):
# For power, smaller is better
# For gain, larger (compared to the target/goal) is better
# For other specs (pm, ugbw, etc.), smaller is better
assert self.specs_id[i] in ["ibias_max", "gain_min", "ugbw_min", "phm_min"]
if self.specs_id[i] == "ibias_max" and rel_spec > 0:
reward += np.abs(rel_spec) # /10
elif self.specs_id[i] == "gain_min" and rel_spec < 0:
reward += 3 * np.abs(rel_spec) # /10
elif self.specs_id[i] != "ibias_max" and rel_spec < 0:
reward += np.abs(rel_spec)
return -reward


def update(self, params_idx):
"""

:param action: an int between 0 ... n-1
:return:
"""

params = [self.params[i][params_idx[i]] for i in range(len(self.params_id))]
param_val = [OrderedDict(list(zip(self.params_id, params)))]

# run param vals and simulate
cur_specs = OrderedDict(
sorted(
self.sim_env.create_design_and_simulate(param_val[0])[1].items(),
key=lambda k: k[0],
)
)
cur_specs = np.array(list(cur_specs.values()))

return cur_specs


def main():
env_config = {"generalize": True, "valid": True}
env = LEDRO_D_FC(env_config)
env.reset()
# env.step(
# [
# 2,
# 2,
# 2,
# 2,
# 2,
# 2,
# 10 - 9,
# 10 - 9,
# 10 - 9,
# 10 - 9,
# 10 - 9,
# 10 - 9,
# 0.2,
# 0.2,
# 0.2,
# 0.2,
# 0.2,
# ]
# )
env.step([2] * 17)

IPython.embed()


if __name__ == "__main__":
main()
Loading