13

I'd love to automatically graph, on a number line, the solution to non-linear inequalities. Basically, the input would be an equation (e.g. x^2>=1) and the output would be a visual graph like the following example image.

Solution Graph

Edit

As this question has been abandoned for more than 3 years so let me restrict the meaning of "non linear inequalities" to the following. I hope it is easy enough.

enter image description here

  • 3
    Without more precise specification than "nonlinear inequality", it is impossible to automate. – Paul Gaborit Feb 27 at 21:44
  • 1
    @TheInventorofGod I think you exaggerated with the easyness :) – CarLaTeX Feb 28 at 17:26
  • @TheInventorofGod Since you are now in charge of the question, what are the rules? External programs: yes or no? (If yes, then pgfplots with gnuplot does that out of the box.) And what are the a_i and b_i? (And why don't you ask a new question. This is not an implicit criticism, but a question.) – marmot Mar 1 at 4:37
  • @TheInventorofGod ;-) Well, yes, they are real because otherwise the inequality doesn't make sense. My question was a more practical one: are answers supposed to use random numbers or what? – marmot Mar 1 at 4:45
  • Also nonlinear equality come up in some proofs to show continuity, Limits, derivative and basically everything built off that. – marshal craft Mar 2 at 15:49
12

Here is a proposal. (Math mode added, thanks to JouleV!)

\documentclass[tikz,border=3.14mm]{standalone}
\begin{document}
\begin{tikzpicture}
\draw [-stealth,thick] (-3.5,0) -- (3.5,0);
\foreach \X in {-3,...,3} {\draw (\X,0.1) -- (\X,-0.1) node[below]{$\X$}; }
\clip (-3.5cm,4pt) rectangle (3.5cm,6pt);
\draw[blue,line width=2pt] plot[variable=\x,domain=-3.5:3.5,samples=141]
({\x},{ifthenelse(\x*\x>=2%<- your condition
,5,20)*1pt});
\end{tikzpicture}
\end{document}

enter image description here

And this shows how I cheat.

\documentclass[tikz,border=3.14mm]{standalone}
\begin{document}
\begin{tikzpicture}
\draw [-stealth,thick] (-3.5,0) -- (3.5,0);
\foreach \X in {-3,...,3} {\draw (\X,0.1) -- (\X,-0.1) node[below]{$\X$}; }
\draw[blue,line width=2pt] plot[variable=\x,domain=-3.5:3.5,samples=141]
({\x},{ifthenelse(\x*\x>=2%<- your condition
,5,20)*1pt});
\draw[red] (-3.5cm,4pt) rectangle (3.5cm,6pt) node[above left,font=\sffamily]{clip region};
\end{tikzpicture}
\end{document}

enter image description here

Of course, one can cast this in a macro, if needed.

12

A TikZ-only solution that also draws arrows-heads and dots where necessary. It works with any inequality, not only rational functions, and requires only TikZ.

Update, casting the whole thing into something like a macro as suggested by @AndréC and with help from @marmot:

Code:

