### Install Spinning Up Source: https://spinningup.openai.com/en/latest/user/installation.html Clones the Spinning Up repository from GitHub and installs it in editable mode using pip. This allows for development and modification of the Spinning Up code directly. ```bash git clone https://github.com/openai/spinningup.git cd spinningup pip install -e . ``` -------------------------------- ### Install MuJoCo Environments Source: https://spinningup.openai.com/en/latest/user/installation.html Installs the MuJoCo physics engine and related Gym environments, which are optional but recommended for certain reinforcement learning tasks. This command installs the necessary Gym packages. ```bash pip install gym[mujoco,robotics] ``` -------------------------------- ### Check Spinning Up Installation Source: https://spinningup.openai.com/en/latest/user/installation.html Runs a test case using the PPO algorithm on the LunarLander-v2 environment to verify the Spinning Up installation. This includes training, watching the policy, and plotting results. ```python python -m spinup.run ppo --hid "[32,32]" --env LunarLander-v2 --exp_name installtest --gamma 0.999 python -m spinup.run test_policy data/installtest/installtest_s0 python -m spinup.run plot data/installtest/installtest_s0 ``` -------------------------------- ### Example: Run PPO Algorithm (Python) Source: https://spinningup.openai.com/en/latest/user/running.html An example demonstrating how to run the Proximal Policy Optimization (PPO) algorithm from the command line. This specific command targets the Walker2d environment and names the experiment 'walker'. It utilizes the `spinup.run` script for execution. ```bash python -m spinup.run ppo --env Walker2d-v2 --exp_name walker ``` -------------------------------- ### Install OpenMPI on Ubuntu Source: https://spinningup.openai.com/en/latest/user/installation.html Installs the OpenMPI development libraries on Ubuntu systems using the apt package manager. OpenMPI is a requirement for distributed computing tasks within Spinning Up. ```bash sudo apt-get update && sudo apt-get install libopenmpi-dev ``` -------------------------------- ### Test MuJoCo Installation Source: https://spinningup.openai.com/en/latest/user/installation.html Runs a test case with PPO on the Walker2d-v2 environment to verify the successful installation of MuJoCo and its associated Gym environments. This confirms that MuJoCo-dependent functionalities are working correctly. ```python python -m spinup.run ppo --hid "[32,32]" --env Walker2d-v2 --exp_name mujocotest ``` -------------------------------- ### Initialize PPO Training Environment Source: https://spinningup.openai.com/en/latest/_modules/spinup/algos/pytorch/ppo/ppo.html Sets up the training environment, logger, and random seeds for the PPO algorithm. It includes MPI-aware PyTorch setup and configuration logging. ```python setup_pytorch_for_mpi() logger = EpochLogger(**logger_kwargs) logger.save_config(locals()) seed += 10000 * proc_id() torch.manual_seed(seed) np.random.seed(seed) env = env_fn() obs_dim = env.observation_space.shape act_dim = env.action_space.shape ``` -------------------------------- ### ExperimentGrid Example Usage Source: https://spinningup.openai.com/en/latest/_modules/spinup/utils/run_utils.html This is a test function demonstrating how to create and populate an ExperimentGrid. It shows adding parameters with nested keys, simple keys, fixed values, and parameters that are included in the experiment name. ```python def test_eg(): eg = ExperimentGrid() eg.add('test:a', [1,2,3], 'ta', True) eg.add('test:b', [1,2,3]) eg.add('some', [4,5]) eg.add('why', [True,False]) eg.add('huh', 5) eg.add('no', 6, in_name=True) return eg.variants() ``` -------------------------------- ### Setup Logger Keyword Arguments for Experiments Source: https://spinningup.openai.com/en/latest/_modules/spinup/utils/run_utils.html Configures the output directory for a logger based on experiment name, seed, and datestamp preferences. It supports default data directories and forces datestamping if configured globally. Returns a dictionary containing the output directory and experiment name. ```python from spinup.user_config import DEFAULT_DATA_DIR, FORCE_DATESTAMP from spinup.utils.logx import colorize import os.path as osp import time DIV_LINE_WIDTH = 80 def setup_logger_kwargs(exp_name, seed=None, data_dir=None, datestamp=False): """ Sets up the output_dir for a logger and returns a dict for logger kwargs. If no seed is given and datestamp is false, :: output_dir = data_dir/exp_name If a seed is given and datestamp is false, :: output_dir = data_dir/exp_name/exp_name_s[seed] If datestamp is true, amend to :: output_dir = data_dir/YY-MM-DD_exp_name/YY-MM-DD_HH-MM-SS_exp_name_s[seed] You can force datestamp=True by setting ``FORCE_DATESTAMP=True`` in ``spinup/user_config.py``. Args: exp_name (string): Name for experiment. seed (int): Seed for random number generators used by experiment. data_dir (string): Path to folder where results should be saved. Default is the ``DEFAULT_DATA_DIR`` in ``spinup/user_config.py``. datestamp (bool): Whether to include a date and timestamp in the name of the save directory. Returns: logger_kwargs, a dict containing output_dir and exp_name. """ # Datestamp forcing datestamp = datestamp or FORCE_DATESTAMP # Make base path ymd_time = time.strftime("%Y-%m-%d_") if datestamp else '' relpath = ''.join([ymd_time, exp_name]) if seed is not None: # Make a seed-specific subfolder in the experiment directory. if datestamp: hms_time = time.strftime("%Y-%m-%d_%H-%M-%S") subfolder = ''.join([hms_time, '-', exp_name, '_s', str(seed)]) else: subfolder = ''.join([exp_name, '_s', str(seed)]) relpath = osp.join(relpath, subfolder) data_dir = data_dir or DEFAULT_DATA_DIR logger_kwargs = dict(output_dir=osp.join(data_dir, relpath), exp_name=exp_name) return logger_kwargs ``` -------------------------------- ### Setup Logger Output Directory - Python Source: https://spinningup.openai.com/en/latest/utils/run_utils.html The `setup_logger_kwargs` function configures the output directory for experiment logs based on the experiment name, seed, data directory, and whether to include a datestamp. It returns a dictionary containing the `output_dir` and `exp_name`. ```python def setup_logger_kwargs(_exp_name , _seed=None , _data_dir=None , _datestamp=False): """ Sets up the output_dir for a logger and returns a dict for logger kwargs. If no seed is given and datestamp is false, output_dir = data_dir/exp_name If a seed is given and datestamp is false, output_dir = data_dir/exp_name/exp_name_s[seed] If datestamp is true, amend to output_dir = data_dir/YY-MM-DD_exp_name/YY-MM-DD_HH-MM-SS_exp_name_s[seed] You can force datestamp=True by setting `FORCE_DATESTAMP=True` in `spinup/user_config.py`. Parameters: exp_name (string) – Name for experiment. seed (int) – Seed for random number generators used by experiment. data_dir (string) – Path to folder where results should be saved. Default is the `DEFAULT_DATA_DIR` in `spinup/user_config.py`. datestamp (bool) – Whether to include a date and timestamp in the name of the save directory. Returns: logger_kwargs, a dict containing output_dir and exp_name. """ pass ``` -------------------------------- ### TD3 Actor-Critic Network Initialization and Setup Source: https://spinningup.openai.com/en/latest/_modules/spinup/algos/pytorch/td3/td3.html Initializes the actor-critic module, target networks, and associated parameters for the TD3 algorithm. It sets up the replay buffer and logs the number of parameters in the policy and Q-networks. Dependencies include PyTorch and NumPy. ```python logger = EpochLogger(**logger_kwargs) logger.save_config(locals()) torch.manual_seed(seed) np.random.seed(seed) env, test_env = env_fn(), env_fn() obs_dim = env.observation_space.shape act_dim = env.action_space.shape[0] # Action limit for clamping: critically, assumes all dimensions share the same bound! act_limit = env.action_space.high[0] # Create actor-critic module and target networks ac = actor_critic(env.observation_space, env.action_space, **ac_kwargs) ac_targ = deepcopy(ac) # Freeze target networks with respect to optimizers (only update via polyak averaging) for p in ac_targ.parameters(): p.requires_grad = False # List of parameters for both Q-networks (save this for convenience) q_params = itertools.chain(ac.q1.parameters(), ac.q2.parameters()) # Experience buffer replay_buffer = ReplayBuffer(obs_dim=obs_dim, act_dim=act_dim, size=replay_size) # Count variables (protip: try to get a feel for how different size networks behave!) var_counts = tuple(core.count_vars(module) for module in [ac.pi, ac.q1, ac.q2]) logger.log('\nNumber of parameters: \t pi: %d, \t q1: %d, \t q2: %d\n'%var_counts) # Set up model saving logger.setup_pytorch_saver(ac) ``` -------------------------------- ### DDPG Initialization and Setup (PyTorch) Source: https://spinningup.openai.com/en/latest/_modules/spinup/algos/pytorch/ddpg/ddpg.html Initializes the DDPG agent, including the actor-critic networks, target networks, replay buffer, and logger. It sets up random seeds and determines observation and action dimensions from the environment. ```python logger = EpochLogger(**logger_kwargs) logger.save_config(locals()) torch.manual_seed(seed) np.random.seed(seed) env, test_env = env_fn(), env_fn() obs_dim = env.observation_space.shape act_dim = env.action_space.shape[0] # Action limit for clamping: critically, assumes all dimensions share the same bound! act_limit = env.action_space.high[0] # Create actor-critic module and target networks ac = actor_critic(env.observation_space, env.action_space, **ac_kwargs) ac_targ = deepcopy(ac) # Freeze target networks with respect to optimizers (only update via polyak averaging) for p in ac_targ.parameters(): p.requires_grad = False # Experience buffer replay_buffer = ReplayBuffer(obs_dim=obs_dim, act_dim=act_dim, size=replay_size) # Count variables (protip: try to get a feel for how different size networks behave!) var_counts = tuple(core.count_vars(module) for module in [ac.pi, ac.q]) logger.log('\nNumber of parameters: \t pi: %d, \t q: %d\n'%var_counts) # Set up model saving logger.setup_pytorch_saver(ac) ``` -------------------------------- ### Loading and Using Saved PyTorch Actor-Critic Model Source: https://spinningup.openai.com/en/latest/algorithms/ppo.html Demonstrates how to load a saved PyTorch actor-critic model and use its 'act' method to get actions for given observations. The loaded model is expected to have the properties defined in the ppo_pytorch docstring. ```python import torch ac = torch.load('path/to/model.pt') # obs is a numpy array of observations actions = ac.act(torch.as_tensor(obs, dtype=torch.float32)) ``` -------------------------------- ### Initialize TRPO/NPG Agent Environment Source: https://spinningup.openai.com/en/latest/_modules/spinup/algos/tf1/trpo/trpo.html Initializes the agent environment, sets random seeds for reproducibility, and configures the logger. This setup is required before starting the training loop for TRPO or NPG algorithms. ```python logger = EpochLogger(**logger_kwargs) logger.save_config(locals()) seed += 10000 * proc_id() tf.set_random_seed(seed) np.random.seed(seed) env = env_fn() obs_dim = env.observation_space.shape act_dim = env.action_space.shape ``` -------------------------------- ### Initialize and Configure Logger Source: https://spinningup.openai.com/en/latest/utils/logger.html Demonstrates how to instantiate the EpochLogger and save experiment configuration parameters using the local scope. ```python from spinup.utils.logx import EpochLogger logger = EpochLogger(**logger_kwargs) logger.save_config(locals()) ``` -------------------------------- ### Install OpenMPI on Mac OS X Source: https://spinningup.openai.com/en/latest/user/installation.html Installs OpenMPI on Mac OS X using the Homebrew package manager. This command assumes Homebrew is already installed and configured on the system. ```bash brew install openmpi ``` -------------------------------- ### Activate Conda Environment Source: https://spinningup.openai.com/en/latest/user/installation.html Activates the 'spinningup' conda environment, making its Python interpreter and installed packages available for use. This command is essential before running any Spinning Up related scripts or installations. ```bash conda activate spinningup ``` -------------------------------- ### Launch experiments via command line with custom suffixes Source: https://spinningup.openai.com/en/latest/user/running.html Demonstrates how to run experiments using spinup.run with user-supplied shorthands for hyperparameters. The command generates folder suffixes based on varying parameters to organize experiment results. ```bash python -m spinup.run ddpg_tf1 --env Hopper-v2 --hid[h] [300] [128,128] --act tf.nn.tanh tf.nn.relu ``` -------------------------------- ### TensorFlow Broadcasting Example Source: https://spinningup.openai.com/en/latest/spinningup/exercise2_2_soln.html Demonstrates how TensorFlow handles broadcasting between tensors of shapes [batch size] and [batch size, 1]. This example illustrates that operations like multiplication and addition can result in unexpected shape expansions, leading to errors in calculations like the Bellman backup. ```python import tensorflow as tf import numpy as np x = tf.constant(np.arange(5)) y = tf.constant(np.arange(5).reshape(-1,1)) z1 = x * y z2 = x + y z3 = x + z1 sess = tf.InteractiveSession() print(sess.run(z1)) print(sess.run(z2)) print(sess.run(z3)) ``` -------------------------------- ### Utility Functions for MPI TensorFlow Source: https://spinningup.openai.com/en/latest/_modules/spinup/utils/mpi_tf.html Provides functions for flattening and reshaping TensorFlow tensors, and synchronizing parameters across MPI processes. These are essential for distributed training setups. ```python import numpy as np import tensorflow as tf from mpi4py import MPI from spinup.utils.mpi_tools import broadcast def flat_concat(xs): return tf.concat([tf.reshape(x,(-1,)) for x in xs], axis=0) def assign_params_from_flat(x, params): flat_size = lambda p : int(np.prod(p.shape.as_list())) # the 'int' is important for scalars splits = tf.split(x, [flat_size(p) for p in params]) new_params = [tf.reshape(p_new, p.shape) for p, p_new in zip(params, splits)] return tf.group([tf.assign(p, p_new) for p, p_new in zip(params, new_params)]) def sync_params(params): get_params = flat_concat(params) def _broadcast(x): broadcast(x) return x synced_params = tf.py_func(_broadcast, [get_params], tf.float32) return assign_params_from_flat(synced_params, params) def sync_all_params(): """Sync all tf variables across MPI processes.""" return sync_params(tf.global_variables()) ``` -------------------------------- ### Setup TensorFlow Saver for Logging Source: https://spinningup.openai.com/en/latest/_modules/spinup/algos/tf1/ppo/ppo.html Configures the TensorFlow saver to log specific inputs and outputs during training. This is crucial for monitoring and debugging the training process. ```python logger.setup_tf_saver(sess, inputs={'x': x_ph}, outputs={'pi': pi, 'v': v}) ``` -------------------------------- ### Launch Algorithm from Command Line (Python) Source: https://spinningup.openai.com/en/latest/user/running.html This command launches a specified reinforcement learning algorithm using the `spinup.run` utility. It allows for setting experiment flags to control hyperparameters, environments, and other run configurations. The default behavior may vary based on the algorithm and user configuration. ```bash python -m spinup.run [algo name] [experiment flags] ``` -------------------------------- ### Create Conda Python Environment Source: https://spinningup.openai.com/en/latest/user/installation.html Creates a new conda environment named 'spinningup' with Python 3.6, which is recommended for organizing packages used in Spinning Up. This ensures package isolation and reproducibility. ```bash conda create -n spinningup python=3.6 ``` -------------------------------- ### Calculate Reward-to-Go (Python) Source: https://spinningup.openai.com/en/latest/spinningup/extra_tf_pg_implementation.html This function calculates the reward-to-go for a given sequence of rewards. It iterates backwards through the rewards, accumulating the sum of future rewards for each time step. ```python def reward_to_go(rews): n = len(rews) rtgs = np.zeros_like(rews) for i in reversed(range(n)): rtgs[i] = rews[i] + (rtgs[i+1] if i+1 < n else 0) return rtgs ``` -------------------------------- ### Import and execute algorithms from Python scripts Source: https://spinningup.openai.com/en/latest/user/running.html Shows how to import algorithm functions directly from the spinup package and execute them with custom environment configurations, actor-critic arguments, and logger settings. ```python from spinup import ppo_tf1 as ppo import tensorflow as tf import gym env_fn = lambda : gym.make('LunarLander-v2') ac_kwargs = dict(hidden_sizes=[64,64], activation=tf.nn.relu) logger_kwargs = dict(output_dir='path/to/output_dir', exp_name='experiment_name') ppo(env_fn=env_fn, ac_kwargs=ac_kwargs, steps_per_epoch=5000, epochs=250, logger_kwargs=logger_kwargs) ``` -------------------------------- ### Advanced Command Line Execution (Python) Source: https://spinningup.openai.com/en/latest/user/running.html This command showcases advanced usage of `spinup.run` for executing the PPO algorithm with multiple configurations. It specifies the environment, experiment name, multiple values for hyperparameters like clip ratio and hidden layer sizes, activation functions, random seeds, and a custom data directory. ```bash python -m spinup.run ppo --exp_name ppo_ant --env Ant-v2 --clip_ratio 0.1 0.2 \ --hid[h] [32,32] [64,32] --act torch.nn.Tanh --seed 0 10 20 --dt \ --data_dir path/to/data ``` -------------------------------- ### PPO PyTorch Implementation Documentation Source: https://spinningup.openai.com/en/latest/algorithms/ppo.html Documentation for the PyTorch implementation of Proximal Policy Optimization (PPO). It details the function signature, parameters, and expected structure for the actor-critic model. The function trains a policy using an on-policy approach with clipping for stability. ```python spinup.ppo_pytorch( env_fn, actor_critic=, ac_kwargs={}, seed=0, steps_per_epoch=4000, epochs=50, gamma=0.99, clip_ratio=0.2, pi_lr=0.0003, vf_lr=0.001, train_pi_iters=80, train_v_iters=80, lam=0.97, max_ep_len=1000, target_kl=0.01, logger_kwargs={}, save_freq=10 ) ``` -------------------------------- ### Run Experiment Entrypoint Source: https://spinningup.openai.com/en/latest/_modules/spinup/utils/run_utils.html This function prepares and launches an experiment script. It serializes a callable object (thunk) using cloudpickle, compresses it with zlib, and encodes it in base64. This encoded thunk is then passed as a command-line argument to a Python entrypoint script, which will deserialize and execute the experiment. ```python def thunk_plus(): # Make 'env_fn' from 'env_name' if 'env_name' in kwargs: import gym env_name = kwargs['env_name'] kwargs['env_fn'] = lambda : gym.make(env_name) del kwargs['env_name'] # Fork into multiple processes mpi_fork(num_cpu) # Run thunk thunk(**kwargs) # Prepare to launch a script to run the experiment pickled_thunk = cloudpickle.dumps(thunk_plus) encoded_thunk = base64.b64encode(zlib.compress(pickled_thunk)).decode('utf-8') entrypoint = osp.join(osp.abspath(osp.dirname(__file__)),'run_entrypoint.py') cmd = [sys.executable if sys.executable else 'python', entrypoint, encoded_thunk] try: subprocess.check_call(cmd, env=os.environ) except CalledProcessError: err_msg = '\n'*3 + '='*DIV_LINE_WIDTH + '\n' + dedent(""" There appears to have been an error in your experiment. Check the traceback above to see what actually went wrong. The traceback below, included for completeness (but probably not useful for diagnosing the error), shows the stack leading up to the experiment launch. """) + '='*DIV_LINE_WIDTH + '\n'*3 print(err_msg) raise # Tell the user about where results are, and how to check them logger_kwargs = kwargs['logger_kwargs'] plot_cmd = 'python -m spinup.run plot '+logger_kwargs['output_dir'] plot_cmd = colorize(plot_cmd, 'green') test_cmd = 'python -m spinup.run test_policy '+logger_kwargs['output_dir'] test_cmd = colorize(test_cmd, 'green') output_msg = '\n'*5 + '='*DIV_LINE_WIDTH + '\n' + dedent(""" End of experiment. Plot results from this run with: %s Watch the trained agent with: %s """%(plot_cmd,test_cmd)) + '='*DIV_LINE_WIDTH + '\n'*5 print(output_msg) ``` -------------------------------- ### EpochLogger Get Statistics Source: https://spinningup.openai.com/en/latest/_modules/spinup/utils/logx.html Allows an algorithm to retrieve the mean, standard deviation, minimum, and maximum values of a specific diagnostic that has been logged by the EpochLogger. This is useful for algorithms that need to access historical performance metrics. ```python def get_stats(self, key): """ Lets an algorithm ask the logger for mean/std/min/max of a diagnostic. """ ``` -------------------------------- ### POST /vpg_tf1 Source: https://spinningup.openai.com/en/latest/algorithms/vpg.html Initializes and runs the Vanilla Policy Gradient algorithm using the provided environment and configuration parameters. ```APIDOC ## POST /vpg_tf1 ### Description Initializes and executes the Vanilla Policy Gradient (VPG) training process with GAE-Lambda for advantage estimation. ### Method POST ### Endpoint spinup.vpg_tf1 ### Parameters #### Request Body - **env_fn** (function) - Required - A function which creates a copy of the environment (OpenAI Gym API). - **actor_critic** (function) - Optional - Function defining the computation graph (default: mlp_actor_critic). - **ac_kwargs** (dict) - Optional - Keyword arguments for the actor_critic function. - **seed** (int) - Optional - Seed for random number generators (default: 0). - **steps_per_epoch** (int) - Optional - Interaction steps per epoch (default: 4000). - **epochs** (int) - Optional - Number of training epochs (default: 50). - **gamma** (float) - Optional - Discount factor (default: 0.99). - **pi_lr** (float) - Optional - Learning rate for policy optimizer (default: 0.0003). - **vf_lr** (float) - Optional - Learning rate for value function optimizer (default: 0.001). - **train_v_iters** (int) - Optional - Gradient steps for value function per epoch (default: 80). - **lam** (float) - Optional - Lambda for GAE-Lambda (default: 0.97). - **max_ep_len** (int) - Optional - Maximum trajectory length (default: 1000). - **save_freq** (int) - Optional - Frequency of saving the model (default: 10). ### Request Example { "env_fn": "lambda: gym.make('CartPole-v0')", "epochs": 50, "gamma": 0.99 } ### Response #### Success Response (200) - **status** (string) - Training completion status. - **model_path** (string) - Path to the saved computation graph. #### Response Example { "status": "success", "model_path": "/data/models/vpg_run_01" } ``` -------------------------------- ### Optimizer Setup for SAC Source: https://spinningup.openai.com/en/latest/_modules/spinup/algos/pytorch/sac/sac.html Sets up Adam optimizers for the actor (policy) and the critic (Q-functions) in the SAC algorithm. It initializes the optimizers with the specified learning rate (lr) and associates them with the respective network parameters. ```python # Set up optimizers for policy and q-function pi_optimizer = Adam(ac.pi.parameters(), lr=lr) q_optimizer = Adam(q_params, lr=lr) ``` -------------------------------- ### Log Training Metrics with Logger Source: https://spinningup.openai.com/en/latest/_modules/spinup/algos/pytorch/ppo/ppo.html This snippet demonstrates how to log various training metrics such as loss, entropy, and time using the logger utility. It assumes a 'logger' object is available and configured to dump tabular data. Metrics logged include LossV, DeltaLossPi, DeltaLossV, Entropy, KL, ClipFrac, StopIter, and Time. ```python logger.log_tabular('LossV', average_only=True) logger.log_tabular('DeltaLossPi', average_only=True) logger.log_tabular('DeltaLossV', average_only=True) logger.log_tabular('Entropy', average_only=True) logger.log_tabular('KL', average_only=True) logger.log_tabular('ClipFrac', average_only=True) logger.log_tabular('StopIter', average_only=True) logger.log_tabular('Time', time.time()-start_time) logger.dump_tabular() ``` -------------------------------- ### Store and Log Epoch Metrics Source: https://spinningup.openai.com/en/latest/utils/logger.html Shows the workflow for tracking metrics during an epoch using store() and finalizing them with log_tabular() for reporting. ```python # During training loop epoch_logger.store(NameOfQuantity=quantity_value) # At the end of the epoch epoch_logger.log_tabular("NameOfQuantity", average_only=True) epoch_logger.dump_tabular() ``` -------------------------------- ### Initialize VPG Training Environment Source: https://spinningup.openai.com/en/latest/_modules/spinup/algos/pytorch/vpg/vpg.html Initializes the VPG training process by setting up the logger, random seeds, environment, and the actor-critic module. It also synchronizes parameters across processes and calculates variable counts. ```python setup_pytorch_for_mpi() logger = EpochLogger(**logger_kwargs) logger.save_config(locals()) seed += 10000 * proc_id() torch.manual_seed(seed) np.random.seed(seed) env = env_fn() obs_dim = env.observation_space.shape act_dim = env.action_space.shape ac = actor_critic(env.observation_space, env.action_space, **ac_kwargs) sync_params(ac) var_counts = tuple(core.count_vars(module) for module in [ac.pi, ac.v]) logger.log('\nNumber of parameters: \t pi: %d, \t v: %d\n'%var_counts) ```