simulação

Boids — Comportamento Emergente de Partículas

Em 1986, Craig Reynolds publicou um modelo surpreendentemente simples para simular o voo de bandos de pássaros, cardumes de peixes e enxames de insetos. Nenhuma partícula conhece o plano global: cada uma age apenas com base nos vizinhos imediatos. O comportamento coletivo — formação, divisão, reunificação — emerge espontaneamente dessas três regras locais.

O modelo Boids

Cada partícula $i$ tem posição $\vec{x}_i$ e velocidade $\vec{v}_i$. A cada passo de tempo, três forças são calculadas a partir dos vizinhos dentro do raio $r_v$:

1. Separação — afasta-se de vizinhos muito próximos ($d_{ij} < r_s$):

$$\vec{s}_i = \frac{1}{n_s} \sum_{\substack{j \neq i \\ d_{ij} < r_s}} \frac{\vec{x}_i - \vec{x}_j}{d_{ij}}$$

2. Alinhamento — ajusta a velocidade à média dos vizinhos:

$$\vec{a}_i = \frac{1}{|N_i|} \sum_{j \in N_i} \vec{v}_j$$

3. Coesão — move-se em direção ao centro de massa dos vizinhos:

$$\vec{c}_i = \frac{1}{|N_i|} \sum_{j \in N_i} \vec{x}_j - \vec{x}_i$$

Força de esterçamento

Cada regra produz uma direção desejada $\hat{u}$. A força de esterçamento correspondente é:

$$\vec{f} = \text{clip}\!\left(\hat{u}\, v_\text{max} - \vec{v}_i,\; f_\text{max}\right)$$

onde $\text{clip}(\vec{w}, f_\text{max})$ limita a magnitude: $\min\!\left(1,\, \tfrac{f_\text{max}}{|\vec{w}|}\right)\vec{w}$. Isso impede acelerações abruptas, produzindo curvas suaves.

A aceleração total é a soma ponderada:

$$\vec{F}_i = w_s\,\vec{f}_\text{sep} + w_a\,\vec{f}_\text{ali} + w_c\,\vec{f}_\text{coh}$$

com pesos $w_s = 2{,}0$, $w_a = 1{,}0$, $w_c = 0{,}8$ nesta simulação.

Integração numérica

A cada frame (passo de tempo $\Delta t = 1$):

$$\vec{v}_{t+1} = \text{clip}(\vec{v}_t + \vec{F}_i,\; v_\text{max}), \qquad \vec{x}_{t+1} = \vec{x}_t + \vec{v}_{t+1}$$

Trata-se de integração de Euler explícita — simples e eficiente para este propósito.

Repulsão das bordas

Para evitar que as partículas saiam da tela, aplica-se uma força proporcional à proximidade da borda. Se a distância da partícula à borda é $d_b < m$ (margem):

$$f_b = f_0 \left(1 - \frac{d_b}{m}\right)$$

A força cresce linearmente até zero na margem e atinge $f_0$ encostada na borda.

Detecção de grupos

A cada frame, um algoritmo de busca em largura (BFS) encontra as componentes conexas: dois partículas pertencem ao mesmo grupo se $d_{ij} < r_v$. Quando exatamente dois grupos se formam, as forças de alinhamento e coesão passam a agir apenas dentro de cada grupo — o que impede a fusão e faz os grupos "repelirem" um ao outro.

Simulação interativa

Código completo

Salve como boids.html e abra no navegador.

<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simulação de Partículas Boids</title>
    <style>
        html, body { height: 100%; margin: 0; overflow: hidden; }
        body {
            background-color: #000;
            display: flex;
            flex-direction: column;
            align-items: center;
            height: 100vh;
            box-sizing: border-box;
        }
        svg {
            border: 1px solid #222;
            background-color: #050505;
            width: 90%;
            flex-grow: 1;
            margin-bottom: 5vh;
        }
        #controls {
            margin: 20px 0;
            background: rgba(40,40,40,0.8);
            padding: 10px;
            border-radius: 8px;
            color: #eee;
            font-family: Arial, sans-serif;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        #controls input {
            width: 50px; padding: 5px;
            border-radius: 4px; border: 1px solid #555;
            background: #222; color: #eee;
        }
        #controls button {
            padding: 5px 10px; border-radius: 4px;
            border: none; background: #007bff;
            color: white; cursor: pointer;
        }
        #controls button:hover { background: #0056b3; }
    </style>
