stdlib: Add Vector2d to PyStats

Change-Id: Icb2f691abf88ef4bac8d277e421329edb000209b
This commit is contained in:
Bobby R. Bruce
2024-03-24 15:57:30 -07:00
parent a3af819d82
commit 6ae3692057
9 changed files with 408 additions and 1 deletions

View File

@@ -142,6 +142,64 @@ class Vector(Statistic):
return sum(float(self.value[key]) for key in self.values)
class Vector2d(Statistic):
"""
A 2D vector of scalar values.
"""
value: Dict[Union[str, int, float], Vector]
def __init__(
self,
value: Dict[Union[str, int, float], Vector],
type: Optional[str] = None,
description: Optional[str] = None,
):
assert (
len({vector.size() for vector in value.values()}) == 1
), "All the Vectors in the 2d Vector are not of equal length."
super().__init__(
value=value,
type=type,
description=description,
)
def x_size(self) -> int:
"""Returns the number of elements in the x dimension."""
assert self.value is not None
return len(self.value)
def y_size(self) -> int:
"""Returns the number of elements in the y dimension."""
assert self.value is not None
return len(self.value[0])
def size(self) -> int:
"""Returns the total number of elements."""
return self.x_size() * self.y_size()
def total(self) -> int:
"""The total (sum) of all the entries in the 2d vector/"""
assert self.value is not None
total = 0
for vector in self.value.values():
for scalar in vector.values():
total += scalar.value
return total
def __getitem__(self, index: Union[str, int, float]) -> Vector:
assert self.value is not None
# In the case of string, we cast strings to integers of floats if they
# are numeric. This avoids users having to cast strings to integers.
if isinstance(index, str):
if index.isindex():
index = int(index)
elif index.isnumeric():
index = float(index)
return self.value[index]
class Distribution(Vector):
"""
A statistic type that stores information relating to distributions. Each

View File

@@ -138,6 +138,8 @@ def __get_statistic(statistic: _m5.stats.Info) -> Optional[Statistic]:
pass
elif isinstance(statistic, _m5.stats.VectorInfo):
return __get_vector(statistic)
elif isinstance(statistic, _m5.stats.Vector2dInfo):
return __get_vector2d(statistic)
return None
@@ -193,7 +195,6 @@ def __get_distribution(statistic: _m5.stats.DistInfo) -> Distribution:
def __get_vector(statistic: _m5.stats.VectorInfo) -> Vector:
vec: Dict[Union[str, int, float], Scalar] = {}
for index in range(statistic.size):
# All the values in a Vector are Scalar values
value = statistic.value[index]
@@ -231,6 +232,42 @@ def __get_vector(statistic: _m5.stats.VectorInfo) -> Vector:
)
def __get_vector2d(statistic: _m5.stats.Vector2dInfo) -> Vector2d:
# All the values in a 2D Vector are Scalar values
description = statistic.desc
x_size = statistic.x_size
y_size = statistic.y_size
vector_rep: Dict[Union[str, int, float], Vector] = {}
for x_index in range(x_size):
x_index_string = x_index
if x_index in statistic.subnames:
x_index_string = str(statistic.subnames[x_index])
x_desc = description
if x_index in statistic.subdescs:
x_desc = str(statistic.subdescs[x_index])
x_vec: Dict[str, Scalar] = {}
for y_index in range(y_size):
y_index_val = y_index
if y_index in statistic.ysubnames:
y_index_val = str(statistic.subnames[y_index])
x_vec[y_index_val] = Scalar(
value=statistic.value[x_index * y_size + y_index],
unit=statistic.unit,
datatype=StorageType["f64"],
)
vector_rep[x_index_string] = Vector(
x_vec,
type="Vector",
description=x_desc,
)
return Vector2d(value=vector_rep, type="Vector2d", description=description)
def _prepare_stats(group: _m5.stats.Group):
"""
Prepares the statistics for dumping.

View File

