Skip to content

Fix latex() and unicode(), and seek the advice of Derivative - #2132

Open
Xingye-Dujing wants to merge 10 commits into
symengine:masterfrom
Xingye-Dujing:fix
Open

Fix latex() and unicode(), and seek the advice of Derivative#2132
Xingye-Dujing wants to merge 10 commits into
symengine:masterfrom
Xingye-Dujing:fix

Conversation

@Xingye-Dujing

Copy link
Copy Markdown

#2131
In the function UnicodePrinter::bvisit(const Add &x), when x.get_coef() is zero and the first term’s multiplier is -1, the expression is printed as the negative of the original via unicode(*expr).

@Xingye-Dujing

Xingye-Dujing commented Jan 27, 2026

Copy link
Copy Markdown
Author

Description

I discovered a problem with the latex() function. When we compile and run in the Debug environment, the behavior of something like Derivative::create(mul(sin(x), cos(x)), {x}); will be blocked by is_canonical(). However, if we use the Release version, the above code will run normally. At this point, calling latex(* Derivative()) will cause an issue.
Here is an example to illustrate:

diff = Derivative::create(add(sin(x), cos(x)), {x});
expr = add(diff, x);
std::cout << latex(*expr) << '\n';

The result is

x + \frac{d}{d x} \sin{\left(x\right)} + \cos{\left(x\right)}

It is lacking (), which has led to the incorrect display of the differentially expressed formula.
The correct result should be

x + \frac{d}{d x} (\sin{\left(x\right)} + \cos{\left(x\right)})

Fix

This issue was triggered by the function void LatexPrinter::bvisit(const Derivative &x).
Just remove the ending part.

s << "} " << apply(x.get_arg());
str_ = s.str();

Change to

const RCP<const Basic> arg = x.get_arg();
std::string arg_str = apply(arg);
if (is_a<Add>(*arg) || is_a<Mul>(*arg)) {
    arg_str = "\\left(" + arg_str + "\\right)";
}
s << "} " << arg_str;
str_ = s.str();

End

Since this behavior falls under the category of improper usage by the users, I would like to first inquire whether it is necessary to take this into consideration. If deemed necessary, I will submit the amendments.

@Xingye-Dujing

Xingye-Dujing commented Jan 27, 2026

Copy link
Copy Markdown
Author

Description

I want to enhance the functionality of the class Derivative.

  1. Remove the is_canonical() restriction. We can now use the Derivative object more freely.
  2. Add a doit() method, similar to how SymPy operates.
  3. Expand the unicode() function to enable a more visually appealing display of the Derivative object.

Proposed implementation

  1. To maintain compatibility, we directly create a new constructor.
Derivative::Derivative(const RCP<const Basic>& arg, const multiset_basic& x, const bool&)
    : arg_{ arg }, x_{ x }
{
    SYMENGINE_ASSIGN_TYPEID()
}

This way, even in the Debug environment, the check of is_canonical() can be skipped.

  1. Derivative add new methods
RCP<const Basic> Derivative::doit() const
{
    return this->arg_->diff(rcp_static_cast<const Symbol>(*x_.begin()));
}
  1. UnicodePrinter add new methods
void UnicodePrinter::bvisit(const Derivative& x)
{
	const auto& symbols = x.get_symbols();
	StringBox Box;

	if (symbols.size() == 1) {
		if (free_symbols(*x.get_arg()).size() == 1) {
			StringBox box("d");
			StringBox div_d("d" + str(*(*(symbols.begin()))), 2);
			box.add_below_unicode_line(div_d);
			Box.add_right(box);
		}
		else {
			StringBox box(U8("\u2202"), 1);
			StringBox div_d(U8("\u2202") + str(*(*(symbols.begin()))), 2);
			box.add_below_unicode_line(div_d);
			Box.add_right(box);
		}
	}
	else
	{
		StringBox div_d, box(U8("\u2202") + std::to_string(symbols.size()), 2);
		unsigned count = 1;
		auto it = symbols.begin();
		RCP<const Basic> prev = *it;
		++it;
		for (; it != symbols.end(); ++it) {
			if (neq(*prev, **it)) {
				if (count == 1)
				{
					div_d.add_right(StringBox(U8("\u2202") + str(*prev), 2));
				}
				else
				{
					div_d.add_right(StringBox(U8("\u2202") + str(*prev) + std::to_string(count), 3));
				}
				count = 1;
			}
			else {
				count++;
			}
			prev = *it;
		}
		if (count == 1) {
			div_d.add_right(StringBox(U8("\u2202") + str(*prev), 2));
		}
		else {
			div_d.add_right(StringBox(U8("\u2202") + str(*prev) + std::to_string(count), 3));
		}

		box.add_below_unicode_line(div_d);
		Box.add_right(box);
	}

	const RCP<const Basic> arg = x.get_arg();
	StringBox arg_box = apply(arg);
	StringBox pad_arg_box(" ");
	if (is_a<Add>(*arg) || is_a<Mul>(*arg)) {
		arg_box.enclose_parens(true);
		pad_arg_box = arg_box;
	}
	else
	{
		pad_arg_box.add_right(arg_box);
	}

	Box.add_right(pad_arg_box);
	box_ = Box;
}