</head>
<body>
    <div id="controls">
        <label for="particleCountInput">Partículas (5-40):</label>
        <input type="number" id="particleCountInput" min="5" max="40" value="30">
        <button id="reloadButton">Recarregar</button>
    </div>
    <svg id="particleSVG"></svg>

    <script>
        const svg = document.getElementById('particleSVG');
        const particleCountInput = document.getElementById('particleCountInput');
        const reloadButton = document.getElementById('reloadButton');

        function getParticleCountFromURL() {
            const urlParams = new URLSearchParams(window.location.search);
            let count = parseInt(urlParams.get('particulas'), 10);
            if (isNaN(count) || count < 5) count = 30;
            else if (count > 40) count = 40;
            return count;
        }
        const numParticles = getParticleCountFromURL();
        particleCountInput.value = numParticles;

        reloadButton.addEventListener('click', () => {
            let newCount = parseInt(particleCountInput.value, 10);
            if (isNaN(newCount) || newCount < 5) newCount = 5;
            else if (newCount > 40) newCount = 40;
            window.location.href = window.location.pathname + `?particulas=${newCount}`;
        });

        let svgWidth, svgHeight;

        function setupSvgCanvas() {
            const svgRect = svg.getBoundingClientRect();
            svgWidth = svgRect.width;
            svgHeight = svgRect.height;
            svg.setAttribute('viewBox', `0 0 ${svgWidth} ${svgHeight}`);
            init();
        }
        window.addEventListener('resize', setupSvgCanvas);

        const NEIGHBOR_RADIUS = 70;
        const DESIRED_SEPARATION = 25;
        const MAX_SPEED = 2.8;
        const MAX_FORCE = 0.16;
        const SEPARATION_WEIGHT = 2.0;
        const ALIGNMENT_WEIGHT = 1.0;
        const COHESION_WEIGHT = 0.8;
        const BORDER_REPULSION_MARGIN = 50;
        const BORDER_REPULSION_FORCE = 0.3;

        class Particle {
            constructor(x, y, color) {
                this.x = x; this.y = y;
                const initialAngle = Math.random() * 2 * Math.PI;
                this.vx = Math.cos(initialAngle) * MAX_SPEED;
                this.vy = Math.sin(initialAngle) * MAX_SPEED;
                this.ax = 0; this.ay = 0;
                this.angle = Math.atan2(this.vy, this.vx);
                this.radius = 4 + Math.random() * 1.5;
                this.bodyLength = this.radius * 2.8;
                this.originalColor = color;
                this.color = color;
                this.tailAttachLocalX = -this.bodyLength * 0.65;
                this.tailLength = 25 + Math.floor(Math.random() * 15);
                this.tailPoints = [];
                const initialCosA = Math.cos(this.angle);
                const initialSinA = Math.sin(this.angle);
                for (let i = 0; i < this.tailLength; i++) {
                    this.tailPoints.push({
                        x: this.x + (this.tailAttachLocalX - (i+1)*2) * initialCosA,
                        y: this.y + (this.tailAttachLocalX - (i+1)*2) * initialSinA
                    });
                }
                this.group = null;
                this.createSVGElements();
            }

            createSVGElements() {
                this.bodyElement = document.createElementNS(svg.namespaceURI, 'path');
                this.bodyElement.setAttribute('fill', this.color);
                svg.appendChild(this.bodyElement);
                this.tailElement = document.createElementNS(svg.namespaceURI, 'path');
                this.tailElement.setAttribute('fill', 'none');
                this.tailElement.setAttribute('stroke', this.color);
                this.tailElement.setAttribute('stroke-linecap', 'round');
                this.tailElement.setAttribute('stroke-linejoin', 'round');
                svg.appendChild(this.tailElement);
            }

            applyForce(forceX, forceY) { this.ax += forceX; this.ay += forceY; }

            flock(allParticles) {
                let sepSumX=0,sepSumY=0,aliSumX=0,aliSumY=0;
                let cohSumX=0,cohSumY=0,separationCount=0,alignmentCohesionCount=0;

                for (let other of allParticles) {
                    if (other === this) continue;
                    const inDifferentGroups = this.group!==null && other.group!==null
                        && this.group !== other.group;
                    const dx = other.x - this.x, dy = other.y - this.y;
                    const distance = Math.sqrt(dx*dx + dy*dy);

                    if (distance > 0 && distance < DESIRED_SEPARATION) {
                        sepSumX += (this.x - other.x) / distance;
                        sepSumY += (this.y - other.y) / distance;
                        separationCount++;
                    }
                    if (!inDifferentGroups && distance > 0 && distance < NEIGHBOR_RADIUS) {
                        aliSumX += other.vx; aliSumY += other.vy;
                        cohSumX += other.x; cohSumY += other.y;
                        alignmentCohesionCount++;
                    }
                }

                let totalSteerX = 0, totalSteerY = 0;

                if (separationCount > 0) {
                    sepSumX /= separationCount; sepSumY /= separationCount;
                    let mag = Math.sqrt(sepSumX*sepSumX + sepSumY*sepSumY);
                    if (mag > 0) {
                        sepSumX = (sepSumX/mag)*MAX_SPEED;
                        sepSumY = (sepSumY/mag)*MAX_SPEED;
                        let steerX = sepSumX - this.vx, steerY = sepSumY - this.vy;
                        let steerMag = Math.sqrt(steerX*steerX + steerY*steerY);
                        if (steerMag > MAX_FORCE) {
                            steerX = (steerX/steerMag)*MAX_FORCE;
                            steerY = (steerY/steerMag)*MAX_FORCE;
                        }
                        totalSteerX += steerX * SEPARATION_WEIGHT;
                        totalSteerY += steerY * SEPARATION_WEIGHT;
                    }
                }

                if (alignmentCohesionCount > 0) {
                    aliSumX /= alignmentCohesionCount; aliSumY /= alignmentCohesionCount;
                    let magAli = Math.sqrt(aliSumX*aliSumX + aliSumY*aliSumY);
                    if (magAli > 0) {
                        aliSumX = (aliSumX/magAli)*MAX_SPEED;
                        aliSumY = (aliSumY/magAli)*MAX_SPEED;
                        let steerX = aliSumX - this.vx, steerY = aliSumY - this.vy;
                        let steerMag = Math.sqrt(steerX*steerX + steerY*steerY);
                        if (steerMag > MAX_FORCE) {
                            steerX = (steerX/steerMag)*MAX_FORCE;
                            steerY = (steerY/steerMag)*MAX_FORCE;
                        }
                        totalSteerX += steerX * ALIGNMENT_WEIGHT;
                        totalSteerY += steerY * ALIGNMENT_WEIGHT;
                    }

                    cohSumX /= alignmentCohesionCount; cohSumY /= alignmentCohesionCount;
                    let desiredCohX = cohSumX - this.x, desiredCohY = cohSumY - this.y;
                    let magCoh = Math.sqrt(desiredCohX*desiredCohX + desiredCohY*desiredCohY);
                    if (magCoh > 0) {
                        desiredCohX = (desiredCohX/magCoh)*MAX_SPEED;
                        desiredCohY = (desiredCohY/magCoh)*MAX_SPEED;
                        let steerX = desiredCohX - this.vx, steerY = desiredCohY - this.vy;
                        let steerMag = Math.sqrt(steerX*steerX + steerY*steerY);
                        if (steerMag > MAX_FORCE) {
                            steerX = (steerX/steerMag)*MAX_FORCE;
                            steerY = (steerY/steerMag)*MAX_FORCE;
                        }
                        totalSteerX += steerX * COHESION_WEIGHT;
                        totalSteerY += steerY * COHESION_WEIGHT;
                    }
                }
                this.applyForce(totalSteerX, totalSteerY);
            }

            applyBorderRepulsion() {
                const r = this.radius;
                if (this.x - r < BORDER_REPULSION_MARGIN)
                    this.applyForce(BORDER_REPULSION_FORCE*(1-(this.x-r)/BORDER_REPULSION_MARGIN),0);
                if (this.x + r > svgWidth - BORDER_REPULSION_MARGIN)
                    this.applyForce(-BORDER_REPULSION_FORCE*(1-(svgWidth-this.x-r)/BORDER_REPULSION_MARGIN),0);
                if (this.y - r < BORDER_REPULSION_MARGIN)
                    this.applyForce(0,BORDER_REPULSION_FORCE*(1-(this.y-r)/BORDER_REPULSION_MARGIN));
                if (this.y + r > svgHeight - BORDER_REPULSION_MARGIN)
                    this.applyForce(0,-BORDER_REPULSION_FORCE*(1-(svgHeight-this.y-r)/BORDER_REPULSION_MARGIN));
            }

            update(allParticles) {
                this.flock(allParticles);
                this.applyBorderRepulsion();
                this.vx += this.ax; this.vy += this.ay;
                let speed = Math.sqrt(this.vx*this.vx + this.vy*this.vy);
                if (speed > MAX_SPEED) {
                    this.vx = (this.vx/speed)*MAX_SPEED;
                    this.vy = (this.vy/speed)*MAX_SPEED;
                }
                this.x += this.vx; this.y += this.vy;
                this.ax = 0; this.ay = 0;
                this.angle = Math.atan2(this.vy, this.vx);
                const cosA = Math.cos(this.angle), sinA = Math.sin(this.angle);
                let targetTailX = this.x + this.tailAttachLocalX * cosA;
                let targetTailY = this.y + this.tailAttachLocalX * sinA;
                const tailFollowFactor = 0.15;
                for (let i = 0; i < this.tailLength; i++) {
                    const point = this.tailPoints[i];
                    point.x += (targetTailX - point.x) * tailFollowFactor;
                    point.y += (targetTailY - point.y) * tailFollowFactor;
                    targetTailX = point.x; targetTailY = point.y;
                }
                this.updateSVG();
            }

            updateSVG() {
                this.bodyElement.setAttribute('fill', this.color);
                this.tailElement.setAttribute('stroke', this.color);
                const cosA = Math.cos(this.angle), sinA = Math.sin(this.angle);
                const arcCenterX = this.x + this.bodyLength*0.25*cosA;
                const arcCenterY = this.y + this.bodyLength*0.25*sinA;
                const tailAttachX = this.x + this.tailAttachLocalX*cosA;
                const tailAttachY = this.y + this.tailAttachLocalX*sinA;
                const perpX = -sinA*this.radius, perpY = cosA*this.radius;
                const arcStartX = arcCenterX+perpX, arcStartY = arcCenterY+perpY;
                const arcEndX   = arcCenterX-perpX, arcEndY   = arcCenterY-perpY;
                let bodyPath = `M ${arcStartX} ${arcStartY} A ${this.radius} ${this.radius} 0 1 1 ${arcEndX} ${arcEndY} L ${tailAttachX} ${tailAttachY} Z`;
                this.bodyElement.setAttribute('d', bodyPath);
                if (this.tailPoints.length > 0) {
                    let tailPath = `M ${tailAttachX} ${tailAttachY}`;
                    const maxTailWidth = this.radius*1.4, minTailWidth = 0.6;
                    for (let i = 0; i < this.tailLength; i++) {
                        const progress = i / this.tailLength;
                        const strokeWidth = Math.max(minTailWidth, (1-progress)*maxTailWidth+minTailWidth*progress);
                        this.tailElement.setAttribute('stroke-width', strokeWidth);
                        const point = this.tailPoints[i];
                        tailPath += ` L ${point.x} ${point.y}`;
                    }
                    this.tailElement.setAttribute('d', tailPath);
                }
            }

            remove() {
                if (this.bodyElement) svg.removeChild(this.bodyElement);
                if (this.tailElement) svg.removeChild(this.tailElement);
            }
        }

        const particles = [];

        function findGroups(allParticles) {
            const groups = [], visited = new Set();
            for (const particle of allParticles) {
                if (!visited.has(particle)) {
                    const newGroup = [], queue = [particle];
                    visited.add(particle);
                    while (queue.length > 0) {
                        const current = queue.shift();
                        newGroup.push(current);
                        for (const other of allParticles) {
                            if (!visited.has(other)) {
                                const dx = current.x - other.x, dy = current.y - other.y;
                                if (Math.sqrt(dx*dx+dy*dy) < NEIGHBOR_RADIUS) {
                                    visited.add(other); queue.push(other);
                                }
                            }
                        }
                    }
                    groups.push(newGroup);
                }
            }
            return groups;
        }

        function init() {
            particles.forEach(p => p.remove());
            particles.length = 0;
            for (let i = 0; i < numParticles; i++) {
                const hue = 20 + i*4 + Math.random()*25;
                const margin = BORDER_REPULSION_MARGIN + 20;
                particles.push(new Particle(
                    margin + Math.random()*(svgWidth - margin*2),
                    margin + Math.random()*(svgHeight - margin*2),
                    `hsl(${hue}, 100%, 80%)`
                ));
            }
        }

        let twoGroupMode = false;

        function animate() {
            const groups = findGroups(particles);
            if (groups.length === 2 && !twoGroupMode) {
                twoGroupMode = true;
                groups.forEach((group, i) => { group.forEach(p => { p.group = i; }); });
            } else if (groups.length > 2) {
                twoGroupMode = false;
                particles.forEach(p => { p.group = null; });
            }
            particles.forEach(p => { p.update(particles); });
            requestAnimationFrame(animate);
        }

        setupSvgCanvas();
        animate();
    </script>
</body>
</html>

química

Cafeína — A Molécula do Café

A cafeína (1,3,7-trimetilxantina) é um alcaloide purínico encontrado em mais de 60 espécies de plantas — café, chá, cacau, guaraná. É o psicoativo legal mais consumido no mundo: cerca de 90 % dos adultos ingerem alguma dose diariamente.

Fórmula e estrutura

Fórmula molecular: $\mathrm{C_8H_{10}N_4O_2}$ — massa molar 194,19 g/mol.

A molécula é uma xantina trimetilada: um sistema bicíclico formado pela fusão de um anel pirimidínico (6 membros) com um anel imidazólico (5 membros), base da família das purinas.

Elementos estruturais principais:

  • 2 grupos carbonila $\mathrm{C{=}O}$ (posições $\mathrm{C_2}$ e $\mathrm{C_6}$), que tornam a molécula levemente polar.
  • 3 grupos metil $\mathrm{-CH_3}$ ligados aos nitrogênios $\mathrm{N_1}$, $\mathrm{N_3}$ e $\mathrm{N_7}$, responsáveis pela solubilidade lipídica e pela passagem pela barreira hematoencefálica.
  • Sistema $\pi$ aromático deslocalizado ao longo do núcleo purínico.

Propriedades físico-químicas

  • Ponto de fusão / sublimação: 235 °C
  • Solubilidade em água: 21,7 g/L a 25 °C (aumenta para ~670 g/L a 100 °C)
  • LogP: −0,07 (levemente hidrofílica, porém suficiente para cruzar membranas lipídicas)
  • $\mathrm{p}K_\mathrm{a}$ (base conjugada): 0,6 — base muito fraca, praticamente não ionizada em pH fisiológico