@@ -76,6 +76,7 @@ cast_stat_info(const statistics::Info *info)
*/
TRY_CAST(statistics::FormulaInfo);
TRY_CAST(statistics::VectorInfo);
TRY_CAST(statistics::Vector2dInfo);
TRY_CAST(statistics::DistInfo);
return py::cast(info);
@@ -183,6 +184,17 @@ pybind_init_stats(py::module_ &m_native)
[](const statistics::VectorInfo &info) { return info.total(); })
;
py::class_<statistics::Vector2dInfo, statistics::Info,
std::unique_ptr<statistics::Vector2dInfo, py::nodelete>>(
m, "Vector2dInfo")
.def_readonly("x_size", &statistics::Vector2dInfo::x)
.def_readonly("y_size", &statistics::Vector2dInfo::y)
.def_readonly("subnames", &statistics::Vector2dInfo::subnames)
.def_readonly("subdescs", &statistics::Vector2dInfo::subdescs)
.def_readonly("ysubnames", &statistics::Vector2dInfo::y_subnames)
.def_readonly("value", &statistics::Vector2dInfo::cvec)
;
py::class_<statistics::FormulaInfo, statistics::VectorInfo,
std::unique_ptr<statistics::FormulaInfo, py::nodelete>>(
m, "FormulaInfo")

View File

@@ -31,5 +31,6 @@ if env['CONF']['USE_TEST_OBJECTS']:
'StatTester',
'ScalarStatTester',
'VectorStatTester',
'Vector2dStatTester',
])
Source('stat_tester.cc')

View File

@@ -62,3 +62,28 @@ class VectorStatTester(StatTester):
"The vector stat's subdescriptions. If empty, the subdescriptions "
"are not used.",
)
class Vector2dStatTester(StatTester):
type = "Vector2dStatTester"
cxx_header = "test_objects/stat_tester.hh"
cxx_class = "gem5::Vector2dStatTester"
x_size = Param.Int("The number of elements in the x dimension.")
y_size = Param.Int("The number of elements in the y dimension.")
values = VectorParam.Float("The vector stat's values, flattened.")
subnames = VectorParam.String(
[],
"The vector stat's subnames. If position is empty, index int is "
"used instead.",
)
subdescs = VectorParam.String(
[],
"The vector stat's subdescriptions. If empty, the subdescriptions "
"are not used.",
)
ysubnames = VectorParam.String(
[],
"The vector stat's y subdescriptions. If empty, the subdescriptions ",
)

View File

@@ -84,4 +84,53 @@ VectorStatTester::VectorStatTesterStats::VectorStatTesterStats(
}
}
void
Vector2dStatTester::setStats()
{
for (int i = 0; i < params.x_size; i++)
{
for (int j = 0; j < params.y_size; j++)
{
stats.vector2d[i][j] = (params.values[j + i * params.y_size]);
}
}
}
Vector2dStatTester::Vector2dStatTesterStats::Vector2dStatTesterStats(
statistics::Group *parent,
const Vector2dStatTesterParams &params
) : statistics::Group(parent),
vector2d(this,
params.name.c_str(),
statistics::units::Count::get(),
params.description.c_str()
)
{
vector2d.init(params.x_size, params.y_size);
assert(params.x_size * params.y_size == params.values.size());
for (int i = 0; i < params.x_size; i++)
{
if (params.subnames.size() > i) {
vector2d.subname(i, params.subnames[i]);
} else {
vector2d.subname(i, std::to_string(i));
}
if (params.subdescs.size() > i) {
vector2d.subdesc(i, params.subdescs[i]);
}
}
for (int j = 0; j < params.y_size; j++)
{
if (params.ysubnames.size() > j) {
vector2d.ysubname(j, params.ysubnames[j]);
} else {
vector2d.ysubname(j, std::to_string(j));
}
}
}
} // namespace gem5

View File

@@ -32,6 +32,7 @@
#include "base/statistics.hh"
#include "params/ScalarStatTester.hh"
#include "params/StatTester.hh"
#include "params/Vector2dStatTester.hh"
#include "params/VectorStatTester.hh"
#include "sim/sim_object.hh"
@@ -136,6 +137,28 @@ class VectorStatTester : public StatTester
} stats;
};
class Vector2dStatTester : public StatTester
{
private:
Vector2dStatTesterParams params;
public:
Vector2dStatTester(const Vector2dStatTesterParams &p) :
StatTester(p), params(p), stats(this, p) {}
protected:
void setStats() override;
struct Vector2dStatTesterStats : public statistics::Group
{
Vector2dStatTesterStats(
statistics::Group *parent,
const Vector2dStatTesterParams &params
);
statistics::Vector2d vector2d;
} stats;
};
} // namespace gem5
#endif // __STAT_TESTER_HH__