Note: For the sake of aesthetic presentation, I did not use add_power(), but instead used add_right(). Here, the introduction of add_power() is unnecessary and would be unattractive.

  1. Improve UnicodePrinter::_print_pow() for Derivative
void StringBox::enclose_parens(const bool& big)
{
	add_left_parens(big);
	add_right_parens();
}
void StringBox::add_left_parens(const bool& big)
{
	if (lines_.size() == 1)
	{
		if (big)
		{
			const std::string pad(width_, ' ');
			lines_.insert(lines_.begin(), pad);
			lines_.insert(lines_.end(), pad);
			lines_[0].insert(0, U8("\u239B "));
			lines_.back().insert(0, U8("\u239D "));
			for (unsigned i = 1; i < lines_.size() - 1; i++) {
				lines_[i].insert(0, U8("\u239C "));
			}
			width_ += 2;
		}
		else {
			width_ += 1;
			lines_[0].insert(0, "(");
		}
	}
	else {
		lines_[0].insert(0, U8("\u239B "));
		lines_.back().insert(0, U8("\u239D "));
		for (unsigned i = 1; i < lines_.size() - 1; i++) {
			lines_[i].insert(0, U8("\u239C "));
		}
		width_ += 2;
	}
}
void UnicodePrinter::_print_pow(const RCP<const Basic>& a,
	const RCP<const Basic>& b)
{
	if (eq(*b, *rational(1, 2))) {
		StringBox box = apply(a);
		box.enclose_sqrt();
		box_ = box;
	}
	else {
		StringBox base;
		if (is_a<Derivative>(*a))
		{
			base = apply(a);
			base.enclose_parens(true);
		}
		else
		{
			base = parenthesizeLE(a, PrecedenceEnum::Pow);
		}
		StringBox exp = parenthesizeLE(b, PrecedenceEnum::Pow);
		base.add_power(exp);
		box_ = base;
	}
}
image image
  1. fix the problem of latex(*Derivative).

End

Do you think this expansion is necessary? If you think it is, I will submit my implementation plan.

@Xingye-Dujing

Xingye-Dujing commented Jan 27, 2026

Copy link
Copy Markdown
Author

Description

I have discovered another issue with the unicode() function. It is void StringBox::add_power(StringBox& other) Caused. When exp is greater than one line, it will completely reversly output it.
b1e8eb34431d4908d801af7a68dfd0a2

Fix

All need to do is reverse other.lines_.

void StringBox::add_power(StringBox& other)
{
	for (std::string& line : lines_) {
		line.append(std::string(other.width_, ' '));
	}
	std::reverse(other.lines_.begin(), other.lines_.end());
	for (std::string& line : other.lines_) {
		lines_.insert(lines_.begin(), std::string(width_, ' ') + line);
	}
	width_ += other.width_;
}
74254d75acf0be46038375a8ce937c45

@Xingye-Dujing Xingye-Dujing changed the title Fix void UnicodePrinter::bvisit(const Add &x) Fix latex() and unicode() Jan 28, 2026
@Xingye-Dujing

Copy link
Copy Markdown
Author

Now I have fixed the issues I discovered in latex() and unicode(), and there is still the expansion of Derivative functions awaiting for feedback.

@Xingye-Dujing Xingye-Dujing changed the title Fix latex() and unicode() Fix latex() and unicode(), and seek the advice of Derivative Jan 28, 2026
@Xingye-Dujing

Xingye-Dujing commented Feb 1, 2026

Copy link
Copy Markdown
Author

Description

Sometimes, the unicode() function mistakenly add two '-'.

unicode:2 - -3.y
str: 2 - 3*y

Solution

Use abs to handle the coef (add the '-' separately at the end).

else {
    t = parenthesizeLT(abs(p.second), PrecedenceEnum::Mul);                   # abs(p.second)
    auto op = print_mul();
    t.add_right(op);
    auto rhs = parenthesizeLT(p.first, PrecedenceEnum::Mul);
    t.add_right(rhs);
}

@Xingye-Dujing

Copy link
Copy Markdown
Author

Description

When the numerator is 1, the num will mistakenly add an extra multiplication sign.

0f9d353aa9a7d3c112a5532c51716211

Solution

Just delete box1.add_right(mulbox); (See the submitted code)

@Xingye-Dujing

Copy link
Copy Markdown
Author

Description

Simplify the -1 of the num to - for output.

Solution

Just add a special case handling when the numerator is -1. (See the submitted code)

Comment thread symengine/printers/latex.cpp Outdated
@isuruf
isuruf requested a review from rikardn June 1, 2026 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants