Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

_repr_markdown_ -> markdown #988

Merged
merged 3 commits into from
Dec 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion gtsam/discrete/DecisionTreeFactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ namespace gtsam {
}

/* ************************************************************************* */
std::string DecisionTreeFactor::_repr_markdown_(
std::string DecisionTreeFactor::markdown(
const KeyFormatter& keyFormatter) const {
std::stringstream ss;

Expand Down
2 changes: 1 addition & 1 deletion gtsam/discrete/DecisionTreeFactor.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ namespace gtsam {
/// @{

/// Render as markdown table.
std::string _repr_markdown_(
std::string markdown(
const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override;

/// @}
Expand Down
2 changes: 1 addition & 1 deletion gtsam/discrete/DiscreteConditional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ size_t DiscreteConditional::sample(const DiscreteValues& parentsValues) const {
}

/* ************************************************************************* */
std::string DiscreteConditional::_repr_markdown_(
std::string DiscreteConditional::markdown(
const KeyFormatter& keyFormatter) const {
std::stringstream ss;

Expand Down
2 changes: 1 addition & 1 deletion gtsam/discrete/DiscreteConditional.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class GTSAM_EXPORT DiscreteConditional: public DecisionTreeFactor,
/// @{

/// Render as markdown table.
std::string _repr_markdown_(
std::string markdown(
const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override;

/// @}
Expand Down
4 changes: 2 additions & 2 deletions gtsam/discrete/discrete.i
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ virtual class DecisionTreeFactor: gtsam::DiscreteFactor {
gtsam::DefaultKeyFormatter) const;
bool equals(const gtsam::DecisionTreeFactor& other, double tol = 1e-9) const;
string dot(bool showZero = false) const;
string _repr_markdown_(const gtsam::KeyFormatter& keyFormatter =
string markdown(const gtsam::KeyFormatter& keyFormatter =
gtsam::DefaultKeyFormatter) const;
};

Expand Down Expand Up @@ -67,7 +67,7 @@ virtual class DiscreteConditional : gtsam::DecisionTreeFactor {
size_t sample(const gtsam::DiscreteValues& parentsValues) const;
void solveInPlace(gtsam::DiscreteValues@ parentsValues) const;
void sampleInPlace(gtsam::DiscreteValues@ parentsValues) const;
string _repr_markdown_(const gtsam::KeyFormatter& keyFormatter =
string markdown(const gtsam::KeyFormatter& keyFormatter =
gtsam::DefaultKeyFormatter) const;
};

Expand Down
2 changes: 1 addition & 1 deletion gtsam/discrete/tests/testDecisionTreeFactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ TEST(DecisionTreeFactor, markdown) {
"|2|0|5|\n"
"|2|1|6|\n";
auto formatter = [](Key key) { return key == 12 ? "A" : "B"; };
string actual = f1._repr_markdown_(formatter);
string actual = f1.markdown(formatter);
EXPECT(actual == expected);
}

Expand Down
6 changes: 3 additions & 3 deletions gtsam/discrete/tests/testDiscreteConditional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ TEST(DiscreteConditional, markdown_prior) {
"|0|0.2|\n"
"|1|0.4|\n"
"|2|0.4|\n";
string actual = conditional._repr_markdown_();
string actual = conditional.markdown();
EXPECT(actual == expected);
}

Expand All @@ -138,7 +138,7 @@ TEST(DiscreteConditional, markdown_multivalued) {
"|2|0.33|0.33|0.34|\n"
"|3|0.33|0.33|0.34|\n"
"|4|0.95|0.02|0.03|\n";
string actual = conditional._repr_markdown_();
string actual = conditional.markdown();
EXPECT(actual == expected);
}

Expand All @@ -159,7 +159,7 @@ TEST(DiscreteConditional, markdown) {
"|1|2|1|0|\n";
vector<string> names{"C", "B", "A"};
auto formatter = [names](Key key) { return names[key]; };
string actual = conditional._repr_markdown_(formatter);
string actual = conditional.markdown(formatter);
EXPECT(actual == expected);
}

Expand Down
54 changes: 54 additions & 0 deletions python/gtsam/tests/test_DiscreteConditional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
GTSAM Copyright 2010-2021, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved

See LICENSE for the license information

Unit tests for Discrete Conditionals.
Author: Varun Agrawal
"""

# pylint: disable=no-name-in-module, invalid-name

import unittest

from gtsam import DiscreteConditional, DiscreteKeys
from gtsam.utils.test_case import GtsamTestCase


class TestDiscreteConditional(GtsamTestCase):
"""Tests for Discrete Conditionals."""
def test_markdown(self):
"""Test whether the _repr_markdown_ method."""

A = (2, 2)
B = (1, 2)
C = (0, 3)
parents = DiscreteKeys()
parents.push_back(B)
parents.push_back(C)

conditional = DiscreteConditional(A, parents,
"0/1 1/3 1/1 3/1 0/1 1/0")
expected = \
" $P(A|B,C)$:\n" \
"|B|C|0|1|\n" \
"|:-:|:-:|:-:|:-:|\n" \
"|0|0|0|1|\n" \
"|0|1|0.25|0.75|\n" \
"|0|2|0.5|0.5|\n" \
"|1|0|0.75|0.25|\n" \
"|1|1|0|1|\n" \
"|1|2|1|0|\n"

def formatter(x: int):
names = ["C", "B", "A"]
return names[x]

actual = conditional._repr_markdown_(formatter)
self.assertEqual(actual, expected)


if __name__ == "__main__":
unittest.main()
119 changes: 81 additions & 38 deletions wrap/gtwrap/pybind_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import re
from pathlib import Path
from typing import List

import gtwrap.interface_parser as parser
import gtwrap.template_instantiator as instantiator
Expand Down Expand Up @@ -46,6 +47,11 @@ def __init__(self,
# amount of indentation to add before each function/method declaration.
self.method_indent = '\n' + (' ' * 8)

# Special methods which are leveraged by ipython/jupyter notebooks
self._ipython_special_methods = [
"svg", "png", "jpeg", "html", "javascript", "markdown", "latex"
]

def _py_args_names(self, args):
"""Set the argument names in Pybind11 format."""
names = args.names()
Expand Down Expand Up @@ -86,6 +92,67 @@ def wrap_ctors(self, my_class):
))
return res

def _wrap_serialization(self, cpp_class):
"""Helper method to add serialize, deserialize and pickle methods to the wrapped class."""
if not cpp_class in self._serializing_classes:
self._serializing_classes.append(cpp_class)

serialize_method = self.method_indent + \
".def(\"serialize\", []({class_inst} self){{ return gtsam::serialize(*self); }})".format(class_inst=cpp_class + '*')

deserialize_method = self.method_indent + \
'.def("deserialize", []({class_inst} self, string serialized)' \
'{{ gtsam::deserialize(serialized, *self); }}, py::arg("serialized"))' \
.format(class_inst=cpp_class + '*')

# Since this class supports serialization, we also add the pickle method.
pickle_method = self.method_indent + \
".def(py::pickle({indent} [](const {cpp_class} &a){{ /* __getstate__: Returns a string that encodes the state of the object */ return py::make_tuple(gtsam::serialize(a)); }},{indent} [](py::tuple t){{ /* __setstate__ */ {cpp_class} obj; gtsam::deserialize(t[0].cast<std::string>(), obj); return obj; }}))"

return serialize_method + deserialize_method + \
pickle_method.format(cpp_class=cpp_class, indent=self.method_indent)

def _wrap_print(self, ret: str, method: parser.Method, cpp_class: str,
args_names: List[str], args_signature_with_names: str,
py_args_names: str, prefix: str, suffix: str):
"""
Update the print method to print to the output stream and append a __repr__ method.

Args:
ret (str): The result of the parser.
method (parser.Method): The method to be wrapped.
cpp_class (str): The C++ name of the class to which the method belongs.
args_names (List[str]): List of argument variable names passed to the method.
args_signature_with_names (str): C++ arguments containing their names and type signatures.
py_args_names (str): The pybind11 formatted version of the argument list.
prefix (str): Prefix to add to the wrapped method when writing to the cpp file.
suffix (str): Suffix to add to the wrapped method when writing to the cpp file.

Returns:
str: The wrapped print method.
"""
# Redirect stdout - see pybind docs for why this is a good idea:
# https://pybind11.readthedocs.io/en/stable/advanced/pycpp/utilities.html#capturing-standard-output-from-ostream
ret = ret.replace('self->print',
'py::scoped_ostream_redirect output; self->print')

# Make __repr__() call .print() internally
ret += '''{prefix}.def("__repr__",
[](const {cpp_class}& self{opt_comma}{args_signature_with_names}){{
gtsam::RedirectCout redirect;
self.{method_name}({method_args});
return redirect.str();
}}{py_args_names}){suffix}'''.format(
prefix=prefix,
cpp_class=cpp_class,
opt_comma=', ' if args_names else '',
args_signature_with_names=args_signature_with_names,
method_name=method.name,
method_args=", ".join(args_names) if args_names else '',
py_args_names=py_args_names,
suffix=suffix)
return ret

def _wrap_method(self,
method,
cpp_class,
Expand All @@ -105,22 +172,19 @@ def _wrap_method(self,
py_method = method.name + method_suffix
cpp_method = method.to_cpp()

args_names = method.args.names()
py_args_names = self._py_args_names(method.args)
args_signature_with_names = self._method_args_signature(method.args)

# Special handling for the serialize/serializable method
if cpp_method in ["serialize", "serializable"]:
if not cpp_class in self._serializing_classes:
self._serializing_classes.append(cpp_class)
serialize_method = self.method_indent + \
".def(\"serialize\", []({class_inst} self){{ return gtsam::serialize(*self); }})".format(class_inst=cpp_class + '*')
deserialize_method = self.method_indent + \
'.def("deserialize", []({class_inst} self, string serialized)' \
'{{ gtsam::deserialize(serialized, *self); }}, py::arg("serialized"))' \
.format(class_inst=cpp_class + '*')

# Since this class supports serialization, we also add the pickle method.
pickle_method = self.method_indent + \
".def(py::pickle({indent} [](const {cpp_class} &a){{ /* __getstate__: Returns a string that encodes the state of the object */ return py::make_tuple(gtsam::serialize(a)); }},{indent} [](py::tuple t){{ /* __setstate__ */ {cpp_class} obj; gtsam::deserialize(t[0].cast<std::string>(), obj); return obj; }}))"
return serialize_method + deserialize_method + \
pickle_method.format(cpp_class=cpp_class, indent=self.method_indent)
return self._wrap_serialization(cpp_class)

# Special handling of ipython specific methods
# https://ipython.readthedocs.io/en/stable/config/integrating.html
if cpp_method in self._ipython_special_methods:
idx = self._ipython_special_methods.index(cpp_method)
py_method = f"_repr_{self._ipython_special_methods[idx]}_"

# Add underscore to disambiguate if the method name matches a python keyword
if py_method in self.python_keywords:
Expand All @@ -132,9 +196,6 @@ def _wrap_method(self,
method,
(parser.StaticMethod, instantiator.InstantiatedStaticMethod))
return_void = method.return_type.is_void()
args_names = method.args.names()
py_args_names = self._py_args_names(method.args)
args_signature_with_names = self._method_args_signature(method.args)

caller = cpp_class + "::" if not is_method else "self->"
function_call = ('{opt_return} {caller}{method_name}'
Expand Down Expand Up @@ -165,27 +226,9 @@ def _wrap_method(self,
# Create __repr__ override
# We allow all arguments to .print() and let the compiler handle type mismatches.
if method.name == 'print':
# Redirect stdout - see pybind docs for why this is a good idea:
# https://pybind11.readthedocs.io/en/stable/advanced/pycpp/utilities.html#capturing-standard-output-from-ostream
ret = ret.replace(
'self->print',
'py::scoped_ostream_redirect output; self->print')

# Make __repr__() call .print() internally
ret += '''{prefix}.def("__repr__",
[](const {cpp_class}& self{opt_comma}{args_signature_with_names}){{
gtsam::RedirectCout redirect;
self.{method_name}({method_args});
return redirect.str();
}}{py_args_names}){suffix}'''.format(
prefix=prefix,
cpp_class=cpp_class,
opt_comma=', ' if args_names else '',
args_signature_with_names=args_signature_with_names,
method_name=method.name,
method_args=", ".join(args_names) if args_names else '',
py_args_names=py_args_names,
suffix=suffix)
ret = self._wrap_print(ret, method, cpp_class, args_names,
args_signature_with_names, py_args_names,
prefix, suffix)

return ret

Expand Down
8 changes: 4 additions & 4 deletions wrap/tests/expected/matlab/ForwardKinematics.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@
function obj = ForwardKinematics(varargin)
if nargin == 2 && isa(varargin{1}, 'uint64') && varargin{1} == uint64(5139824614673773682)
my_ptr = varargin{2};
class_wrapper(55, my_ptr);
class_wrapper(57, my_ptr);
elseif nargin == 5 && isa(varargin{1},'gtdynamics.Robot') && isa(varargin{2},'char') && isa(varargin{3},'char') && isa(varargin{4},'gtsam.Values') && isa(varargin{5},'gtsam.Pose3')
my_ptr = class_wrapper(56, varargin{1}, varargin{2}, varargin{3}, varargin{4}, varargin{5});
my_ptr = class_wrapper(58, varargin{1}, varargin{2}, varargin{3}, varargin{4}, varargin{5});
elseif nargin == 4 && isa(varargin{1},'gtdynamics.Robot') && isa(varargin{2},'char') && isa(varargin{3},'char') && isa(varargin{4},'gtsam.Values')
my_ptr = class_wrapper(57, varargin{1}, varargin{2}, varargin{3}, varargin{4});
my_ptr = class_wrapper(59, varargin{1}, varargin{2}, varargin{3}, varargin{4});
else
error('Arguments do not match any overload of ForwardKinematics constructor');
end
obj.ptr_ForwardKinematics = my_ptr;
end

function delete(obj)
class_wrapper(58, obj.ptr_ForwardKinematics);
class_wrapper(60, obj.ptr_ForwardKinematics);
end

function display(obj), obj.print(''); end
Expand Down
4 changes: 2 additions & 2 deletions wrap/tests/expected/matlab/MultipleTemplatesIntDouble.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
function obj = MultipleTemplatesIntDouble(varargin)
if nargin == 2 && isa(varargin{1}, 'uint64') && varargin{1} == uint64(5139824614673773682)
my_ptr = varargin{2};
class_wrapper(51, my_ptr);
class_wrapper(53, my_ptr);
else
error('Arguments do not match any overload of MultipleTemplatesIntDouble constructor');
end
obj.ptr_MultipleTemplatesIntDouble = my_ptr;
end

function delete(obj)
class_wrapper(52, obj.ptr_MultipleTemplatesIntDouble);
class_wrapper(54, obj.ptr_MultipleTemplatesIntDouble);
end

function display(obj), obj.print(''); end
Expand Down
4 changes: 2 additions & 2 deletions wrap/tests/expected/matlab/MultipleTemplatesIntFloat.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
function obj = MultipleTemplatesIntFloat(varargin)
if nargin == 2 && isa(varargin{1}, 'uint64') && varargin{1} == uint64(5139824614673773682)
my_ptr = varargin{2};
class_wrapper(53, my_ptr);
class_wrapper(55, my_ptr);
else
error('Arguments do not match any overload of MultipleTemplatesIntFloat constructor');
end
obj.ptr_MultipleTemplatesIntFloat = my_ptr;
end

function delete(obj)
class_wrapper(54, obj.ptr_MultipleTemplatesIntFloat);
class_wrapper(56, obj.ptr_MultipleTemplatesIntFloat);
end

function display(obj), obj.print(''); end
Expand Down
Loading