View File

@@ -0,0 +1,172 @@
# Copyright (c) 2024 The Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import argparse
import sys
import m5
from m5.objects import (
Root,
Vector2dStatTester,
)
from m5.stats.gem5stats import get_simstat
"""This script is used for checking that Vector2d statistics set in the
simulation are correctly parsed through to the python Pystats.
"""
parser = argparse.ArgumentParser(
description="Tests the output of a Vector2D Pystat."
)
parser.add_argument(
"value",
help="Comma delimited list representing the 2d vector in a flattened "
"state.",
type=lambda s: [float(item) for item in s.split(",")],
)
parser.add_argument(
"num_vectors",
help="The number of vectors in the vector of vectors",
type=int,
)
parser.add_argument(
"--name",
type=str,
default="vector2d",
required=False,
help="Name of the vector statistic.",
)
parser.add_argument(
"--description",
type=str,
default="",
required=False,
help="Description of the vector statistic.",
)
parser.add_argument(
"--subnames",
help="delimited list representing the vector subnames",
type=str,
)
parser.add_argument(
"--subdescs",
help="delimited list representing the vector subdescs",
type=str,
)
parser.add_argument(
"--ysubnames",
help="delimited list representing the vector ysubnames",
type=str,
)
args = parser.parse_args()
expected_output = None
stat_tester = None
stat_tester = Vector2dStatTester()
stat_tester.name = args.name
stat_tester.description = args.description
stat_tester.subnames = []
if args.subnames:
stat_tester.subnames = [str(item) for item in args.subnames.split(",")]
stat_tester.subdescs = []
if args.subdescs:
stat_tester.subdescs = [str(item) for item in args.subdescs.split(",")]
stat_tester.ysubnames = []
if args.ysubnames:
stat_tester.ysubnames = [str(item) for item in args.ysubnames.split(",")]
assert (
len(args.value) % args.num_vectors == 0
), "The number of values is not divisable by the number of vectors."
stat_tester.x_size = args.num_vectors
stat_tester.y_size = len(args.value) / args.num_vectors
stat_tester.values = args.value
vectors = {} # The representation we expect output.
for x in range(args.num_vectors):
x_index = x if x not in stat_tester.subnames else stat_tester.subnames[x]
vector = {}
for y in range(stat_tester.y_size):
to_add = args.value[
int(y + (x * (len(args.value) / args.num_vectors)))
]
vector[y] = {
"value": to_add,
"type": "Scalar",
"unit": "Count",
"description": None,
"datatype": "f64",
}
vectors[x_index] = {
"type": "Vector",
"description": stat_tester.subdescs[x]
if x in stat_tester.subdescs
else stat_tester.description,
"value": vector,
}
expected_output = {
"type": "Group",
"time_conversion": None,
args.name: {
"type": "Vector2d",
"value": vectors,
"description": args.description,
},
}
root = Root(full_system=False, system=stat_tester)
m5.instantiate()
m5.simulate()
simstats = get_simstat(stat_tester)
output = simstats.to_json()["system"]
if output != expected_output:
print("Output statistics do not match expected:", file=sys.stderr)
print("", file=sys.stderr)
print("Expected:", file=sys.stderr)
print(expected_output, file=sys.stderr)
print("", file=sys.stderr)
print("Actual:", file=sys.stderr)
print(output, file=sys.stderr)
sys.exit(1)

View File

@@ -182,3 +182,33 @@ gem5_verify_config(
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)
gem5_verify_config(
name="pystat_vector2d_test",
fixtures=(),
verifiers=[],
config=joinpath(
config.base_dir,
"tests",
"gem5",
"stats",
"configs",
"pystat_vector2d_check.py",
),
config_args=[
"2.4,4.3,3.7,-1.4,-2,4,0,0",
2,
"--name",
"vector2d_stat",
"--description",
"A 2d vector statistic with",
"--subnames",
"decimals,integers",
"--subdescs",
"A random collection of decimals,A random collection of integers",
"--ysubnames",
"first,second,third,fourth",
],
valid_isas=(constants.all_compiled_tag,),
length=constants.quick_tag,
)