Mecanismo de ação

A cafeína atua como antagonista competitivo da adenosina, bloqueando principalmente os receptores $\mathrm{A_1}$ e $\mathrm{A_{2A}}$ do sistema nervoso central. A adenosina endógena, ao se acumular durante a vigília, sinaliza fadiga e induz o sono; ao bloquear esses receptores, a cafeína suprime esse sinal.

Secundariamente, em doses elevadas, inibe a fosfodiesterase (aumentando os níveis de $\mathrm{AMPc}$) e estimula a liberação de adrenalina.

Fórmula espacial

Arraste para girar. Cinza = $\mathrm{C}$, azul = $\mathrm{N}$, vermelho = $\mathrm{O}$, branco = $\mathrm{H}$.


álgebra linear

O Teorema de Cayley-Hamilton

Toda matriz satisfaz sua própria equação característica — uma das afirmações mais elegantes de toda a álgebra linear.

Motivação

Dada uma matriz $A \in M_{n\times n}(\mathbb{F})$, define-se seu polinômio característico como

$$p(\lambda) = \det(\lambda I - A).$$

As raízes de $p$ são os autovalores de $A$. Uma pergunta natural surge: o que acontece quando substituímos $\lambda$ pela própria matriz $A$?

Teorema

Teorema (Cayley-Hamilton). Seja $A \in M_{n\times n}(\mathbb{F})$ e $p(\lambda) = \det(\lambda I - A)$ seu polinômio característico. Então