\documentclass[tikz,border=5mm]{standalone}
\usetikzlibrary{intersections,arrows.meta}
\makeatletter\long\def\ifnodedefined#1#2{\@ifundefined{pgf@sh@ns@#1}{}{#2}}\makeatother
\tikzset{
    %line-styles
        OO/.style={fill=blue,draw=blue,{Circle[width=1mm,length=1mm]}-{Circle[width=1mm,length=1mm]},shorten <= -0.5mm,shorten >= -0.5mm}, <O/.style={stealth-{Circle[width=1mm,length=1mm]},shorten <= 0mm}, O>/.style={{Circle[width=1mm,length=1mm]}-stealth,shorten >= 0mm}, <OO>/.style={stealth-stealth,shorten <= 0mm,shorten >= 0mm},every edge/.append style={OO},
    %how to plot:
        pics/.cd, plot inequality/.code n args={4}{
        %all the paths we need to figure out the intersections etc.
            \path[name path=conditionline] plot[variable=\x,domain={#2*1.01}:{#3*1.01},samples=300] ({\x},{ifthenelse(#1,{1+#4},{-1+#4})}); \path[name path=zeroline] (#2,#4) -- (#3,#4);\path[name path=start] (#2,{#4+1.1}) -- (#2,{#4+.9});\path[name path=end] (#3,{#4+1.1}) -- (#3,{#4+0.9});\path[name intersections={of=conditionline and start,name=startt}];\path[name intersections={of=conditionline and end,name=endd}];\path[name intersections={of=conditionline and zeroline,name=zerolinee}];\coordinate (intersection-0) at (#2,#4);
        %draw lines, nots and arrows
            \ifnum0\ifnodedefined{zerolinee-1}{1}\ifnodedefined{startt-1}{1}>0\draw[name intersections={of=zeroline and conditionline,total=\t,by=x}]\foreach \i [count=\s, evaluate=\s as \startswitch using iseven(\s+0\ifnodedefined{startt-1}{1})] in {0,...,{\t}}{\ifnum\i=\t coordinate (intersection-\s) at (#3,#4)\fi\if1\startswitch(intersection-\i) edge [\ifnodedefined{startt-1}{\ifnum\i=0<O\fi}\ifnodedefined{endd-1}{\ifnum\i=\t O>\fi}] (intersection-\s)\fi};\pgfnoderename{}{startt-1}\pgfnoderename{}{endd-1}\pgfnoderename{}{zerolinee-1}\fi}
}

\begin{document}
\begin{tikzpicture}

%real line
\draw[stealth-stealth] (-3.5,-.2) -- (3.5,-.2);\foreach \X in {-3,...,3} {\draw (\X,-0.1) -- (\X,-0.3) node[below]{$\X$}; }

\pic{plot inequality={0.05*(\x*\x-1)>sin(deg(\x*3)) }{-3.5}{3.5}{0}}; \node at (5.5,0) {\(\frac{(x^2-1)}{20}>\sin(3x)\)};
\pic{plot inequality={0.05*(\x*\x-1)<sin(deg(\x*3)) }{-3.5}{3.5}{1}}; \node at (5.5,1)  {\(\frac{(x^2-1)}{20}<\sin(3x)\)};
\pic{plot inequality={\x*\x>=1                      }{-3.5}{3.5}{2}}; \node at (5.5,2) {\(x^2\geq1\)};
\pic{plot inequality={0<1                           }{-3.5}{3.5}{3}}; \node at (5.5,3) {\(0<1\)};
\end{tikzpicture}
\end{document}

Result: enter image description here [For the sake of brevity I'm deleting some intermediate solutions that are now obsolete. The current solution is more compact, more general and more precise than what I had before, but uses the same logic.]

The idea is to find all intersections of the indicator function for inequality with the 0-line. We then take those intersections and connect every second of with the next one. Depending on whether the inequality is true or false left side of the function, we start with the odd ones or the even ones. This logic is best illustrated by looking at the output from my first example, plotted against the left hand side of the inequality: enter image description here

Advantages:

  • Works with any mathematical expression that can be evaluated by pgfmath
  • Automatically draws dots or arrows to indicate whether or not the condition is true or false at the end of the displayed interval.
  • Now (after first update) also correctly deals with cases where the condition is never or always true (ht @marmot)
  • Now (after second update) test condition exactly at the two margins. No resolution-based uncertainty left.

Code-snippet with explanations:

% path that is at 1 where the condition is true and at -1 where it's  not
\path[name path=conditionline] plot[variable=\x,domain={#2*1.01}:{#3*1.01},samples=300] ({\x},{ifthenelse(#1,{1+#4},{-1+#4})});
% the zero-line
\path[name path=zeroline] (#2,#4) -- (#3,#4);
% two lines that intersect the conditionline if it is true at the margins of the interval
\path[name path=start] (#2,{#4+1.1}) -- (#2,{#4+.9});\path[name path=end] (#3,{#4+1.1}) -- (#3,{#4+0.9});
%testing whether the conditionline intersects these two lines and the zero line
\path[name intersections={of=conditionline and start,name=startt}];\path[name intersections={of=conditionline and end,name=endd}];\path[name intersections={of=conditionline and zeroline,name=zerolinee}];
coordinate at the left margin
\coordinate (intersection-0) at (#2,#4);
%draw lines, nots and arrows
   %only draw something if the condition-line intersects the zero-line at all or if it's always true:
   \ifnum0\ifnodedefined{zerolinee-1}{1}\ifnodedefined{startt-1}{1}>0
     %loop over all intersections with the zero line (including a fictonal one for the left margin at (intersection-0):
     \draw[name intersections={of=zeroline and conditionline,total=\t,by=x}]
        \foreach \i [count=\s, evaluate=\s as \startswitch using iseven(\s+0\ifnodedefined{startt-1}{1})] in {0,...,{\t}}{
           %if we're in the last round. create a coordinate for the right arrow-tip
           \ifnum\i=\t coordinate (intersection-\s) at (#3,#4)\fi
           %draw an edge from this intersection to the next, determine arrow heads depending on whether we are in the middle or the margins and whether the condition is true at the margins or not
           \if1\startswitch(intersection-\i) edge [\ifnodedefined{startt-1}{\ifnum\i=0<O\fi}\ifnodedefined{endd-1}{\ifnum\i=\t O>\fi}] (intersection-\s)\fi};
           %Delete all the old nodes used for testing in case another plot is drawn later
           \pgfnoderename{}{startt-1}\pgfnoderename{}{endd-1}\pgfnoderename{}{zerolinee-1}
       \fi
   }
}

Old solution:

\documentclass[tikz,border=5mm]{standalone}
\usepackage{tikz}
\usetikzlibrary{intersections,arrows.meta}
\makeatletter
\long\def\ifnodedefined#1#2#3{\@ifundefined{pgf@sh@ns@#1}{#3}{#2}}
\makeatother
\begin{document}
\begin{tikzpicture}

%define condition
\path[name path=conditionline] plot[variable=\x,domain=-3.5:3.5,samples=150] ({\x},{ifthenelse(%
0.05*(\x*\x-1)+sin(deg(\x*3))>=0%<=any condition
,1,-1)});

%real line
\draw[stealth-stealth] (-3.5,-.2) -- (3.5,-.2);\foreach \X in {-3,...,3} {\draw (\X,-0.1) -- (\X,-0.3) node[below]{$\X$}; }

%helpers / testing if corners of interval are in or out
\path[name path=zeroline]  (-3.5,0) -- (3.5,0);
\path[name path=start] (-3.5,1) -- (-3.5,0);
\path[name path=end] (3.5,1) -- (3.5,0);
\path[name intersections={of=conditionline and start,name=startt}];
\path[name intersections={of=conditionline and end,name=endd}];

%draw lines, nots and arrows
\def\lasts{1}
\draw[fill=blue,draw=blue,name intersections={of=zeroline and conditionline,total=\t,by=x}]
        \foreach[remember=\s as \lasts] \s in {1,...,{\t}}{
            %if required, draw left arrow
            \if\s1\ifnodedefined{startt-1}{  (intersection-1) edge [-stealth    ] (startt-1|-0,0)}\fi
            %if required, draw right arrow
            \if\s\t\ifnodedefined{endd-1}{  (intersection-\t) edge [-stealth] (endd-1|-0,0)}\fi
            %draw intermediate lines
            \ifodd\s
                \ifnodedefined{startt-1}{(intersection-\s) -- (intersection-\lasts)}{}
            \else
                \ifnodedefined{startt-1}{}{(intersection-\s) -- (intersection-\lasts)}
            \fi
            %draw points
            (intersection-\s) circle (2pt) node {}
        };

\end{tikzpicture}
\end{document}

For completeness: I copied the code for the real line from @marmot's solution to this questions and \ifnodedefined from here

  • The link you gave points to a question that marmot did not answer. Only Jake answered. – AndréC Mar 1 at 15:30
  • 1
    +1 I really like your idea. It would be interesting to make it a macro. – AndréC Mar 1 at 15:35
  • 1
    The question is 3 years old and as the PO has not specified anything, you are free to interpret its question as you wish. Moreover, the bonus is not given by the OP but by The Inventor of God. He has so far been able to leave comments, if he doesn't like what you do, he will say it. – AndréC Mar 1 at 15:48
  • 1
    +1 (If there is no intersection, the code throws an error, but this can be fixed easily.) – marmot Mar 1 at 19:12
  • 1
    Well illustrated, fully reproducible and comprehensive answer. Love it. – Gui_struggling_with_R Mar 4 at 21:04
10

1D is a special case of 2D

\documentclass[tikz]{standalone}
\pgfdeclarefunctionalshading{inequality}{\pgfpoint{-25bp}{-25bp}}{\pgfpoint{25bp}{25bp}}{}{
                % x y
    2 copy      % x y x y
    2 copy      % x y x y x y
    exch atan   % x y x y theta
    3 1 roll    % x y theta x y
    dup mul exch dup mul
    add sqrt    % x y theta r
    20 mul      % x y theta 20r
    add sin     % x y sin(theta+20r)
    3 1 roll    % sin(theta+20r) x y
    200 mul sin exch 200 mul sin
    mul         % sin(theta+20r) sin(200x)sin(200y)
    gt          % sin(theta+20r) > sin(200x)sin(200y) ?
    {0 0 0}{1 1 1}ifelse
}
\begin{document}
    \tikz\path[shading=inequality](-10,-10)rectangle(10,10);
\end{document}

  • I did not understand your answer. Can you explain it? – AndréC Mar 1 at 5:16
  • 1
    @TheInventorofGod My question is addressed to Symbol 1, I do not understand his answer. You and Symbol 1 are the same person? – AndréC Mar 1 at 10:42
  • 1
    @AndréC OP asks for a method to depict a subset of real numbers defined by an inequality. And I give an answer that depicts a subset of R^2 defined by an inequality. My answer is more general since subsets of R are subsets of R^2. – Symbol 1 Mar 1 at 18:37
  • 1
    When I see your pretty graphic, I see a spiral. But there is no indication how all the solutions can be read. A straight line representation is much easier to read and understand. – AndréC Mar 1 at 18:52
  • 2
    The spiral is defined by sin(theta+20r) > sin(200x)sin(200y). For an 1D inequality A(x) > B(x), it could be translate into A(x) > B(x) and -1 < y < 1. Hence my solution applies. – Symbol 1 Mar 1 at 18:55
10
+500

I notice that your diagram has arrows, to indicate the interval continues on forever and filled circles, to indicate the endpoints satisfy the inequality. In order to achieve these features, which are important, I resorted to using the sagetex package to smooth out the complications. You can find the documentation on sagetex on CTAN here. This package relies on a computer algebra system (CAS) called Sage which is similar to Mathematica. Sage is opensource software which is free; the documentation is on the internet here. Sage is too big to include in a LaTeX distribution, so it needs to be installed locally on your machine (which can be a bit of a pain if you don't use Linux) or it can be accessed online with a free Cocalc account here. A Cocalc account is the quickest way to get started with Sage and sagetex. Here is the code:

\documentclass{article}
\usepackage[usenames,dvipsnames]{xcolor}
\usepackage{tikz}
\usepackage{sagetex}
\usetikzlibrary{backgrounds}
\usetikzlibrary{decorations}
\begin{document}
\begin{sagesilent}
# the function, f, should a nonconstant polynomial
#f = expand((x+5)^2)
#f = x^3-2*x-1
#f = x^3-x
#f = x^2+1
f = expand((x^2-5)*(x^2+x+1)*(x^2+2))
ineq = f<=0
epsilon = .01
root_list = f.roots(x)
real_roots = []
for root in root_list:
    if root[0].is_real():
        real_roots += [root[0].n(digits=3)]
### Order the roots from smallest to largest ###
roots = sorted(real_roots)
if len(roots)==0:   # if no roots, set a start and end
    start = -2
    end = 2
else:               # set start 2 below smallest root, end 2 above biggest root
    start = roots[0]-2
    end = roots[len(real_roots)-1]+2
#####################TIKZ PICTURE SET UP ###########
output = r""
output += r"\begin{tikzpicture}"
##### Draw numberline ######
output += r"\draw (%s,0)--(%s,0);"%(start,end)
for i in range(start,end+1):
    output += r"\draw (%s,.2)--(%s,-.2) node[below] {%s} ;"%(i,i,i)

##### Draw solution ######
if len(roots)==0:                       # No roots are found
    if ineq(0):                        # all values satisfy inequality
        output += r"\draw[thick,blue,<-] (%s,1)--(0,1);"%(start)
        output += r"\draw[thick,blue,->] (0,1)--(%s,1);"%(end)
else:                                   # there are roots
    if ineq(start):
        output += r"\draw[thick,blue,<-] (%s,1)--(%s,1);"%(start,roots[0])
    for i in range(0,len(roots)-1):
        if ineq(roots[i]+epsilon):
            output += r"\draw[blue] (%s,1)--(%s,1);"%(roots[i],roots[i+1])
    if ineq(end):
        output += r"\draw[thick,blue,->] (%s,1)--(%s,1);"%(roots[len(roots)-1],end)

########### Draw circles  #############
if len(roots)>0:
    if "geq" in latex(ineq) or "leq" in latex(ineq):
        for i in range(0,len(roots)):
            if not(ineq(roots[i]-epsilon) and ineq(roots[i]+epsilon)):
                output += r"\filldraw[blue] (%s,1) circle (0.07);"%(roots[i])
    else:
        for i in range(0,len(roots)):
            output += r"\draw[blue] (%s,1) circle (0.07);"%(roots[i])
output += r"\end{tikzpicture}"
\end{sagesilent}
The real roots of $\sage{f}$ are approximately $\sage{roots}$. 
Graphing the inequality $\sage{ineq}$ gives:\\\\
\sagestr{output}
\end{document}

Here is the output from some sample runs: enter image description here enter image description here enter image description here enter image description here enter image description here

Comments on the code: In sagesilent you're working with Python and # designates a comment as opposed to LaTeX where % sign is what you need. I've got several polynomials I used in testing commented out. After the function, f, is declared you'll need to change the next line, ineq = f<=0, to the inequality you want. The command root_list = f.roots(x) tells Sage to find the roots. However, since Sage is a CAS they need to be turned into numbers (such as sqrt(2) to 1.414) and then these roots are ordered from smallest to largest. This allows me to set start 2 smaller than the smallest root and end 2 bigger than the biggest root. I now know how big a numberline I need to show all the roots.

Next, the tikzpicture is set up as a string. Working with Sage is a 3 step process. First LaTeX is run, then Sage, then LaTeX is run again. Using a Cocalc account, pressing the Build button does all 3 at once. In order to use Sage calculations in my LaTeX commands, they have to show up after Sage has run. This is why a string is needed. That string gets placed into LaTeX code for the third run. The picture is broken into 3 pieces: the numberline (using start and end) followed by the solution, which also determines whether arrows are needed and then finally the circles are drawn shaded or not depending on the inequality. After that, sagesilent ends and LaTeX begins. Using the sage command I can get the function along with its roots.

Some negatives:

  1. The input is not a rational function in its most general form. However, multiplying both sides by the denominator gives us the polynomial case being considered here (similar but not identical). The code is designed for a nonconstant polynomial on one side and 0 on the other.
  2. The code requires you to set the function and then the inequality on the next line.
  3. Requires Sage which is not part of LaTeX.

Some Positives:

  1. The circles are shaded or unshaded as in your diagram.
  2. The arrows are shown, as in your diagram.
  3. The code uses the minimum and maximum roots to determine where to start graphing and where to finish graphing. No trial and error needed.

  4. function input is more natural; e.g. f = x^2+1 versus \x*\x+1. Additionally, you can write f = expand((x^2-5)(x^2+x+1)(x^2+2)) and Sage will do the expansion.

  5. Code works in case of no distance between intervals; e.g. f(x)=x^2 where the intervals are -infty to 0 and 0 to infinity

  6. Sage can output the roots.

EDIT: Code fixed to account for no roots case marmot mentioned and additional positive and negative points added or revised.

EDIT 2: In response to your question below, "Is it possible to show the exact values of roots as labels on the number lines and remove the distracting integer labels?" the short answer is yes. I've revised code to mark roots on the numberline instead of numbers.

\documentclass{article}
\usepackage[usenames,dvipsnames]{xcolor}
\usepackage{tikz}
\usepackage{sagetex}
\usetikzlibrary{backgrounds}
\usetikzlibrary{decorations}
\begin{document}
\begin{sagesilent}
# the function, f, should a nonconstant polynomial
#f = expand((x+5)^2)
#f = x^3-2*x-1
#f = x^3-x
#f = x^2+1
f = expand((x^2-5)*(x^2+x+1)*(x^2+2))
ineq = f>0
epsilon = .01
root_list = f.roots(x)
real_roots = []
Eroots = []
for root in root_list:
    if root[0].is_real():
        real_roots += [root[0].n(digits=3)]
        Eroots += [root[0]]
### Order the roots from smallest to largest ###
roots = sorted(real_roots)
Eroots = sorted(Eroots)
if len(roots)==0:   # if no roots, set a start and end
    start = -2
    end = 2
else:               # set start 2 below smallest root, end 2 above    biggest root
    start = roots[0]-2
    end = roots[len(real_roots)-1]+2
#####################TIKZ PICTURE SET UP ###########
output = r""
output += r"\begin{tikzpicture}"
##### Draw numberline ######
output += r"\draw[<->] (%s,0)--(%s,0);"%(start,end)
if len(roots)>0:
    for i in range(0,len(Eroots)):
        output += r"\draw (%s,.2)--(%s,-.2) node[below] {$%s$} ;"%(roots[i],roots[i],latex(Eroots[i]))

##### Draw solution ######
if len(roots)==0:                       # No roots are found
    if ineq(0):                        # all values satisfy inequality
        output += r"\draw[thick,blue,<-] (%s,1)--(0,1);"%(start)
        output += r"\draw[thick,blue,->] (0,1)--(%s,1);"%(end)
else:                                   # there are roots
    if ineq(start):
        output += r"\draw[thick,blue,<-] (%s,1)--(%s,1);"%(start,roots[0])
    for i in range(0,len(roots)-1):
        if ineq(roots[i]+epsilon):
            output += r"\draw[blue] (%s,1)--(%s,1);"%(roots[i],roots[i+1])
    if ineq(end):
        output += r"\draw[thick,blue,->] (%s,1)--(%s,1);"%(roots[len(roots)-1],end)

########### Draw circles  #############
if len(roots)>0:
    if "geq" in latex(ineq) or "leq" in latex(ineq):
        for i in range(0,len(roots)):
            if not(ineq(roots[i]-epsilon) and ineq(roots[i]+epsilon)):
                output += r"\filldraw[blue] (%s,1) circle (0.07);"%(roots[i])
    else:
        for i in range(0,len(roots)):
            output += r"\draw[blue] (%s,1) circle (0.07);"%(roots[i])
output += r"\end{tikzpicture}"
\end{sagesilent}
The real roots of $\sage{f}$ are $\sage{Eroots}$ which are approximately 
$\sage{roots}$. Graphing the inequality $\sage{ineq}$ gives:\\\\
\sagestr{output}
\end{document}

But there is a problem. Although the output looks fine in many cases: enter image description here

it won't look so good if the roots are complicated and too close together. One root is printing over the other: enter image description here

Therefore, I've kept the first version of code. It seems a bit more robust to me.

  • Is it possible to show the exact values of roots as labels on the number lines and remove the distracting integer labels? – Artificial Odorless Armpit Mar 2 at 19:48
  • 1
    Yes, I've added the code for that in my answer below the original. There is an issue depending upon what you're graphing. I've given a sample screenshot. – DJP Mar 2 at 23:01
8
\documentclass[border=15pt]{standalone}
\usepackage{pst-plot}
\makeatletter
\def\specialPlot{\pst@object{specialPlot}}
\def\specialPlot@i(#1,#2)#3#4{%
    \pst@killglue
    \addto@par{algebraic}%
    \begin@OpenObj
    \addto@pscode{
        #2 #1 sub \psk@plotpoints div /dx ED
        0 0.25 \tx@ScreenCoor translate
        /getY
            \ifPst@algebraic (#3) tx@AlgToPs begin AlgToPs end cvx
            \else { #3 } \fi def
        #1 0 \tx@ScreenCoor moveto 
        #1 dx add dx #2 {
          dup /x ED
          getY 0 #4 { 0 \tx@ScreenCoor lineto }
                    { 0 \tx@ScreenCoor moveto } ifelse  
        }  for
    }%
    \end@OpenObj
    \ignorespaces
}
\makeatother

\begin{document}
\begin{pspicture}(-3.5,-0.5)(5,1.75)
\psaxes[linecolor=gray,yAxis=false,subticks=5,ticksize=0 5pt](0,0)(-3.2,0)(3.2,0)%  
\psset{linewidth=1.5pt}
\specialPlot[linecolor=red](-3,3){x^2-1}{ge}
\rput[lb](3.5,0.1){$x^2-1\ge 0$}
\rput(0,0.5){\specialPlot[linecolor=red](-3,3){x^2-1}{lt}}
\rput[lb](3.5,0.6){$x^2-1< 0$}
\rput(0,1){\specialPlot[linecolor=blue](-3,3){x^3-x}{ge}}
\rput[lb](3.5,1.1){$x^3-x\ge 0$}
\rput(0,1.5){\specialPlot[linecolor=blue](-3,3){x^3-x}{lt}}
\rput[lb](3.5,1.6){$x^3-x< 0$}
\end{pspicture}

\end{document}

enter image description here

  • Please tell me know the difference between ED and def ? – Trong Vuong Mar 1 at 12:33
  • /ED {exch def } def is only an abbreviation – user2478 Mar 1 at 12:50
7

I propose a solution using only Lua as intermediate tool, requiring only LuaLaTeX as engine. Since the the inequality was restricted to products of 2-degree-polynomials, we can numerically solve the inequalities by the quadratic equation. We collect all roots of all polynomials, and analyze the resulting intervals between the roots. From this, we let TikZ draw the final diagram.

Here are some test runs. Red circles indicate singularities, i.e. undefined points of the polynomial where the denominator is zero. Blue circles indicate that the point is not part of the solution set.

enter image description here

Advantages:

  • Only requires LuaLaTeX and the packages tikz and luacode, nothing else, to generate the diagrams.
  • Circles and arrows are shown, as in your diagram.
  • Roots are represented as dots or circles, depending on whether the root is part of the solution set, or resp. not.
  • Correctly handles singularities of the rational function.

Disadvantages:

  • As opposed to DJP's answer using Sage and symbolic solving, the accuracy of this solution is limited to Lua's floating-point accuracy.
  • Similarly, the inequality is restricted to products of quadratic polynomials, as there is no general numerical method to root finding.
  • The function needs to be entered via coefficients. For example, the code ineq.tikz_ineq({{-3,2,10},{3,-1,0}}, {{4,5,-5}}, '<='), generates the first diagram.

This code generated the diagrams, using only LuaLaTeX (the file ineq.lua is placed in the same directory as the tex-file):

\documentclass[tikz,border=20pt]{standalone}
\usepackage{luacode}
\directlua{ineq = require('ineq')}
\begin{document}
\begin{luacode}
    local nom_polys = {{-3,2,10},{3,-1,0}}
    local denom_polys = {{4,5,-5}}
    ineq.tikz_ineq(nom_polys, denom_polys, '<=')
\end{luacode}

\begin{luacode}
    local nom_polys = {{0,0,0}}
    local denom_polys = {{1,0,0}}
    ineq.tikz_ineq(nom_polys, denom_polys, '>=')
\end{luacode}

\begin{luacode}
    local nom_polys = {{-3,0,1}}
    local denom_polys = {{1,0,0}}
    ineq.tikz_ineq(nom_polys, denom_polys, '<=')
\end{luacode}

\begin{luacode}
    local nom_polys = {{1,0,0}}
    local denom_polys = {{0,0,1}}
    ineq.tikz_ineq(nom_polys, denom_polys, '>=')
\end{luacode}

\begin{luacode}
    local nom_polys = {{1,0,-1},{0,1,0}}
    local denom_polys = {{0,0,1}}
    ineq.tikz_ineq(nom_polys, denom_polys, '<')
\end{luacode}
\end{document}

ineq.lua

function tikz_ineq(num_polys, denom_polys, ineq_str)

    local function ineq(v) 
        if ineq_str == '<=' then return v <= 0 end
        if ineq_str == '<' then return v < 0 end
        if ineq_str == '>=' then return v >= 0 end
        if ineq_str == '>' then return v > 0 end
    end

    local function fullpoly(x)
        local num, denom = 1, 1
        for i,p in ipairs(num_polys) do
            local a,b,c = unpack(p)
            num = num * (a*x*x + b*x + c)
        end
        for i,p in ipairs(denom_polys) do
            local a,b,c = unpack(p)
            denom = denom * (a*x*x + b*x + c)
        end

        return num/denom
    end

    local function roots(c)
        local a,b,c = unpack(c)

        if a ~= 0 then
            if (b*b - 4*a*c) >= 0 then
                local root1 = (-b + math.sqrt(b*b - 4*a*c))/(2*a) 
                local root2 = (-b - math.sqrt(b*b - 4*a*c))/(2*a) 
                return {root1, root2}
            else return {} end
        elseif b ~= 0 then
            return { -c/b }
        else return {} end
    end

    local function f(x) 
        return string.format('% 0.2f',x)
    end

    local function draw_point(x,include)
        if include then
            tex.print('\\draw[blue,fill=blue] ('..f(x)..',0.2) circle (.04);')
        else
            tex.print('\\draw[blue,fill=white] ('..f(x)..',0.2) circle (.04);')
        end
    end


    -- collect roots
    local roots_num = {}
    local roots_denom = {}
    local all_roots = {}

    for i,p in ipairs(num_polys) do
        for j,x in ipairs(roots(p)) do
            table.insert(roots_num, x)
            table.insert(all_roots, x)
        end
    end
    for i,p in ipairs(denom_polys) do
        for j,x in ipairs(roots(p)) do
            table.insert(roots_denom, x)
            table.insert(all_roots, x)
        end
    end

    table.sort(all_roots)

    -- test if point is root of denominator -> singularitiy
    local jumppoint = {}
    for i=1,#all_roots do
        jumppoint[i] = false
        for j=1,#roots_denom do
            if all_roots[i] == roots_denom[j] then
                jumppoint[i] = true
            end
        end
    end

    local min = all_roots[1]
    local max = all_roots[#all_roots]

    tex.print('\\begin{tikzpicture}[scale=2]')
    local left_boundary = math.floor(math.min(-3, min or 0))
    local right_boundary = math.ceil(math.max(3, max or 0))
    tex.print('\\draw [-stealth,thick] ('..f(left_boundary-0.5)..',0) -- ('..f(right_boundary+0.5)..',0); \\foreach \\X in {' .. left_boundary ..',...,'..right_boundary..'} {\\draw (\\X,0.1) -- (\\X,-0.1) node[below]{$\\X$}; }')

    if #all_roots == 0 then -- no roots, test globally
        if ineq(fullpoly(0)) then
            tex.print('\\draw[blue,<->] ('..f(left_boundary-0.5)..',0.2) -- ('..f(right_boundary+0.5)..',0.2);')
        end
    else
        for i=0,#all_roots do -- iterate trough each interval

            if i==0 then -- (-inf,min)
                if ineq(fullpoly(min-10)) then
                    local left = math.min(left_boundary-0.5, min-0.5)
                    tex.print('\\draw[blue,<-] ('..f(left)..',0.2) -- ('..f(min)..',0.2);')
                    draw_point(min, ineq(0) and not jumppoint[1])
                end
            elseif i==#all_roots then -- (max,inf)
                if ineq(fullpoly(max+10)) then
                    local right = math.max(right_boundary+0.5, max+0.5)
                    tex.print('\\draw[blue,->] ('..f(max)..',0.2) -- ('..f(right)..',0.2);')
                    draw_point(max, ineq(0) and not jumppoint[#all_roots])
                end
            else -- (all_roots[i],all_roots[i+1])
                local left = all_roots[i]
                local right = all_roots[i+1]
                if ineq(fullpoly((left + right)/2)) then
                    tex.print('\\draw[blue,-] ('..f(left)..',0.2) -- ('..f(right)..',0.2);')
                    draw_point(left, ineq(0) and not jumppoint[i])
                    draw_point(right, ineq(0) and not jumppoint[i+1])
                end
            end

            -- optional: highlight singularities
            if i>0 and jumppoint[i] then
                tex.print('\\draw[red] ('..f(all_roots[i])..',0.2) circle (.06);')
            end

        end
    end

    -- optional: write inequality
    local num = ''
    local denom = ''
    for i,c in ipairs(num_polys) do
        num = num .. string.format('(%dx^2%+dx%+d)', c[1], c[2], c[3])
    end
    for i,c in ipairs(denom_polys) do
        denom = denom .. string.format('(%dx^2%+dx%+d)', c[1], c[2], c[3])
    end
    if ineq_str == '>=' then ineq_str = '\\geq' end
    if ineq_str == '<=' then ineq_str = '\\leq' end
    tex.print('\\node at (0, -1) {$\\displaystyle\\frac{'..num..'}{'..denom..'}'..ineq_str..'0$}; ')

    tex.print('\\end{tikzpicture}')

end

return { tikz_ineq = tikz_ineq }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.