$$p(A) = 0.$$

Isto é, toda matriz anula seu próprio polinômio característico. O resultado vale sobre qualquer corpo $\mathbb{F}$.

Um aviso importante

A "demonstração" ingênua consiste em substituir $\lambda = A$ diretamente em $\det(\lambda I - A)$, obtendo $\det(AI - A) = \det(0) = 0$. Esse argumento está errado: $\det(\lambda I - A)$ é um polinômio escalar em $\lambda$, e a substituição $\lambda \mapsto A$ não faz sentido dentro do determinante.

Demonstração

Usamos a matriz adjunta (adjugada). Para qualquer $\lambda$, existe uma matriz polinomial $B(\lambda)$ com entradas polinomiais em $\lambda$ tal que

$$(\lambda I - A)\, B(\lambda) = p(\lambda)\, I.$$

Isso é a identidade $M \cdot \text{adj}(M) = \det(M)\,I$ aplicada a $M = \lambda I - A$. Como $B(\lambda)$ tem grau no máximo $n-1$, escrevemos

$$B(\lambda) = B_{n-1}\lambda^{n-1} + B_{n-2}\lambda^{n-2} + \cdots + B_0,$$

com $B_k \in M_{n\times n}(\mathbb{F})$. Igualando coeficientes de cada potência de $\lambda$ com os de $p(\lambda) = \lambda^n + c_{n-1}\lambda^{n-1} + \cdots + c_0$, obtemos:

$$B_{n-1} = I,$$ $$B_{k-1} - A B_k = c_k\, I \quad (k = n{-}1, \ldots, 1),$$ $$-A B_0 = c_0\, I.$$

Multiplicamos a $k$-ésima equação por $A^k$ e somamos tudo. O lado esquerdo colapsa a zero por cancelamento telescópico, portanto

$$p(A) = A^n + c_{n-1}A^{n-1} + \cdots + c_1 A + c_0 I = 0. \quad \square$$

Exemplo numérico

Seja $A = \begin{pmatrix} 1 & 2 \\ 0 & 3 \end{pmatrix}$. O polinômio característico é

$$p(\lambda) = (\lambda-1)(\lambda-3) = \lambda^2 - 4\lambda + 3.$$

Calculemos $p(A) = A^2 - 4A + 3I$:

$$A^2 = \begin{pmatrix}1&8\\0&9\end{pmatrix}, \qquad 4A = \begin{pmatrix}4&8\\0&12\end{pmatrix}, \qquad 3I = \begin{pmatrix}3&0\\0&3\end{pmatrix}.$$

$$p(A) = \begin{pmatrix}1&8\\0&9\end{pmatrix} - \begin{pmatrix}4&8\\0&12\end{pmatrix} + \begin{pmatrix}3&0\\0&3\end{pmatrix} = \begin{pmatrix}0&0\\0&0\end{pmatrix}. \checkmark$$

Corolários e aplicações

Toda potência $A^k$ com $k \geq n$ pode ser escrita como combinação linear de $I, A, \ldots, A^{n-1}$. Em particular, o polinômio mínimo de $A$ divide $p(\lambda)$.

Se $A$ é invertível ($c_0 \neq 0$), podemos expressar $A^{-1}$ diretamente:

$$A^{-1} = -\frac{1}{c_0}\bigl(A^{n-1} + c_{n-1}A^{n-2} + \cdots + c_1 I\bigr).$$


análise

Funções Afins — y = ax + b

A função afim (ou de primeiro grau) é talvez a função mais simples com variação não-trivial. Ela aparece em física como movimento uniforme, em economia como custo marginal constante e em geometria como a equação de uma reta.

Definição

Uma função $f : \mathbb{R} \to \mathbb{R}$ é afim quando pode ser escrita na forma

$$f(x) = ax + b, \qquad a, b \in \mathbb{R},\; a \neq 0.$$

Os dois parâmetros têm interpretações geométricas precisas:

  • $a$ é a inclinação (coeficiente angular): mede a taxa de variação de $f$ por unidade de $x$.
  • $b$ é o intercepto (coeficiente linear): o valor $f(0)$, onde a reta cruza o eixo $y$.

Taxa de variação constante

Dados quaisquer dois pontos $x_1 \neq x_2$, a razão de variação é sempre a mesma:

$$\frac{f(x_2) - f(x_1)}{x_2 - x_1} = \frac{(ax_2 + b) - (ax_1 + b)}{x_2 - x_1} = \frac{a(x_2 - x_1)}{x_2 - x_1} = a.$$

Essa constância é a propriedade que caracteriza as funções afins entre todas as funções reais.

Casos especiais

  • $a > 0$: função crescente — a reta sobe da esquerda para a direita.
  • $a < 0$: função decrescente — a reta desce.
  • $b = 0$: a reta passa pela origem; $f$ é chamada de função linear (ou homogênea de grau 1).

Gráfico interativo

Use os controles abaixo para explorar como $a$ e $b$ moldam a reta.

a (inclinação) = 1.0 b (intercepto) = 0.0

Raiz da função

A função afim tem exatamente uma raiz (zero) para $a \neq 0$:

$$ax + b = 0 \implies x_0 = -\frac{b}{a}.$$

Geometricamente, $x_0$ é o ponto onde a reta cruza o eixo $x$. No gráfico acima, experimente $b = 0$ — a raiz coincide com a origem — e depois aumente $b$ para ver o zero se deslocar para a esquerda.